docs: add customer family protocol link design.

Record agreed model for mother/father/child customer IDs on protocol parties, save-time sync, id-card merge, and customer detail relations.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-10-07 15:52:29 +08:00
1 parent cf86c585b7
commit e027b5abc4
1 file changed
+210
@@ -0,0 +1,210 @@
# 客户管理 · 协议家庭成员关联设计
> 日期:2026-10-07
> 状态:已确认(2026-10-07)
> 范围:市场基础信息「客户管理」+ 细胞存储采集协议单 + 科研捐赠协议单
---
## 1. 背景
`biz_customer` 是独立客户主数据。采集协议保存时,目前只把**母亲**同步进客户表,并写到当事人表唯一的 `customer_id`。父亲、子女姓名等信息只存在当事人子表字段上,没有客户 ID。客户详情只展示本人字段,看不到同协议其他人和关联协议。
产品要求:
- 母亲、父亲、子女只要有姓名即可写入客户管理(有证、无证均可)。
- 同一张协议上的三人通过协议当事人上的三个客户 ID 关联。
- 点开任一客户详情:上方按协议看同单其他人,下方看关联协议摘要;点协议号弹出协议只读详情。
- 一人可对应多张协议,详情按协议分组列出。
- 细胞存储与科研捐赠一起做。
---
## 2. 目标与非目标
### 2.1 目标
- 当事人表增加父亲、子女客户 ID;母亲继续用已有 `customer_id`。
- 保存细胞存储 / 科研捐赠协议时,对本单三个角色走同一套 upsert 同步(仅当前协议,不全库)。
- 上线跑一次幂等迁移,把存量当事人补建客户并回写 ID;之后保存继续同步。
- 补身份证撞到已有客户时:合并有用字段 → 全部引用改挂 → 软删临时客户。
- 客户详情提供「关联客户」「关联协议」;点协议号打开对应查看弹窗。
### 2.2 非目标
- 不新建「协议–客户–角色」关系表。
- 不把客户管理改成协议列表的派生视图。
- 不在客户详情内嵌完整协议表单,只弹现有只读查看。
- 不自动因协议软删而删除客户。
- 不把客户管理里的手工改名回写到协议当事人字段。
- 本期不改中试申请患者 ↔ 客户逻辑。
---
## 3. 数据模型
不改 `biz_customer` 结构。扩展两张当事人表:
### 3.1 `biz_protocol_party_customer`
| 字段 | 说明 |
|------|------|
| `customer_id` | 已有,**母亲**客户 ID |
| `father_customer_id` | 新增 VARCHAR(64) NULL,父亲客户 ID |
| `newborn_customer_id` | 新增 VARCHAR(64) NULL,子女客户 ID |
### 3.2 `biz_research_donation_party`
同样增加 `father_customer_id`、`newborn_customer_id`;已有 `customer_id` 仍表示母亲。
不把 `customer_id` 改名为 `mother_customer_id`,避免无谓迁移;代码与 API 注释写明「母亲」。
SQL 放在 `docs/sql/migrations/`(例如 `2026-10-07-party-father-newborn-customer-id.sql`),并同步 SQLAlchemy 模型。
关联约定:同一行上的三个 ID 即同一协议家庭;查询某客户相关协议时:
```text
customer_id = :id
OR father_customer_id = :id
OR newborn_customer_id = :id
```
细胞存储与科研捐赠各查一次,按租户、`del_flag=0` 过滤。
---
## 4. 同步规则(保存当前协议)
抽公共函数(建议 `backend/app/services/party_customer_sync.py`),细胞存储与科研捐赠保存当事人时调用。**范围仅当前这一张协议的三个角色。**
每个角色(母亲 / 父亲 / 子女)独立处理:
1. **无姓名**:不同步客户;该角色客户 ID 置空(不删已有 `biz_customer` 记录)。
2. **有姓名、无身份证**:新建客户,回写本单对应客户 ID。不按姓名去重(允许重名)。
3. **有姓名、有身份证**:同租户未删除客户中按身份证查找(排除软删)。
- 未找到:若本单该角色已有客户 ID,则**不换 ID**,把身份证等写到该客户;若无客户 ID,则新建并回写。
- 找到且就是本单已挂的客户:更新该客户可写字段(姓名、电话等按下方合并策略)。
- 找到且是**另一条**客户:进入第 5 节合并改挂。
性别默认:母亲 `1`,父亲 `0`,子女用当事人 `newborn_sex`,缺省 `2`。
年龄:能从身份证推算则写入,否则空。
协议当事人上的母亲/父亲/子女姓名电话等业务字段仍照旧写入子表,客户表只存客户主数据字段。
---
## 5. 补身份证撞库:合并后软删
当本单某角色「当前挂着的客户 A」与「身份证已存在的客户 B」且 `A.id != B.id`:
1. 把 A 的有用信息合并进 B:
- `phone_no`、`age`、`sex`(仅当目标为空或未知 `2` 时用源值)、`remark`:**目标为空才补,不覆盖已有**。
- `customer_tags`:按逗号拆开后做并集再写回 B。
- `customer_name`:**保留 B**(身份证命中的已有客户为准)。
2. 将细胞存储当事人、科研捐赠当事人中,三个客户 ID 字段里所有等于 A 的值,改为 B(避免 A 仍被其它协议引用)。
3. 本单该角色 ID 写为 B。
4. 软删 A(`del_flag=1`)。
5. 客户管理列表默认不展示 A。
若身份证不撞库:不换 ID,只更新当前客户。
---
## 6. 存量迁移
上线脚本(Python 或 SQL + 服务函数均可,须幂等):
- 扫描未删除的 `biz_protocol_party_customer`、`biz_research_donation_party`。
- 对已有角色姓名、却缺少对应客户 ID 的记录,调用与保存相同的同步函数。
- 已有正确客户 ID 的跳过。
- 可重复执行,结果一致。
之后日常只在保存**当前协议**时同步,不再全库重跑。
---
## 7. 客户详情
### 7.1 界面
客户管理查看弹窗自上而下:
1. **基本信息**:现有字段不变。
2. **关联客户**:按该客户出现过的协议分组(协议 `create_time` 倒序)。每组展示:
- 类型(细胞存储 / 科研捐赠)、协议号、本客户角色(母亲 / 父亲 / 子女)
- 同单已填角色(含本人,标记「本人」):角色、姓名、电话、身份证;非本人点姓名打开对方客户详情
- 未填角色不展示
3. **关联协议**:摘要列表字段:协议号、类型、状态、日期、本客户角色。
日期:细胞存储用 `register_date`,科研捐赠用 `donation_date`,接口统一字段名 `registerDate`。
**点击协议号**弹出只读详情:
- 细胞存储 → `ProtocolViewDialog`
- 科研捐赠 → `DonationViewDialog`
无关联协议时,关联客户、关联协议区块展示空态文案。
### 7.2 API
`GET /api/admin/customers/{id}/relations`(需客户查看权限,租户隔离):
```text
{
customer: { ...与客户详情一致 },
relatedGroups: [
{
bizType, // cell_storage | research_donation
protocolId,
protocolNo, // 细胞存储 protocol_no;科研捐赠 donation_no
protocolStatus, // protocol_status / donation_status
registerDate, // register_date / donation_date
myRole, // mother | father | newborn
members: [
{ role, customerId, name, phone, idCardNo }
]
}
],
protocols: [
{ bizType, protocolId, protocolNo, protocolStatus, registerDate, myRole }
]
}
```
`relatedGroups` 与 `protocols` 同源:每张相关协议一组;`members` 含本客户及同单其他人(有客户 ID 或至少有姓名的角色)。`protocols` 便于下方摘要表直接绑定。
打开协议详情:前端复用现有协议/捐赠详情接口与弹窗。无对应协议查看权限时:协议号不可点或点击后提示无权限(与现有权限码一致,不新造权限)。
---
## 8. 列表与其它入口
- 客户列表仍是 `biz_customer` 全量未删除记录;父亲、子女会出现为独立行。
- 协议表单「选择客户」仍选自 `biz_customer`;保存后按第 4 节回写三个 ID。
- 客户管理手工新增/编辑/删除行为不变;删除客户不级联改协议(协议上可能留下已删 ID;详情关联客户对该 ID 显示姓名占位或「客户已删除」)。
---
## 9. 测试要点
- 无证三人保存 → 客户表 3 条,当事人三个 ID 均有值。
- 后补子女身份证撞已有客户 → 字段合并、所有引用改挂、临时户软删、详情指向 B。
- 后补身份证不撞库 → 客户 ID 不变,身份证写到原客户。
- 同一母亲两张协议 → 详情两组关联客户 + 两条协议摘要。
- 点协议号分别打开细胞存储 / 科研捐赠只读弹窗。
- 仅填母亲 → 只同步母亲;关联客户组内无父/子女行。
- 迁移脚本跑两遍结果一致。
- 科研捐赠保存与细胞存储同一套规则。
---
## 10. 已确认决策
| 项 | 结论 |
|----|------|
| 关联落点 | 当事人表三个客户 ID,不新建关系表 |
| 母亲 ID | 沿用已有 `customer_id` |
| 业务范围 | 细胞存储 + 科研捐赠 |
| 无证 | 有姓名即可建客户 |
| 补证撞库 | 合并后软删临时客户,协议改挂已有客户 |
| 存量 | 上线全量脚本 + 之后仅保存当前单时同步 |
| 详情布局 | 上:按协议分组的关联客户;下:协议摘要;点协议号弹详情 |
| 中试 | 本期不动 |