逐入口字段对齐验收清单(票 07 产出)
用途:冒烟断言 + 人工验收双用。范围 = 覆盖矩阵(01)「缺口动作≠豁免」全部入口;豁免入口不展开(见 06 号票面板 C 区)。
权威出处缩写:.bru 相对 D:\code\crm-api-docs\;[DTO:x]/[PARAM:x] = crm-customer/src/main/java/com/crm/customer/domain/{dto,param}/x.java;[PORT:x] = 同树 port/x.java;crm-rule = crm-rule/src/main/java/com/crm/rule/。
两部分正文各自带「通用口径」与「字典枚举/脱敏专节」,先读下放校准注记再查表。
跨票校准注记(实施/冒烟必须遵守,覆盖旧口径)
| # |
校准 |
影响面 |
| K1 |
G4 收窄为「两件」:detail 实测 createTime/updateTime 经 BaseDTO 有值,仅 creatorId/updaterId 不暴露 → 四件套只有「创建人/更新人」恒"--" |
Demo G4 角标文案、冒烟步 13 断言 |
| K2 |
关联项目页签有真实端点(CustomerProjectQueryPort 已建,当前 Noop 实现 → 数据空):页签接线真端点、空态显"--",不做硬占位 |
矩阵 #42「无端点」过时;实施票 11 |
| K3 |
看板 summary 字段 = cnt/groupValue(demo 旧代码读 count/name/key 必显"--");cards 必传 groupValue,summary 显式传 groupColumn |
实施票 10 |
| K4 |
oplog action 权威值域 18 种(含 GRAPH_EDIT;AS §3「17 种」未同步) |
实施票 11 筛选下拉 |
| K5 |
projectCount 三方漂移(AS 写恒 null、SQL 恒 0、E2E 序列化 "0"):渲染按实返回,"--"兜底 |
列表列口径 |
| K6 |
contact_source 真值 = group_meeting/group_activity/referral/other(crm-dict DictDataInitializer);demo 硬编码 contact_source_01..04 为字典改版前遗留,须改字典驱动 |
实施票 12/13 |
| K7 |
reminder save 控制器实为表单绑定(.bru 自称 JSON 例外不实);demo form 提交正确,不改 |
冒烟断言口径 |
| K8 |
focus-batch 返回 Void(其余批量端点 BatchResult):UI 以 toast「已关注 N 条」自算 |
实施票 10 |
| K9 |
quick-create .bru 漏 confirmSimilar、误标星级选填(DTO 全 7 必填):以 DTO 为准 |
实施票 12 |
| K10 |
联系人电话全读路径脱敏 138****1234(短号全遮;导出同口径;明文唯一通道 reveal);.bru 示例明文系漂移;similarHits/check-phone 行级 masked;核心域其余字段 V1 明文(creditCode 明文回显) |
冒烟脱敏断言 |
| K11 |
transfer/page .bru 示例 status=2 超值域(权威 0/1);reminder 64023 精确条件 = 两天数均须非空且 >0 |
冒烟断言 |
第一部分 · 核心域(矩阵 #1-#49)
用途:.scratch/customer-demo-ref/demo/index.html 的冒烟断言 + 人工验收双用。入口编号对齐 assets/coverage-matrix.md(缺口动作≠豁免)。
契约权威优先级:Bruno .bru 出参示例 > DTO/Param 源码 > API-SUMMARY(下称 AS,.scratch/customer-module/API-SUMMARY.md)。字段名逐字取自源码/.bru;「示例」列取 .bru E2E 实测真值或任务书 seed。
envelope:统一 Result<T> = {code, success, message, data}(code=0 成功,错误码见 AS §1);本表只写 data 内字段。
通用口径(各入口共用,正文不再重复)
- 分页请求(
BaseParam,crm-base):current(默认 1)/ size(默认 10,上限 500)/ keyword / orderBy / asc(默认 false=倒序)。
- 分页出参(
PageResult):content[] / total / size / current / pages / empty(=total==0)。
- Long 序列化:雪花 id 与 Long 计数 JSON 序列化为字符串(E2E 实测
"opportunityCount":"0"、"total":"5");前端勿做 Number 运算,判空按 === null / != null。
- 时间格式:LocalDateTime
yyyy-MM-dd HH:mm:ss;LocalDate yyyy-MM-dd;表单提交 LocalDateTime 用 ISO T 分隔(空格分隔报 400,见 #40 follow/add)。
- 空值"--"口径:demo 渲染
value || '--'(grid 兜底,旧 demo L1124-1135 同);契约 null 字段显 --,不显 0/空串。
- 脱敏总述:核心域唯一脱敏位=查重两 DTO 的
masked 布尔标志——无权命中时 customerId/customerName/ownerName/customerNo 等业务字段不透出(null)+ masked=true,前端只渲染「存在匹配记录」(check-name)/「系统已存在该企业」(check-credit-code)。客户列表/详情 V1 无字段级脱敏:unifiedCreditCode、presidentClassPhone 等明文回显(E2E 实测 "13800000001";敏感信息三档脱敏定稿划出,AS §0/§5.4)。联系人电话脱敏 + contact/reveal 明文端点属 contact 域,不在本表。
- 冒烟 seed(e2e 种子库):admin
userId=739564171091247104(罗伟健);客户 750844477165273088(e2c-全字段-科技,编号 KH202609030010,负责人=admin);公海客户 750844487390986240(e2c-公海-甲,无主);联系人 750844500171030528(张关键)。
#1 workspace 列表分页 — POST /api/customer/workspace/page(mine/overview/pool 三 workspace 共用一份契约)
- 出处:
A4 客户管理\我的客户\workspace 列表分页.bru(客户总览/客户公海同名 .bru 各一件)+ crm-customer/.../domain/param/CustomerWorkspacePageParam.java + domain/dto/CustomerListRowDTO.java(出参 SQL 见 mapper/CustomerMapper.java pageWorkspace)
- 数据范围:overview=裸表+
@DataScope(管理员可见全部);mine=负责人或协同人(子查询包裹);pool=owner 空且按部门可见范围(子查询包裹)。
请求参数(form 表单绑定;含 BaseParam 五参):
| 名 |
类型 |
必填 |
示例 |
口径 |
| workspace |
string |
是 |
mine |
mine/overview/pool;未知值 67001「未知 workspace」 |
| viewType |
string |
否 |
ASSIGNED |
内置五视图(硬编码不落库),空=基础集;适配矩阵见下 |
| savedViewId |
string |
否 |
|
叠加自定义视图(#7)检索条件于数据集之上,不改 @DataScope 边界;RECENT 视图不接;视图不存在静默不叠加;board/cards 不接 |
| archiveStatus |
number |
否 |
1 |
1 有效 / 2 已归档;默认 1(D19 归档=删除语义) |
| customerStage |
number |
否 |
1 |
1 潜在 / 2 重潜 / 3 已成交 |
| customerType |
string |
否 |
customer_type_01 |
customer_type 字典 code |
| customerStarLevel |
number |
否 |
3 |
1~5 |
| relationStarLevel |
number |
否 |
3 |
1~5 |
| industryCode |
string |
否 |
gov |
行业一级 code |
| provinceCode |
string |
否 |
440000 |
省国标 code |
| groupColumn |
string |
否 |
stage |
仅看板两端点有意义:白名单 stage/star/relation,越界静默忽略 |
| groupValue |
number |
否 |
1 |
仅 board/cards(与 groupColumn 配对) |
| current / size / keyword / orderBy / asc |
— |
否 |
1 / 50 / 恒信达 |
BaseParam;keyword 按名称/编号等业务列模糊 |
viewType 内置五视图 × workspace 适配矩阵(越界视图不硬拒,SQL 自然空集;F7 口径:ownerOnly 等布尔未做,单 viewType 参数承载五视图):
| viewType |
文案 |
mine |
overview |
pool |
| ASSIGNED |
我负责的 |
✅ |
✅ |
❌ |
| COLLABORATING |
我协同的 |
✅ |
✅ |
❌ |
| FOLLOW_UP_DUE |
待跟进(next_follow_time 到期未跟) |
✅ |
❌ |
❌ |
| FOCUSED |
我关注的 |
✅ |
✅ |
✅ |
| RECENT |
最近访问(customer_view_log 倒序锚点) |
✅ |
✅ |
✅(不接 saved-view) |
出参(PageResult<CustomerListRowDTO>,data 内 24 字段;示例值=E2E 公海行 + mine 行):
| 字段路径 |
类型 |
口径/文案 |
脱敏 |
示例 |
| content[].id |
Long→str |
客户 ID |
- |
"750844487390986240" |
| content[].customerNo |
string |
客户编号 KH+yyyyMMdd+4 位 |
- |
"KH202609030020" |
| content[].customerName |
string |
客户名称(固定列不可隐藏) |
- |
"e2c-公海-甲" |
| content[].customerType |
string |
字典 code(前端字典缓存翻译,后端不回显名) |
- |
"customer_type_01" |
| content[].provinceCode / cityCode / districtCode |
string |
国标 code(名称前端字典渲染) |
- |
"440000" / "440100" / "440103" |
| content[].industryCode / industryChildCode |
string |
行业一级/二级 code |
- |
"gov" / "gov_1" |
| content[].customerStage |
number |
1 潜在 2 重潜 3 已成交 |
- |
1 |
| content[].customerStarLevel / relationStarLevel |
number |
1~5 |
- |
3 / 3 |
| content[].strategicAgreementLevel |
number |
战略协议等级快照,null=未签 |
- |
null |
| content[].vipCustomerLevel |
number |
已成交 VIP 客户等级快照,null=无 |
- |
null |
| content[].opportunityCount |
Long→str |
真算:SQL 标量子查询 opportunity_customer join opportunity(oc.delete_key=0 且 o.deleted=0) |
- |
"0" |
| content[].projectCount |
Long→str |
恒空:SQL SELECT 0 AS project_count(A5 未接);AS §2.1 写「恒 null 显--」、DTO @Schema 注「恒 0」、E2E 实测 "0"——demo 按「恒空显 --」渲染即可,值不具业务含义 |
- |
"0" |
| content[].lastValidFollowTime |
string |
最近有效跟进时间(超期锚点) |
- |
"2026-09-03 22:55:18" |
| content[].ownerUserId |
Long→str |
销售负责人 ID;pool 行恒 null |
- |
null(pool)/ "739564171091247104"(mine) |
| content[].ownerUserNameSnapshot |
string |
负责人姓名快照 |
- |
null / "罗伟健" |
| content[].ownerDeptId |
Long→str |
销售部门 ID 快照 |
- |
"744841292348915712" |
| content[].ownerDeptNameSnapshot |
string |
部门名快照 |
- |
"广东保伦电子股份有限公司" |
| content[].enterPoolTime |
string |
进入公海时间;pool 固定列可排序(D29 pool 默认排序=enter_pool_time 倒序;其余 workspace 默认 create_time 倒序) |
- |
"2026-09-11 11:45:49" / null |
| content[].archiveStatus |
number |
1 有效 2 已归档 |
- |
1 |
| content[].lastViewTime |
string |
最近访问时间;仅 RECENT 视图非 null(同时是 RECENT 排序锚点) |
- |
null |
| content[].focusFlag |
number |
是否关注:customer_focus 行存在=1(按当前登录用户),0=未关注;三 workspace 通用 |
- |
0 |
| total / size / current / pages |
Long→str |
分页元信息 |
- |
"5" / "1" / "1" / "5" |
| empty |
boolean |
total==0 |
- |
false |
- 备注:原型差异列=mine/overview 加负责人+部门四件,pool 加 enterPoolTime 且无负责人列;demo 现 11 列,类型/行业/战略协议等级/VIP 等级/进入公海时间列未渲染(矩阵 #11 字段级挂 07 票)。字典列后端一律回 code 不回名(CustomerListRowDTO 类注释明示)。
#9 看板分组汇总 — POST /api/customer/workspace/board/summary
- 出处:
A4 客户管理\我的客户\看板列表\看板分组汇总.bru(客户总览同名一件;公海无看板 .bru)+ domain/dto/CustomerBoardSummaryDTO.java + service/impl/CustomerWorkspaceServiceImpl.java
- 请求参数:复用
CustomerWorkspacePageParam(同 #1),groupColumn 必填(stage/star/relation),无需 groupValue;分页/keyword 意义有限(同 WHERE 口径)。示例载荷:workspace=mine&groupColumn=stage¤t=1&size=1&keyword=恒信达
- 出参(
List<CustomerBoardSummaryDTO>,数组元素两字段):
| 字段路径 |
类型 |
口径/文案 |
示例 |
| [].groupValue |
number |
分组取值(stage 1~3 / star 1~5) |
1 |
| [].cnt |
Long→str |
该分组客户数(仅返回有数据分组,空列骨架前端补全;本期重潜/已成交恒空=D20 预期) |
"28" |
- 备注(demo 旧代码字段名漂移):旧 demo
loadSummary(L590-592)读 it.count / it.name——契约字段是 cnt / groupValue,旧读法必显 "--",改造时按 groupValue→文案、cnt→数字渲染。E2E 实测:[{"groupValue":1,"cnt":"28"},{"groupValue":2,"cnt":"6"},{"groupValue":3,"cnt":"3"}]。
#9 看板单列卡片 — POST /api/customer/workspace/board/cards
- 出处:
A4 客户管理\我的客户\看板列表\看板单列卡片.bru(总览同名)+ 同上源码
- 请求参数:同 #1 +
groupColumn(stage/star/relation)+ groupValue 必填(缺省 67001「看板取卡必须指定分组值」)。示例载荷:groupColumn=stage&groupValue=2&workspace=mine¤t=1&size=20&keyword=恒信达
- 出参:
PageResult<CustomerListRowDTO>——与列表 #1 逐字段同构(24 字段,E2E 卡片样例:customerStage=2、customerStarLevel=4、ownerUserId="739564171091247104"、total="6")。
- 备注:仅 mine/overview 可用;pool 调看板两端口一律 67001「公海无卡片视图,看板仅支持客户总览/我的客户」(源码
assertBoardAllowed 守卫在,AS §2.1 文档口径一致;GR §六.1 记 jar 实测 code=0 漂移待 04 票核实);board/cards 不接 saved-view;summary 与 page/cards 同一 WHERE 口径,数字自洽。
#7 preference 自定义视图四端点(saved-view) — GET|POST /api/preference/view/list|save|delete|set-default
- 出处:A4 无 saved-view
.bru 文档(契约源=AS §2.14 + crm-preference 源码 controller/SavedViewController.java、domain/dto/SavedView.java、SavedViewCondition.java);示例形态借 A3 商机管理\销售机会\自定义视图\*.bru 四件(字段结构同源,商机域 scopeKey 实测 opportunity)
- scopeKey(客户域,
CustomerConstants L67-69):customer.mine / customer.overview / customer.pool;userId 取登录态,不接受前端传入。
- 端点逐个:
| 端点 |
请求参数 |
出参 data |
GET /api/preference/view/list?scopeKey= |
scopeKey |
List<SavedView>,按 seqNo 排序;出参不回显 scopeKey |
POST /api/preference/view/save?scopeKey= |
scopeKey(query 表单参)+ @RequestBody SavedView JSON——全平台唯一 JSON 例外(ADR-0017 范围外) |
String viewId(新建/覆盖后服务端生成) |
POST /api/preference/view/delete?scopeKey=&viewId= |
scopeKey、viewId |
null |
POST /api/preference/view/set-default?scopeKey=&viewId= |
scopeKey、viewId(单值互斥,设默认自动取消原默认) |
null |
| 字段 |
类型 |
必填 |
口径 |
| viewId |
string |
新建可空;编辑/删除/设默认须回传 |
服务端生成 32 位 hex(样例 "e7b9595f7ea54641b093e9d6bfb096f9") |
| name |
string |
是 |
检索名称(用户填) |
| conditions |
array |
否 |
本期前端只提交 1 组(契约按列表预留复合扩展) |
| conditions[].field |
string |
- |
业务方字段池稳定 code(如 industryCode) |
| conditions[].operator |
string |
- |
业务方自定义:等于/不等于/包含/不包含/为空/不为空(demo 用 eq/like 等) |
| conditions[].value |
string |
- |
为空/不为空操作符时可为 null |
| sortField / sortDirection |
string |
否 |
排序字段 key / asc |
| isDefault |
boolean |
否 |
默认视图图钉,单值互斥 |
| seqNo |
number |
否 |
列表排序序号 |
- 备注:内置五视图(viewType)不走本接口;workspace/page 的
savedViewId 消费本接口存的 viewId(#1);视图为用户私有。
#8 preference 视图形态两件 — GET|POST /api/preference/view-form/get|save
- 出处:
A4 客户管理\我的客户\读取视图形态.bru + 保存视图形态.bru(总览/公海同名共 6 件)+ ViewFormController.java、ViewFormServiceImpl.java
GET /api/preference/view-form/get?scopeKey= → String:list/split/board;null=未设置,前端默认 list(E2E:"data": "list")
POST /api/preference/view-form/save(form:scopeKey + viewForm)→ null;白名单 {list,split,board},非法 68001(平台段位,不在 67xxx)
- 备注:scopeKey 同 #7 三码位(
customer.mine/overview/pool)。文档口径「pool 仅 list/split,board 被拒 680xx」实现侧无 pool 特判——pool 保存 board 实回 code=0(AS §2.14 ⚠ I-06 r2 漂移),前端不得依赖拒绝行为。
#13-19 归属/关注/重点 动作族(合并表,每端点一行;全 POST,id/ids 走查询参数)
- 出处:
crm-customer/.../controller/CustomerOwnershipController.java、CustomerFocusController.java;.bru 逐件:客户公海\领取客户.bru、批量领取.bru、分配客户.bru、批量分配.bru、抛公海.bru、批量抛公海.bru、归档客户.bru、批量归档.bru、恢复客户.bru、关注客户.bru、取消关注客户.bru、批量关注客户.bru、客户详情\标记重点客户.bru、取消重点标记.bru
- 示例值:单条
id=750844487390986240;批量 ids=750844487390986240(重复键 ids=..&ids=..);分配目标 userId=744842318024015872。
| 端点 |
参数 |
返回 data |
主要错误码 |
口径 |
POST /api/customer/focus?id= |
id |
null(Result) |
67002 |
幂等;行存在性=关注态;不写 oplog |
POST /api/customer/unfocus?id= |
id |
null |
— |
幂等;物理删行,连带清重点标记 |
POST /api/customer/focus-batch?ids= |
ids[] |
null(注意:非 BatchResult——controller 循环逐个 focus 后恒 success) |
67002 |
逐个幂等关注,对称商机 |
POST /api/customer/star?id= |
id |
null |
67002 |
幂等;未关注自动建行 starred=1;不写 oplog |
POST /api/customer/unstar?id= |
id |
null |
— |
幂等;保留行仅置 starred=0 |
POST /api/customer/assign?id=&userId= |
id、userId |
null |
67002/67012 |
目标人员启用在职+非原负责人;不校验进行中商机 |
POST /api/customer/assign-batch?ids=&userId= |
ids[]、userId |
BatchResult |
67002/67012 |
目标人员统一校验一次(无效整批拒绝);其余逐条独立事务 |
POST /api/customer/claim?id= |
id |
null |
67002/67004 |
校验仍在公海;D25 领取即赋 last_valid_follow_time 锚点(为空时赋当下) |
POST /api/customer/claim-batch?ids= |
ids[] |
BatchResult |
67002/67004 |
逐条独立事务,部分成功 |
POST /api/customer/release-pool?id= |
id |
null |
67002/67007 |
清 owner+协同人(部门锚点保留),软删 team_member;进行中商机 67007 阻断 |
POST /api/customer/release-pool-batch?ids= |
ids[] |
BatchResult |
67002/67007 |
逐条独立事务,部分成功 |
POST /api/customer/archive?id= |
id |
null |
67002/67004/67006 |
仅销售负责人;归档=删除语义(D19);进行中商机 67006 阻断 |
POST /api/customer/archive-batch?ids= |
ids[] |
BatchResult |
67002/67004/67006 |
整批预检:任一失败整批不执行(预检失败=顶层非 0,E2E 实测顶层 code:67006);预检过后逐条独立事务 |
POST /api/customer/restore?id= |
id |
null |
67002/67004 |
仅销售负责人(已归档→有效) |
- BatchResult 结构(
crm-base/.../result/BatchResult.java):{total:int, successCount:int, failCount:int, failures:[{id:Long→str, reason:string, message:string}]}。reason 语义枚举(CustomerBatchFailReason):NOT_FOUND / STATUS_NOT_ALLOWED / CONCURRENT_MODIFIED / ARCHIVE_BLOCKED / POOL_BLOCKED / ASSIGN_INVALID / UNKNOWN。E2E 失败行样例:{"id":"753612204858671104","reason":"STATUS_NOT_ALLOWED","message":"客户已被领取,不在公海"}。
- 错误码含义(AS §1):67004 当前状态不允许(归档/恢复/抛公海守卫);67006 归档被阻断:存在进行中商机;67007 抛公海被阻断:存在进行中商机;67012 分配非法(明细已分配/对象越权/为发起人本人/为当前负责人)。
- 单条动作 CAS 乐观锁 + 写 oplog(TRANSFER/CLAIM/POOL/ARCHIVE/RESTORE 均入 #45 oplog 值域);关注/重点族不写 oplog。
#21 新建客户 — POST /api/customer/create(form 绑定 CustomerCreateDTO,禁 @RequestBody)
- 出处:
A4 客户管理\我的客户\新增客户.bru + domain/dto/CustomerCreateDTO.java;出参 CustomerSaveResultDTO.java + CustomerSimilarHitDTO.java
- 三层查重内建:L1 停输防抖(check-name,只提示)→ L2 名称相似 needConfirm 弹窗(未落库)→ L3 信用代码硬拦(67003)。
ownerUserId 空=直接进公海并记 enter_pool_time(D25,锚点 NULL);有值=直接指派(快照服务端补齐,不信任前端传部门)。
- 请求参数(DTO 全字段,按区块分组;「必填」以 .bru 参数表+DTO @Schema 为准):
| 名 |
类型 |
必填 |
示例 |
值域/口径 |
| customerName |
string |
是 |
e2c-查重-撞码-003045 |
停输防抖触发相似查重 |
| customerType |
string |
是 |
customer_type_01 |
customer_type 字典(6 值,见字典节) |
| provinceCode / cityCode |
string |
是 |
440000 / 440100 |
国标 code |
| districtCode |
string |
否 |
440103 |
国标 code |
| industryCode |
string |
是 |
gov |
行业一级 code |
| industryChildCode |
string |
否 |
gov_1 |
行业二级 code |
| customerStarLevel / relationStarLevel |
number |
是 |
3 |
1~5 |
| networkUnits |
string |
否 |
|
人脉关系单位 JSON 数组(标签名) |
| ownerUserId |
string(Long) |
否 |
739564171091247104 |
空=进公海;无效/停用 67012 |
| companyDecisionMaker |
string |
否 |
王总 |
公司决策人 |
| isBizNegotiated |
number |
是 |
0 |
1 是 / 0 否 |
| directorVisitTime |
string |
否 |
|
总监近期拜访时间 yyyy-MM-dd HH:mm:ss |
| isChild |
number |
是 |
0 |
1 是 / 0 否(默认 0) |
| parentCustomerId |
string(Long) |
否 |
|
isChild=1 时必填,服务端防环校验 |
| cooperationSystem |
string |
否 |
OA对接 |
合作系统 |
| nonCoopReason |
string |
否 |
|
不合作原因 |
| joinedPresidentClass |
number |
否 |
1 |
1/0/null(未选) |
| presidentClassPerson / presidentClassPhone |
string |
条件 |
王总 / 13800000001 |
joinedPresidentClass=1 时 Person 必填 |
| joinedProductClass |
number |
否 |
|
1/0/null |
| productClassPerson / productClassPhone |
string |
条件 |
|
joinedProductClass=1 时 Person 必填 |
| unifiedCreditCode |
string |
否 |
91440101E2CHXDA001 |
18 位数字或大写字母;撞码 L3 硬拦 67003 |
| legalRepresentative |
string |
否 |
王测试 |
|
| establishedDate |
string |
否 |
2018-05-20 |
yyyy-MM-dd,不得晚于当前日期 |
| registeredCapital / businessScope / staffSize / annualRevenue / businessAddress |
string |
否 |
1000万元人民币 等 |
工商信息区文本 |
| bizNameFromLookup |
string |
否 |
|
企查查确认的工商名称(只存不覆盖客户名称) |
| remark |
string |
否 |
e2e 实测备注 |
≤500 字 |
| confirmSimilar |
boolean |
否 |
true |
L2 弹窗后「仍要创建」重发置 true |
- 出参(
CustomerSaveResultDTO,data 内 5 字段):id(Long→str,落库成功时)、needConfirm(Boolean,true=名称相似需确认未落库)、similarHits[](见 #22)、customerNo(落库成功时,"KH202609110010")、customerName。
- 错误码:67001 入参非法 / 67003 信用代码撞码 / 67012 销售负责人无效 / 67015 查重设置单例缺失。
- E2E 落库成功样例:
{"id":"753584451841163264","needConfirm":null,"similarHits":null,"customerNo":"KH202609110010","customerName":"e2c-cg-A-098932"}。
#22 名称相似查重 — GET /api/customer/check-name?name=&excludeId=
- 出处:
A4 客户管理\客户查重\名称相似提示.bru + CustomerSimilarHitDTO.java(L1 停输防抖 / L2 弹窗共用载荷;create/quick-create 内建查重同款)
- 请求参数:
name(string,必填,待查客户名称);excludeId(string,可选,编辑页传自身排除)。
- 出参(
List<CustomerSimilarHitDTO>,data 内 6 字段/行):
| 字段路径 |
类型 |
口径 |
脱敏 |
示例 |
| [].customerId |
Long→str |
命中客户 ID |
无权命中=null |
"750844478243209216" |
| [].customerName |
string |
命中客户名称 |
无权命中=null |
"e2c-恒信达科技有限公司" |
| [].ownerName |
string |
命中客户负责人 |
无权命中=null |
"罗伟健" |
| [].customerStage |
number |
1 潜在 2 重潜 3 已成交 |
无权命中=null |
1 |
| [].archiveStatus |
number |
1 有效 2 已归档 |
无权命中=null |
1 |
| [].masked |
boolean |
无权命中=true |
true 时以上五字段全 null,前端只渲染「存在匹配记录」 |
false |
- 备注:masked 脱敏形态(契约=DTO 注释,E2E 无 masked=true 样例):不是打星号字符串,而是「字段置 null + masked=true + 固定文案」。匹配方式/阈值走查重设置(AS §2.13:nameMatchMode 1 精确 / 2 模糊,similarityThreshold 0~100 出厂 80;.bru docs 提 ngram 阈值 70/80/90 为旧口径,以查重设置单例为准)。错误码 67015(单例未落地时走出厂默认不报)。
#23 信用代码查重 — GET /api/customer/check-credit-code?creditCode=
- 出处:
A4 客户管理\客户查重\信用代码查重.bru + CreditCodeCheckDTO.java
- 请求参数:
creditCode(string,必填,18 位)。示例 91440101MA9ABC1234。
- 出参(
CreditCodeCheckDTO,data 内 6 字段):
| 字段路径 |
类型 |
口径 |
脱敏 |
示例 |
| exists |
boolean |
是否已存在同码客户 |
- |
true |
| masked |
boolean |
无权命中=true |
true 时只提示「系统已存在该企业」 |
false |
| customerId |
Long→str |
命中客户 ID |
有权可见时给 |
"750844478243209216" |
| customerName |
string |
命中客户名称 |
有权可见时给 |
"e2c-恒信达科技有限公司" |
| customerNo |
string |
命中客户编号 |
有权可见时给 |
"KH202609030011" |
| ownerName |
string |
命中客户负责人 |
有权可见时给 |
"罗伟健" |
- 备注:L1 即时校验(失焦/企查查回填后调用);L3 保存时硬拦 67003;系统强制无开关(AS §2.13)。
#24 工商联想 — GET /api/customer/company-lookup?companyName=(参数名是 companyName 不是 name)
- 出处:
A4 客户管理\我的客户\工商信息查询.bru + port/CompanyLookupPort.java
- 请求参数:
companyName(string,必填,公司名称关键字)。示例 恒信达。
- 出参(
List<CompanyCandidate>,9 字段/候选):bizName / unifiedCreditCode / legalRepresentative / establishedDate(LocalDate)/ registeredCapital / businessScope / staffSize / annualRevenue / address。
- 备注:一期 stub 恒返回空数组(E2E:
"data": []),前端按「未查询到匹配企业」渲染,可手工录入;真实企查查接入后自动激活。前端职责:只回填当前为空的字段,已填字段再选须弹「受影响字段确认」;回填后立即调 check-credit-code;无结果/失败/超时均不阻断手工提交。
#25 编辑客户 — POST /api/customer/edit?id=(form 绑定 CustomerUpdateDTO + CAS)
- 出处:
A4 客户管理\客户详情\编辑客户.bru + CustomerUpdateDTO.java
- 请求参数:查询参数
id + 表单(与 #21 create 的差异):
- 多:
version(number,必传,乐观锁 CAS,不匹配报 67005「乐观锁冲突」;编辑回显取 GET /detail 的 version 带回)。
- 少:无
ownerUserId(归属字段不可改——负责人/部门「✅(新增时)」;服务端忽略任何归属入参,归属变更走 assign/transfer)。
- 其余 33 字段与 create 同名同义(customerName…remark + confirmSimilar),必填区相同(isBizNegotiated/isChild 必填)。
- 出参:同
CustomerSaveResultDTO(needConfirm 相似确认流同样适用,confirmSimilar=true 重发)。
- 错误码:67001 / 67002 / 67003 / 67005。
- 备注(AS §2.3 edit 行注):冒烟实测需一并提交
version+isBizNegotiated+isChild,缺省因 DB NOT NULL 无默认而失败(关联 AS §5 D-05 已修,冒烟口径保守保留)。E2E 反例样例:{"code":67005,"success":false,"message":"缺少乐观锁版本号","data":null}。
#47 快速创建客户 — POST /api/customer/quick-create(CustomerQuickCreateDTO 最小集)
- 出处:
A4 客户管理\我的客户\快速创建客户.bru + CustomerQuickCreateDTO.java
- 请求参数(7+1 字段,DTO
requiredMode=REQUIRED 全 7 必填):
| 名 |
类型 |
必填 |
示例 |
| customerName |
string |
是 |
e2c-快创-448049 |
| customerType |
string |
是 |
customer_type_01 |
| provinceCode / cityCode |
string |
是 |
440000 / 440100 |
| industryCode |
string |
是 |
gov |
| customerStarLevel / relationStarLevel |
number |
是 |
2 |
| confirmSimilar |
boolean |
否(默认 false) |
命中相似确认后重发置 true(返工票 04 补齐) |
- 出参:
CustomerSaveResultDTO(同 #21)。E2E needConfirm 样例:{"needConfirm":true,"similarHits":[{"customerId":"753620871771324416","customerName":"e2c-t05qc-相似源甲科技有限公司","ownerName":"罗伟健","customerStage":1,"archiveStatus":1,"masked":false}],"customerNo":null,"customerName":null}。
- 错误码:67001 / 67003。
- 备注(两处 .bru 漂移,以源码/AS 为准):① .bru 参数表把 industryCode/两星级标「否」且漏
confirmSimilar——DTO 源码有该字段(返工票 04 已补);② .bru docs 文本「QuickCreateDTO 无 confirmSimilar 字段」为陈旧描述。服务端默认:owner=当前用户、is_child=否、isBizNegotiated=否;查重三层与编号生成复用主表 create。
#48 商机侧客户搜索 — POST /api/customer/search(form 绑定 CustomerSearchParam)
- 出处:无 A4
.bru(契约源=源码 CustomerSearchParam.java + CustomerSearchItemDTO.java + AS §2.4);A3 侧演示形态待 03 票
- 请求参数:BaseParam 五参(keyword=名称/联系人/电话三维模糊)+
customerType(string,字典 code 精确,可空)+ archiveStatus(number,1/2,可空=全部)+ excludeCustomerIds(Long 数组,重复键;商机候选池排除已关联客户,可空)。
- 出参(
PageResult<CustomerSearchItemDTO>,行 9 字段):
| 字段路径 |
类型 |
口径 |
示例 |
| content[].id |
Long→str |
客户 id |
— |
| content[].customerNo |
string |
客户编号 |
— |
| content[].customerName |
string |
客户名称 |
— |
| content[].customerType |
string |
字典 code |
— |
| content[].ownerUserId |
Long→str |
销售负责人 id |
— |
| content[].ownerName |
string |
负责人姓名(快照) |
— |
| content[].ownerDeptId |
Long→str |
负责人部门 id |
— |
| content[].customerStage |
number |
1 潜在/2 重潜/3 已成交 |
— |
| content[].archiveStatus |
number |
1 有效/2 已归档 |
— |
- 备注:轻量出参;选中后走
GET /api/customer/detail?id= 带出全量;DataScope 注入可见范围。
#29 详情公共头部 — GET /api/customer/detail-head?id=
- 出处:
A4 客户管理\客户详情\详情公共头部.bru + domain/dto/CustomerDetailHeadDTO.java
- 请求参数:
id(string,必填,查询参数)。示例 750844477165273088。
- 副作用:upsert
customer_view_log 刷新「最近访问」锚点(F7 viewTouch 收口;UNIQUE(user_id,customer_id) 去重;高频行为不落 oplog)——冒烟断言 RECENT 视图排序前先打一次详情头。
- 出参(
CustomerDetailHeadDTO,data 内 24 字段;示例=E2E 客户甲):
| 字段路径 |
类型 |
口径/文案 |
示例 |
| id |
Long→str |
客户 ID |
"750844477165273088" |
| customerNo |
string |
客户编号 |
"KH202609030010" |
| customerName |
string |
客户名称 |
"e2c-全字段-科技" |
| customerStage |
number |
1 潜在 2 重潜 3 已成交(只读条,不可手动改) |
1 |
| customerStageName |
string |
阶段名回显(CustomerStageEnum) |
"潜在客户" |
| customerStarLevel / relationStarLevel |
number |
1~5 |
3 / 3 |
| strategicAgreementLevel |
number |
快照,null=未签 |
null |
| vipCustomerLevel |
number |
已成交 VIP 等级快照,null=无 |
null |
| archiveStatus |
number |
1 有效 2 已归档(已归档态 badge 数据源) |
1 |
| ownerUserId |
Long→str |
销售负责人 ID(公海 null) |
"739564171091247104" |
| ownerUserNameSnapshot |
string |
负责人姓名快照 |
"罗伟健" |
| ownerDeptNameSnapshot |
string |
部门名快照 |
"广东保伦电子股份有限公司" |
| lastFollowSummary |
string |
最近跟进摘要(最新一条跟进内容) |
"e2c-F08 跟进实测:电话沟通年度合作意向" |
| lastFollowTime |
string |
最近跟进时间 |
"2026-09-11 11:45:33" |
| nextFollowTime |
string |
待跟进提醒(未来最近一条下次跟进时间,null=无待跟进) |
"2026-09-12 22:34:06" |
| opportunityCount |
Long→str |
关联商机数(外部反查,失败前端显 "--") |
"0" |
| projectCount |
Long→str |
DTO @Schema=「恒 null 显 --」;E2E 实测 "0"——按恒空渲染 |
"0" |
| wonProjectAmount |
BigDecimal |
汇总卡①已中标项目总额——恒 null 显 "--"(A5/投标 seam 未接入) |
null |
| planEstimateAmount |
BigDecimal |
汇总卡②跟进中方案预估总额——恒 null 显 "--"(方案卡 seam) |
null |
| ongoingOpportunityAmount |
BigDecimal |
汇总卡③进行中商机总额——恒 null 显 "--"(可经 port 扩展后切真值) |
null |
| contractAmount |
BigDecimal |
汇总卡④合同总额——恒 null 显 "--"(合同模块未建) |
null |
| paidAmount |
BigDecimal |
汇总卡④累计回款——恒 null 显 "--"(与合同总额同卡) |
null |
- 备注:金额 5 字段恒 null 显 "--" 是 grill Q18 契约:前端渲染 4 张汇总卡、显 "--" 不显 0(矩阵 #29=P3-6 改造点:demo 未渲染卡)。错误码 67002。
#30 客户详情(页签1 客户信息 / 编辑页回显同源) — GET /api/customer/detail?id=
- 出处:
A4 客户管理\客户详情\客户详情.bru + domain/dto/CustomerDetailDTO.java(extends BaseDTO)
- 请求参数:
id(string,必填,查询参数)。错误码 67002。
- 出参(
CustomerDetailDTO,data 内 48 字段=BaseDTO 3 + 本类 45;示例=E2E 客户甲):
| 字段路径 |
类型 |
口径/文案 |
脱敏 |
| id |
Long→str |
主键 |
- |
| createTime / updateTime |
string |
经 BaseDTO 带出,E2E 实测有值 "2026-09-03 22:27:53"(见下方 G4 备注) |
- |
| customerNo |
string |
KH+yyyyMMdd+4 位序,只读 |
- |
| customerStage |
number |
1 潜在 2 重潜 3 已成交(只读条) |
- |
| archiveStatus |
number |
1 有效 2 已归档 |
- |
| version |
number |
乐观锁版本号(编辑回传,E2E 3) |
- |
| customerName |
string |
客户名称 |
- |
| customerType / customerTypeName |
string |
code + 字典名回显(customer_type_01/总包) |
- |
| provinceCode / cityCode / districtCode |
string |
国标 code(名称前端字典渲染,后端不回名) |
- |
| industryCode / industryName |
string |
gov/政府机关(一级字典名回显) |
- |
| industryChildCode / industryChildName |
string |
gov_1/人大政协(二级字典名回显) |
- |
| customerStarLevel / relationStarLevel |
number |
1~5 |
- |
| networkUnits |
string |
人脉关系单位 JSON 数组 |
- |
| ownerUserId |
Long→str |
销售负责人 ID(公海 null) |
- |
| ownerUserNameSnapshot / ownerDeptId / ownerDeptNameSnapshot |
— |
负责人姓名/部门 ID/部门名快照 |
- |
| enterPoolTime |
string |
进入公海时间(公海客户才有值) |
- |
| lastValidFollowTime |
string |
最近有效跟进时间(超期锚点) |
- |
| strategicAgreementLevel / vipCustomerLevel |
number |
快照(null=未签/无) |
- |
| companyDecisionMaker |
string |
公司决策人 |
- |
| isBizNegotiated |
number |
是否商机勾兑 1/0 |
- |
| directorVisitTime |
string |
总监近期拜访时间 |
- |
| isChild / parentCustomerId / parentCustomerNameSnapshot |
— |
子客户标志/父客户 ID/父客户名快照 |
- |
| cooperationSystem / nonCoopReason |
string |
合作系统/不合作原因 |
- |
| joinedPresidentClass / presidentClassPerson / presidentClassPhone |
— |
总裁班三件(电话明文,E2E "13800000001") |
无脱敏 |
| joinedProductClass / productClassPerson / productClassPhone |
— |
产品班三件(电话明文) |
无脱敏 |
| unifiedCreditCode |
string |
统一社会信用代码(E2E 91440101E2CTEST001 明文) |
无脱敏(V1 三档脱敏划出) |
| legalRepresentative / establishedDate / registeredCapital / businessScope / staffSize / annualRevenue / businessAddress / bizNameFromLookup |
— |
工商信息区(establishedDate=LocalDate) |
- |
| remark |
string |
备注 |
- |
- 备注(G4 校准):矩阵 #30 记档「系统信息四件套 creatorId/createTime/updaterId/updateTime 不在 DTO 恒"–"」——逐字核对源码后应修正为:
creatorId/updaterId 确实不在 CustomerDetailDTO(无字段);但 createTime/updateTime 由 BaseDTO 继承带出且 E2E 实测有值。demo 现渲染四件套恒 "--" 是保守可行口径;若要显示创建/更新时间可直接取值,只有操作人两件必须 "--"。
#40 跟进记录分页 — GET /api/customer/follow/page?id= + 新增跟进 — POST /api/customer/follow/add?id=
- 出处:
A4 客户管理\客户详情\跟进记录\跟进记录分页.bru + 新增跟进.bru + domain/param/FollowPageParam.java、domain/dto/FollowCreateDTO.java、domain/entity/CustomerFollow.java(出参=实体直出)
- page 请求参数(GET query):
id(必填)+ followWay(string,follow_way 字典 code 精确,例 follow_way_01)+ startTime/endTime(string,yyyy-MM-dd HH:mm:ss 含端)+ BaseParam 五参。
- G1 标注:原型有「操作人」筛选,
FollowPageParam 无 operatorUserId 参数——后端无此过滤能力,demo 侧豁免该筛选(P2-G1 记档)。
- page 出参(
PageResult<CustomerFollow>,行 13 字段,实体直出含审计四件套):
| 字段路径 |
类型 |
口径 |
示例 |
| content[].id / creatorId / createTime / updaterId / updateTime / deleted |
— |
BaseEntity 审计字段(E2E 实测序列化;"deleted":false) |
"750854396027338752" |
| content[].customerId |
Long→str |
所属客户 ID |
"750844477165273088" |
| content[].followWay |
string |
follow_way 字典 code(5 值见字典节;后端不回名) |
"follow_way_01" |
| content[].followContent |
string |
跟进内容(长文本) |
"e2c-F08 跟进实测:电话沟通年度合作意向" |
| content[].nextFollowTime |
string |
下次跟进时间(有值进「待跟进」筛选) |
"2026-09-10 23:07:18" |
| content[].followBy |
Long→str |
跟进人用户 ID |
"739564171091247104" |
| content[].followByName |
string |
跟进人姓名(服务端快照,前端不传) |
"罗伟健" |
| content[].followDeptName |
string |
跟进人部门名 |
"广东保伦电子股份有限公司" |
- add 请求参数(form):
followWay(必填,5 值域)、followContent(必填,空→67009)、nextFollowTime(选填,ISO T 分隔 2026-09-10T10:00:00;空格分隔报 400;有值进待跟进)。
- add 出参:
Result<Long> 跟进 id(E2E "753583108837605376")。
- 备注:append-only 提交后不可改删(纠错=新增补充);同客户同内容 30 秒窗口防重复提交;写库刷新
last_valid_follow_time 超期锚点(旧未发提醒自动置已失效)。错误码 67002/67009。已知缺陷 D-02(P2):nextFollowTime 早于当前未拦截(.bru docs 记档)。
#45 操作日志分页 — GET /api/customer/oplog/page?id=
- 出处:
A4 客户管理\客户详情\操作日志\操作日志分页.bru + domain/param/OplogPageParam.java、domain/entity/CustomerOplog.java(出参=实体直出)
- 请求参数(GET query):
id(必填)+ startTime/endTime(yyyy-MM-dd HH:mm:ss 含端)+ action(string,操作类型精确)+ BaseParam 五参(keyword 匹配 detail 叙事)。
- action 值域:权威=
CustomerConstants 全部 ACTION_* 常量,**18 种**:CREATE / UPDATE / FOLLOW / ARCHIVE / RESTORE / TRANSFER / CLAIM / POOL / STAGE_CHANGE / MEMBER_ADD / MEMBER_REMOVE / CONTACT_ADD / CONTACT_EDIT / CONTACT_DELETE / AGREEMENT_ADD / AGREEMENT_EDIT / AGREEMENT_DELETE / GRAPH_EDIT。(漂移标注:AS §3 写「17 种」未含 GRAPH_EDIT,未同步;demo 筛选下拉按 18 种渲染。)
- 出参(
PageResult<CustomerOplog>,行 12 字段):
| 字段路径 |
类型 |
口径 |
示例 |
| content[].id / creatorId / createTime / updaterId / updateTime / deleted |
— |
BaseEntity 审计字段 |
"750854380575522816" |
| content[].customerId |
Long→str |
所属客户 ID |
"750844477165273088" |
| content[].action |
string |
18 种值域(见上) |
"UPDATE" |
| content[].detail |
string |
叙事形态 把 {字段} 从 {旧} 修改为 {新}(D13;多字段变更串联一条) |
"把人脉关系单位从[...]修改为空;把公司决策人从王总修改为空;…" |
| content[].operatorId |
Long→str |
操作人用户 ID(系统动作为 null,如阶段自动流转/导入) |
"739564171091247104" |
| content[].operatorName |
string |
操作人姓名(系统动作记「系统」) |
"罗伟健" |
| content[].operatorDeptName |
string |
操作人部门名快照 |
"广东保伦电子股份有限公司" |
- 备注:append-only 审计、无限期保留;时间倒序;日期分组由前端渲染。错误码 67002。导出按钮无端点=豁免(P3-4)。
#41 关联商机页签 — GET /api/customer/opportunity/page?id=(port 反查 crm-opportunity)
- 出处:
A4 客户管理\客户详情\关联商机\关联商机页签.bru + port/CustomerOpportunityQueryPort.java(record OpportunityItem,7 字段真名逐字如下)
- 请求参数:
id(必填)+ BaseParam 五参。
- 出参(
PageResult<OpportunityItem>,契约 7 字段;G2:原型 13 列 vs 契约 7 字段,demo 现 5 列):
| 字段路径 |
类型 |
口径 |
示例 |
| content[].id |
Long→str |
商机 ID |
"750844498195513344" |
| content[].oppName |
string |
商机名称 |
"e2c-联动-交割" |
| content[].stageName |
string |
商机阶段名(已回显名) |
"客户圈定" |
| content[].estimateAmount |
BigDecimal |
预计金额 |
null |
| content[].ownerUserId |
Long→str |
销售负责人 ID |
"739564171091247104" |
| content[].ownerNameSnapshot |
string |
负责人姓名快照(可 null→"--") |
null |
| content[].updateTime |
string |
最近更新时间 |
"2026-09-03 22:27:58" |
- 备注:port 失败前端显 "--" 不显 0(issues-07 §A);列清单以 03 票实现实返为准收口 G2。错误码 67002。
#43 战略协议五端点 — CustomerAgreementController /api/customer/agreement/*
- 出处:
A4 客户管理\战略协议\*.bru 五件 + domain/dto/AgreementDTO.java(ADR-0017 Route A:写入参/出参双向同类)
- AgreementDTO 字段(双向):
| 字段 |
类型 |
必填(写) |
口径 |
| customerId |
Long→str |
新增必填;编辑传入忽略(归属不可换客户,以库内为准) |
所属客户 |
| agreementLevel |
number |
是 |
1~3=一级/二级/三级;越界 67017 |
| amount |
string |
否 |
签订金额文本(数字/区间/含单位,E2E "500万"、"128000") |
| remark |
string |
否 |
备注 |
| fileFileId |
string |
否 |
附件 fileId(crm-file 引用字符串,单文件可空);demo 以纯文本框呈现(附件上传位豁免 D2),下载由前端走 crm-file 通用端点 |
| customerName |
string |
仅出参 |
所属客户名称(卡片带出) |
| id / createTime / updateTime |
— |
仅出参 |
BaseDTO(E2E detail 实测有值) |
| 端点 |
参数 |
出参 data |
错误码 |
GET /api/customer/agreement/list?customerId= |
customerId |
List<AgreementDTO>(页签卡片,时间倒序非分页;E2E 空列表 []) |
67002 |
GET /api/customer/agreement/detail?id= |
id |
AgreementDTO 全量 |
67017/67002 |
POST /api/customer/agreement/create(form) |
AgreementDTO |
Long 协议 id |
67001/67017/67002 |
POST /api/customer/agreement/edit?id=(form) |
id + AgreementDTO |
null |
67001/67017/67002 |
POST /api/customer/agreement/delete?id= |
id |
null(软删) |
67002/67017 |
- 备注(快照联动):新增/编辑/删除协议同步刷新客户主表
strategic_agreement_level(删除=删空后快照回 NULL=未签);oplog 记 AGREEMENT_ADD/EDIT/DELETE;67017 语义=等级缺失或越界/协议不存在或无权访问。权限继承所属客户(无独立 ACL,无权 67002/67017)。
#44 团队成员三端点 — GET member/list?id= + POST member/add?id= + POST member/remove?id=&memberUserId=
- 出处:
A4 客户管理\客户详情\团队成员\*.bru 三件 + domain/dto/CustomerMemberDTO.java、MemberAddDTO.java
GET /api/customer/member/list?id= 出参(List<CustomerMemberDTO>,data 内仅 4 字段;负责人 ROLE_OWNER 在前):
| 字段路径 |
类型 |
口径 |
示例 |
| [].userId |
Long→str |
成员用户 ID |
"739564171091247104" |
| [].userName |
string |
姓名(负责人=主表快照,协同人=团队成员快照) |
"罗伟健" |
| [].deptName |
string |
部门名(负责人=主表快照,协同人=实时回显) |
"广东保伦电子股份有限公司" |
| [].role |
string |
角色枚举:OWNER 负责人 / COLLABORATOR 协同人(字段名是 role,不是 memberRole) |
"OWNER" |
- G3 标注:原型另有职务/加入时间两列——
memberRole、createTime 均不在此 DTO(不暴露),demo 恒 "--" 渲染=记档口径(P2-G3,字段级挂 07 票)。
POST /api/customer/member/add?id=:form 绑定 MemberAddDTO{memberUserIds: List<Long>}(重复键 memberUserIds=..&memberUserIds=..,示例 744842318024015872)→ null。两阶段校验整批拒绝:名单空/含销售负责人/重复加入/用户停用→67016;批内幂等去重;姓名快照;oplog MEMBER_ADD。E2E 反例:{"code":67016,"message":"成员名单不能为空"}。
POST /api/customer/member/remove?id=&memberUserId=:查询参数两件 → null。软删保留历史(delete_key 复用键,可重复加入);oplog MEMBER_REMOVE;错误码 67002/67016。
#42 关联项目页签 — 占位(demo 无端点调用,内容恒 "--")
- 矩阵 #42 口径:demo 侧无入口调用、页签占位显 "--"(「新增占位页签」动作)。漂移提示:契约面实际已存在
GET /api/customer/project/page?id=(CustomerDetailController.pageProjects,CustomerProjectQueryPort.ProjectItem 9 字段),且 A4 客户管理\客户详情\关联项目\关联项目页签.bru 已有 E2E 真值(projectName=e2c-r3d-n3-项目甲、stageName=冲突处理、statusName=进行中、filingStatusName=报备审核中)——AS §5.3「A5 未建恒 --」记档已过时;demo 维持占位,切真值属后续票,本表不展开。
字典与枚举(值域 + 文案 + 出处)
| 字典/枚举 |
值域与文案 |
出处 |
| customer_stage |
1 潜在客户 / 2 重潜客户 / 3 已成交客户(三态只前进,CAS;本期两个跃迁均不触发,实际只有「潜在」,D20 seam) |
domain/enums/CustomerStageEnum.java;detail-head customerStageName 回显「潜在客户」;AS §3 |
| customer_star_level |
1~5 整数,无文案字典(星级组件渲染) |
DTO @Schema;AS §2.1 |
| relation_star_level |
1~5 整数(导入 INSERT 缺省补 0=未评估占位,D-05 修复口径);看板 groupColumn=relation 分组 1~5 |
AS §5.7/§2.1 |
| customer_type |
customer_type_01 总包 / _02 工程商 / _03 投资方 / _04 设计院 / _05 集成商 / _06 投标公司 |
crm-dict/.../config/DictDataInitializer.java L325-332 |
| industryCode |
运行时字典接口/字典树核对(industry 分组两级行业树,客户用二级/商机用一级);E2E 样例 gov=政府机关、gov_1=人大政协 |
DictDataInitializer L95;E2E detail 实测 |
| follow_way |
follow_way_01 电话沟通 / _02 上门拜访 / _03 微信对接 / _04 线上会议 / _05 展会沟通 |
DictDataInitializer L293-299;FollowCreateDTO @Schema |
| viewType(内置五视图) |
ASSIGNED 我负责的 / COLLABORATING 我协同的 / FOLLOW_UP_DUE 待跟进 / FOCUSED 我关注的 / RECENT 最近访问;适配矩阵见 #1 |
AS §2.1/§3 |
| archiveStatus |
1 有效 / 2 已归档(无合并态,D19 归档=删除语义) |
AS §3 |
| view-form |
list / split / board(白名单外 68001;pool 拒 board 文档口径未实现=I-06 漂移) |
ViewFormController/Impl;.bru 保存视图形态.bru;AS §2.14 |
| oplog action |
18 种(含 GRAPH_EDIT),逐值见 #45;AS §3「17 种」为未同步漂移 |
CustomerConstants L97-114 |
| BatchFailReason |
NOT_FOUND / STATUS_NOT_ALLOWED / CONCURRENT_MODIFIED / ARCHIVE_BLOCKED / POOL_BLOCKED / ASSIGN_INVALID / UNKNOWN |
domain/enums/CustomerBatchFailReason.java |
| member role |
OWNER / COLLABORATOR |
CustomerMemberDTO 常量 |
| transferReason(交割,本表外引用) |
resign 离职 / transfer_post 岗位调动 / region_adjust 区域调整 |
AS §3 |
| job_title |
运行时两级字典树(demo 动态拉取),无静态值域,不落本表 |
AS §2.7(jobTitleId/Name/Category/Level 联动 D1/D2) |
来源文件清单
Bruno 仓 D:\code\crm-api-docs\(相对根):
A4 客户管理\我的客户\workspace 列表分页.bru、新增客户.bru、快速创建客户.bru、编辑客户.bru(客户详情\)、工商信息查询.bru
A4 客户管理\客户总览\workspace 列表分页.bru、看板列表\看板分组汇总.bru、看板单列卡片.bru(我的客户同名)
A4 客户管理\客户查重\名称相似提示.bru、信用代码查重.bru
A4 客户管理\客户详情\详情公共头部.bru、客户详情.bru、标记重点客户.bru、取消重点标记.bru、跟进记录\跟进记录分页.bru、跟进记录\新增跟进.bru、操作日志\操作日志分页.bru、关联商机\关联商机页签.bru、关联项目\关联项目页签.bru、团队成员\团队成员页签.bru、团队成员\添加团队成员.bru、团队成员\移除团队成员.bru
A4 客户管理\客户公海\关注客户.bru、取消关注客户.bru、批量关注客户.bru、领取客户.bru、批量领取.bru、分配客户.bru、批量分配.bru、抛公海.bru、批量抛公海.bru、归档客户.bru、批量归档.bru、恢复客户.bru、读取视图形态.bru、保存视图形态.bru(我的客户/客户总览同名)
A4 客户管理\战略协议\战略协议页签列表.bru、战略协议详情.bru、新增战略协议.bru、编辑战略协议.bru、删除战略协议.bru
A3 商机管理\销售机会\自定义视图\自定义视图列表.bru、保存自定义视图.bru(saved-view 形态佐证,A4 无同名 .bru)
后端源码 D:\code\crm-backend-matt\(绝对路径根同上):
crm-customer\src\main\java\com\crm\customer\domain\dto\:CustomerListRowDTO、CustomerBoardSummaryDTO、CustomerCreateDTO、CustomerUpdateDTO、CustomerQuickCreateDTO、CustomerSaveResultDTO、CustomerSimilarHitDTO、CreditCodeCheckDTO、CustomerSearchItemDTO、CustomerDetailHeadDTO、CustomerDetailDTO、FollowCreateDTO、AgreementDTO、CustomerMemberDTO、MemberAddDTO、CustomerBatchFailItem
crm-customer\...\domain\param\:CustomerWorkspacePageParam、CustomerSearchParam、FollowPageParam、OplogPageParam
crm-customer\...\domain\enums\:CustomerStageEnum、CustomerBatchFailReason;constant\CustomerConstants.java
crm-customer\...\port\:CustomerOpportunityQueryPort、CompanyLookupPort;mapper\CustomerMapper.java(pageWorkspace SQL:opportunityCount 真算/projectCount 恒 0/focusFlag/lastViewTime);service\impl\CustomerWorkspaceServiceImpl.java(board 守卫/排序/viewType);controller\:CustomerWorkspaceController、CustomerOwnershipController、CustomerFocusController、CustomerController、CustomerDetailController、CustomerAgreementController
crm-preference\src\main\java\com\crm\preference\:controller\SavedViewController、controller\ViewFormController、domain\dto\SavedView、domain\dto\SavedViewCondition
crm-base\src\main\java\com\crm\base\domain\:param\BaseParam、dto\BaseDTO、result\PageResult、result\BatchResult、result\Result
crm-dict\src\main\java\com\crm\dict\config\DictDataInitializer.java(customer_type/follow_way 种子)
仓内文档:
.scratch\customer-module\API-SUMMARY.md(§0/§1/§2.1-2.6/§2.9/§2.14/§3/§5)
.scratch\customer-demo-ref\assets\coverage-matrix.md(入口编号/G1-G6/P3-x 依据)
.scratch\customer-frontend-handover\mapping-a4-pages.md(原型列文案)
.scratch\customer-e2e\demo\index.html(fork 基线渲染口径:|| '--'、maskPhone/reveal、旧 loadSummary 读 count/name 的漂移证据)
第二部分 · 联系人族 + 重流程 + 规则族(矩阵 #31-#69)
用途:客户单文件 Demo 冒烟断言 + 人工验收双用。字段名逐字来自 .bru / DTO·Param 源码,不许凭记忆编。
契约权威优先级:Bruno .bru 出参示例 > DTO/Param 源码 > API-SUMMARY(下称 AS);请求绑定方式以控制器源码 + AS §0 为准(两处 .bru 请求体漂移已标注)。
Bruno 仓根 D:\code\crm-api-docs\(下引 .bru 相对该根);后端源码根 crm-customer/src/main/java/com/crm/customer/(下称 [DTO:x]=domain/dto/x.java、[PARAM:x]=domain/param/x.java);crm-rule 源 = crm-rule/src/main/java/com/crm/rule/。
覆盖范围:覆盖矩阵「缺口动作≠豁免」中联系人族(#31-39、#50-52)+ 重流程(#55-63)+ 规则族(#66-69)。
0. 阅读口径(全表通用)
- envelope:所有 JSON 端点统一
{code, success, message, data}(Result<T>,code=0 成功);下表「出参」一律指 data 内 字段,不再重复信封。二进制流端点(模板/导出)不走 envelope。
- 空值口径:Demo 渲染空值(null/空串/缺失字段)一律显
--;雪花 Long id JSON 序列化为字符串,前端禁 Number 运算。
- 脱敏口径:联系人电话在全部读路径服务端脱敏(
138****1234,源 ContactPhoneMasker);明细见文末「脱敏专节」。
- 分页:入参继承
BaseParam(current=1/size=10(≤500)/keyword/orderBy/asc=false,源 crm-base/.../param/BaseParam.java);出参 PageResult = {content[], total, size, current, pages, empty}(total/size/current/pages 实测序列化为字符串)。
- 标注缩写:G5=联系人负责人列缺(P2 记档);D-G1=batchEdit 空串清空不可达(P2 豁免);D26=电话软提示受查重开关控制;D22=resign 自动推导接收总监;D23=全分配自动置已完成;D-07=assignable 超时(已修复);P1-1=导入 INSERT 必填前移(已修复);POOL-CFG=公海池只读占位卡。
- seed 示例值:admin userId=
739564171091247104;客户 750844477165273088(e2c-全字段-科技,KH202609030010);联系人 750844500171030528(张关键)/750844501387378688(王重复,同号 13812340001 重复样例);交接单号形态 JG202609030001。
一、联系人独立菜单(TREE A4-5)
#50 顶级联系人列表 — GET /api/customer/contact/page
- 出处:
A4 客户管理\联系人\顶级联系人列表.bru + [PARAM:ContactPageParam](extends BaseParam,无自有字段,keyword 匹配姓名/电话/所属客户名,客户可见性收口)+ [DTO:ContactDTO]
- 请求参数:
| 名 |
类型 |
必填 |
示例 |
| current |
number |
否 |
1 |
| size |
number |
否 |
10 |
| keyword |
string |
否 |
恒信达 |
| orderBy / asc |
string/boolean |
否 |
createTime / false |
- 出参(data = PageResult,content[] 元素):
| 字段路径 |
类型 |
口径/文案 |
脱敏 |
| content[].id |
Long(字符串) |
联系人主键 |
-- |
| content[].customerId |
Long(字符串) |
所属客户 id(新增必填;编辑以库内归属为准不可换客户) |
-- |
| content[].name |
String |
联系人姓名 |
-- |
| content[].jobTitleName |
String |
职务名称(文本,必填;自定义职务时下面三列空) |
-- |
| content[].jobTitleId |
Long(字符串) |
标准职务 id(可空) |
-- |
| content[].jobTitleCategory |
String |
职务类别(高层管理/采购/…,可空) |
-- |
| content[].jobTitleLevel |
Integer |
职务层级 1–10;null=未设置等级落右侧栏 |
-- |
| content[].phone |
String |
联系电话 |
是(前3+****+后4) |
| content[].isKeyContact |
Integer |
是否关键联系人(是1/否0) |
-- |
| content[].isInternal |
Integer |
是否内线(是1/否0) |
-- |
| content[].giftRemark |
String |
礼品备注 |
-- |
| content[].source |
String |
来源字典 contact_source:组会/组局/转介绍/其他 |
-- |
| content[].customerName / customerNo |
String |
所属客户名称/编号(仅出参) |
-- |
| total/size/current/pages/empty |
— |
分页元信息 |
-- |
- 备注:
.bru 示例漂移:示例里 phone:"13812340001"、source:"contact_source_01" 均为票 04 脱敏/字典改版前的陈旧真值;现行源码 CustomerContactServiceImpl.page 对每行 ContactPhoneMasker.mask(...)(service L150),权威 source 值域见字典节(group_meeting 等)。
- 新增入口:Demo 独立页签 +
scopeKey=contact.list 列偏好(A4 客户管理\联系人\读取列偏好.bru/保存列偏好.bru)与形态偏好(读取/保存视图形态.bru)接线。
#51 联系人详情 — GET /api/customer/contact/detail?id=
- 出处:
A4 客户管理\联系人\联系人详情.bru + [DTO:ContactDTO]
- 请求参数:
id(string,必填,联系人 id,查询参数)
- 出参(data):与 #50 列表行同一 ContactDTO 字段集(id/customerId/name/jobTitleName/jobTitleId/jobTitleCategory/jobTitleLevel/phone/isKeyContact/isInternal/giftRemark/source + customerName/customerNo),示例另带 createTime/updateTime。
- 备注:
.bru 实测示例 phone:"138****0101"(脱敏形态✅,与现行代码一致);错误码 67008 联系人不存在(AS §2.7 行记 67002,以 .bru 为准并注漂移)。
- 与列表行差异:无(同 DTO);Demo 旧代码编辑弹窗直接用行数据回显(L1143),新入口回显应走本端点(明细行级最新值)。
- phone 脱敏回显形态(含
*)提交回 edit/batchEdit = 用户未改号,服务端跳过 phone 更新(CustomerContactServiceImpl.edit L89)。
#52 联系人全局搜索 — GET /api/customer/contact/search?keyword=&limit=
- 出处:
A4 客户管理\联系人\联系人全局搜索.bru + CustomerContactServiceImpl.search(L155)
- 请求参数:
| 名 |
类型 |
必填 |
示例 |
| keyword |
string |
是 |
恒信达 |
| limit |
number |
否 |
20(默认 20;≤0 回落默认) |
- 出参(data = 数组):元素字段与 #50 列表行完全一致(ContactDTO + customerName/customerNo);phone 服务端脱敏(
.bru 示例明文=陈旧漂移,同 #50)。
- 备注:商机「商机关键联系人」数据源(姓名/电话模糊 + 带出所属公司名);客户可见性收口;Demo 新增入口,演示商机选联系人场景。
二、联系人列表(详情页签数据源)
#31 客户联系人列表 — GET /api/customer/contacts?customerId=
- 出处:
A3 商机管理\销售机会\按客户查联系人列表.bru(A4 侧无独立 .bru,商机字段 17 级联数据源兼详情页签 2;AS §2.5 页签2 / §2.4)+ [DTO:ContactDTO](CustomerContactServiceImpl.listByCustomer L123)
- 请求参数:
| 名 |
类型 |
必填 |
示例 |
| customerId |
string |
是 |
750844477165273088 |
- 出参(data = 数组,ContactDTO 全字段):
id / createTime / updateTime / customerId / name / jobTitleName / jobTitleId / jobTitleCategory / jobTitleLevel / phone / isKeyContact / isInternal / giftRemark / source / customerName / customerNo(口径同 #50 表)。
- 备注:
- 8 列渲染(Demo renderContactsList L844):姓名/职务/电话/关键联系人/内线/来源/礼品备注(+勾选/序号/操作列);
isKeyContact/isInternal 1→是 0→否;空文本显 --。
- 无负责人列 = G5 标注:ContactDTO 不含 owner 字段(DTO 全字段核对),G5 挂 07 号票「现状值域+定稿口径」两栏。
- 电话脱敏形态:
.bru 示例 13812340002 明文 = 票 04 前陈旧真值;现行代码 listByCustomer 逐行 ContactPhoneMasker.mask(...)(L131)→ 断言按 138****0002 形态。
- 权限继承所属客户(无独立 ACL,无权 67002);错误码 67002。
三、联系人图谱
#32a 图谱全量读 — GET /api/customer/contact/graph/detail?customerId=
- 出处:
A4 客户管理\客户详情\联系人图谱\图谱全量读.bru + [DTO:GraphDTO/GraphNodeDTO/GraphEdgeDTO]
- 请求参数:
customerId(string,必填,查询参数)
- 出参(data):
| 字段路径 |
类型 |
口径/文案 |
脱敏 |
| version |
Integer |
图谱版本号(save 乐观锁比对口径;联系人增删等节点集变化也会 bump;首次读懒建版本行) |
-- |
| nodes[].contactId |
Long(字符串) |
联系人 id |
-- |
| nodes[].name |
String |
联系人姓名 |
-- |
| nodes[].jobTitleName |
String |
职务名称 |
-- |
| nodes[].jobTitleCategory |
String |
职务类别(一级类别文本) |
-- |
| nodes[].level |
Integer |
等级 1–10(10=顶层);null=未设置等级(右侧栏) |
-- |
| nodes[].levelSource |
Integer |
等级来源:1=字典跟随 2=手工锁定(D2) |
-- |
| nodes[].phoneMasked |
String |
联系电话(字段名即脱敏承诺) |
是 138****9901 |
| nodes[].isKeyContact |
Integer |
是否关键联系人(1/0) |
-- |
| nodes[].isInternal |
Integer |
是否内线(1/0,悬停气泡展示) |
-- |
| nodes[].giftRemark |
String |
礼品备注(悬停气泡,可空) |
-- |
| edges[].parentId / childId |
Long(字符串) |
手动边:上级/下级联系人 id(level 高者→低者) |
-- |
- 备注:公司边派生注——nodes 只含客户联系人,公司节点/公司边不在数据里,由前端按
level==10 派生渲染(D8,修订⑧口径:只有手动边,无自动生成/否决);edges 只含手动边全量终态;布局纯由 level 推导(y=等级行、x=行内稳定序),零坐标数据。错误码 67002。跨客户人脉/节点展开折叠=豁免(原型本期暂不支持)。
#32b 图谱整体保存 — POST /api/customer/contact/graph/save
- 出处:
A4 客户管理\客户详情\联系人图谱\图谱整体保存.bru + [PARAM:GraphSaveParam]
- 请求参数(表单绑定,嵌套行走 indexed):
| 名 |
类型 |
必填 |
示例 |
| customerId |
string |
是 |
753314901191032832 |
| version |
number |
是 |
1(读端点返回值原样带回;不符→67018) |
| nodes[i].contactId |
string |
是 |
(必须属于 customerId 客户) |
| nodes[i].level |
number |
否 |
1–10;null=撤级(撤级须无任何边) |
| edges[i].parentId / childId |
string |
是 |
上级/下级联系人 id |
- 出参(data):
Integer 新版本号(实测示例 2)。
- 备注:整体保存语义——全量快照单事务:版本 CAS(67018 冲突)→ 边校验(67019 自连/重复/等级倒挂或越界/未定级端点/端点越界;67020 单父违反/等级调整约束)→ level 批写 + 边全量替换(edges 空=清空全部边);D4 编辑态变更驻前端 pending,save 一次落库。错误码 67002/67018/67019/67020。
四、联系人列表侧增强
#33 页内批量编辑 — POST /api/customer/contact/batchEdit
- 出处:
A4 客户管理\客户详情\联系人\页内批量编辑.bru + [PARAM:ContactBatchEditParam] + [DTO:ContactBatchResultDTO]
- 请求参数(表单绑定,
rows[0].xxx indexed):
| 名 |
类型 |
必填 |
口径 |
| rows[i].id |
string |
是 |
联系人 id |
| rows[i].name |
string |
否 |
null=不改;提交则须非空 |
| rows[i].jobTitleCode |
string |
否 |
标准职务字典 code;null=不改;变更走 D2 联动重算 level |
| rows[i].phone |
string |
否 |
null=不改;脱敏形态(含)=不改*(回显原样提交);明文=修改 |
| rows[i].source |
string |
否 |
来源字典 code;null=不改 |
| rows[i].isKeyContact |
number |
否 |
null=不改 |
- 出参(data = ContactBatchResultDTO):
successCount:int(原子批:全过=N,任一失败=0)、failedRows[]:rowIndex:int(提交数组下标从 0 计)+ reason:String。
- 备注:
- 空串提交=不修改(D-G1 标注):Param.phone 注释声称「空串=清空」,但 Spring 表单绑定(StringEditor allowEmpty)把空串转 null=不改,「空串=清空」不可达——D-G1(P2 语义缺口)记档豁免,Demo 按「绑定语义空串→null=不修改」断言。
- 原子批:任一行校验失败零落库并返回失败明细(前端定位首个错误行);「只改当前页」由前端提交集合表达,后端无页语义;职务变更 + 任一行改成功 bump 图谱版本。错误码 67008/67022(整批不可用=行集空)。
#34 电话明文查看 — GET /api/customer/contact/reveal?id=
- 出处:
A4 客户管理\客户详情\联系人\电话明文查看.bru
- 请求参数:
id(string,必填,联系人 id)
- 出参(data):
String 明文手机号(实测示例 13800001003)——reveal 是唯一明文通道。
- 备注:行为埋点落
customer_contact_reveal_log(谁/何时/哪个联系人/IP,失败自吞不阻塞);错误码 67008。Demo 侧列表与图谱共享 revealed 态(revealed[id]=true 双写列表行与图谱节点,index.html L609/L1848-1864),「明文查看」按钮=全量逐个 reveal。
#35 联系人导出 — POST /api/customer/contact/export?customerId=
- 出处:
A4 客户管理\客户详情\联系人\联系人导出.bru + [DTO:ContactExportRow](domain/excel)
- 请求参数:
customerId(string,必填,查询参数)
- 出参:HTTP 200 xlsx 二进制流(PK 头
50 4b 03 04,不走 JSON 信封,前端按附件流下载);服务端 MinIO 留档 90 天。
- 备注:导出电话脱敏口径同列表——已从源码确认(
CustomerContactEnhanceServiceImpl L307 row.setPhone(ContactPhoneMasker.mask(...)) // 脱敏口径不因导出放开;.bru docs 同句)。xlsx 列集(ContactExportRow 7 列):姓名 / 职务 / 职务类别 / 职务等级 / 手机号(脱敏) / 来源 / 关键联系人(1=是)。
五、联系人导入六端点(#36,镜像客户导入)
端点×语义总表(出处:A4 客户管理\客户详情\联系人导入\*.bru 六件 + CustomerContactImportController /api/customer/contact/import):
| 端点 |
方法 |
语义 |
关键错误码 |
| /template |
GET |
模板下载:单 sheet 纯表头五列(姓名 / 职务(字典编码) / 手机号 / 来源(字典编码) / 是否关键联系人(是/否),源 ContactImportTemplateRow;表头即版本锚点,无示例行;RFC 5987 中文文件名),xlsx 流不走信封 |
— |
| /upload |
POST(multipart) |
上传+预校验(见下),任务落待确认态,返回预计数+明细 |
67023 |
| /confirm?taskId= |
POST |
待确认→处理中+异步执行(行级部分成功);重复确认 67024 |
67024 |
| /result?taskId= |
GET |
任务结果(状态+六计数+提交人/时间) |
67024 |
| /failures?taskId= |
GET |
失败/疑似重复明细(疑似重复行以「疑似重复」前缀区分) |
67024 |
| /page |
GET |
最近导入记录(仅当前用户任务,状态/方式过滤,时间倒序) |
— |
/upload 请求参数(multipart,源控制器 L49 @RequestParam 逐字):
| 名 |
类型 |
必填 |
口径 |
| customerId |
string |
是 |
导入目标客户 id(固定,不可切换) |
| file |
file |
是 |
.xls/.xlsx,≤20MB,单 sheet ≤5 万行,模板版本校验 |
| importMode |
string |
是 |
APPEND_ONLY 仅新增 / UPDATE_ONLY 仅更新 / UPSERT 新增或更新 |
| duplicateStrategy |
string |
否 |
SKIP 不导入(默认)/ OVERWRITE 覆盖导入 |
关键 DTO 字段:
ContactImportPreviewDTO(upload 出参):taskId / customerId / importMode / duplicateStrategy / totalCount(数据行总数)/ insertCount(预计新增)/ updateCount(预计更新,含未变化)/ failCount(预校验失败)/ suspectCount(疑似重复,不写入人工处理)/ rows[](预校验明细,**上限 100 条**);rows[] 行:rowNum(数据行号从 1 起)/ identity(联系人识别=姓名原值)/ verdict(INSERT/UPDATE/FAIL/SUSPECT)/ reason(失败/疑似原因,可空)。预检计数=insert/update/fail/suspect 四计数齐全(源码逐字核对)。
ContactImportResultDTO(result/page 行出参):taskId / customerId / importMode / templateVersion(V1)/ status(**0待确认 1处理中 2已完成 3已失败**)/ totalCount / insertCount / updateCount / unchangedCount(未变化,UPDATE 行值相同时)/ failCount / suspectCount / failReason(已失败时)/ creatorName / createTime / finishTime。
ContactImportFailDTO(failures 出参行):rowNum / name(姓名原值,行静态缺失时为空)/ phone(手机号原值,可空)/ failReason;无 sheet 维度(单 sheet 导入,对称 ImportFailDTO 有 sheetName 的差异点)。
ContactImportPageParam(page 入参):BaseParam + status(0-3)+ importMode(三值)。
- 备注:行级判定与执行同口径(必填/字典值域/客户内手机号查重);有手机号按手机号查重;任一行成功 bump 图谱版本;Demo cimp 三步弹窗已全覆盖六端点(L2131-2186)。
六、快速添加 / CRUD / 电话软提示
#37 快速添加联系人(多行)— POST /api/customer/contact/quickAdd
- 出处:
A4 客户管理\客户详情\联系人\快速添加联系人.bru + [PARAM:ContactQuickAddParam]
- 请求参数(表单绑定,
rows[0].xxx indexed):
| 名 |
类型 |
必填 |
口径 |
| customerId |
string |
是 |
目标客户 id(固定,不可切换) |
| rows[i].name |
string |
是 |
联系人姓名 |
| rows[i].jobTitleCode |
string |
是 |
job_title 组二级项字典 code(联动补全 id/名称/类别/档位,D1 初始值 level_source=1) |
| rows[i].phone |
string |
否 |
可空;非空须 11 位手机号格式 |
| rows[i].source |
string |
否 |
contact_source 组字典 code,可空 |
| rows[i].isKeyContact |
number |
否 |
是1/否0,空=0 |
- 出参(data = ContactBatchResultDTO):
successCount:int + failedRows[]{rowIndex:int(提交数组下标从 0 计), reason:String}。
- 备注:部分成功(合法行落库、非法行明细返回;空行跳过;失败行留在编辑器改完只重提失败行不重复创建);有手机号按手机号查重、无手机号按客户+姓名+职务疑似重复提示不静默创建;任一行成功 bump 图谱版本;错误码 67021(行集空=整批不可用)/67008。
#38 联系人新建 / 编辑 / 删除 — POST contact/create、POST contact/edit?id=、POST contact/delete?id=
- 出处:
A4 客户管理\联系人\新增联系人.bru + 编辑联系人.bru + 删除联系人.bru + [DTO:ContactDTO](写入参/出参双向,ADR-0017 Route A)
- 请求参数(表单绑定):
| 名 |
类型 |
必填 |
口径 |
| customerId |
string |
create 必填 |
所属客户 id;编辑以库内归属为准不可换(入参忽略) |
| name |
string |
是 |
联系人姓名 |
| jobTitleName |
string |
是 |
职务名称(自由文本;标准职务另传 jobTitleId 三列) |
| jobTitleId / jobTitleCategory / jobTitleLevel |
string/string/number |
否 |
标准职务 id / 类别 / 层级 1–10 |
| phone |
string |
否 |
手机号格式;脱敏回显形态(含)提交=不改号保留库内原值*(edit,L89) |
| isKeyContact / isInternal |
number |
否 |
是1/否0 |
| giftRemark |
string |
否 |
礼品备注 |
| source |
string |
否 |
contact_source 字典 code |
| id(查询参数) |
string |
edit/delete 必填 |
联系人 id |
- 出参(data):create =
Long 新联系人 id(字符串);edit/delete = null。
- 备注:
- 扩展档案字段(性别/生日/微信/邮件/地址)V1 收窄不在 ContactDTO——豁免标注(GR 豁免台账),DTO 逐字段核对无此类字段,Demo 不得渲染这些输入位。
- 权限继承所属客户(无独立 ACL),无权 67008/67002;edit 错误 67008,delete 软删(可能被商机/项目引用做快照,级联清图谱边 + bump 版本)。
- 写动作记 oplog
CONTACT_ADD/CONTACT_EDIT/CONTACT_DELETE(F10)。
- Demo 旧代码 dlgContact(L1142-1159)直接行数据回显 + 硬编码 source 旧 code(见字典节漂移)。
#39 电话软提示 — GET /api/customer/contact/check-phone?phone=&excludeId=
- 出处:
A4 客户管理\联系人\电话软提示.bru + [DTO:ContactPhoneHitDTO](service L171)
- 请求参数:
| 名 |
类型 |
必填 |
口径 |
| phone |
string |
是 |
联系人手机号 |
| excludeId |
string |
否 |
排除的联系人 id(编辑页传自身) |
| 字段路径 |
类型 |
口径/文案 |
脱敏 |
| [].contactId |
Long(字符串) |
联系人 id(masked 时 null) |
masked 时 -- |
| [].contactName |
String |
联系人姓名(masked 时 null) |
masked 时 -- |
| [].jobTitleName |
String |
职务名称(masked 时 null) |
masked 时 -- |
| [].customerId |
Long(字符串) |
所属客户 id(masked 时 null) |
masked 时 -- |
| [].customerName |
String |
所属客户名称(masked 时 null) |
masked 时 -- |
| [].masked |
Boolean |
命中行对当前用户是否脱敏(所属客户不可见=只提示「存在匹配记录」) |
-- |
- 备注:
- D26 注:跨客户电话命中只提示疑似重复、不阻断;受查重设置「联系电话」开关(
phoneEnabled,crm-rule 单例)控制——settings.phoneSoftCheckEnabled() 为假时恒返回空数组(单例未落地=出厂默认电话软提示开)。
- 序列化漂移:源码
ownerUserId/ownerDeptId 标 @Schema(hidden=true)(服务层判权用),但 hidden 只影响 swagger 文档不影响 JSON 序列化——.bru 实测示例确带这两个字段;前端不应依赖,Demo 断言只按上表 6 字段。
七、客户交割(#55-59,TREE A4-7)
级联语义注(全组):发起交割整批原子(@Transactional)——圈定=当前用户名下可交接客户(唯一负责人+有效+非归档+非公海),initiate 无 customerIds 入参,圈定=preview 全量;名下客户 owner→接收总监 + 同步商机/项目 owner(port 联动),任一失败全部回滚不生成交接单;oplog TRANSFER(系统操作,含交接编号)。交接编号形态 JG+yyyyMMdd+4位序(实测 JG202609110001)。
#55 发起前预览 + 发起交割 — GET transfer/preview + POST transfer/initiate
- 出处:
A4 客户管理\客户交割\发起前客户预览.bru + 发起交接.bru + [DTO:TransferPreviewDTO/TransferInitiateDTO](initiate 返回 TransferDetailDTO,控制器 L48)
- preview 请求参数:无业务参数(当前用户上下文圈定)。
- preview 出参(data = 数组,TransferPreviewDTO):
| 字段路径 |
类型 |
口径/文案 |
| [].customerId |
Long(字符串) |
客户ID |
| [].customerName |
String |
客户名称 |
| [].customerNo |
String |
客户编号(KH 形态) |
| [].customerStarLevel |
Integer |
客户星级 1~5 |
| [].customerStage |
Integer |
客户阶段:1潜在 2重潜 3已成交 |
- initiate 请求参数(表单绑定 TransferInitiateDTO):
| 名 |
类型 |
必填 |
口径 |
| reason |
string |
是 |
transfer_reason 字典 code:resign 员工离职 / transfer_post 岗位调动 / region_adjust 区域调整 |
| toDirectorId |
string |
否 |
接收总监用户 id;resign 自动推导不传(D22);调动/区域调整必传且校验在直属总监范围内(67011) |
| remark |
string |
否 |
备注(选填) |
- initiate 出参(data = TransferDetailDTO,字段同 #57 详情表)。
- 备注:错误码 67010(存在未完成交接单,防重入)/67011(无可交接客户/接收总监不合法/原因缺失)/67012。
#56 交接单列表 — GET transfer/page
- 出处:
A4 客户管理\客户交割\交接记录分页.bru + [PARAM:TransferPageParam](javadoc:keyword 匹配发起人姓名;时间倒序)
- 请求参数:
| 名 |
类型 |
必填 |
口径 |
| transferNo |
string |
否 |
交接编号(模糊,JG* 形态) |
| reason |
string |
否 |
transfer_reason code(精确) |
| status |
number |
否 |
0 待分配 1 已完成(.bru 示例查询串填 status=2 超值域,以文档表+源码注释 0/1 为准) |
| current/size/keyword/orderBy/asc |
— |
否 |
BaseParam |
- 出参(data = PageResult,content[] 元素 = TransferDetailDTO):
id / transferNo(JG+yyyyMMdd+4位序)/ fromUserName(发起人)/ toDirectorName(接收总监)/ reason(字典 code)/ remark / totalCount / assignedCount(进度=assigned/total)/ status(0/1)/ createTime / details[](明细行,同 #57) + 分页元信息。
- 备注:列表行即完整单头(含明细嵌套),Demo 渲染进度条用 assigned/totalCount。
#57 交接单详情 — GET transfer/detail?id=
- 出处:
A4 客户管理\客户交割\交接详情.bru + [DTO:TransferDetailDTO]
- 请求参数:
id(string,必填,交接单 id)。
- 出参(data = TransferDetailDTO):
| 字段路径 |
类型 |
口径/文案 |
| id |
Long(字符串) |
交割单ID |
| transferNo |
String |
交接编号(JG+yyyyMMdd+4位序) |
| fromUserName / toDirectorName |
String |
发起人 / 接收总监姓名 |
| reason |
String |
交接原因字典 code(transfer_reason) |
| remark |
String |
备注 |
| totalCount / assignedCount |
Integer |
交接客户总数 / 已分配数(进度=assigned/total) |
| status |
Integer |
0待分配 1已完成 |
| createTime |
String |
发起时间(yyyy-MM-dd HH:mm:ss) |
| details[].id |
Long(字符串) |
明细ID |
| details[].customerId |
Long(字符串) |
客户ID |
| details[].customerName |
String |
客户名称(实时回显;已删客户 null 前端兜底 --,DTO 注释) |
| details[].assignStatus |
Integer |
明细处置态值域:0待分配 1已分配 |
| details[].assignUserName |
String |
分配对象姓名(待分配 null→--) |
| details[].assignTime |
String |
分配时间(待分配 null) |
- 备注:错误码 67002(不存在);单头+明细与 initiate 返回共用同一 DTO。
#58 明细分配 + 可分配销售 — POST transfer/assign?id=&customerIds=&assignUserId= + GET transfer/assignable?id=
- 出处:
A4 客户管理\客户交割\客户分配.bru + 可分配销售列表.bru + [DTO:AssignableUserDTO]
- assign 请求参数:
id(交接单 id,必填)+ customerIds(客户 id 集合,重复键 customerIds=a&customerIds=b,必填)+ assignUserId(目标销售用户 id,必填)——均查询参数。
- assign 出参(data = BatchResult):
total:int / successCount:int / failCount:int / failures[]:{id:Long(失败客户 id), reason:CustomerBatchFailReason(语义枚举), message:String(失败文案)}。
- assignable 出参(data = 数组,AssignableUserDTO):
userId:Long(字符串) / userName:String(姓名)/ deptName:String(部门名)——接收总监部门(含子部门)+ 启用在职,排除发起人与总监本人。
- 备注:
- 分配语义:接收总监→具体销售;逐条独立事务部分成功,失败明细列原因(67012:已分配/越权/为发起人本人/为当前负责人);全部分配完成自动置已完成(D23)。
- D-07 已修复注(customer-defectfix 2026-09-06,AS §5.6/§4):assignable 曾稳定超时(P1)——根因两处全修(根部门全树递归改 ancestors 前缀查询、逐用户 getDeptNames N+1 改批量回显),6046 人实测 0.42s/0.40s(目标 <5s),heavy 13.10 E2E 断言绿;Demo 可正常拉取并断言 <5s。
#59 发起交割入口 —(列表批量面板「发起交割」按钮)
- 无独立端点(跳转 dlgTransferEntry L1033 → 走 #55 预览/发起)。无缺口。
八、客户导入六端点(#60-63,TREE A4-3-2)
端点×语义总表(出处:A4 客户管理\我的客户\客户导入\*.bru 六件 + CustomerImportController /api/customer/import):
| 端点 |
方法 |
语义 |
关键错误码 |
| /template |
GET |
模板下载:V1 双 sheet 纯表头(「客户」9 列:客户编号/客户名称/客户类型/统一社会信用代码/省份编码/城市编码/行业编码/客户星级(1-5)/备注,源 CustomerImportServiceImpl.CUSTOMER_HEADERS;「联系人」5 列:客户编号/联系人姓名/联系电话/职务名称/是否关键联系人(是/否),CONTACT_HEADERS;表头即版本锚点,无示例行),xlsx 流不走信封 |
— |
| /upload |
POST(multipart) |
上传+预校验:文件守门(.xls/.xlsx、≤20MB、模板版本、单 sheet ≤5 万行)→ 行级判定(与执行同口径)→ 任务落待确认(DRAFT),返回预计数+明细 |
67013 |
| /confirm?taskId= |
POST |
待确认→处理中+异步执行(两段式);重复确认 67014 |
67014 |
| /result?taskId= |
GET |
任务结果(状态+六计数+提交人/时间) |
67002(任务不存在) |
| /failures?taskId= |
GET |
失败/疑似重复明细 |
67014 |
| /page |
GET |
任务分页(仅当前用户任务,状态/方式过滤,时间倒序) |
— |
/upload 请求参数(multipart,源控制器 L50 逐字):
| 名 |
类型 |
必填 |
口径 |
| file |
file |
是 |
Excel 文件 |
| importMode |
string |
是 |
APPEND_ONLY 仅新增 / UPDATE_ONLY 仅更新 / UPSERT 有则更新 |
| duplicateStrategy |
string |
否 |
SKIP 不导入(缺省)/ OVERWRITE 覆盖导入(返工票 09-F8;非法值 67013 拦截) |
关键 DTO 字段:
ImportPreviewDTO(upload 出参):taskId / importMode / duplicateStrategy(响应回显)/ totalCount(数据行总数,**客户 sheet**)/ insertCount / updateCount(含未变化)/ failCount / suspectCount(疑似重复,不写入人工处理)/ contactCount(**联系人 sheet 行数,可选**)/ rows[](预校验明细,上限 100 条);rows[] 行:sheetName(客户/联系人)/ rowNum(从 1 起)/ identity(客户识别=编号或名称原值)/ verdict(INSERT/UPDATE/FAIL/SUSPECT)/ reason。与联系人导入 preview 的差异=多 sheetName + contactCount、少 customerId。
ImportResultDTO(result/page 行出参):taskId / importMode / templateVersion(V1)/ status(0待确认 1处理中 2已完成 3已失败,状态机 0 DRAFT→1 RUNNING→2 DONE/3 FAILED)/ totalCount / insertCount / updateCount / unchangedCount / failCount / suspectCount / failReason / creatorName / createTime / finishTime(无 customerId——差异点同上)。
ImportFailDTO(failures 出参行):sheetName(客户/联系人)/ rowNum / customerIdentity(文件内编号或名称原值)/ failField(行级字段错误记列名,整行失败为空)/ failReason(疑似重复行以「疑似重复」前缀区分)。
ImportPageParam(page 入参):BaseParam + status(0-3)+ importMode(三值)。
- 备注:
- INSERT 行必填五列(customerType/provinceCode/cityCode/industryCode/customerStarLevel)预检即 FAIL「XX必填」= P1-1 已修复注(handover 票 03 前移,AS §2.12;预计数=执行同口径);UPDATE 行部分更新不受限。
- D-05 已修复注:is_biz_negotiated NOT NULL 无默认值致 INSERT 必 FAILED——已修(INSERT 显式补缺省 isBizNegotiated=0/isChild=0/relationStarLevel=0)。
- D-04 已修复注:失败原因超长写不进明细——已修(行级/任务级失败原因统一截断 ≤500)。
- F8
duplicateStrategy 语义(D24 冻结):SKIP=文件内重复组全部标失败不写入;OVERWRITE=文件内自上而下最后一条通过校验的行 UPDATE 生效;作用面仅文件内重复组,不触名称相似 SUSPECT 判定。
- 统一社会信用代码列仅作匹配锚+新增写入,更新行不回改(CustomerImportRow javadoc)。
九、规则族(#66-69,TREE A7 业务规则/客户规则)
两单例均 crm-rule 跨模块服务于客户域:GET 读单行 / POST /save 全量覆盖保存即生效;单例 id 固定=1(CustomerRuleConstants.SINGLETON_ID,Initializer 首装种子幂等);审计字段不出报文;操作轨迹以应用日志记录(crm-rule 无 oplog 基建)。
#67a 查重设置读取 — GET /api/rule/customer/dedup
- 出处:
A7 后台管理\业务规则\客户规则\客户管理设置(客户查重设置)\读取查重设置.bru + crm-rule domain/dto/CustomerDedupRuleDTO.java + controller/CustomerDedupRuleController.java
- 请求参数:无。
- 出参(data,5 业务字段 + 元信息):
| 字段路径 |
类型 |
口径/文案 |
| id |
string |
配置行 id(单例恒 "1") |
| masterEnabled |
number |
总开关:1启用 0禁用(仅控制名称/电话提示;信用代码强制规则不受影响) |
| nameEnabled |
number |
客户名称查重独立开关:1启用 0禁用 |
| nameMatchMode |
number |
名称匹配方式:1精确 2模糊(模糊时相似度阈值生效;出厂默认 2) |
| similarityThreshold |
number |
名称相似度阈值 0~100(模糊匹配生效;出厂标准 80;a7-3-3-2 §4.1 档位 70/80/90/自定义0-100) |
| phoneEnabled |
number |
联系电话查重独立开关:1启用 0禁用(精确命中仅提示不阻断 → 控制 #39 check-phone,D26) |
| updateTime |
string |
最近保存时间(回显只读) |
#67b 查重设置保存 — POST /api/rule/customer/dedup/save
- 出处:
A7 后台管理\业务规则\客户规则\客户管理设置(客户查重设置)\保存查重设置.bru + 同上控制器/Service
- 请求参数(表单绑定,ADR-0017):
masterEnabled / nameEnabled / phoneEnabled(number,否,缺省=0 关闭)+ nameMatchMode(否,缺省→出厂 2 模糊;显式传入须 ∈{1,2})+ similarityThreshold(否,缺省→出厂 80;显式传入须 ∈[0,100])。
- 出参(data):保存后全量配置(同 #67a 结构,含 id/updateTime)。
- 备注:
- 校验越界 64023(
CODE_CUSTOMER_RULE_INVALID,service:匹配方式必须 1/2、阈值必须 0~100 整数;不落库)。
- 统一社会信用代码恒开无配置字段(DTO 逐字段核对无 creditCode 项;前端固定渲染「精确匹配+不可关闭」;消费侧 L3 硬拦 67003)。
- 三预设对照:宽松 70 / 标准 80(出厂推荐)/ 严格 90,Demo 阈值控件建议三档+自定义。
- 消费侧:crm-customer
RuleBackedCustomerDedupSettingProvider(@Primary)读单例驱动新增/编辑内建查重——总开关∧单项开关控名称/电话,阈值/方式透传(EXACT=trim+大小写不敏感全等;模糊=ngram);单例缺失回退出厂值;匹配方式消费边界=新增/编辑内建查重(导入路径维持模糊)。
#66a 超期提醒读取 — GET /api/rule/customer/reminder
- 出处:
A7 后台管理\业务规则\客户规则\客户管理设置(超期未跟进提醒)\当前提醒规则.bru + crm-rule domain/dto/CustomerReminderRuleDTO.java + controller/CustomerReminderRuleController.java
- 请求参数:无。
- 出参(data):
| 字段路径 |
类型 |
口径/文案 |
| id |
string |
单例 id("1") |
| masterEnabled |
number |
总开关 1启用 0禁用 |
| firstTriggerEnabled |
number |
首次触发独立开关(首触出厂 30 天,DEFAULT_FIRST_TRIGGER_DAYS=30) |
| firstTriggerDays |
number |
首次触发天数(>0 正整数;锚点后 N 天无跟进落首次提醒) |
| secondIntervalEnabled |
number |
二次提醒独立开关(间隔出厂 7 天,DEFAULT_SECOND_INTERVAL_DAYS=7) |
| secondIntervalDays |
number |
二次提醒间隔天数(>0 正整数;首次后每 N 天重复) |
| updateTime |
string |
最近保存时间(回显只读) |
#66b 超期提醒保存 — POST /api/rule/customer/reminder/save
- 出处:
A7 后台管理\业务规则\客户规则\客户管理设置(超期未跟进提醒)\保存提醒规则.bru + 同上控制器/Service
- 请求参数:
masterEnabled / firstTriggerEnabled / secondIntervalEnabled(number,否,缺省=0 禁用,不静默保留旧值)+ firstTriggerDays / secondIntervalDays(number,必须非空且 >0)。
- 出参(data):保存后全量配置(同 #66a)。
- 备注:
- 绑定方式漂移(本次核对最重要的请求侧发现):
.bru 请求示例为 JSON body 且 docs 自称「唯一 @RequestBody JSON 端点」,但控制器源码 save(CustomerReminderRuleDTO dto) 无 @RequestBody = 表单绑定(ADR-0017),AS §2.13 亦记「CustomerReminderRuleDTO 表单」;demo 以 form-encoded 提交(L1420)实测通过。Demo 断言按 form 提交,.bru 该件请求体示例为文档漂移(全客户域唯一 JSON 例外是 crm-preference saved-view save,见 AS §0/§4)。
- 校验 64023:firstTriggerDays=0/负/null 或 secondIntervalDays=0/负/null → 「首次触发天数与二次提醒间隔天数必须为正整数」,不落库(task 面票「0 或 <0 报 64023」的精确化)。
- 保存即生效;Job 每轮只补「下一条未建到期提醒」(升级提醒链语义,remindSeq 连续递增)。
#69 公海池配置 —(后端无端点、原型无页)
- 一行结论(02 票收口):后端 crm-rule 客户域仅
CustomerDedupRuleController + CustomerReminderRuleController 两控制器(目录实测,无 CustomerPool*Controller);.bru 客户规则下仅上述 4 件(线索规则\公海池*.bru 7 件属线索域不得混入;客户公海=固定准入规则的虚拟池)——无参数表,Demo 按纪律做只读占位卡明示未实现(POOL-CFG,现象文案归 06 号票,引用本结论)。
十、字典与枚举(联系人/流程/规则)
| 字典/枚举 |
值域(code=文案) |
出处 |
| contact_source(联系人来源) |
group_meeting=组会 / group_activity=组局 / referral=转介绍 / other=其他 |
权威 seed:crm-dict/.../config/DictDataInitializer.java L362-367(GroupSeed("contact_source"),与 customer_contact.source 列注释口径一致) |
| └ demo 硬编码漂移 |
demo dlgContact L1151 硬编码 contact_source_01=组会 / _02=组局 / _03=转介绍 / _04=其他——code 为字典改版前遗留,与权威 seed 不符(列表侧 sourceName 已走 /api/dict/item/enabled-list?groupCode=contact_source 动态拉取✅,L623/629);.bru 顶级联系人列表/按客户查联系表示例中 "source":"contact_source_01" 同为陈旧真值 |
demo index.html L1151;DictDataInitializer L364 |
| job_title(职务) |
运行时两级树(一级类别/二级职务 code,含档位 level 1-10),demo ensureDicts 走 /api/dict/item/tree?groupCode=job_title 动态拉取✅(quickAdd/batchEdit 用 code 联动补全;create/edit 用 jobTitleName 自由文本+jobTitleId) |
demo L627-630;ADR-0036;ContactQuickAddParam |
| transfer_reason(交接原因) |
resign=员工离职 / transfer_post=岗位调动 / region_adjust=区域调整 |
发起交接.bru;TransferInitiateDTO;AS §3 |
| 交接单状态 |
0=待分配 → 1=已完成(全分配自动置位 D23);明细 assignStatus 0=待分配 1=已分配 |
TransferDetailDTO;AS §3 |
| 导入 importMode(客户/联系人导入同构) |
APPEND_ONLY=仅新增 / UPDATE_ONLY=仅更新 / UPSERT=新增或更新 |
两导入 upload .bru;ImportPageParam |
| 导入 duplicateStrategy |
SKIP=不导入(缺省)/ OVERWRITE=覆盖导入 |
AS §2.12 F8;两 upload .bru |
| 导入任务状态 |
0=待确认(DRAFT) / 1=处理中(RUNNING) / 2=已完成(DONE) / 3=已失败(FAILED);两导入同构 |
ContactImportResultDTO/ImportResultDTO |
| 预校验判定 verdict |
INSERT / UPDATE / FAIL / SUSPECT |
ContactImportPreviewDTO.Row |
| dedup nameMatchMode |
1=精确 / 2=模糊(出厂 2) |
CustomerDedupRuleDTO;CustomerRuleConstants |
| customerStage(交割预览行) |
1=潜在 / 2=重潜 / 3=已成交 |
TransferPreviewDTO;AS §3 |
| 客户/交割单编号 |
客户 KH+yyyyMMdd+4位;交割 JG+yyyyMMdd+4位 |
AS §3 |
十一、脱敏专节(客户域脱敏形态汇总)
| # |
字段/位置 |
是否脱敏 |
形态示例 |
权威出处 |
| 1 |
联系人电话·列表(contact/page、contacts?customerId=) |
是(服务端) |
138****0001(前3+****+后4) |
ContactPhoneMasker javadoc「所有人默认脱敏」;CustomerContactServiceImpl L131/L150 |
| 2 |
联系人电话·详情/搜索(contact/detail、contact/search) |
是 |
138****0101(.bru 实测) |
ContactPhoneMasker;CustomerContactServiceImpl L113/L166;联系人详情.bru |
| 3 |
图谱节点 phoneMasked |
是(字段名即承诺) |
138****9901(.bru 实测) |
GraphNodeDTO @Schema;图谱全量读.bru |
| 4 |
联系人导出 xlsx 电话列 |
是(口径同列表,不因导出放开) |
列头即「手机号(脱敏)」 |
ContactExportRow @ExcelProperty;CustomerContactEnhanceServiceImpl L307 |
| 5 |
check-phone 命中行 masked=true |
行级脱敏(其余 5 字段全 null=只提示「存在匹配记录」) |
{"masked":true} |
ContactPhoneHitDTO javadoc;service L177-183 |
| 6 |
similarHits.masked(名称相似命中,#22 消费侧) |
行级脱敏(无权命中 customerId/customerName/ownerName/customerStage/archiveStatus 全 null) |
{"masked":true} |
CustomerSimilarHitDTO @Schema;AS §0 脱敏现状 |
| 7 |
客户 creditCode(统一社会信用代码) |
否(V1 明文出参) |
— |
AS §0/§5.4:详情敏感信息三档 V1 未纳入;creditCode 仅作查重硬拦与导入匹配锚 |
| 8 |
证件号 |
无此字段(客户域出参无证件号列;三档脱敏定稿划出) |
— |
AS §5.4 |
| 9 |
脱敏形态防御 |
短号(<8 位)全遮 ****;空→null(Demo 显 --);含 * 的串提交回 edit/batchEdit=不改号 |
**** |
ContactPhoneMasker.mask/isMaskedForm |
明文唯一路径:GET /api/customer/contact/reveal?id=(data=明文手机号;埋点落 customer_contact_reveal_log)。Demo 侧 reveal 态:revealed[id] 列表/图谱共享,「明文查看」=全量逐个 reveal(index.html L609/L1848-1864)。
与「三档脱敏定稿」的关系:AS §5.4 定稿划出客户域「敏感信息三档脱敏 + 查看日志、合并客户、客户导出」(V1 范围外)——即客户级手机号/证件分档脱敏与「显示完整金额」开关不落地(G 系豁免);而联系人电话全员脱敏是 contact-graph effort「所有人默认脱敏」map 拍板先行落地(票 04),不属三档定稿范畴,两者不冲突:前者=客户档案敏感分档(未做),后者=联系人单字段全量脱敏(已做)。Demo 断言按本表第 1-6 行执行,第 7-8 行如实渲染明文/无字段。
.bru 明文示例漂移清单(demo 冒烟不得按示例明文断言):顶级联系人列表.bru(13812340001)、联系人全局搜索.bru(13812340001)、按客户查联系人列表.bru(13812340002/0001)三件示例为票 04 脱敏上线前采集的陈旧真值;联系人详情.bru 与图谱全量读.bru 为脱敏后真值。
十二、来源文件清单
Bruno(D:\code\crm-api-docs\)
| 分组 |
文件 |
| 联系人独立菜单 |
A4 客户管理\联系人\顶级联系人列表.bru、联系人详情.bru、联系人全局搜索.bru、新增联系人.bru、编辑联系人.bru、删除联系人.bru、电话软提示.bru、读取列偏好.bru、保存列偏好.bru、读取视图形态.bru、保存视图形态.bru |
| 联系人页签增强/图谱/导入 |
A4 客户管理\客户详情\联系人\快速添加联系人.bru、电话明文查看.bru、联系人导出.bru、页内批量编辑.bru;客户详情\联系人图谱\图谱全量读.bru、图谱整体保存.bru;客户详情\联系人导入\下载导入模板.bru、上传导入文件并预校验.bru、确认执行导入.bru、任务结果.bru、失败明细与重复明细.bru、最近导入记录.bru |
| 联系人列表(页签 2 数据源) |
A3 商机管理\销售机会\按客户查联系人列表.bru |
| 客户交割 |
A4 客户管理\客户交割\发起前客户预览.bru、发起交接.bru、交接记录分页.bru、交接详情.bru、客户分配.bru、可分配销售列表.bru |
| 客户导入 |
A4 客户管理\我的客户\客户导入\下载导入模板.bru、上传导入文件并预校验.bru、确认执行导入.bru、任务结果.bru、失败疑似重复明细.bru、最近导入记录.bru |
| 规则族 |
A7 后台管理\业务规则\客户规则\客户管理设置(客户查重设置)\读取查重设置.bru、保存查重设置.bru;客户管理设置(超期未跟进提醒)\当前提醒规则.bru、保存提醒规则.bru |
后端源码(D:\code\crm-backend-matt\)
- crm-customer
domain/dto/:ContactDTO、GraphDTO、GraphNodeDTO、GraphEdgeDTO、ContactBatchResultDTO、ContactPhoneHitDTO、ContactImportPreviewDTO、ContactImportResultDTO、ContactImportFailDTO、TransferPreviewDTO、TransferInitiateDTO、TransferDetailDTO、AssignableUserDTO、ImportPreviewDTO、ImportResultDTO、ImportFailDTO、CustomerSimilarHitDTO、domain/excel/ContactExportRow、ContactImportTemplateRow、CustomerImportRow
- crm-customer
domain/param/:ContactPageParam、ContactBatchEditParam、ContactQuickAddParam、ContactImportPageParam、GraphSaveParam、TransferPageParam、ImportPageParam;crm-base domain/param/BaseParam
- crm-customer
controller/:CustomerContactImportController、CustomerImportController、CustomerTransferController;service/impl/:CustomerContactServiceImpl、CustomerContactEnhanceServiceImpl、CustomerImportServiceImpl(headers/守门常量);util/ContactPhoneMasker
- crm-rule:
controller/CustomerDedupRuleController.java、CustomerReminderRuleController.java、domain/dto/CustomerDedupRuleDTO.java、CustomerReminderRuleDTO.java、service/impl/CustomerDedupRuleServiceImpl.java、CustomerReminderRuleServiceImpl.java、constant/CustomerRuleConstants.java
- crm-dict:
config/DictDataInitializer.java(contact_source seed L362-367)
文档/台账(D:\code\crm-backend-matt\.scratch\)
customer-module/API-SUMMARY.md(§0 通用/脱敏现状、§2.4、§2.7、§2.7b、§2.11、§2.12、§2.13、§3 值域、§4、§5.4/§5.6)
customer-demo-ref/assets/coverage-matrix.md(入口编号权威);customer-demo-ref/map.md
customer-frontend-handover/gap-report.md(豁免台账 D-G1/G5);customer-contact-graph/e2e-report.md(D-G1 根因)
customer-e2e/demo/index.html(fork 基线;引用行号基于此原文件);customer-e2e/seed-ids.json