You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

46 KiB

联系人族 + 重流程 + 规则族 — 字段逐入口对齐表(票 07 / customer-demo-ref)

用途:客户单文件 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);明细见文末「脱敏专节」。
  • 分页:入参继承 BaseParamcurrent=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 客户管理\联系人\联系人全局搜索.bruCustomerContactServiceImpl.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 侧列表与图谱共享 revealedrevealed[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(联系人识别=姓名原值)/ verdictINSERT/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/createPOST 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(编辑页传自身)
  • 出参(data = 数组,命中行):
字段路径 类型 口径/文案 脱敏
[].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)。
  • 备注:
    • 校验越界 64023CODE_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.javaCustomerReminderRuleController.javadomain/dto/CustomerDedupRuleDTO.javaCustomerReminderRuleDTO.javaservice/impl/CustomerDedupRuleServiceImpl.javaCustomerReminderRuleServiceImpl.javaconstant/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