use chrono::NaiveDate;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub trait IndustryTransaction: std::fmt::Debug + Send + Sync {
fn transaction_type(&self) -> &str;
fn date(&self) -> NaiveDate;
fn amount(&self) -> Option<Decimal>;
fn accounts(&self) -> Vec<String>;
fn to_journal_lines(&self) -> Vec<IndustryJournalLine>;
fn metadata(&self) -> HashMap<String, String>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndustryJournalLine {
pub account: String,
pub debit: Decimal,
pub credit: Decimal,
pub description: String,
pub cost_center: Option<String>,
pub dimensions: HashMap<String, String>,
}
impl IndustryJournalLine {
pub fn debit(
account: impl Into<String>,
amount: Decimal,
description: impl Into<String>,
) -> Self {
Self {
account: account.into(),
debit: amount,
credit: Decimal::ZERO,
description: description.into(),
cost_center: None,
dimensions: HashMap::new(),
}
}
pub fn credit(
account: impl Into<String>,
amount: Decimal,
description: impl Into<String>,
) -> Self {
Self {
account: account.into(),
debit: Decimal::ZERO,
credit: amount,
description: description.into(),
cost_center: None,
dimensions: HashMap::new(),
}
}
pub fn with_cost_center(mut self, cost_center: impl Into<String>) -> Self {
self.cost_center = Some(cost_center.into());
self
}
pub fn with_dimension(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.dimensions.insert(key.into(), value.into());
self
}
}
pub trait IndustryAnomaly: std::fmt::Debug + Send + Sync {
fn anomaly_type(&self) -> &str;
fn severity(&self) -> u8;
fn detection_difficulty(&self) -> &str;
fn indicators(&self) -> Vec<String>;
fn regulatory_concerns(&self) -> Vec<String>;
}
#[allow(unused)]
pub trait IndustryTransactionGenerator: Send + Sync {
type Transaction: IndustryTransaction;
type Anomaly: IndustryAnomaly;
fn generate_transactions(
&self,
start_date: NaiveDate,
end_date: NaiveDate,
count: usize,
) -> Vec<Self::Transaction>;
fn generate_anomalies(&self, transactions: &[Self::Transaction]) -> Vec<Self::Anomaly>;
fn gl_accounts(&self) -> Vec<IndustryGlAccount>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndustryGlAccount {
pub account_number: String,
pub name: String,
pub account_type: String,
pub category: String,
pub is_control: bool,
pub normal_balance: String,
}
impl IndustryGlAccount {
pub fn new(
number: impl Into<String>,
name: impl Into<String>,
account_type: impl Into<String>,
category: impl Into<String>,
) -> Self {
Self {
account_number: number.into(),
name: name.into(),
account_type: account_type.into(),
category: category.into(),
is_control: false,
normal_balance: "Debit".to_string(),
}
}
pub fn into_control(mut self) -> Self {
self.is_control = true;
self
}
pub fn with_normal_balance(mut self, balance: impl Into<String>) -> Self {
self.normal_balance = balance.into();
self
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_journal_line() {
let debit = IndustryJournalLine::debit("1000", Decimal::new(1000, 0), "Test debit")
.with_cost_center("CC001")
.with_dimension("project", "P001");
assert_eq!(debit.account, "1000");
assert_eq!(debit.debit, Decimal::new(1000, 0));
assert_eq!(debit.credit, Decimal::ZERO);
assert_eq!(debit.cost_center, Some("CC001".to_string()));
assert_eq!(debit.dimensions.get("project"), Some(&"P001".to_string()));
let credit = IndustryJournalLine::credit("2000", Decimal::new(1000, 0), "Test credit");
assert_eq!(credit.debit, Decimal::ZERO);
assert_eq!(credit.credit, Decimal::new(1000, 0));
}
#[test]
fn test_gl_account() {
let account =
IndustryGlAccount::new("5100", "Cost of Goods Sold", "Expense", "Manufacturing")
.into_control()
.with_normal_balance("Debit");
assert_eq!(account.account_number, "5100");
assert!(account.is_control);
assert_eq!(account.normal_balance, "Debit");
}
}