tangweijie 1460187516 docs: 添加完整技术文档体系
- 系统架构文档 (architecture/README.md)
- 6个领域文档:
  - 账户域 (01-account.md)
  - 账务域 (02-ledger.md)
  - 交易域 (03-transaction.md)
  - 对账域 (04-reconciliation.md)
  - 补偿域 (05-compensation.md)
  - 积分域 (06-points.md)
- API 参考文档 (api/README.md)
- 前后端对接清单 (integration/frontend-backend.md)
2026-01-05 18:12:37 +08:00

13 KiB
Raw Permalink Blame History

账务域 (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 可用余额计算

// 总可用余额 = 个人 + 劳动
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)

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<Utc>,
}

3.2 会计科目 (AccountingSubject)

pub struct AccountingSubject {
    pub code: String,               // 科目代码
    pub name: String,               // 科目名称
    pub category: SubjectCategory,  // 科目类别
    pub direction_default: i8,      // 默认增加方向
    pub parent_code: Option<String>,// 父科目代码
    pub level: i32,                 // 科目级别
}

预定义科目

代码 名称 类别
1002 银行存款 资产
1003 在途资金 资产
2001 客户存款 负债
2002 待清算款项 负债
3001 手续费收入 收入
4001 利息支出 支出

3.3 记账分录 (LedgerEntry)

pub struct LedgerEntry {
    pub id: i64,
    pub entry_no: String,          // 分录编号
    pub txn_no: String,            // 关联交易号
    pub post_date: NaiveDate,      // 记账日期
    pub post_time: DateTime<Utc>,  // 记账时间
    pub description: Option<String>,// 摘要描述
    pub status: EntryStatus,       // 状态
    pub created_at: DateTime<Utc>,
}

3.4 分录明细 (LedgerLine)

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)

pub enum Direction {
    Debit,  // 借方
    Credit, // 贷方
}

4.2 科目类别 (SubjectCategory)

pub enum SubjectCategory {
    Asset,     // 资产类 - 借方增加
    Liability, // 负债类 - 贷方增加
    Income,    // 收入类 - 贷方增加
    Expense,   // 支出类 - 借方增加
}

4.3 分录状态 (EntryStatus)

pub enum EntryStatus {
    Pending,  // 待确认
    Posted,   // 已过账
    Reversed, // 已冲销
}

五、核心业务逻辑

5.1 优先级扣款

出金时按优先级从余额中扣减:先个人,后劳动

pub fn deduct_with_priority(&mut self, amount: Decimal) -> Result<DeductionResult> {
    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 冻结/解冻

冻结操作将可用余额转移到冻结余额,按优先级从个人和劳动中扣减。

// 冻结
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 在途管理

在途金额表示已从可用余额扣减但尚未得到银行确认的资金。

// 建立在途(出金时)
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 不变量校验

确保三科目之和等于银行余额。

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

impl LedgerService {
    // ========== 余额操作 ==========
    
    // 获取账户余额
    pub async fn get_balance(&self, account_id: i64, account_type: AccountType) -> Result<AccountBalance>;
    
    // 冻结金额
    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<DeductionResult>;
    
    // 建立在途
    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<LedgerEntry>;
    
    // 获取账户分录
    pub async fn get_account_entries(&self, account_id: i64, account_type: AccountType) -> Result<Vec<LedgerEntry>>;
    
    // ========== 科目操作 ==========
    
    // 获取所有科目
    pub async fn list_subjects(&self) -> Result<Vec<AccountingSubject>>;
    
    // 初始化预定义科目
    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 分录验证

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 表

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 表

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 表

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 校验结果

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. 告警通知:大额差异触发告警