# 结算与财务系统设计文档

> **版本**：v2.0  
> **最后更新**：2026-05-21  
> **状态**：✅ 已实现并优化

---

## 📋 目录

1. [系统概述](#系统概述)
2. [核心概念](#核心概念)
3. [资金流转流程](#资金流转流程)
4. [平台钱包设计](#平台钱包设计)
5. [结算周期](#结算周期)
6. [服务费计算](#服务费计算)
7. [数据库设计](#数据库设计)
8. [API接口](#api接口)
9. [技术实现](#技术实现)
10. [前端页面](#前端页面)
11. [扩展方案](#扩展方案)

---

## 系统概述

结算与财务系统是平台的核心模块，负责处理订单支付、资金流转、商家结算、平台钱包和提现等业务。

### 核心功能

- ✅ 订单支付时记录平台账户收入
- ✅ 自动周结算（每周一凌晨2点）
- ✅ 手动执行周结算（管理员触发）
- ✅ 结算预览功能
- ✅ 防止重复结算
- ✅ 服务费自动计算和扣除
- ✅ 平台钱包管理
- ✅ 商家账户余额管理
- ✅ 提现申请和审核
- ✅ 完整的交易流水记录

---

## 核心概念

### 平台账户 vs 平台钱包

#### 平台账户（Platform Account）
- **定位**：资金汇总账户，记录所有资金流转
- **包含内容**：
  - 用户支付的订单金额（全额）
  - 待结算给商家的金额
  - 平台的收入（服务费、广告费等）
  - 平台的支出（商家结算、邀请返现等）

#### 平台钱包（Platform Wallet）
- **定位**：平台可支配的资金
- **包含内容**：
  - 服务费收入
  - 其他收入（广告费、会员费等）
  - 扣除：邀请返现等支出
- **计算公式**：
  ```
  钱包余额 = 账户总收入 - 账户总支出
           = (订单总额) - (商家结算 + 邀请返现)
           = (商家应得 + 服务费) - 商家应得 - 邀请返现
           = 服务费 + 其他收入 - 邀请返现
  ```

### 三方账户体系

```
系统账户体系：
├─ 平台账户（PlatformAccount）
│  └─ 主账户（全局唯一，资金中转）
├─ 商家账户（MerchantAccount）
│  └─ 每个商家一个账户
└─ 用户账户（UserAccount）
   └─ 每个用户一个账户（用于返现、提现）
```

### 可提现金额

| 角色 | 可提现金额计算 | 说明 |
|------|---------------|------|
| **商家** | `balance - frozen_balance` | 已结算金额（扣除服务费后） |
| **用户** | `balance - frozen_balance` | 邀请返现所得 |
| **平台** | `total_service_fee - frozen_balance` | 服务费收入（未来可扩展其他收入） |

---

## 资金流转流程

### 完整流程图

```
┌─────────────┐
│  用户支付   │
└──────┬──────┘
       │ 订单金额（全额）
       ↓
┌─────────────────────┐
│   平台账户收入      │
│ - 记录交易流水      │
│ - 更新账户余额      │
│ - 累加服务费        │
└──────┬──────────────┘
       │ 等待订单完成
       ↓
┌─────────────────────┐
│   商家确认离店      │
│ - 订单状态: completed│
│ - 记录离店时间      │
└──────┬──────────────┘
       │ 等待周结算
       ↓
┌─────────────────────┐
│   周结算（自动）    │
│ - 每周一凌晨2点     │
│ - 统计上周完成订单  │
│ - 计算结算金额      │
└──────┬──────────────┘
       │ 结算金额 = 订单金额 - 服务费
       ↓
┌─────────────────────┐
│   商家账户入账      │
│ - 平台账户扣减      │
│ - 商家账户增加      │
│ - 复式记账          │
└──────┬──────────────┘
       │ 商家可提现
       ↓
┌─────────────────────┐
│   商家申请提现      │
│ - 冻结余额          │
│ - 管理员审核        │
│ - 确认打款          │
└─────────────────────┘
```

### 详细流程说明

#### 1. 用户支付订单

```
用户支付 1000元
├─ 平台账户收入：+1000元
│  ├─ balance: +1000
│  ├─ total_income: +1000
│  └─ total_service_fee: +50（5%）
└─ 商家账户：无变化（等待结算）
```

**代码位置**：[order.service.ts](../../apps/server/src/modules/app/order/order.service.ts)

```typescript
await this.accountService.addPlatformBalance(
  order.payAmount,           // 订单金额（全额）
  order.serviceFee,          // 服务费（5%）
  transactionNo,
  'order_payment',
  order.id,
  order.orderNo,
  `订单 ${order.orderNo} 支付收入`
);
```

#### 2. 订单完成后周结算

```
每周一凌晨2点自动结算
├─ 计算商家应得：1000 × 95% = 950元
├─ 平台账户支出：-950元
│  ├─ balance: 1000 - 950 = 50
│  └─ total_expense: +950
├─ 商家账户收入：+950元
│  ├─ balance: +950
│  └─ total_income: +950
└─ 平台保留服务费：50元
```

**代码位置**：[settlement.service.ts](../../apps/server/src/modules/shared/finance/settlement.service.ts)

#### 3. 邀请返现（可选）

```
用户邀请新用户注册
├─ 平台账户支出：-10元
│  ├─ balance: 50 - 10 = 40
│  └─ total_expense: +10
└─ 用户账户收入：+10元
   ├─ balance: +10
   └─ total_income: +10
```

#### 4. 各方提现

```
商家提现：500元
├─ 商家账户：balance -500, frozen_balance +500
└─ 审核通过后：frozen_balance -500, total_expense +500

用户提现：10元
├─ 用户账户：balance -10, frozen_balance +10
└─ 审核通过后：frozen_balance -10, total_expense +10

平台提现：30元
├─ 平台账户：balance -30, frozen_balance +30
└─ 审核通过后：frozen_balance -30, total_expense +30
```

---

## 平台钱包设计

### 钱包余额计算

```typescript
// 钱包余额 = 账户总收入 - 账户总支出
balance = total_income - total_expense

// 展开计算
balance = (订单总额) - (商家结算 + 邀请返现)
        = (商家应得 + 服务费) - 商家应得 - 邀请返现
        = 服务费 - 邀请返现
```

### 可提现金额计算

**当前实现：**
```typescript
// 可提现金额 = 累计服务费 - 冻结余额
const withdrawableAmount = total_service_fee - frozen_balance;
```

**未来扩展：**
```typescript
// 方案1：使用钱包余额（推荐）
const withdrawableAmount = balance - frozen_balance;

// 方案2：累加所有收入类型
const withdrawableAmount = (total_service_fee + total_ad_revenue + ...) - frozen_balance;
```

### 前端展示

#### 核心指标卡片

```
┌─────────────┬─────────────┬─────────────┬─────────────┐
│ 钱包余额    │ 可提现金额  │ 冻结金额    │ 服务费收入  │
│ 40.00元     │ 40.00元     │ 0.00元      │ 50.00元     │
│ 平台可支配  │ 余额-冻结   │ 提现申请中  │ 累计服务费  │
└─────────────┴─────────────┴─────────────┴─────────────┘
```

#### 收入支出统计

```
┌─────────────────┬─────────────────┬─────────────────┐
│ 账户总收入      │ 账户总支出      │ 其他收入        │
│ 1000.00元       │ 960.00元        │ 0.00元          │
│ 所有订单支付    │ 结算+返现       │ 广告费、会员费  │
└─────────────────┴─────────────────┴─────────────────┘
```

---

## 结算周期

### 周结算机制

**执行时间**：每周一凌晨 2:00  
**结算周期**：上周一 00:00:00 ~ 上周日 23:59:59  
**结算对象**：状态为 `completed` 的订单  
**判断依据**：订单的 `checkout_at`（离店时间）字段

### 定时任务配置

**文件位置**：[settlement.schedule.ts](../../apps/server/src/schedule/settlement.schedule.ts)

```typescript
@Cron('0 2 * * 1') // 每周一凌晨2点
async handleWeeklySettlement() {
  await this.settlementService.handleWeeklySettlement();
}
```

### 结算逻辑

1. **查询上周已完成订单**
   ```typescript
   const orders = await this.orderRepo
     .createQueryBuilder('o')
     .where('o.status = :status', { status: 'completed' })
     .andWhere('o.checkout_at BETWEEN :start AND :end', {
       start: `${lastWeekStart} 00:00:00`,
       end: `${lastWeekEnd} 23:59:59`
     })
     .getMany();
   ```

2. **按商家分组统计**
   - 订单数量：`orderCount`
   - 订单总额：`orderAmount`
   - 服务费总额：`serviceFee`
   - 结算金额：`settlementAmount = orderAmount - serviceFee`

3. **生成结算单**
   - 创建 `Settlement` 记录
   - 创建 `SettlementItem` 记录

4. **执行资金转账**
   - 平台账户扣减
   - 商家账户增加
   - 复式记账

### 防止重复结算

```typescript
// 检查该周期是否已经结算过
const existingSettlements = await this.settlementRepo.count({
  where: {
    periodStart: lastWeekStart,
    periodEnd: lastWeekEnd
  }
});

if (existingSettlements > 0) {
  throw new Error(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过，无法重复结算`);
}
```

---

## 服务费计算

### 计算公式

```typescript
// 用户实付金额
payAmount = totalAmount - couponDiscount

// 服务费
serviceFee = Math.round(payAmount * serviceFeeRate * 100) / 100

// 商家实收金额
merchantIncome = payAmount - serviceFee
```

### 服务费率配置

**默认值**：5% (0.05)  
**配置位置**：`platform_configs` 表  
**配置键**：`service_fee_rate`

### 示例计算

```
房费总额：1000元
优惠券抵扣：100元
服务费率：5%

用户实付：1000 - 100 = 900元
服务费：900 × 0.05 = 45元
商家实收：900 - 45 = 855元
平台收入：45元
```

---

## 数据库设计

### 核心表结构

#### 1. platform_accounts（平台账户表）

```sql
CREATE TABLE `platform_accounts` (
  `id` BIGINT UNSIGNED AUTO_INCREMENT,
  `account_name` VARCHAR(50) COMMENT '账户名称',
  `balance` DECIMAL(12,2) DEFAULT 0 COMMENT '钱包余额',
  `frozen_balance` DECIMAL(12,2) DEFAULT 0 COMMENT '冻结余额',
  `total_income` DECIMAL(12,2) DEFAULT 0 COMMENT '账户总收入',
  `total_expense` DECIMAL(12,2) DEFAULT 0 COMMENT '账户总支出',
  `total_service_fee` DECIMAL(12,2) DEFAULT 0 COMMENT '累计服务费',
  `version` INT UNSIGNED DEFAULT 0 COMMENT '乐观锁版本号',
  `status` ENUM('active', 'frozen', 'closed') DEFAULT 'active',
  PRIMARY KEY (`id`)
);
```

**字段说明：**

| 字段 | 说明 | 当前用途 | 未来扩展 |
|------|------|----------|----------|
| `balance` | 钱包余额 | 服务费 - 邀请返现 | 所有收入 - 所有支出 |
| `frozen_balance` | 冻结余额 | 提现申请中的金额 | 同左 |
| `total_income` | 账户总收入 | 所有订单支付金额 | 订单 + 广告 + 会员等 |
| `total_expense` | 账户总支出 | 商家结算 + 邀请返现 | 同左 + 其他支出 |
| `total_service_fee` | 服务费收入 | 订单服务费累计 | 同左 |

#### 2. merchant_accounts（商家账户表）

```sql
CREATE TABLE `merchant_accounts` (
  `id` BIGINT UNSIGNED AUTO_INCREMENT,
  `merchant_id` BIGINT UNSIGNED,
  `balance` DECIMAL(12,2) DEFAULT 0 COMMENT '可用余额',
  `frozen_balance` DECIMAL(12,2) DEFAULT 0 COMMENT '冻结余额',
  `debt_amount` DECIMAL(12,2) DEFAULT 0 COMMENT '欠款金额',
  `total_income` DECIMAL(12,2) DEFAULT 0 COMMENT '累计收入',
  `total_expense` DECIMAL(12,2) DEFAULT 0 COMMENT '累计支出',
  `total_settlement` DECIMAL(12,2) DEFAULT 0 COMMENT '累计结算',
  `total_withdraw` DECIMAL(12,2) DEFAULT 0 COMMENT '累计提现',
  `pending_settlement` DECIMAL(12,2) DEFAULT 0 COMMENT '待结算',
  `last_settlement_at` DATETIME COMMENT '最后结算时间',
  `version` INT UNSIGNED DEFAULT 0,
  `status` ENUM('active', 'frozen', 'closed') DEFAULT 'active',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_merchant_id` (`merchant_id`)
);
```

#### 3. settlements（结算单表）

```sql
CREATE TABLE `settlements` (
  `id` BIGINT UNSIGNED AUTO_INCREMENT,
  `settlement_no` VARCHAR(32) UNIQUE,
  `merchant_id` BIGINT UNSIGNED,
  `period_start` DATE,
  `period_end` DATE,
  `order_count` INT UNSIGNED DEFAULT 0,
  `order_amount` DECIMAL(12,2) DEFAULT 0,
  `service_fee` DECIMAL(12,2) DEFAULT 0,
  `settlement_amount` DECIMAL(12,2) DEFAULT 0,
  `status` ENUM('pending','settled','failed'),
  `settled_at` DATETIME,
  PRIMARY KEY (`id`),
  KEY `idx_merchant_id` (`merchant_id`),
  KEY `idx_period` (`period_start`, `period_end`)
);
```

#### 4. platform_transactions（平台交易流水表）

```sql
CREATE TABLE `platform_transactions` (
  `id` BIGINT UNSIGNED AUTO_INCREMENT,
  `transaction_no` VARCHAR(32) UNIQUE,
  `account_id` BIGINT UNSIGNED,
  `direction` ENUM('income','expense'),
  `amount` DECIMAL(12,2),
  `balance_before` DECIMAL(12,2),
  `balance_after` DECIMAL(12,2),
  `transaction_type` VARCHAR(50),
  `business_type` VARCHAR(50),
  `business_id` BIGINT UNSIGNED,
  `business_no` VARCHAR(32),
  `related_account_type` ENUM('merchant','user'),
  `related_account_id` BIGINT UNSIGNED,
  `remark` VARCHAR(255),
  PRIMARY KEY (`id`),
  KEY `idx_account_id` (`account_id`),
  KEY `idx_business` (`business_type`, `business_id`)
);
```

---

## API接口

### 平台钱包接口

#### 1. 查询平台账户

**接口**：`GET /api/admin/finance/platform-accounts`  
**权限**：平台管理员

**响应示例**：
```json
{
  "id": 1,
  "account_name": "主账户",
  "balance": 40.00,
  "frozen_balance": 0.00,
  "total_income": 1000.00,
  "total_expense": 960.00,
  "total_service_fee": 50.00,
  "status": "active"
}
```

#### 2. 平台申请提现

**接口**：`POST /api/admin/finance/platform-withdrawals`  
**权限**：平台管理员

**请求参数**：
```json
{
  "amount": 30.00,
  "bankName": "中国工商银行",
  "bankAccount": "6222021234567890123",
  "accountName": "某某科技有限公司",
  "remark": "提现备注"
}
```

### 结算管理接口

#### 3. 预览周结算

**接口**：`GET /api/admin/finance/settlements/preview-weekly`  
**权限**：平台管理员

**响应示例**：
```json
{
  "periodStart": "2026-05-13",
  "periodEnd": "2026-05-19",
  "totalOrders": 45,
  "totalAmount": 35000.00,
  "totalServiceFee": 1750.00,
  "totalSettlementAmount": 33250.00,
  "merchants": [...]
}
```

#### 4. 执行周结算

**接口**：`POST /api/admin/finance/settlements/execute-weekly`  
**权限**：平台管理员

---

## 技术实现

### 核心服务

#### 1. AccountService（账户服务）

**文件位置**：[account.service.ts](../../apps/server/src/modules/shared/finance/account.service.ts)

**核心方法**：

```typescript
// 平台账户增加余额（订单支付）
async addPlatformBalance(
  amount: number,
  serviceFee: number,
  transactionNo: string,
  businessType: string,
  businessId: number,
  businessNo: string,
  remark: string,
): Promise<void> {
  // 更新字段：
  // - balance: +amount
  // - total_income: +amount
  // - total_service_fee: +serviceFee
}

// 平台账户扣减余额（结算/返现）
async deductPlatformBalance(
  amount: number,
  transactionNo: string,
  transactionType: string,
  businessType: string,
  businessId: number,
  businessNo: string,
  remark: string,
): Promise<void> {
  // 更新字段：
  // - balance: -amount
  // - total_expense: +amount
}
```

#### 2. WithdrawalService（提现服务）

**文件位置**：[withdrawal.service.ts](../../apps/server/src/modules/shared/finance/withdrawal.service.ts)

**平台提现逻辑**：

```typescript
async createPlatformWithdrawal(dto: {
  amount: number;
  bankName: string;
  bankAccount: string;
  accountName: string;
}) {
  const account = await this.accountService.getPlatformAccount();

  // 计算可提现金额
  const availableAmount = Number(account.total_service_fee) - Number(account.frozen_balance);

  if (availableAmount < amount) {
    throw new BadRequestException(`可提现金额不足，当前可提现：${availableAmount.toFixed(2)}元`);
  }

  // 冻结余额
  account.balance -= amount;
  account.frozen_balance += amount;
  
  // 创建提现记录
  // ...
}
```

### 并发控制

#### 悲观锁

```typescript
const account = await queryRunner.manager.findOne(PlatformAccount, {
  where: { account_name: '主账户' },
  lock: { mode: 'pessimistic_write' }  // 行级锁
});
```

#### 乐观锁

```typescript
await this.accountRepo.update(
  { id: accountId, version: currentVersion },
  { balance: newBalance, version: currentVersion + 1 }
);
```

### 复式记账

每笔转账生成两条流水记录：
- 一条支出记录（平台账户）
- 一条收入记录（商家账户）

两条流水的 `transaction_no` 相同，通过 `related_account_type` 和 `related_account_id` 关联。

---

## 前端页面

### 1. 平台钱包页面

**文件位置**：[PlatformWallet.tsx](../../apps/platform-admin/src/pages/finance/PlatformWallet.tsx)

**功能特性**：
- 钱包余额、可提现金额、冻结金额、服务费收入展示
- 账户总收入、总支出、其他收入统计
- 钱包详情查看
- 申请提现功能（集成在页面内）

### 2. 平台交易记录

**文件位置**：[PlatformTransactions.tsx](../../apps/platform-admin/src/pages/finance/PlatformTransactions.tsx)

**功能特性**：
- 交易流水查询（按流水号、方向、类型、时间筛选）
- 交易详情展示
- 分页展示

### 3. 结算管理

**文件位置**：[Settlements.tsx](../../apps/platform-admin/src/pages/finance/Settlements.tsx)

**功能特性**：
- 结算单列表查询
- 结算单详情查看
- 预览周结算数据
- 手动执行周结算

### 4. 商家提现审核

**文件位置**：[Withdrawals.tsx](../../apps/platform-admin/src/pages/finance/Withdrawals.tsx)

**功能特性**：
- 商家提现申请列表
- 审核通过/拒绝
- 确认打款

---

## 扩展方案

### 未来增加其他收入

#### 1. 数据库增加字段

```sql
ALTER TABLE platform_accounts 
ADD COLUMN total_ad_revenue DECIMAL(12,2) DEFAULT 0 COMMENT '累计广告收入',
ADD COLUMN total_membership_fee DECIMAL(12,2) DEFAULT 0 COMMENT '累计会员费收入',
ADD COLUMN total_other_income DECIMAL(12,2) DEFAULT 0 COMMENT '累计其他收入';
```

#### 2. 后端增加方法

```typescript
// 广告收入
async addPlatformAdRevenue(amount: number, ...): Promise<void> {
  account.balance += amount;
  account.total_income += amount;
  account.total_ad_revenue += amount;
}

// 会员费收入
async addPlatformMembershipFee(amount: number, ...): Promise<void> {
  account.balance += amount;
  account.total_income += amount;
  account.total_membership_fee += amount;
}
```

#### 3. 前端增加展示

```typescript
<Statistic
  title="广告收入"
  value={account.total_ad_revenue}
  precision={2}
  suffix="元"
/>

<Statistic
  title="会员费收入"
  value={account.total_membership_fee}
  precision={2}
  suffix="元"
/>
```

#### 4. 调整可提现计算

```typescript
// 方案1：继续使用各项收入累加
const withdrawableAmount = (
  total_service_fee + 
  total_ad_revenue + 
  total_membership_fee
) - frozen_balance;

// 方案2：直接使用钱包余额（推荐）
const withdrawableAmount = balance - frozen_balance;
```

**推荐方案2**，因为：
- `balance` 已经包含了所有收入和支出
- 不需要每次增加新收入类型都修改代码
- 更简洁、更易维护

---

## 数据一致性保证

### 1. 事务保证

所有涉及金额变动的操作都在事务中执行：
- 订单支付：订单状态更新 + 平台账户收入
- 结算转账：平台账户扣减 + 商家账户增加 + 结算单生成
- 提现：余额冻结/扣减 + 提现记录更新

### 2. 乐观锁

账户表使用 `version` 字段实现乐观锁，防止并发更新导致余额错误。

### 3. 复式记账

每笔转账生成两条流水记录，确保资金流向可追溯。

### 4. CHECK 约束

```sql
CONSTRAINT `chk_balance` CHECK (`balance` >= 0)
CONSTRAINT `chk_frozen_balance` CHECK (`frozen_balance` >= 0)
```

---

## 相关文档

- [项目需求文档](../requirements/项目需求文档.md)
- [数据库设计](../database/finance-database.md)
- [开发总结](../DEVELOPMENT_SUMMARY.md)

---

**维护团队**：开发团队  
**最后更新**：2026-05-21
