Преглед на файлове

夜市摊位抽成设计文档

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
qmj преди 1 месец
родител
ревизия
7c7761a57e
променени са 1 файла, в които са добавени 101 реда и са изтрити 0 реда
  1. 101 0
      docs/superpowers/specs/2026-05-09-night-market-commission-design.md

+ 101 - 0
docs/superpowers/specs/2026-05-09-night-market-commission-design.md

@@ -0,0 +1,101 @@
+# 夜市摊位抽成设计
+
+## 背景
+
+现有抽成模式:平台从商家(userType=1)每笔交易中按 `commission` 比例收取佣金,收入入商家个人钱包。
+
+新增夜市层级后,交易主体变为摊位(PosStore, isStall=1),摊位归属夜市(InfoUser, userType=3)管理。摊位可能有多个摊位主,钱包是门店维度的,不是个人维度的。
+
+## 业务规则
+
+- 平台从摊位每笔交易中收取佣金,比例由所属夜市统一设定
+- 夜市不参与交易抽成,只线下收取摊位费
+- 抽成比例存在夜市用户的 `commission` 字段上,下属所有摊位统一使用
+- 资金流向:交易额 - 平台抽成 = 摊位到手,平台直接扣除
+- 摊位收入进入摊位门店钱包(UserWallet.storeId 关联),不进摊位主个人钱包
+
+## 技术设计
+
+### 改动范围
+
+| 改动 | 文件 |
+|------|------|
+| 抽成判断逻辑 | `PosOrderController.setSanghuBilling()` |
+| 摊位钱包更新 | `WalletService` 新增摊位钱包操作方法 |
+| 账单关联摊位 | `UserBilling` 新增 `storeId` 字段 |
+| 数据库 | `user_billing` 表新增 `store_id` 列 |
+
+### 1. 抽成判断逻辑
+
+修改 `PosOrderController.setSanghuBilling()`:
+
+```
+PosStore store = posStoreService.getById(posOrder.getMdId())
+if (store.getIsStall() == 1 && store.getNightMarketId() != null) {
+    // 摊位订单:用夜市的抽成比例
+    InfoUser nightMarket = infoUserService.getById(store.getNightMarketId())
+    fcbili = nightMarket.getCommission() == null ? 0.00 : nightMarket.getCommission()
+} else {
+    // 普通商家:保持原逻辑
+    InfoUser user = infoUserService.getById(posOrder.getShId())
+    fcbili = user.getCommission() == null ? 0.00 : user.getCommission()
+}
+```
+
+后续计算不变:`chouc = fenc * fcbili`,`shfc = fenc - chouc`。
+
+### 2. 账单记录
+
+`user_billing` 表新增 `store_id` 列(BIGINT, NULL, 默认NULL)。
+
+摊位订单账单记录:
+
+| 字段 | 值 | 说明 |
+|------|-----|------|
+| userId | shId(夜市ID) | 标识交易归属哪个夜市 |
+| mdId | 摊位门店ID | 标识哪个摊位的交易 |
+| storeId | 摊位门店ID | 关联摊位钱包(新增字段) |
+| amount | shfc | 摊位到手金额 |
+| divvy | chouc | 平台抽成金额 |
+| divvyRate | fcbili | 抽成比例 |
+
+普通商家订单不受影响,`storeId` 为 NULL。
+
+### 3. 钱包更新
+
+`WalletService` 新增摊位钱包操作方法 `addStallBalance(Long storeId, BigDecimal amount, UserBilling billing)`:
+
+```
+1. 通过 storeId 查找 UserWallet(WHERE store_id = ?)
+2. 加分布式锁
+3. 乐观锁更新钱包余额(与现有 addBalance 逻辑相同)
+4. 创建账单记录(billing.storeId 已设置)
+```
+
+`setSanghuBilling()` 中的调用变更:
+
+```
+if (isStall) {
+    billing.setStoreId(store.getId())
+    walletService.addStallBalance(store.getId(), amount, billing)
+} else {
+    walletService.addBalance(userId, amount, billing)  // 原逻辑不变
+}
+```
+
+### 4. 数据模型
+
+仅一个数据库变更:
+
+```sql
+ALTER TABLE user_billing ADD COLUMN store_id BIGINT DEFAULT NULL COMMENT '关联摊位ID,非摊位账单为NULL';
+```
+
+复用现有字段:
+
+| 字段 | 所在实体 | 用途 |
+|------|---------|------|
+| commission | InfoUser (夜市) | 夜市设定的平台抽成比例 |
+| isStall | PosStore | 判断是否为摊位(1=摊位) |
+| nightMarketId | PosStore | 摊位关联的夜市用户ID |
+| storeId | UserWallet | 摊位钱包关联的门店ID(已有) |