# 账务域 (Ledger Domain) ## 一、领域概述 账务域是系统的核心财务引擎,负责余额管理、复式记账、三科目约束校验。该域实现了银行级别的资金安全保障机制。 ## 二、核心概念:三科目余额模型 ### 2.1 模型定义 ``` ┌─────────────────────────────────────────────────────────┐ │ 银行余额 (bank_balance) │ │ = 1000.00 │ ├─────────────────────────────────────────────────────────┤ │ 个人余额 │ 劳动报酬 │ 冻结余额 │ │ personal │ labor │ frozen │ │ = 500.00 │ = 300.00 │ = 200.00 │ │ (可用) │ (可用) │ (不可用) │ └─────────────────────────────────────────────────────────┘ 不变量约束: personal + labor + frozen = bank_balance ``` ### 2.2 余额类型说明 | 余额类型 | 字段 | 说明 | 可用性 | |----------|------|------|--------| | 个人余额 | personal_balance | 个人可支配资金 | 可用 | | 劳动报酬 | labor_balance | 劳动所得报酬 | 可用 | | 冻结余额 | frozen_balance | 被冻结的资金 | 不可用 | | 银行余额 | bank_balance | 银行账面余额 | 对照 | | 在途金额 | transit_amount | 已扣未确认 | 过渡 | ### 2.3 可用余额计算 ```rust // 总可用余额 = 个人 + 劳动 fn total_available(&self) -> Decimal { self.personal_balance + self.labor_balance } // 可支配余额 = 总可用 - 在途 fn calculate_available(&self) -> Decimal { self.total_available() - self.transit_amount } ``` ## 三、核心实体 ### 3.1 账户余额 (AccountBalance) ```rust pub struct AccountBalance { pub id: i64, 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, // 在途金额 // 版本控制 pub version: i32, // 乐观锁版本 pub updated_at: DateTime, } ``` ### 3.2 会计科目 (AccountingSubject) ```rust pub struct AccountingSubject { pub code: String, // 科目代码 pub name: String, // 科目名称 pub category: SubjectCategory, // 科目类别 pub direction_default: i8, // 默认增加方向 pub parent_code: Option,// 父科目代码 pub level: i32, // 科目级别 } ``` **预定义科目**: | 代码 | 名称 | 类别 | |------|------|------| | 1002 | 银行存款 | 资产 | | 1003 | 在途资金 | 资产 | | 2001 | 客户存款 | 负债 | | 2002 | 待清算款项 | 负债 | | 3001 | 手续费收入 | 收入 | | 4001 | 利息支出 | 支出 | ### 3.3 记账分录 (LedgerEntry) ```rust pub struct LedgerEntry { 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, } ``` ### 3.4 分录明细 (LedgerLine) ```rust pub struct LedgerLine { pub id: i64, pub entry_id: i64, pub account_id: i64, pub account_type: AccountType, pub subject_code: String, // 科目代码 pub direction: Direction, // 借贷方向 pub amount: Decimal, // 金额 } ``` ## 四、枚举类型 ### 4.1 借贷方向 (Direction) ```rust pub enum Direction { Debit, // 借方 Credit, // 贷方 } ``` ### 4.2 科目类别 (SubjectCategory) ```rust pub enum SubjectCategory { Asset, // 资产类 - 借方增加 Liability, // 负债类 - 贷方增加 Income, // 收入类 - 贷方增加 Expense, // 支出类 - 借方增加 } ``` ### 4.3 分录状态 (EntryStatus) ```rust pub enum EntryStatus { Pending, // 待确认 Posted, // 已过账 Reversed, // 已冲销 } ``` ## 五、核心业务逻辑 ### 5.1 优先级扣款 出金时按优先级从余额中扣减:**先个人,后劳动**。 ```rust pub fn deduct_with_priority(&mut self, amount: Decimal) -> Result { let available = self.available_balance(); if available < amount { return Err(AppError::InsufficientBalance { available, required: amount }); } let mut remaining = amount; // 1. 先扣个人余额 let from_personal = remaining.min(self.personal_balance); self.personal_balance -= from_personal; remaining -= from_personal; // 2. 再扣劳动报酬 let from_labor = remaining.min(self.labor_balance); self.labor_balance -= from_labor; // 3. 同步银行余额 self.bank_balance -= amount; Ok(DeductionResult { from_personal, from_labor, total: amount }) } ``` **扣款示例**: | 场景 | 扣款金额 | 个人余额 | 劳动余额 | 扣款来源 | |------|----------|----------|----------|----------| | 1 | 300 | 500 | 200 | 个人300 | | 2 | 600 | 500 | 200 | 个人500 + 劳动100 | | 3 | 800 | 500 | 200 | 失败(余额不足) | ### 5.2 冻结/解冻 冻结操作将可用余额转移到冻结余额,按优先级从个人和劳动中扣减。 ```rust // 冻结 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; } // 解冻(默认返回到个人余额) 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; } ``` ### 5.3 在途管理 在途金额表示已从可用余额扣减但尚未得到银行确认的资金。 ```rust // 建立在途(出金时) pub fn add_transit(&mut self, amount: Decimal) { self.transit_amount += amount; } // 结转在途(银行确认成功) pub fn settle_transit(&mut self, amount: Decimal) -> Result<()> { if self.transit_amount < amount { return Err(AppError::BusinessRule("在途金额不足".into())); } self.transit_amount -= amount; Ok(()) } // 回退在途(银行失败) pub fn rollback_transit(&mut self, amount: Decimal) { let rollback = amount.min(self.transit_amount); self.transit_amount -= rollback; self.personal_balance += rollback; // 返回到个人余额 self.bank_balance += rollback; // 恢复银行余额 } ``` ### 5.4 不变量校验 确保三科目之和等于银行余额。 ```rust pub fn validate_invariant(&self) -> Result<()> { let sum = self.personal_balance + self.labor_balance + self.frozen_balance; if sum == self.bank_balance { Ok(()) } else { Err(AppError::InvariantViolation { account_id: self.account_id, expected: self.bank_balance, actual: sum, }) } } ``` ## 六、领域服务 ### 6.1 LedgerService ```rust impl LedgerService { // ========== 余额操作 ========== // 获取账户余额 pub async fn get_balance(&self, account_id: i64, account_type: AccountType) -> Result; // 冻结金额 pub async fn freeze_amount(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>; // 解冻金额 pub async fn unfreeze_amount(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>; // 优先级扣款 pub async fn deduct_with_priority(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result; // 建立在途 pub async fn add_transit(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>; // 结转在途 pub async fn settle_transit(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>; // 回退在途 pub async fn rollback_transit(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>; // ========== 记账操作 ========== // 创建分录 pub async fn create_entry(&self, req: CreateEntryRequest) -> Result; // 获取账户分录 pub async fn get_account_entries(&self, account_id: i64, account_type: AccountType) -> Result>; // ========== 科目操作 ========== // 获取所有科目 pub async fn list_subjects(&self) -> Result>; // 初始化预定义科目 pub async fn initialize_subjects(&self) -> Result<()>; // ========== 校验操作 ========== // 校验不变量 pub async fn validate_invariant(&self, account_id: i64, account_type: AccountType) -> Result<()>; } ``` ## 七、复式记账 ### 7.1 记账原则 1. **借贷必相等**:每笔分录的借方金额总和必须等于贷方金额总和 2. **有借必有贷**:每笔分录至少包含一个借方和一个贷方 3. **科目对应**:资产/费用借增贷减,负债/收入贷增借减 ### 7.2 记账示例 **存款入账**: ``` 借: 银行存款 1002 1000.00 贷: 客户存款 2001 1000.00 ``` **转账出金**: ``` 借: 客户存款 2001 500.00 贷: 银行存款 1002 500.00 ``` **收取手续费**: ``` 借: 客户存款 2001 10.00 贷: 手续费收入 3001 10.00 ``` ### 7.3 分录验证 ```rust 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)) } } } ``` ## 八、API 接口 | 方法 | 路径 | 说明 | |------|------|------| | GET | /api/v1/ledger/subjects | 获取会计科目列表 | | GET | /api/v1/ledger/entries/:id | 获取分录详情 | | GET | /api/v1/ledger/accounts/:id/entries | 获取账户分录列表 | ## 九、数据库表结构 ### 9.1 account_balance 表 ```sql CREATE TABLE account_balance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_id BIGINT NOT NULL, account_type VARCHAR(32) NOT NULL, -- 三科目余额 personal_balance DECIMAL(20, 2) DEFAULT 0.00, labor_balance DECIMAL(20, 2) DEFAULT 0.00, frozen_balance DECIMAL(20, 2) DEFAULT 0.00, -- 银行对照 bank_balance DECIMAL(20, 2) DEFAULT 0.00, -- 在途管理 transit_amount DECIMAL(20, 2) DEFAULT 0.00, -- 兼容字段 system_balance DECIMAL(20, 2) DEFAULT 0.00, available_balance DECIMAL(20, 2) DEFAULT 0.00, frozen_amount DECIMAL(20, 2) DEFAULT 0.00, version INT DEFAULT 0, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE INDEX idx_account (account_id, account_type) ); ``` ### 9.2 ledger_entry 表 ```sql CREATE TABLE ledger_entry ( id BIGINT PRIMARY KEY AUTO_INCREMENT, entry_no VARCHAR(64) NOT NULL UNIQUE, txn_no VARCHAR(64) NOT NULL, post_date DATE NOT NULL, post_time TIMESTAMP NOT NULL, description TEXT, status VARCHAR(32) DEFAULT 'pending', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_txn_no (txn_no), INDEX idx_post_date (post_date) ); ``` ### 9.3 ledger_line 表 ```sql CREATE TABLE ledger_line ( id BIGINT PRIMARY KEY AUTO_INCREMENT, entry_id BIGINT NOT NULL, account_id BIGINT NOT NULL, account_type VARCHAR(32) NOT NULL, subject_code VARCHAR(32) NOT NULL, direction VARCHAR(16) NOT NULL, amount DECIMAL(20, 2) NOT NULL, FOREIGN KEY (entry_id) REFERENCES ledger_entry(id), INDEX idx_account (account_id, account_type), INDEX idx_subject (subject_code) ); ``` ## 十、三账校验 ### 10.1 校验公式 ``` 总账余额 = 银行账余额 + 在途净额 即: ledger_total = bank_balance + transit_net ``` ### 10.2 校验结果 ```rust pub struct ThreeAccountResult { pub bank_balance: Decimal, // 银行账余额 pub transit_net: Decimal, // 在途净额 pub ledger_total: Decimal, // 总账余额 pub is_balanced: bool, // 是否平衡 pub difference: Decimal, // 差异金额 } ``` ### 10.3 不平衡处理 1. **记录差异**:生成差异报告 2. **人工审核**:提交人工对账 3. **自动调整**:小额差异自动调平 4. **告警通知**:大额差异触发告警