CaiWuXiTong/docs/ecommerce-finance-development.md

338 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 电商财务扩展开发说明
## 1. 文档目的
本文定义在当前 Akaunting 系统中建设电商财务能力的开发边界、数据模型、接口及交付顺序。目标是让订单、平台结算单、支付/银行流水、履约成本和现有财务交易形成可追溯链路,提供店铺、平台、SKU 和日期维度的利润与资金视图。
本文是实现说明,不替代会计准则、税务政策或各平台开放接口文档。收入确认、税率和费用承担规则必须由财务负责人确认后配置。
## 2. 当前系统基线
### 2.1 技术与扩展方式
| 项目 | 当前实现 | 本次约束 |
| --- | --- | --- |
| 后端 | PHP 8.1、Laravel 10 | 使用 Eloquent、FormRequest、Job、Event/Listener 的既有模式 |
| 前端 | Vue 2、Element UI、Laravel Mix | 新页面沿用现有后台布局和 API 调用方式 |
| 财务主数据 | `companies`、`contacts`、`items`、`taxes`、`categories`、`currencies` | 复用,不复制一套客户、供应商、商品、税率或币种表 |
| 资金 | `accounts`、`transactions`、`reconciliations`、`transfers` | 银行/支付账户和已确认收付款继续使用这些表 |
| 单据 | `documents`、`document_items`、`document_totals` | 发票、账单和人工应收应付继续使用;电商原始订单不直接写入 `documents` |
| 多组织 | `company_id` 全局作用域和 `{company_id}` 路由前缀 | 所有新增业务表必须带 `company_id`,所有查询必须公司隔离 |
| 模块机制 | `modules/*`、`module.json`、`Route::admin` / `Route::api` | 新能力以独立模块交付,避免修改核心业务表语义 |
### 2.2 现有对象的复用边界
| 现有对象 | 电商使用方式 | 不应承担的职责 |
| --- | --- | --- |
| `Item` | SKU 主数据映射、基础采购/销售属性 | 订单行快照、平台 SKU 映射、历史成本 |
| `Contact` | 客户、供应商、平台主体或服务商 | 海量 C 端买家主数据 |
| `Account` | 银行、支付宝、微信、平台钱包等资金账户 | 平台订单账和待结算明细 |
| `Transaction` | 已到账流水、平台打款、费用付款、调整分录来源 | 原始平台订单、预计收入或结算差异 |
| `Document` | 发票、账单、人工应收应付单 | 以订单为粒度的交易事件存储 |
| `Reconciliation` | 银行账期对账与结账凭据 | 平台订单与平台结算单的业务对账 |
> 设计原则:电商原始数据保留在扩展表,只有已经确认的资金或需要进入现有应收应付流程的数据才投影到核心表。这样既能重放核算,又不会污染现有单据和交易状态机。
## 3. 范围与分期
### 3.1 首期(MVP)
1. 店铺和平台配置,以及平台账单/订单文件导入。
2. 订单、退款、结算单、结算明细的幂等入库和原始文件留档。
3. 订单与结算明细匹配、差异队列、人工确认及审计记录。
4. SKU 成本导入,按移动加权平均或指定期间成本计算订单商品成本。
5. 店铺、平台、SKU、日期维度的收入、退款、费用、毛利、净利润和待结算报表。
6. 已确认的结算打款创建或匹配既有 `transactions`,并复用 `reconciliations` 做资金对账。
### 3.2 后续迭代
- 采购入库、库存批次、调拨、盘点及精确存货成本。
- 广告账户/达人佣金接口与活动级费用归因。
- 自动凭证、科目体系、期间关账、发票和税务台账。
- 多币种重估、跨主体内部结算、合并报表。
## 4. 目标架构
```mermaid
flowchart LR
P["平台 API / CSV 账单"] --> I["导入与标准化"]
I --> O["交易账: 订单与退款"]
I --> S["结算账: 结算单与明细"]
C["成本导入 / 后续 WMS"] --> K["成本账"]
O --> M["匹配与利润核算"]
S --> M
K --> M
S --> T["既有 Transaction / Account"]
T --> R["既有 Reconciliation"]
M --> D["利润、待结算、差异报表"]
```
系统分为五个逻辑账:
| 逻辑账 | 权威数据 | 主要输出 |
| --- | --- | --- |
| 交易账 | 订单、订单行、退款事件 | 实收、优惠承担、退款、订单状态 |
| 结算账 | 平台结算单及结算明细 | 应结金额、佣金、手续费、待结算、结算差异 |
| 成本账 | 成本快照、费用分摊 | 商品成本、物流/包材/广告等费用 |
| 资金账 | 核心 `transactions`、`accounts`、`reconciliations` | 已到账、账户余额、银行对账差异 |
| 管理利润账 | 可重算的日汇总/订单利润快照 | 毛利、净利润、经营分析 |
## 5. 模块设计
模块别名为 `ecommerce-finance`,目录建议如下:
```text
modules/EcommerceFinance/
Config/ecommerce-finance.php
Database/Migrations/
Http/Controllers/
Http/Controllers/Api/
Http/Requests/
Jobs/Import/
Jobs/Reconciliation/
Jobs/Profit/
Listeners/
Models/
Providers/Event.php
Providers/Main.php
Reports/
Resources/lang/zh-CN/
Resources/views/
Routes/admin.php
Routes/api.php
Tests/Feature/
module.json
```
- `Providers/Main.php`:加载迁移、路由、翻译、视图和配置。
- `Providers/Event.php`:订阅核心交易创建/更新和公司切换事件;不要用监听器同步执行耗时核算。
- `Routes/admin.php`:页面和导入操作,使用 `Route::admin('ecommerce-finance', ...)`。
- `Routes/api.php`:数据查询、导入任务、匹配、确认和报表,使用 `Route::api('ecommerce-finance', ...)`。
- `Jobs/*`:文件解析、批量写入、匹配、利润重算全部异步队列化。
- `Reports/*`:复用系统报表扩展点,输出表格和导出数据。
## 6. 数据模型
### 6.1 表清单
所有金额字段使用 `decimal(20, 6)`,不得使用 `float`。所有业务表都包含 `id`、`company_id`、`created_by`、`created_from`、`created_at`、`updated_at`;需要保留删除痕迹的主数据表使用软删除。
| 表 | 核心字段 | 说明与索引 |
| --- | --- | --- |
| `ec_channels` | `code`, `name`, `type`, `enabled` | 平台定义,如淘宝、京东、抖音;唯一索引 `(company_id, code)` |
| `ec_stores` | `channel_id`, `name`, `external_id`, `settlement_account_id`, `timezone` | 店铺/广告主体;唯一索引 `(company_id, channel_id, external_id)` |
| `ec_sku_mappings` | `store_id`, `external_sku_id`, `external_sku_code`, `item_id` | 平台 SKU 到核心 `items.id` 的映射;唯一索引 `(store_id, external_sku_id)` |
| `ec_import_batches` | `store_id`, `source_type`, `source_file`, `checksum`, `status`, `row_count`, `error_count` | 原始文件和导入任务;唯一索引 `(company_id, source_type, checksum)` |
| `ec_orders` | `store_id`, `external_order_id`, `order_status`, `ordered_at`, `paid_at`, `shipped_at`, `completed_at`, `currency_code`, `gross_amount`, `buyer_paid_amount`, `platform_subsidy_amount`, `merchant_subsidy_amount`, `refunded_amount` | 原始订单头;唯一索引 `(store_id, external_order_id)`;索引状态和业务日期 |
| `ec_order_items` | `order_id`, `external_line_id`, `item_id`, `sku_code`, `quantity`, `sale_amount`, `discount_amount`, `refund_amount`, `cost_amount` | 订单行与成本快照;唯一索引 `(order_id, external_line_id)` |
| `ec_order_events` | `order_id`, `event_type`, `event_at`, `external_event_id`, `payload` | 订单和退款事件的不可变记录;唯一索引 `(order_id, event_type, external_event_id)` |
| `ec_settlements` | `store_id`, `external_settlement_id`, `period_start`, `period_end`, `settled_at`, `gross_amount`, `fee_amount`, `refund_amount`, `net_amount`, `status`, `transaction_id` | 平台结算单;唯一索引 `(store_id, external_settlement_id)`;`transaction_id` 关联核心流水 |
| `ec_settlement_lines` | `settlement_id`, `line_type`, `external_line_id`, `external_order_id`, `amount`, `occurred_at`, `payload` | 货款、佣金、运费、退款、服务费等明细;唯一索引 `(settlement_id, external_line_id)` |
| `ec_reconciliation_matches` | `settlement_line_id`, `order_id`, `match_type`, `matched_amount`, `status`, `rule_version`, `confirmed_by`, `confirmed_at` | 订单与结算明细映射;索引 `(company_id, status)`、`order_id` |
| `ec_cost_snapshots` | `item_id`, `store_id`, `effective_at`, `cost_method`, `unit_cost`, `source_type`, `source_id` | 可追溯的 SKU 成本来源;索引 `(company_id, item_id, effective_at)` |
| `ec_expense_allocations` | `source_type`, `source_id`, `target_type`, `target_id`, `allocation_rule`, `amount`, `occurred_at` | 物流、包材、广告、佣金等费用分摊 |
| `ec_profit_snapshots` | `business_date`, `scope_type`, `scope_id`, `gross_revenue`, `refund_amount`, `product_cost`, `platform_fee`, `fulfillment_cost`, `marketing_cost`, `net_profit`, `calculation_version` | 日级可重算汇总;唯一索引 `(company_id, business_date, scope_type, scope_id, calculation_version)` |
| `ec_period_locks` | `period`, `lock_type`, `locked_at`, `locked_by`, `calculation_version` | 电商月结锁定;唯一索引 `(company_id, period, lock_type)` |
| `ec_audit_logs` | `subject_type`, `subject_id`, `action`, `before_data`, `after_data`, `actor_id` | 人工匹配、规则修改、解锁和重算审计 |
### 6.2 关联规则
```text
Company 1--N Channel 1--N Store
Store 1--N Order 1--N OrderItem
Store 1--N Settlement 1--N SettlementLine
Order N--N SettlementLine 通过 ReconciliationMatch
Item 1--N SkuMapping / CostSnapshot
Settlement 0..1--1 Transaction
```
`ec_orders`、`ec_settlements` 和 `ec_settlement_lines` 只追加或以同一外部唯一键更新;原始 `payload` 永久留存。任何人工修改必须写入 `ec_audit_logs`,不得覆盖原始数据。
### 6.3 多上游数据边界
- 页面请求不得实时连接多个上游数据库做 JOIN;项目正式使用的上游表先按来源同步到 `caiwu_business_mirror`,再由共享镜像表生成页面和报表。
- 新的物化数据必须能追溯 `source_key`、上游业务键和 `synced_at`;若现有表没有对应列,应单独提交迁移并获得数据库变更授权,不能把来源信息只藏在日志里。
- 同一上游业务键的重复同步必须幂等;不同来源出现相同业务键时不得互相覆盖。
- 上游金额进入本地后仍按本系统的 `decimal`、公司权限、期间锁定和审计规则校验,上游返回值不能直接成为不可追溯的最终财务结果。
普通开发机不以直连上游作为日常页面运行前提。需要尚未存在于共享业务镜像的新表或字段时:
1. 任务说明先列出来源、业务目的、表/视图、字段、过滤与排序、上游业务键、增量游标、最大批次和权限范围;
2. 上游 Repository 只能通过 `UpstreamReadOnlyQuery` 执行固定参数化只读查询;正式页面通过镜像 Repository 读取 `caiwu_business_mirror`;
3. 同一提交必须完成镜像表、断点同步器、同步状态和页面切换,并覆盖幂等、分页、筛选、首次未完成、空数据和上游不可用测试;
4. 功能分支使用脱敏 fixture 开发;只有合并并部署到 `main` 的查询才允许由生产刷新节点执行;
5. 部署代码后经授权执行首次全量同步,对比上游限定计数、镜像总数、第二页或末页和同步时间,第二个应用实例验证复用;
6. 禁止把任意 SQL 存进系统数据库、开放全局 `*`、为了开发便利分发凭据,或让每位开发者各自周期性扫描上游。
## 7. 核心业务规则
### 7.1 订单金额口径
```text
订单净收入 = 买家实付 + 平台承担优惠 - 退款
订单毛利 = 订单净收入 - 商品销售成本
订单净利润 = 订单毛利 - 平台佣金 - 支付手续费 - 履约费用 - 营销费用 - 售后赔付
```
`merchant_subsidy_amount` 是商家承担的折扣,只影响订单净收入的拆解,不得与平台承担优惠混合。费用以结算明细为准;尚未出现在结算单中的费用,报表应标记为“预估”或不计入已结算利润。
### 7.2 订单状态
```text
pending_payment -> paid -> shipped -> completed
\-> cancelled
completed -> refund_pending -> refunded / partially_refunded
```
- 订单状态只允许前进;平台修正历史时新增 `ec_order_events`。
- `cancelled` 订单不确认收入和成本。
- 部分退款按订单行优先核算;无法定位行项目时,按原订单行销售额比例分摊并标记 `allocation_rule=proportional`。
- 首期默认以 `completed_at` 所在自然日计入经营利润;会计收入确认口径需在后续“自动凭证”中单独配置。
### 7.3 匹配状态
| 状态 | 条件 | 后续动作 |
| --- | --- | --- |
| `auto_matched` | 店铺、外部订单号、金额和日期窗口均符合 | 进入利润核算,可抽样复核 |
| `partial_matched` | 存在退款、拆分结算或金额不完整 | 进入人工确认队列 |
| `unmatched` | 找不到订单或映射 | 差异中心处理,不计已结算利润 |
| `ignored` | 平台服务费等不对应订单的明细 | 按费用规则入费用池或店铺维度利润 |
| `confirmed` | 人工确认 | 记录操作人、原因和规则版本 |
自动匹配条件:同一 `store_id`、外部订单号一致、结算明细类型可映射、金额误差不超过币种最小单位、发生日期处于订单完成日后可配置的结算窗口内。
### 7.4 期间锁定与重算
- 每日增量核算只重算受导入、退款、成本变化影响的店铺/SKU/日期范围。
- 月结后写入 `ec_period_locks`;锁定期间禁止覆盖利润快照。
- 锁定期收到退款或结算更正时,创建当期调整快照和审计记录,不回写已出具报表。
- 解锁必须具备 `update-ecommerce-finance-period-locks` 权限并填写原因。
## 8. 导入、同步和幂等
### 8.1 导入流程
1. 用户上传订单、退款、结算或成本文件,创建 `ec_import_batches(status=queued)`。
2. 系统计算 SHA-256 校验值;同公司、同来源、相同校验值的成功批次拒绝重复导入。
3. 队列任务按平台适配器解析为标准 DTO,逐行校验必填字段、币种、金额、时间和店铺。
4. 以外部唯一键执行 upsert;原始行和校验错误写入批次明细或错误文件。
5. 任务完成后触发匹配和受影响范围的利润重算;批次状态变为 `completed`、`completed_with_errors` 或 `failed`。
### 8.2 平台适配器接口
```php
interface ChannelAdapter
{
public function parseOrders(ImportBatch $batch): iterable;
public function parseSettlements(ImportBatch $batch): iterable;
public function normalizeOrder(array $row): NormalizedOrder;
public function normalizeSettlementLine(array $row): NormalizedSettlementLine;
}
```
适配器仅负责字段解析与标准化,不得直接写数据库或创建核心 `Transaction`。写入、匹配和核算由应用服务/Job 统一处理,便于重试和测试。
## 9. API 与页面
所有 API 位于 `/api/ecommerce-finance` 前缀下,继承当前 API 认证和公司上下文。列表接口必须支持分页、店铺、平台、日期、状态和关键字过滤。
| 方法 | 路径 | 用途 | 权限 |
| --- | --- | --- | --- |
| `GET` | `/stores` | 店铺列表 | `read-ecommerce-finance-stores` |
| `POST` | `/stores` | 创建店铺和结算账户映射 | `create-ecommerce-finance-stores` |
| `POST` | `/imports` | 创建上传批次 | `create-ecommerce-finance-imports` |
| `GET` | `/imports/{id}` | 查询导入进度、错误和结果 | `read-ecommerce-finance-imports` |
| `GET` | `/orders` | 订单和退款查询 | `read-ecommerce-finance-orders` |
| `GET` | `/settlements` | 结算单查询 | `read-ecommerce-finance-settlements` |
| `GET` | `/reconciliation-exceptions` | 未匹配/部分匹配差异 | `read-ecommerce-finance-reconciliation` |
| `POST` | `/reconciliation-matches/{id}/confirm` | 人工确认匹配 | `update-ecommerce-finance-reconciliation` |
| `POST` | `/settlements/{id}/link-transaction` | 关联或创建核心资金流水 | `update-ecommerce-finance-settlements` |
| `POST` | `/profit/recalculate` | 按范围发起重算任务 | `update-ecommerce-finance-profit` |
| `GET` | `/reports/profit` | 利润报表 | `read-ecommerce-finance-reports` |
| `GET` | `/reports/unsettled` | 待结算报表 | `read-ecommerce-finance-reports` |
| `POST` | `/period-locks` | 锁定期间 | `update-ecommerce-finance-period-locks` |
后台页面建议为:概览、导入中心、订单与退款、结算中心、差异中心、成本管理、利润报表、待结算资金、期间锁定、平台设置。概览只显示汇总和异常数量,明细操作放在专用页面。
## 10. 与核心资金模块的集成
### 10.1 创建资金流水
仅当平台结算单状态为 `confirmed` 且实际到账金额确认后,创建核心 `Transaction`:
```text
type = income
account_id = ec_stores.settlement_account_id
paid_at = ec_settlements.settled_at
amount = ec_settlements.net_amount
currency_code = settlement currency
payment_method = channel code
reference = external_settlement_id
description = 平台/店铺/结算周期
created_from = ecommerce-finance
```
创建后回写 `ec_settlements.transaction_id`。若已通过银行流水导入生成交易,则按账户、金额、日期窗口和结算单号匹配并关联,禁止重复创建。
### 10.2 对账职责划分
- `ec_reconciliation_matches`:订单与平台结算明细的业务对账。
- `transactions` 与 `reconciliations`:平台打款/支付账户与银行流水的资金对账。
- 两层对账均完成,结算单才可标记为 `cash_reconciled`。
## 11. 队列、事务与性能
- 文件解析、批量导入、自动匹配、重算和导出必须进入队列;HTTP 请求只负责校验并返回任务 ID。
- 单个 Job 按店铺、业务日期和最多 1,000 行分片;失败可按批次、分片和游标重试。
- upsert、结算状态迁移、创建资金交易及其反向关联必须置于数据库事务中。
- 所有写入使用外部唯一键和数据库唯一约束保证幂等;不可只依赖应用层查重。
- 报表从 `ec_profit_snapshots` 查询,不在请求中扫描全部订单/结算明细。
- 跨来源报表只能读取已经物化到共享业务库的标准表;尚未迁移的旧查询快照仅作过渡,不在请求中等待任一上游数据库。
- 大文件导入使用流式读取,原文件通过既有媒介/存储机制保存,并设置访问权限。
## 12. 权限、审计与安全
新增权限按 `read/create/update/delete-ecommerce-finance-{resource}` 命名,至少覆盖 stores、imports、orders、settlements、reconciliation、costs、profit、reports、period-locks。
- 平台密钥、Cookie、签名密钥仅保存于加密配置或密钥服务,不写入导入原始 `payload`、日志或错误文件。
- 导入文件可能含买家隐私信息;首期只保留脱敏展示所需字段,访问受公司和权限限制。
- 人工匹配、解除关联、成本覆盖、重算、锁期和解锁必须写审计日志。
- 所有查询强制 `company_id`;任何通过 ID 查记录的接口必须验证其所属公司。
## 13. 测试与验收
### 13.1 自动化测试
在 `modules/EcommerceFinance/Tests/Feature` 增加以下测试:
1. 同一订单、结算文件重复导入不产生重复记录。
2. 不同公司不可读取、匹配或关联彼此的电商数据。
3. 全额退款、部分退款、拆分结算和跨期退款的金额与利润正确。
4. 平台券、商家券、佣金、运费和服务费按照规则归集。
5. 自动匹配、人工确认、拒绝匹配均保留审计记录。
6. 结算单只能关联一条核心 `Transaction`;重复关联或重复创建会失败。
7. 锁定期间的利润不可覆盖,调整会生成新期间记录。
8. 成本变更只重算受影响的日期、店铺和 SKU。
### 13.2 首期验收标准
| 指标 | 验收目标 |
| --- | --- |
| 订单/结算幂等 | 重复导入不重复计收入、退款或费用 |
| 自动匹配率 | 标准订单结算样本不低于 98% |
| 可追溯性 | 任意利润数字可追溯至订单、结算明细、成本快照和资金流水 |
| 数据时效 | 日结数据在 T+1 生成;导入批次可查看任务状态和失败原因 |
| 资金一致性 | 平台实际打款与核心 `Transaction` 可一对一关联并进入银行对账 |
## 14. 实施清单
1. 创建 `EcommerceFinance` 模块骨架、权限、菜单、路由和基础配置。
2. 实现平台/店铺/SKU 映射及导入批次模型、迁移和页面。
3. 实现订单、退款、结算单和结算明细的标准 DTO、适配器和异步导入。
4. 实现自动匹配、差异中心、人工确认与审计日志。
5. 实现成本快照、费用分摊和日利润快照。
6. 接入 `Transaction`、`Account`、`Reconciliation`,完成待结算到到账的闭环。
7. 建立报表、锁期、监控指标和完整的特性测试。
在第 3 步完成前,不应开始自动创建核心资金流水;在第 5 步完成前,报表必须明确标记为“收入/结算视图”,不得作为完整净利润报表使用。