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

101 lines
5.7 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.20 开发文档
> **2026-09-16 / 09-17** · promo / city-scope / store / admin-web / h5-partner / shared-types
> **主题**:推广码渠道负责人(多选主合伙人)+ 关联合伙人(扫码 first-lock)
> **2026-09-17 修订**:HQ 渠道负责人 / 关联合伙人下拉与表格展示为「公司(如有)-人名」
---
## 1. 版本目标
| # | 任务 | 类型 | 交付 |
|---|------|------|------|
| 1 | 渠道负责人 | 需求 | HQ 创建/编辑推广码可多选主合伙人;被指定主账号在合伙人 H5 只看扫码人数、归因人数、订单数量 |
| 2 | 关联合伙人 | 需求 | HQ 可单选绑定主合伙人;登录用户扫该码且尚未关联任何人时 first-lock;已关联他人静默跳过;不回刷历史 |
**不做**:把推广码改成关联码(仍用数字 scene,不复用 `pa_` / `sa_`);回刷历史归因用户;已关联他人换绑;合伙人 H5 开放用户/订单/核销/手机号明细;历史 `ownerUserId`(C 端用户)迁成合伙人;子账号推广码数据入口。
---
## 2. 规则
规则事实源:[`杜康好客-v4-PRD.md`](./杜康好客-v4-PRD.md) §2.1。
```mermaid
flowchart TD
scan[C端扫推广码] --> touch["POST /promo/touch"]
touch --> scanInc[scanCount++ 未登录也计]
touch --> login{已登录?}
login -->|否| done[结束]
login -->|是| attr[首次归因 UserPromoAttribution]
attr --> src[ORGANIC 才标 PROMO_CODE 来源]
src --> hasAssoc{码上有关联合伙人?}
hasAssoc -->|否| done
hasAssoc -->|是| bound{用户已有 assoc?}
bound -->|无| lock["PartnerCityService.tryBindIfUnbound"]
bound -->|已是同一人| skipSame[noop]
bound -->|已是他人| skipOther[静默跳过]
lock --> users[合伙人H5 关联用户可见]
```
- **渠道负责人**:可空、多选 **ACTIVE 主合伙人**(`isPrimary=1`)。只授权看三项汇总,不绑用户。
- **关联合伙人**:可空、单选 ACTIVE 主合伙人。登录 touch 后 `tryBindIfUnbound`:无关联则写 `assoc_partner_account_id` + `assoc_bound_at`(不写 `assoc_sub_account_id`、不改 `sourceType`、不增加关联码 `assoc_scan_count`);已是同一人 noop;已是他人不抛错。只绑以后扫进来的用户。
- 两字段独立:只配渠道负责人 ≠ 绑用户;只配关联合伙人也会把用户写入该合伙人「关联用户」,并允许看三项汇总。
- HQ 原 `ownerUserId` 列保留、创建/编辑不再暴露。
- **展示**:渠道负责人、关联合伙人(筛选/创建/编辑下拉、列表列、详情)统一为「公司-人名」;无公司则只显示人名。多人用顿号拼接。
---
## 3. API
### 3.1 HQ(promo 模块)
`POST /admin/promo-codes`、`PUT /admin/promo-codes/:id` 增加:
- `channelOwnerPartnerIds: string[]`(可空)
- `assocPartnerAccountId: string | null`(可空;空串/null 解绑)
列表/详情返回 `channelOwners[]`、`assocPartner`(`id` / `companyName` / `name` / `phone`)。列表筛:`channelOwnerPartnerId`、`assocPartnerAccountId`。
### 3.2 C 端扫码
`POST /promo/touch`:登录后、归因与来源标记之后,若码上有 `assocPartnerAccountId`,调用 `PartnerCityService.tryBindIfUnbound`(**不** import `StoreModule`,避免 Nest 循环依赖)。
### 3.3 合伙人 H5(仅主账号,`PartnerPrimaryGuard`)
| 方法 | 路径 | 返回 |
|------|------|------|
| GET | `/partner/promo-codes` | `{ items: [{ id, name, code, status, scanCount, attributionCount, orderCount }] }` |
可见范围:当前主账号是渠道负责人 **或** 关联合伙人。订单数 = `status=COMPLETED`。禁止 HQ 的 users / metrics / timeline / 订单快链。
---
## 4. 变更面
| 层 | 路径 |
|----|------|
| Prisma | `CommonPromoCode.assocPartnerAccountId`;`PromoCodeChannelOwner`(`promo_code_channel_owner`) |
| 迁移 | `server/dukang-api/prisma/migrate-promo-partner-fields.sql`(**Review 后生产执行**) |
| shared-types | `promo.ts`:`PromoCodePartnerBrief`、`PromoCodeItem.channelOwners/assocPartner`、`PartnerPromoCodeItem` |
| API promo | `promo-code.service.ts`、`dto/promo-code.dto.ts`、`partner-promo-code.controller.ts`;`PromoModule` import `CityScopeModule` |
| API city-scope | `PartnerCityService.tryBindIfUnbound` |
| API store | `PartnerAssocService.tryBindIfUnbound` 转调 city-scope |
| admin-web | `PromoCodesPage.tsx`、`promo/PromoCodeDetailPage.tsx`:渠道负责人多选 / 关联合伙人单选;去掉关联用户 ID;展示「公司-人名」(`lib/promo-partner-label.ts`) |
| h5-partner | `CenterPage.tsx` 运营管理「推广码数据」;`/center/promo-codes`;`PromoCodesPage.tsx` |
| 本地代理 | admin/partner/shop Vite 默认 `VITE_API_TARGET` → `http://127.0.0.1:3010`(Windows 上 `localhost` 可能打到占用 `::3010` 的其他进程) |
---
## 5. 验收
- [ ] HQ 创建/编辑推广码可多选渠道负责人、可清空单选关联合伙人;下拉与列表/详情展示「公司-人名」(无公司则人名);可按两字段筛选
- [ ] 只配渠道负责人:扫码不写用户关联;该主账号 H5「推广码数据」能看到三项数字,点不开用户/订单/核销
- [ ] 只配关联合伙人:未关联的登录用户扫码后出现在该合伙人「关联用户」;该主账号也能看三项汇总
- [ ] 用户已关联他人:扫带关联合伙人的码不换绑、流程不中断、不报「无法更换」
- [ ] 为已有码补关联合伙人:**不**回刷历史归因用户
- [ ] 子账号中心无「推广码数据」入口
- [ ] 关联码 `pa_` / `sa_` 行为不变;已付佣金快照不回刷
- [ ] 执行迁移后 API 可启动(`PromoModule` 不得再 import `StoreModule`)
- [ ] shared-types 构建通过;相关 lint 过