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.
 
 
 
 
 

100 KiB

逐入口字段对齐验收清单(票 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 .bruconfirmSimilar、误标星级选填(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 无字段级脱敏unifiedCreditCodepresidentClassPhone 等明文回显(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.javadomain/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.javaservice/impl/CustomerWorkspaceServiceImpl.java
  • 请求参数:复用 CustomerWorkspacePageParam(同 #1),groupColumn 必填(stage/star/relation),无需 groupValue;分页/keyword 意义有限(同 WHERE 口径)。示例载荷:workspace=mine&groupColumn=stage&current=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&current=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.javadomain/dto/SavedView.javaSavedViewCondition.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
  • SavedView 字段(JSON body):
字段 类型 必填 口径
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.javaViewFormServiceImpl.java
  • GET /api/preference/view-form/get?scopeKey=Stringlist/split/boardnull=未设置,前端默认 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.javaCustomerFocusController.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 客户管理\我的客户\新增客户.brudomain/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 客户管理\客户查重\名称相似提示.bruCustomerSimilarHitDTO.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 客户管理\客户查重\信用代码查重.bruCreditCodeCheckDTO.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 客户管理\我的客户\工商信息查询.bruport/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 客户管理\客户详情\编辑客户.bruCustomerUpdateDTO.java
  • 请求参数:查询参数 id + 表单(与 #21 create 的差异):
    • version(number,必传,乐观锁 CAS,不匹配报 67005「乐观锁冲突」;编辑回显取 GET /detailversion 带回)。
    • :无 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-createCustomerQuickCreateDTO 最小集)

  • 出处:A4 客户管理\我的客户\快速创建客户.bruCustomerQuickCreateDTO.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 客户管理\客户详情\详情公共头部.brudomain/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 客户管理\客户详情\客户详情.brudomain/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 + 新增跟进.brudomain/param/FollowPageParam.javadomain/dto/FollowCreateDTO.javadomain/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 客户管理\客户详情\操作日志\操作日志分页.brudomain/param/OplogPageParam.javadomain/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 客户管理\客户详情\关联商机\关联商机页签.bruport/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.javaMemberAddDTO.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 标注:原型另有职务/加入时间两列——memberRolecreateTime 均不在此 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.pageProjectsCustomerProjectQueryPort.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);明细见文末「脱敏专节」。
  • 分页:入参继承 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