use std::sync::Arc;
use async_trait::async_trait;
use uuid::Uuid;
#[derive(Debug, thiserror::Error)]
pub enum SaleInvoicedAmountError {
#[error("sale invoiced amount port not composed: {detail}")]
NotComposed { detail: String },
#[error("billing backend: {0}")]
Backend(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourceInvoicedAmount {
pub source_id: Uuid,
pub amount_untaxed_total: rust_decimal::Decimal,
}
#[async_trait]
pub trait SaleInvoicedAmountPort: Send + Sync {
async fn invoiced_amount_for_source(
&self,
source_id: Uuid,
) -> Result<SourceInvoicedAmount, SaleInvoicedAmountError>;
}
pub struct RefusingSaleInvoicedAmount;
#[async_trait]
impl SaleInvoicedAmountPort for RefusingSaleInvoicedAmount {
async fn invoiced_amount_for_source(
&self,
source_id: Uuid,
) -> Result<SourceInvoicedAmount, SaleInvoicedAmountError> {
Err(SaleInvoicedAmountError::NotComposed {
detail: format!(
"no SaleInvoicedAmountPort is installed; refusing the invoiced-amount lookup \
for source {source_id} — compose one via \
MailingWriteService::with_sale_invoiced_amount_port"
),
})
}
}
pub struct CannedSaleInvoicedAmount {
pub refuse: bool,
pub amount: rust_decimal::Decimal,
}
#[async_trait]
impl SaleInvoicedAmountPort for CannedSaleInvoicedAmount {
async fn invoiced_amount_for_source(
&self,
source_id: Uuid,
) -> Result<SourceInvoicedAmount, SaleInvoicedAmountError> {
if self.refuse {
return Err(SaleInvoicedAmountError::NotComposed {
detail: format!("canned refusal for source {source_id}"),
});
}
Ok(SourceInvoicedAmount {
source_id,
amount_untaxed_total: self.amount,
})
}
}
#[derive(Clone)]
pub struct SaleInvoicedAmountSlot {
inner: Arc<std::sync::RwLock<Arc<dyn SaleInvoicedAmountPort>>>,
}
impl SaleInvoicedAmountSlot {
pub fn install(&self, port: Arc<dyn SaleInvoicedAmountPort>) {
*self.inner.write().unwrap_or_else(|e| e.into_inner()) = port;
}
pub fn current(&self) -> Arc<dyn SaleInvoicedAmountPort> {
self.inner.read().unwrap_or_else(|e| e.into_inner()).clone()
}
}
impl Default for SaleInvoicedAmountSlot {
fn default() -> Self {
Self {
inner: Arc::new(std::sync::RwLock::new(Arc::new(RefusingSaleInvoicedAmount))),
}
}
}
#[async_trait]
impl SaleInvoicedAmountPort for SaleInvoicedAmountSlot {
async fn invoiced_amount_for_source(
&self,
source_id: Uuid,
) -> Result<SourceInvoicedAmount, SaleInvoicedAmountError> {
self.current().invoiced_amount_for_source(source_id).await
}
}