Files
dukang/docs/杜康好客-v4.0.18-开发文档.md
T

146 lines
11 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 / 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/单测过