Files
dukang/docs/杜康好客-v4.0.18-开发文档.md
T
jacy 4bdb09068c feat(store): v4.0.18 门店多收款账户与子账号继承二维码
C 端门店列表拼接省市区县地址;门店多银行账户与默认打款账户;子账号独立 sa_ 关联码及统计维度;同步 v4.0.18 开发文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 15:56:16 +08:00

107 lines
6.8 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.
# 杜康好客 · v4.0.18 开发文档
> **2026-09-07** · mini-user / store / settlement / iam / h5-shop / admin-web / h5-partner / shared-types
> **主题**:C 端门店列表省市区县地址;门店多收款账户;子账号独立继承二维码
---
## 1. 版本目标
| # | 任务 | 类型 | 交付 |
|---|------|------|------|
| 1 | C 端门店列表地址 | 需求 | 「省市区县 + 详细地址」拼接展示,搜索同步匹配完整地址 |
| 2 | 门店多银行账号 | 需求 | 门店级收款账户列表;设「默认」账户用于打款;**切换无需重启** |
| 3 | 子账号二维码 | 需求 | 子账号独立继承码 `sa_{subId}`;新增一级子账号维度统计 |
**不做**:银行账号历史打款回刷;子账号佣金独立归属(佣金仍归主账号);按承运商维度的收款账户。
---
## 2. 规则
### 2.1 门店列表地址
接口已返回 `province` / `cityName` / `district` / `address`,纯前端拼接。复用助手 `fullStoreAddress(store)`(`province + (cityName ?? city) + district + address` trim,空回退「地址待完善」),与门店详情页逻辑一致。
### 2.2 门店多银行账号
- 表 `store_bank_account` 挂在 `storeId`:一门店多账户;`is_default=1` 为打款默认账户(每店至多一个)。
- 迁移时按现有主账号银行字段(`StoreAccount.bank_account_name/no/branch`)为每店回填一条默认账户。
- 打款/结算/提现导出统一读取**默认账户**(无默认则取第一个 ACTIVE;再退回旧主账号字段,兼容未迁移数据)。
- **切换/新增/删除账户不需要重启服务器**:全仓无进程内缓存,`StoreAccount`/`PartnerAccount`/`FulfillmentProvider` 银行字段与酒厂 `system_config WINERY_BANK_*` 均每次请求实时查库。唯一「改后需重启」的是微信支付商户号 `WX_MCH_ID`(`system-config.registry.ts`,`requiresRestart: true`),属微信支付、非银行账号。
- 权限:门店主账号可维护本店账户(子账号只读);总部可维护任意门店账户。
### 2.3 子账号继承二维码
```mermaid
flowchart LR
scanC[C端扫码 sa_subId] --> touch[touchScan 子账号 assocScanCount+1]
scanC --> bind[bindUser]
bind -->|first-lock| setAssoc[user.assocPartnerAccountId=主账号, assocSubAccountId=子账号]
setAssoc --> main[主账号聚合统计: 关联用户/订单]
setAssoc --> sub[子账号统计: 已扫码/已关联/订单]
```
- 复用 `partner_account.assoc_qrcode_id / assoc_qrcode_resource_id / assoc_scan_count`:子账号行存自己的 `sa_{subId}` 码(`assoc_qrcode_id` 为 `@unique`,`sa_` 与 `pa_` 前缀、id 均不同,不冲突)。
- 新增 `user_user.assoc_sub_account_id`:记录带来该用户的子账号(主账号码扫码则为空)。
- `touchScan`:`sa_` 场景对**子账号行** `assocScanCount` 自增;主账号已扫码在读取时聚合 own+children。
- `bindUser`:`sa_` 场景解析子账号 → 主账号;first-lock 仍校验主账号,写入 `assocPartnerAccountId=主账号` + `assocSubAccountId=子账号`。
- `getSummary`/`getStats`/`listUsers`/`listAssocOrders`:子账号调用时返回**自己维度**数据(`assocSubAccountId=子账号`);`activityPosterId` 恒为空;主账号保持聚合行为不变。
- 佣金仍归主账号,不因扫码来源为子账号而改变归属。
---
## 3. API
### 3.1 门店收款账户(store 模块)
| 方法 | 路径 | Guard | 说明 |
|------|------|-------|------|
| GET | `/shop/store/bank-accounts` | 门店 | 本店账户列表 |
| POST | `/shop/store/bank-accounts` | 门店主账号 | 新增账户(首条自动默认) |
| PUT | `/shop/store/bank-accounts/:id` | 门店主账号 | 编辑账户 |
| DELETE | `/shop/store/bank-accounts/:id` | 门店主账号 | 删除(默认账户不可删) |
| POST | `/shop/store/bank-accounts/:id/default` | 门店主账号 | 设为默认 |
| GET | `/admin/stores/:storeId/bank-accounts` | HQ | 指定门店账户列表 |
| POST / PUT / DELETE / POST `:id/default` | `/admin/stores/:storeId/bank-accounts` | HQ | 总部维护 |
入参:`{ bankAccountName, bankAccountNo, bankBranch? }`;`bankAccountNo` 校验 `^\d{8,32}$`。
### 3.2 关联码(store 模块)
- `POST /user/partner-assoc/touch`:scene 支持 `pa_{id}` 与 `sa_{subId}`;`sa_` 对子账号行计数,返回 `{ partnerId, subAccountId, scanCounted, scanCount }`。
- `POST /user/partner-assoc/bind`:scene 支持 `sa_`;返回 `{ bound, alreadyBound, partnerId, subAccountId, partnerName }`。
- `GET /partner/assoc`(summary):子账号返回 `{ partnerId, primaryAccountId, isSubAccount, qrcodeUrl, userCount, scanCount }`。
- `GET /partner/assoc/stats`、`GET /partner/assoc/users`、`GET /partner/assoc/orders`:子账号返回自己维度。
- `GET /partner/assoc/qrcode`:子账号下载自己的码。
---
## 4. 变更面
| 层 | 路径 |
|----|------|
| Prisma | `schema.prisma`(新增 `StoreBankAccount`;`User.assocSubAccountId`) |
| 迁移 | `migrate-store-bank-account-v4018.sql`、`migrate-user-assoc-sub-account-v4018.sql` |
| shared-types | `settlement.ts`(`StoreBankAccountDto`);`partner-assoc.ts`(summary/touch/bind 增 `subAccountId`/`isSubAccount`) |
| API store | `store-bank.service.ts`、`store-bank.controller.ts`(新增);`store.module.ts`;`store-bank.util.ts`(`loadStoreBankAccounts`/`loadStoreDefaultBank`);`partner-assoc.service.ts`(`sa_` 解析/生成/touch/bind/统计) |
| API settlement | `settlement.service.ts`(summary/withdraw/admin-withdrawal/export 改读默认账户) |
| API ops | `admin-redeem.service.ts`(默认账户) |
| mini-user | `stores/index.tsx`、`lib/store-display.ts`(`fullStoreAddress`)、`lib/promo.ts`(放行 `sa_`) |
| h5-shop | `BankAccountsPage.tsx`(新增)、`App.tsx`、`MinePage.tsx`、`styles.css` |
| admin-web | `StoreBankAccountsPanel.tsx`(新增)、`StoresPage.tsx`(收款账户页签) |
| h5-partner | `AssocQrcodePage.tsx`、`UsersManagePage.tsx`(子账号提示) |
---
## 5. 验收
- [ ] C 端门店列表地址显示「省市区县 + 详细地址」;空值回退「地址待完善」;搜索「省/市/区县」关键词可命中
- [ ] 门店可新增/编辑/删除/设默认收款账户;默认账户用于提现打款与账单导出
- [ ] 切换默认账户后(无需重启)提现 summary 与打款信息即时生效
- [ ] 子账号可生成并下载自己的二维码(scene `sa_{subId}`),与主账号 `pa_{id}` 不冲突
- [ ] 扫子账号码:用户仍锁定主账号(佣金归主账号),并写入 `assoc_sub_account_id`
- [ ] 子账号用户管理/关联码页展示自己维度的已扫码/已关联/订单统计;主账号聚合 own+children 不变
- [ ] 主账号码扫码:`assoc_sub_account_id` 为空,行为与 v4.0.9 一致
- [ ] shared-types 构建通过;相关 lint/单测过