146 lines
11 KiB
Markdown
146 lines
11 KiB
Markdown
# 杜康好客 · v4.0.18 开发文档
|
||
|
||
> **2026-09-07** · mini-user / store / settlement / iam / h5-shop / admin-web / h5-partner / shared-types / domain
|
||
> **主题**:C 端门店列表省市区县地址;子账号独立继承二维码;HQ 财务全部银行账户目录
|
||
> **2026-09-08 修订**:门店列表地址按「省+市+区+详细地址」原样拼接,详细地址已含省市区时**不去重**
|
||
> **2026-09-08 追加**:HQ 财务「全部银行账户」目录(聚合门店结算资质/酒厂/合伙人/物流 + 不挂门店的「其他」账户)
|
||
> **2026-09-08 再修订**:撤销门店多收款账户(`store_bank_account`);打款与财务门店行均读门店详情「结算资质」主账号 `bank_account_*`
|
||
|
||
---
|
||
|
||
## 1. 版本目标
|
||
|
||
| # | 任务 | 类型 | 交付 |
|
||
|---|------|------|------|
|
||
| 1 | C 端门店列表地址 | 需求 | 「省 + 市 + 区 + 详细地址」原样拼接展示(不去重);搜索匹配完整地址 |
|
||
| 2 | 门店打款账户 | 需求 | 打款读门店详情「结算资质」(结算户名 / 银行账号 / 开户银行);**不**再维护独立收款账户列表 |
|
||
| 3 | 子账号二维码 | 需求 | 子账号独立继承码 `sa_{subId}`;新增一级子账号维度统计 |
|
||
| 4 | HQ 财务全部银行账户 | 需求 | 财务菜单「全部银行账户」列出有效账户;门店行来自结算资质;可新增不挂门店的「其他」账户;类型/备注/筛选/Excel+PDF 导出 |
|
||
|
||
**不做**:银行账号历史打款回刷;子账号佣金独立归属(佣金仍归主账号);按承运商维度的收款账户;「其他」账户接入打款;本页改写门店/合伙人/酒厂/物流源字段。
|
||
|
||
---
|
||
|
||
## 2. 规则
|
||
|
||
### 2.1 门店列表地址
|
||
|
||
C 端门店列表卡片「地址」一栏(门店详情页同规则)= **下拉省 + 下拉市 + 下拉区 + 详细地址 input 原文**,四段按字段原样拼接,中间不加分隔符。
|
||
|
||
| 字段 | 来源 | 存库 |
|
||
|------|------|------|
|
||
| 省 | 录店/改店省市区下拉 | `store.province` |
|
||
| 市 | 同上 | `store.cityName` |
|
||
| 区 | 同上 | `store.district` |
|
||
| 详细地址 | 用户自由输入(地图选点若回填也写入此字段) | `store.address` |
|
||
|
||
**禁止去重**:不得因详细地址已包含省/市/区而省略前缀。助手 `formatStoreDisplayAddress`(`packages/domain`)/ `fullStoreAddress`(mini-user)只做 trim 后拼接;空则回退「地址待完善」。
|
||
|
||
| 下拉省市区 | 详细地址 | 列表展示 |
|
||
|------------|----------|----------|
|
||
| 河南省 / 郑州市 / 金水区 | 金水东路333号 | `河南省郑州市金水区金水东路333号` |
|
||
| 河南省 / 郑州市 / 金水区 | 河南省郑州市金水区金水东路333号 | `河南省郑州市金水区河南省郑州市金水区金水东路333号` |
|
||
|
||
搜索关键字同时匹配 `address` / `district` / 完整拼接结果。规则实现:`formatStoreDisplayAddress`(domain,后端地理编码同用)与 mini-user `fullStoreAddress`(列表/详情展示)。
|
||
|
||
### 2.2 门店打款账户(结算资质)
|
||
|
||
- **撤销**独立表 `store_bank_account` 与门店端/HQ「收款账户」维护页。一门店一行,取绑定主账号(`is_primary=1`)的 `bank_account_name` / `bank_account_no` / `bank_branch`;无主账号则取最早绑定账号。
|
||
- 对应 HQ 门店详情页签「结算资质」:结算户名、银行账号、开户银行。
|
||
- 打款/结算/提现/账单导出统一读上述字段(`loadStoreDefaultBank`),实时查库,改后无需重启。
|
||
- 门店端不再提供收款账户入口;银行信息由总部在结算资质维护。
|
||
|
||
### 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` 恒为空;主账号保持聚合行为不变。
|
||
- 佣金仍归主账号,不因扫码来源为子账号而改变归属。
|
||
|
||
### 2.4 HQ 财务全部银行账户
|
||
|
||
财务查阅与手工登记目录,**不改打款主数据**。打款仍读各业务源表(门店结算资质、合伙人主账号字段、承运商字段、酒厂 `WINERY_BANK_*`)。菜单名:**全部银行账户**。
|
||
|
||
**有效账户**(同时满足才入列):结算户名、银行账号均非空;来源状态为有效。
|
||
|
||
| 类型 | 数据源 | 有效条件 | 本页可写 |
|
||
|------|--------|----------|----------|
|
||
| 门店 | 门店详情结算资质(主账号 `StoreAccount.bank_account_*`) | 户名+账号已填;一店一行 | 仅备注 |
|
||
| 酒厂 | `system_config` `WINERY_BANK_*` | 户名+账号已填 | 仅备注 |
|
||
| 合伙人 | `partner_account` | `status=ACTIVE`、主账号、户名+账号已填 | 仅备注 |
|
||
| 物流 | `common_fulfillment_provider` | `status=ACTIVE`、户名+账号已填(仓无银行字段) | 仅备注 |
|
||
| 其他 | `finance_bank_account` | `status=ACTIVE` | 增删改 |
|
||
|
||
- 列表:类型、归属(快链)、城市、结算户名、银行账号、开户银行、备注。归属快链:门店→详情「结算资质」;酒厂→系统设置「酒厂银行账户」;合伙人→城市合伙人详情;物流→仓配管理并打开该承运商;其他→本页编辑弹窗。
|
||
- **新增不关联门店** = 类型「其他」,不进入提现/账单打款/酒厂/物流对账。
|
||
- 来源账户字段仍在原处改;本页只读这些字段。备注:其他写本表;来源写 overlay 表 `finance_bank_account_note`(门店 `sourceId` = `storeId`)。
|
||
- 筛选:类型、城市、关键字(户名/账号/开户银行/归属名/备注)。导出按当前筛选全量 Excel / PDF。
|
||
- 权限:HQ `finance`。
|
||
|
||
---
|
||
|
||
## 3. API
|
||
|
||
### 3.1 关联码(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`:子账号下载自己的码。
|
||
|
||
### 3.2 财务全部银行账户(settlement 模块)
|
||
|
||
| 方法 | 路径 | Guard | 说明 |
|
||
|------|------|-------|------|
|
||
| GET | `/admin/finance/bank-accounts` | HQ `finance` | 聚合列表;`type` / `cityId` / `keyword` / `page` / `pageSize` |
|
||
| GET | `/admin/finance/bank-accounts/export` | HQ `finance` | `format=xlsx\|pdf`,同筛选全量 |
|
||
| POST | `/admin/finance/bank-accounts` | HQ `finance` | 新增「其他」 |
|
||
| PUT | `/admin/finance/bank-accounts/other/:id` | HQ `finance` | 编辑「其他」 |
|
||
| DELETE | `/admin/finance/bank-accounts/other/:id` | HQ `finance` | 删除「其他」 |
|
||
| PUT | `/admin/finance/bank-accounts/:id/remark` | HQ `finance` | 任意类型写备注;`id` 为复合键 |
|
||
|
||
行 `id`:`STORE:{storeId}` / `PARTNER:{partnerId}` / `LOGISTICS:{providerId}` / `WINERY:winery` / `OTHER:{id}`。`bankAccountNo` 校验 `^\d{8,32}$`(「其他」入参)。
|
||
|
||
---
|
||
|
||
## 4. 变更面
|
||
|
||
| 层 | 路径 |
|
||
|----|------|
|
||
| Prisma | `schema.prisma`(`FinanceBankAccount`、`FinanceBankAccountNote`;`User.assocSubAccountId`;**已删除** `StoreBankAccount`) |
|
||
| 迁移 | `migrate-user-assoc-sub-account-v4018.sql`、`migrate-finance-bank-account-v4018.sql`、`migrate-drop-store-bank-account-v4018.sql`(历史 `migrate-store-bank-account-v4018.sql` 仅作曾上线回填) |
|
||
| domain | `store-address.ts`(`formatStoreDisplayAddress`:省+市+区+详细地址原样拼接,不去重) |
|
||
| shared-types | `settlement.ts`(`FinanceBankAccountDto`);`hq-list-columns.ts`(`finance-bank-accounts`);`partner-assoc.ts`(summary/touch/bind 增 `subAccountId`/`isSubAccount`) |
|
||
| API store | `store-bank.util.ts`(`loadStoreDefaultBank` 读结算资质主账号);`partner-assoc.service.ts`(`sa_` 解析/生成/touch/bind/统计);`store.service.ts`(地理编码地址与 C 端同规则拼接) |
|
||
| API settlement | `settlement.service.ts`(summary/withdraw/admin-withdrawal/export 读结算资质);`finance-bank-account.service.ts`、`finance-bank-export.util.ts`(财务全部银行账户) |
|
||
| API ops | `admin-redeem.service.ts`(结算资质银行字段) |
|
||
| mini-user | `stores/index.tsx`、`store-detail/index.tsx`、`lib/store-display.ts`(`fullStoreAddress`)、`lib/promo.ts`(放行 `sa_`) |
|
||
| h5-shop | 已删除收款账户页;`MinePage.tsx` 去掉入口 |
|
||
| admin-web | `StoresPage.tsx` 结算资质;`BankAccountsPage.tsx`(财务全部银行账户);已删除收款账户页签 |
|
||
| h5-partner | `AssocQrcodePage.tsx`、`UsersManagePage.tsx`(子账号提示) |
|
||
|
||
---
|
||
|
||
## 5. 验收
|
||
|
||
- [ ] C 端门店列表地址 = 省+市+区+详细地址原文;详细地址已含省市区时仍重复拼接(例:金水东路333号 → `河南省郑州市金水区金水东路333号`;详细地址写全称 → `河南省郑州市金水区河南省郑州市金水区金水东路333号`);空值回退「地址待完善」;搜索「省/市/区县」关键词可命中
|
||
- [ ] 门店无独立收款账户页;打款/提现/账单导出读结算资质(结算户名、银行账号、开户银行);改后无需重启即时生效
|
||
- [ ] 子账号可生成并下载自己的二维码(scene `sa_{subId}`),与主账号 `pa_{id}` 不冲突
|
||
- [ ] 扫子账号码:用户仍锁定主账号(佣金归主账号),并写入 `assoc_sub_account_id`
|
||
- [ ] 子账号用户管理/关联码页展示自己维度的已扫码/已关联/订单统计;主账号聚合 own+children 不变
|
||
- [ ] 主账号码扫码:`assoc_sub_account_id` 为空,行为与 v4.0.9 一致
|
||
- [ ] HQ 财务菜单「全部银行账户」:门店行来自结算资质(一店一行)+ 有效酒厂/主合伙人/承运商账户;可新增不挂门店账户;类型/城市/关键字筛选;Excel 与 PDF 导出与筛选一致;备注可写;结算资质改后刷新即更新
|
||
- [ ] shared-types 构建通过;相关 lint/单测过
|