use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GlPostLine {
pub account_id: Uuid,
pub debit: Decimal,
pub credit: Decimal,
pub party_type: Option<String>,
pub party_id: Option<Uuid>,
pub description: Option<String>,
}
impl GlPostLine {
pub fn debit(account_id: Uuid, amount: Decimal) -> Self {
Self { account_id, debit: amount, credit: Decimal::ZERO, party_type: None, party_id: None, description: None }
}
pub fn credit(account_id: Uuid, amount: Decimal) -> Self {
Self { account_id, debit: Decimal::ZERO, credit: amount, party_type: None, party_id: None, description: None }
}
pub fn with_description(mut self, d: impl Into<String>) -> Self {
self.description = Some(d.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AccountingPostEnvelope {
pub idempotency_key: String,
pub company_id: Uuid,
pub branch_id: Option<Uuid>,
pub source_type: String,
pub source_id: Uuid,
pub source_reference: Option<String>,
pub posting_date: chrono::NaiveDate,
pub currency: String,
pub posting_type: String,
pub description: Option<String>,
pub lines: Vec<GlPostLine>,
}
impl AccountingPostEnvelope {
pub fn totals(&self) -> (Decimal, Decimal) {
(
self.lines.iter().map(|l| l.debit).sum(),
self.lines.iter().map(|l| l.credit).sum(),
)
}
pub fn is_balanced(&self) -> bool {
let (d, c) = self.totals();
d == c && !self.lines.is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GlPostAck {
pub post_id: Uuid,
pub journal_id: Uuid,
pub idempotent_reuse: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GlPostRejected {
pub code: String,
pub message: String,
}
#[async_trait::async_trait]
pub trait GlPostSink: Send + Sync {
async fn post(&self, envelope: &AccountingPostEnvelope) -> Result<GlPostAck, GlPostRejected>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct UnwiredGlSink;
#[async_trait::async_trait]
impl GlPostSink for UnwiredGlSink {
async fn post(&self, _envelope: &AccountingPostEnvelope) -> Result<GlPostAck, GlPostRejected> {
Err(GlPostRejected {
code: "gl_seam_unwired".into(),
message: "no GL post sink is composed into this deployment".into(),
})
}
}