//! 账务域实体定义 use chrono::{DateTime, NaiveDate, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use crate::domain::account::AccountType; /// 借贷方向 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Direction { /// 借方 Debit, /// 贷方 Credit, } impl Direction { /// 获取相反方向 pub fn opposite(&self) -> Self { match self { Self::Debit => Self::Credit, Self::Credit => Self::Debit, } } } impl std::fmt::Display for Direction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Debit => write!(f, "debit"), Self::Credit => write!(f, "credit"), } } } /// 会计科目类别 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SubjectCategory { /// 资产类 Asset, /// 负债类 Liability, /// 收入类 Income, /// 支出类 Expense, } impl SubjectCategory { /// 获取默认增加方向 pub fn default_direction(&self) -> Direction { match self { Self::Asset | Self::Expense => Direction::Debit, Self::Liability | Self::Income => Direction::Credit, } } } impl std::fmt::Display for SubjectCategory { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Asset => write!(f, "asset"), Self::Liability => write!(f, "liability"), Self::Income => write!(f, "income"), Self::Expense => write!(f, "expense"), } } } /// 分录状态 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum EntryStatus { /// 待确认 Pending, /// 已过账 Posted, /// 已冲销 Reversed, } impl Default for EntryStatus { fn default() -> Self { Self::Pending } } impl std::fmt::Display for EntryStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Pending => write!(f, "pending"), Self::Posted => write!(f, "posted"), Self::Reversed => write!(f, "reversed"), } } } /// 会计科目 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccountingSubject { /// 科目代码 pub code: String, /// 科目名称 pub name: String, /// 科目类别 pub category: SubjectCategory, /// 默认增加方向 (1=借方增加, -1=贷方增加) pub direction_default: i8, /// 父科目代码 pub parent_code: Option, /// 科目级别 pub level: i32, } impl AccountingSubject { /// 预定义科目:银行存款 pub const BANK_DEPOSIT: &'static str = "1002"; /// 预定义科目:在途资金 pub const IN_TRANSIT: &'static str = "1003"; /// 预定义科目:客户存款 pub const CUSTOMER_DEPOSIT: &'static str = "2001"; /// 预定义科目:待清算款项 pub const PENDING_SETTLEMENT: &'static str = "2002"; /// 预定义科目:手续费收入 pub const FEE_INCOME: &'static str = "3001"; /// 预定义科目:利息支出 pub const INTEREST_EXPENSE: &'static str = "4001"; } /// 账户余额 - 三科目模型 /// /// 不变量约束: personal_balance + labor_balance + frozen_balance = bank_balance #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccountBalance { /// 余额ID pub id: i64, /// 账户ID pub account_id: i64, /// 账户类型 pub account_type: AccountType, // ========== 三科目余额 ========== /// 个人余额(可用) pub personal_balance: Decimal, /// 劳动报酬(可用) pub labor_balance: Decimal, /// 冻结余额(不可用) pub frozen_balance: Decimal, // ========== 银行对照 ========== /// 银行余额(对照账面) pub bank_balance: Decimal, // ========== 在途管理 ========== /// 在途金额(已从可用划转,等待银行确认) pub transit_amount: Decimal, // ========== 兼容字段(逐步废弃)========== /// 系统余额(内部记账)- 兼容旧代码 #[serde(default)] pub system_balance: Decimal, /// 可支配余额 - 兼容旧代码 #[serde(default)] pub available_balance: Decimal, /// 冻结金额 - 兼容旧代码,映射到 frozen_balance #[serde(default)] pub frozen_amount: Decimal, /// 乐观锁版本 pub version: i32, /// 更新时间 pub updated_at: DateTime, } impl AccountBalance { /// 创建新余额记录 pub fn new(account_id: i64, account_type: AccountType) -> Self { Self { id: 0, account_id, account_type, // 三科目余额 personal_balance: Decimal::ZERO, labor_balance: Decimal::ZERO, frozen_balance: Decimal::ZERO, // 银行对照 bank_balance: Decimal::ZERO, // 在途 transit_amount: Decimal::ZERO, // 兼容字段 system_balance: Decimal::ZERO, available_balance: Decimal::ZERO, frozen_amount: Decimal::ZERO, version: 0, updated_at: Utc::now(), } } /// 计算总可用余额(个人 + 劳动) pub fn total_available(&self) -> Decimal { self.personal_balance + self.labor_balance } /// 计算可支配余额(兼容旧代码) pub fn calculate_available(&self) -> Decimal { self.total_available() - self.transit_amount } /// 检查是否有足够可支配余额 pub fn has_sufficient_balance(&self, amount: Decimal) -> bool { self.calculate_available() >= amount } /// 校验不变量: personal + labor + frozen = bank_balance /// /// 返回 Ok(()) 如果不变量成立,否则返回 InvariantViolation 错误 pub fn validate_invariant(&self) -> Result<(), crate::error::AppError> { let sum = self.personal_balance + self.labor_balance + self.frozen_balance; if sum == self.bank_balance { Ok(()) } else { Err(crate::error::AppError::InvariantViolation { account_id: self.account_id, expected: self.bank_balance, actual: sum, }) } } /// 增加个人余额 pub fn add_personal_balance(&mut self, amount: Decimal) { self.personal_balance += amount; self.sync_legacy_fields(); } /// 减少个人余额 pub fn subtract_personal_balance(&mut self, amount: Decimal) { self.personal_balance -= amount; self.sync_legacy_fields(); } /// 增加劳动报酬 pub fn add_labor_balance(&mut self, amount: Decimal) { self.labor_balance += amount; self.sync_legacy_fields(); } /// 减少劳动报酬 pub fn subtract_labor_balance(&mut self, amount: Decimal) { self.labor_balance -= amount; self.sync_legacy_fields(); } /// 增加系统余额(兼容旧代码,默认增加到个人余额) pub fn add_system_balance(&mut self, amount: Decimal) { self.personal_balance += amount; self.sync_legacy_fields(); } /// 减少系统余额(兼容旧代码,按优先级从个人/劳动扣减) pub fn subtract_system_balance(&mut self, amount: Decimal) { let mut remaining = amount; // 先扣个人 let from_personal = remaining.min(self.personal_balance); self.personal_balance -= from_personal; remaining -= from_personal; // 再扣劳动 if remaining > Decimal::ZERO { let from_labor = remaining.min(self.labor_balance); self.labor_balance -= from_labor; } self.sync_legacy_fields(); } /// 冻结金额(从可用余额转移到冻结余额) /// /// 按优先级从个人余额和劳动报酬扣减 pub fn freeze(&mut self, amount: Decimal) { let mut remaining = amount; // 先从个人余额扣减 let from_personal = remaining.min(self.personal_balance); self.personal_balance -= from_personal; remaining -= from_personal; // 再从劳动报酬扣减 if remaining > Decimal::ZERO { let from_labor = remaining.min(self.labor_balance); self.labor_balance -= from_labor; } // 增加冻结余额 self.frozen_balance += amount; self.sync_legacy_fields(); } /// 解冻金额(从冻结余额转移回个人余额) pub fn unfreeze(&mut self, amount: Decimal) { let unfreeze_amount = amount.min(self.frozen_balance); self.frozen_balance -= unfreeze_amount; // 解冻默认返回到个人余额 self.personal_balance += unfreeze_amount; self.sync_legacy_fields(); } /// 设置在途金额 pub fn set_transit(&mut self, amount: Decimal) { self.transit_amount = amount; self.sync_legacy_fields(); } /// 增加在途金额 pub fn add_transit(&mut self, amount: Decimal) { self.transit_amount += amount; self.sync_legacy_fields(); } /// 减少在途金额 pub fn subtract_transit(&mut self, amount: Decimal) { self.transit_amount -= amount; if self.transit_amount < Decimal::ZERO { self.transit_amount = Decimal::ZERO; } self.sync_legacy_fields(); } /// 同步银行余额 pub fn sync_bank_balance(&mut self, amount: Decimal) { self.bank_balance = amount; } /// 同步遗留字段(保持向后兼容) fn sync_legacy_fields(&mut self) { self.system_balance = self.personal_balance + self.labor_balance; self.available_balance = self.calculate_available(); self.frozen_amount = self.frozen_balance; } // ========== 新增便捷方法(用于测试) ========== /// 增加个人余额(简化版) pub fn add_personal(&mut self, amount: Decimal) { self.personal_balance += amount; self.bank_balance += amount; self.sync_legacy_fields(); } /// 减少个人余额(带校验) pub fn subtract_personal(&mut self, amount: Decimal) -> Result<(), crate::error::AppError> { if self.personal_balance < amount { return Err(crate::error::AppError::InsufficientBalance { available: self.personal_balance, required: amount, }); } self.personal_balance -= amount; self.bank_balance -= amount; self.sync_legacy_fields(); Ok(()) } /// 增加劳动报酬(简化版) pub fn add_labor(&mut self, amount: Decimal) { self.labor_balance += amount; self.bank_balance += amount; self.sync_legacy_fields(); } /// 减少劳动报酬(带校验) pub fn subtract_labor(&mut self, amount: Decimal) -> Result<(), crate::error::AppError> { if self.labor_balance < amount { return Err(crate::error::AppError::InsufficientBalance { available: self.labor_balance, required: amount, }); } self.labor_balance -= amount; self.bank_balance -= amount; self.sync_legacy_fields(); Ok(()) } /// 可用余额(个人 + 劳动) pub fn available_balance(&self) -> Decimal { self.personal_balance + self.labor_balance } /// 总余额(三科目之和,应等于银行余额) pub fn total_balance(&self) -> Decimal { self.personal_balance + self.labor_balance + self.frozen_balance } /// 按优先级扣款: 个人 -> 劳动 /// /// 先从个人余额扣减,不足再从劳动报酬扣减。 /// 返回扣款明细,失败时余额不变。 pub fn deduct_with_priority(&mut self, amount: Decimal) -> Result { if amount.is_zero() { return Ok(DeductionResult { from_personal: Decimal::ZERO, from_labor: Decimal::ZERO, total: Decimal::ZERO, }); } let available = self.available_balance(); if available < amount { return Err(crate::error::AppError::InsufficientBalance { available, required: amount, }); } let mut remaining = amount; // 先扣个人 let from_personal = remaining.min(self.personal_balance); self.personal_balance -= from_personal; remaining -= from_personal; // 再扣劳动 let from_labor = remaining.min(self.labor_balance); self.labor_balance -= from_labor; // 同步银行余额 self.bank_balance -= amount; self.sync_legacy_fields(); Ok(DeductionResult { from_personal, from_labor, total: amount, }) } /// 结转在途(银行确认成功) /// /// 银行已确认扣款,从在途中扣除金额,银行余额应该已经同步 pub fn settle_transit(&mut self, amount: Decimal) -> Result<(), crate::error::AppError> { if self.transit_amount < amount { return Err(crate::error::AppError::BusinessRule( format!("在途金额不足: 在途 {}, 需要 {}", self.transit_amount, amount) )); } self.transit_amount -= amount; self.sync_legacy_fields(); Ok(()) } /// 回退在途(银行失败) /// /// 银行失败或超时,将在途金额返回到个人余额 pub fn rollback_transit(&mut self, amount: Decimal) { let rollback_amount = amount.min(self.transit_amount); self.transit_amount -= rollback_amount; // 返回到个人余额 self.personal_balance += rollback_amount; // 恢复银行余额 self.bank_balance += rollback_amount; self.sync_legacy_fields(); } } /// 扣款结果 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DeductionResult { /// 从个人余额扣减的金额 pub from_personal: Decimal, /// 从劳动报酬扣减的金额 pub from_labor: Decimal, /// 总扣减金额 pub total: Decimal, } /// 三账对账结果 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ThreeAccountResult { /// 银行账余额 pub bank_balance: Decimal, /// 在途净额 pub transit_net: Decimal, /// 总账余额 pub ledger_total: Decimal, /// 是否平衡 pub is_balanced: bool, /// 差异金额 pub difference: Decimal, } /// 余额组成(按会计科目分类) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BalanceComponent { /// 组成ID pub id: i64, /// 余额ID pub balance_id: i64, /// 科目代码 pub subject_code: String, /// 金额 pub amount: Decimal, } /// 记账分录(凭证头) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LedgerEntry { /// 分录ID pub id: i64, /// 分录编号 pub entry_no: String, /// 关联交易号 pub txn_no: String, /// 记账日期 pub post_date: NaiveDate, /// 记账时间 pub post_time: DateTime, /// 摘要描述 pub description: Option, /// 状态 pub status: EntryStatus, /// 创建时间 pub created_at: DateTime, } /// 分录明细(凭证行) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LedgerLine { /// 明细ID pub id: i64, /// 分录ID pub entry_id: i64, /// 账户ID pub account_id: i64, /// 账户类型 pub account_type: AccountType, /// 科目代码 pub subject_code: String, /// 借贷方向 pub direction: Direction, /// 金额 pub amount: Decimal, } /// 创建分录请求 #[derive(Debug, Clone, Deserialize)] pub struct CreateEntryRequest { /// 关联交易号 pub txn_no: String, /// 摘要描述 pub description: Option, /// 分录明细 pub lines: Vec, } /// 创建分录明细请求 #[derive(Debug, Clone, Deserialize)] pub struct CreateEntryLineRequest { /// 账户ID pub account_id: i64, /// 账户类型 pub account_type: AccountType, /// 科目代码 pub subject_code: String, /// 借贷方向 pub direction: Direction, /// 金额 pub amount: Decimal, } impl CreateEntryRequest { /// 验证借贷是否平衡 pub fn validate_balance(&self) -> Result<(), (Decimal, Decimal)> { let mut total_debit = Decimal::ZERO; let mut total_credit = Decimal::ZERO; for line in &self.lines { match line.direction { Direction::Debit => total_debit += line.amount, Direction::Credit => total_credit += line.amount, } } if total_debit == total_credit { Ok(()) } else { Err((total_debit, total_credit)) } } } /// 余额变动记录 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BalanceChange { /// 账户ID pub account_id: i64, /// 账户类型 pub account_type: AccountType, /// 变动前余额 pub before: Decimal, /// 变动后余额 pub after: Decimal, /// 变动金额 pub change: Decimal, /// 关联分录ID pub entry_id: i64, }