4bdb09068c
C 端门店列表拼接省市区县地址;门店多银行账户与默认打款账户;子账号独立 sa_ 关联码及统计维度;同步 v4.0.18 开发文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
6.8 KiB
Markdown
107 lines
6.8 KiB
Markdown
# 杜康好客 · 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/单测过
|