# OMS 接口整理 整理日期:2026-06-10 本文档基于当前后端代码整理,覆盖两类接口: - CRM 主动推送/同步项目到 OMS。 - OMS 或内部系统回写更新 CRM 项目/商机。 相关代码位置: - `backend/src/main/java/com/unis/crm/controller/OpportunityController.java` - `backend/src/main/java/com/unis/crm/controller/OpportunityIntegrationController.java` - `backend/src/main/java/com/unis/crm/service/OmsClient.java` - `backend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.java` - `backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml` ## 一、CRM 推送项目到 OMS ### 1. 前端/CRM 触发推送接口 该接口由 CRM 前端调用后端,后端再调用 OMS。 | 项目 | 内容 | | --- | --- | | 请求方式 | `POST` | | CRM 路径 | `/api/opportunities/{opportunityId}/push-oms` | | 鉴权头 | `X-User-Id: <当前用户ID>` | | Content-Type | `application/json` | | 返回 | `ApiResponse`,成功时 `data` 为 CRM 商机 ID | 请求体可为空;如需要指定 OMS 售前人员,可传: ```json { "preSalesId": 123, "preSalesName": "张三" } ``` 字段说明: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `preSalesId` | number | 否 | OMS 售前用户 ID。优先按该字段匹配 OMS 售前人员。 | | `preSalesName` | string | 否 | OMS 售前姓名。未传 `preSalesId` 时按姓名匹配。 | 成功示例: ```json { "code": "0", "msg": "success", "data": 10001 } ``` ### 2. 推送前校验规则 后端会在推送前校验: | 校验项 | 失败提示 | | --- | --- | | 商机 ID 不能为空且必须大于 0 | `商机不存在` | | 当前用户有权操作该商机 | `无权操作该商机` | | 已签单/归档商机不可推送 | `已签单商机不允许推送` | | 商机名称不能为空 | `商机名称不能为空` | | 最终客户不能为空 | `最终客户不能为空` | | 运作方不能为空 | `运作方不能为空` | | 预计金额必须大于 0 | `预计金额不能为空` | | 预计下单时间不能为空 | `预计下单时间不能为空` | | 项目把握度不能为空且需为字典有效值,兼容 A/B/C | `项目把握度不能为空` 或 `项目把握度无效: xxx` | | 项目阶段不能为空且需为字典有效值 | `项目阶段不能为空` 或 `项目阶段无效: xxx` | | 建设类型不能为空 | `建设类型不能为空` | | 运作方包含渠道时必须关联渠道名称 | `推送 OMS 前请先关联渠道名称` | | 必须能在 OMS 售前列表中解析售前人员 | `推送 OMS 前请选择售前人员` / `所选售前人员在OMS中不存在` | ### 3. CRM 调用 OMS 售前列表 用于获取 OMS 售前人员列表,也是推送前校验售前人员的依据。 | 项目 | 内容 | | --- | --- | | CRM 路径 | `GET /api/opportunities/oms/pre-sales` | | CRM 鉴权头 | `X-User-Id: <当前用户ID>` | | 后端调用 OMS | `GET {OMS_BASE_URL}{OMS_USER_INFO_PATH}` | | 默认 OMS 路径 | `/api/v1/user/info` | | OMS 请求头 | `{OMS_API_KEY_HEADER}: {OMS_API_KEY}`,默认头名为 `apiKey` | | OMS 查询参数 | `userCode=`、`roleName=售前` | CRM 返回示例: ```json { "code": "0", "msg": "success", "data": [ { "userId": 123, "loginName": "zhangsan", "userName": "张三" } ] } ``` ### 4. CRM 确保当前用户存在于 OMS 推送项目时,CRM 会把当前登录用户作为 OMS 创建人来源: 1. 先调用 `GET {OMS_BASE_URL}{OMS_USER_INFO_PATH}` 查询用户: ```text userCode= roleName= ``` 2. 如果 OMS 中不存在该用户,则调用 `POST {OMS_BASE_URL}{OMS_USER_ADD_PATH}` 新增用户。 默认新增用户路径:`/api/v1/user/add` 新增用户请求体: ```json { "userName": "CRM用户显示名", "loginName": "CRM用户名" } ``` ### 5. CRM 调用 OMS 新增/更新项目 后端统一调用 OMS 的项目新增路径;当请求体中带 `projectCode` 时,该调用被当前代码视为更新已有 OMS 项目。 | 项目 | 内容 | | --- | --- | | 后端调用 OMS | `POST {OMS_BASE_URL}{OMS_PROJECT_ADD_PATH}` | | 默认 OMS 路径 | `/api/v1/project/add` | | OMS 请求头 | `{OMS_API_KEY_HEADER}: {OMS_API_KEY}` | | Content-Type | `application/json` | | 连接超时 | `OMS_CONNECT_TIMEOUT_SECONDS`,默认 5 秒 | | 读取超时 | `OMS_READ_TIMEOUT_SECONDS`,默认 15 秒 | 请求体字段映射: | OMS 字段 | 来源 | 说明 | | --- | --- | --- | | `projectCode` | `crm_opportunity.opportunity_code` | 有值时传入,用于更新已有 OMS 项目;无值时不传。 | | `projectName` | `opportunity_name` | 项目/商机名称。 | | `operateInstitution` | `operator_name` | 运作方。 | | `h3cPerson` | `crm_sales_expansion.candidate_name` | 新华三负责人姓名。 | | `h3cPhone` | `crm_sales_expansion.mobile` | 新华三负责人手机号。 | | `estimatedAmount` | `amount` | 预计金额,转为字符串并去掉多余 0。 | | `estimatedOrderTime` | `expected_close_date` | 预计下单时间,格式 `YYYY-MM-DD`。 | | `projectGraspDegree` | `confidence_pct` | 项目把握度,统一映射为 `A`、`B`、`C`。 | | `projectStage` | `stage` | CRM 项目阶段码值。 | | `competitorList` | `competitor_name` | 竞品名称按 `,,、;;换行` 拆分为数组。 | | `hzSupportUser` | `pre_sales_id` | 售前 ID,转字符串。 | | `createBy` | 当前 CRM 用户在 OMS 的 `userId` | 创建人。 | | `constructionType` | `opportunity_type` | 建设类型。 | | `partner` | 渠道拓展信息 | 渠道伙伴对象。 | `partner` 对象字段: | OMS 字段 | 来源 | 说明 | | --- | --- | --- | | `partnerCode` | 固定 `null` | 当前代码不传渠道编码。 | | `partnerName` | `crm_channel_expansion.channel_name` | 渠道名称。 | | `province` | 渠道省份,优先匹配 `cnarea.name` | 省份。 | | `city` | 渠道城市,优先匹配 `cnarea.name` | 城市。 | | `address` | `crm_channel_expansion.office_address` | 办公地址。 | | `contactPerson` | 首要渠道联系人或渠道主联系人 | 联系人。 | | `contactPhone` | 首要渠道联系人手机号或渠道主联系人手机号 | 联系电话。 | | `level` | `crm_channel_expansion.certification_level` | 认证级别。 | 请求示例: ```json { "projectCode": "V001234", "projectName": "某云桌面项目", "operateInstitution": "新华三+渠道", "h3cPerson": "李四", "h3cPhone": "13800000000", "estimatedAmount": "2800000", "estimatedOrderTime": "2026-06-30", "projectGraspDegree": "A", "projectStage": "business_negotiation", "competitorList": ["竞品A", "竞品B"], "hzSupportUser": "123", "createBy": "456", "constructionType": "新建", "partner": { "partnerCode": null, "partnerName": "某渠道公司", "province": "浙江省", "city": "杭州市", "address": "杭州市某地址", "contactPerson": "王五", "contactPhone": "13900000000", "level": "金牌" } } ``` ### 6. OMS 返回约定 后端期望 OMS 返回 JSON。HTTP 非 2xx、非 JSON、业务 `code` 非成功都会被视为失败。 成功 `code` 兼容: ```text 0, 200, success, SUCCESS ``` `code` 为空也按成功处理。 项目编号提取规则: 1. 如果 `data` 是字符串/数字,直接作为项目编号。 2. 如果 `data` 是对象或数组,优先查找以下字段: - `project_code` - `projectCode` - `projectNo` - `omsProjectCode` - `code` - `projectId` - `id` 3. 如果仍未找到,会递归查找字段名包含 `code`、`no` 或以 `id` 结尾的标量字段。 若 OMS 返回成功但无法提取项目编号,后端抛出: ```text OMS返回成功,但未返回项目编号 ``` 成功返回示例: ```json { "code": "0", "msg": "success", "data": { "projectCode": "V001234" } } ``` ### 7. 推送成功后 CRM 本地更新 手动推送成功后,CRM 会更新 `crm_opportunity`: | 字段 | 值 | | --- | --- | | `pushed_to_oms` | `true` | | `oms_push_time` | `now()` | | `opportunity_code` | OMS 返回的项目编号,或已有非 `OPP-` 编号 | | `updated_at` | `now()` | 项目编号处理规则: - 如果 CRM 已有 `opportunity_code` 且不是 `OPP-` 开头,保留已有编号。 - 否则使用 OMS 返回的项目编号。 ## 二、CRM 新增/编辑商机时自动同步 OMS 除手动推送外,当前服务层还在新增/编辑商机时调用 OMS。 ### 1. 新增商机 | 项目 | 内容 | | --- | --- | | CRM 接口 | `POST /api/opportunities` | | 同步方式 | 新增本地商机后,严格调用 OMS 项目新增接口 | | 是否传 `projectCode` | 不传 | | 成功后 | 将 OMS 返回项目编号写入 `crm_opportunity.opportunity_code` | | 失败影响 | 抛出异常,新增流程失败 | | 是否标记 `pushed_to_oms` | 否 | ### 2. 编辑商机 | 项目 | 内容 | | --- | --- | | CRM 接口 | `PUT /api/opportunities/{opportunityId}` | | 同步方式 | 本地更新成功后,尽力调用 OMS 项目接口 | | 是否传 `projectCode` | 有 `opportunity_code` 时传入 | | 成功后 | 如返回编号与本地需更新编号不同,则更新 `opportunity_code` | | 失败影响 | 仅记录 warn 日志,不阻断编辑流程 | | 是否标记 `pushed_to_oms` | 否 | ## 三、OMS 或内部系统更新 CRM 项目/商机 该接口用于外部系统按 `opportunityCode` 回写 CRM 商机信息。 ### 1. 接口信息 | 项目 | 内容 | | --- | --- | | 请求方式 | `PUT` | | CRM 路径 | `/api/opportunities/integration/update` | | Content-Type | `application/json` | | 鉴权头 | `X-Internal-Secret: <内部接口密钥>` | | 返回 | `ApiResponse`,成功时 `data` 为 CRM 商机 ID | 该路径已加入安全白名单,但仍由接口自身校验内部密钥。 ### 2. 请求参数 必填字段: | 字段 | 类型 | 说明 | | --- | --- | --- | | `opportunityCode` | string | 商机编号/OMS 项目编号,用于定位 CRM 商机。最大 50 字符。 | 其他字段均为可选;未传字段不修改。为兼容历史数据,当前接口不再要求除 `opportunityCode` 外必须存在更新字段。 可更新字段: | 字段 | 类型 | 说明 | | --- | --- | --- | | `opportunityName` | string | 商机名称,最大 200 字符。 | | `projectLocation` | string | 项目地,最大 100 字符。 | | `operatorName` | string | 运作方,最大 100 字符。 | | `amount` | number | 预计金额;历史数据回写不再限制必须大于 0。 | | `actualSignedAmount` | number | 实际签约金额;兼容字段名 `actual_signed_amount`。 | | `expectedCloseDate` | string | 预计结单日期,格式 `YYYY-MM-DD`。 | | `confidencePct` | string | 项目把握度,支持 `A`、`B`、`C`,兼容 `40`、`60`、`80`。 | | `stage` | string | 项目阶段,支持 CRM 字典值,也兼容部分中文阶段名。 | | `opportunityType` | string | 建设类型,最大 50 字符。 | | `productType` | string | 产品类型,最大 100 字符;空字符串会存为 `null`。 | | `source` | string | 商机来源,最大 50 字符;空字符串会存为 `null`。 | | `salesExpansionId` | number | 销售拓展 ID;历史数据回写不再限制必须大于 0。 | | `channelExpansionId` | number | 渠道拓展 ID;历史数据回写不再限制必须大于 0。 | | `preSalesId` | number | 售前 ID;历史数据回写不再限制必须大于 0。 | | `preSalesName` | string | 售前姓名,最大 100 字符;空字符串会存为 `null`。 | | `competitorName` | string | 竞品名称,最大 200 字符;空字符串会存为 `null`。 | | `archived` | boolean | 是否归档。 | | `archivedAt` | string | 归档时间,ISO 8601 格式。传该字段且未传 `archived` 时会自动视为归档。 | | `pushedToOms` | boolean | 是否已推送 OMS。传 `true` 且未传 `omsPushTime` 时自动填当前时间。 | | `omsPushTime` | string | 推送 OMS 时间,ISO 8601 格式。 | | `isPoc` | boolean | 是否 POC 测试项目;兼容字段名 `is_poc`。 | | `status` | string | 商机状态,支持 `active`、`closed`、`won`、`lost`,但 `won/lost` 需配合对应阶段。 | | `description` | string | 备注;空字符串会存为 `null`。 | ### 3. 阶段与状态规则 阶段兼容值: | 传入值 | 归一化结果 | | --- | --- | | `初步沟通` / `initial_contact` | `initial_contact` | | `方案交流` / `solution_discussion` | `solution_discussion` | | `招投标` / `bidding` | `bidding` | | `商务谈判` / `business_negotiation` | `business_negotiation` | | `已成交` / `won` | `won` | | `已放弃` / `lost` | `lost` | 状态联动规则: | 条件 | 结果 | | --- | --- | | `stage = won` 且未传 `status` | 自动设置 `status = won` | | `stage = won` 且 `status != won` | 报错:`项目阶段为已成交时,状态必须为won` | | `stage = lost` 且未传 `status` | 自动设置 `status = lost` | | `stage = lost` 且 `status != lost` | 报错:`项目阶段为已放弃时,状态必须为lost` | | 传入非终态 `stage` 且未传 `status` | 自动设置 `status = active` | | 仅传 `status = won/lost`,未传对应 `stage` | 报错:`更新状态为won或lost时,请同步传入对应的项目阶段` | ### 4. 请求示例 ```json { "opportunityCode": "V001234", "stage": "won", "status": "won", "confidencePct": "A", "amount": 2800000, "expectedCloseDate": "2026-06-30", "pushedToOms": true, "description": "OMS回写成交结果" } ``` 成功返回: ```json { "code": "0", "msg": "success", "data": 10001 } ``` 失败返回: ```json { "code": "-1", "msg": "商机不存在", "data": null } ``` curl 示例: ```bash curl -X PUT 'http://localhost:8080/api/opportunities/integration/update' \ -H 'Content-Type: application/json' \ -H 'X-Internal-Secret: <内部接口密钥>' \ -d '{ "opportunityCode": "V001234", "stage": "won", "status": "won", "confidencePct": "A", "amount": 2800000, "expectedCloseDate": "2026-06-30", "pushedToOms": true, "description": "OMS回写成交结果" }' ``` ### 5. 常见错误 | 错误信息 | 原因 | | --- | --- | | `内部接口鉴权失败` | 未传内部密钥、密钥错误,或服务端未配置有效密钥。 | | `opportunityCode 不能为空` | 未传商机编号。 | | `商机不存在` | CRM 中找不到对应 `opportunity_code`。 | | `项目把握度无效: xxx` | 把握度无法匹配 CRM 字典或兼容值。 | | `项目阶段无效: xxx` | 阶段无法匹配 CRM 字典或兼容值。 | ## 四、配置项 OMS 配置前缀:`unisbase.app.oms` | 配置项 | 环境变量 | 默认值/说明 | | --- | --- | --- | | `enabled` | `OMS_ENABLED` | 默认 `true`。关闭后调用 OMS 会报 `OMS推送未启用`。 | | `base-url` | `OMS_BASE_URL` | OMS 基础地址。 | | `api-key` | `OMS_API_KEY` | OMS 接口密钥。 | | `api-key-header` | `OMS_API_KEY_HEADER` | 默认 `apiKey`。 | | `user-info-path` | `OMS_USER_INFO_PATH` | 默认 `/api/v1/user/info`。 | | `user-add-path` | `OMS_USER_ADD_PATH` | 默认 `/api/v1/user/add`。 | | `project-add-path` | `OMS_PROJECT_ADD_PATH` | 默认 `/api/v1/project/add`。 | | `pre-sales-role-name` | `OMS_PRE_SALES_ROLE_NAME` | 默认 `售前`。 | | `connect-timeout-seconds` | `OMS_CONNECT_TIMEOUT_SECONDS` | 默认 5 秒。 | | `read-timeout-seconds` | `OMS_READ_TIMEOUT_SECONDS` | 默认 15 秒。 | 内部回写鉴权配置前缀:`unisbase.internal-auth` | 配置项 | 说明 | | --- | --- | | `enabled` | 默认 `true`。关闭后不校验内部密钥。 | | `secret` | 内部接口密钥。 | | `header-name` | 默认 `X-Internal-Secret`。 | ## 五、接口方向速查 | 方向 | 场景 | 接口 | | --- | --- | --- | | CRM 前端 -> CRM 后端 | 获取 OMS 售前列表 | `GET /api/opportunities/oms/pre-sales` | | CRM 前端 -> CRM 后端 | 手动推送商机到 OMS | `POST /api/opportunities/{opportunityId}/push-oms` | | CRM 后端 -> OMS | 查询 OMS 用户/售前 | `GET {OMS_BASE_URL}/api/v1/user/info` | | CRM 后端 -> OMS | 创建 OMS 用户 | `POST {OMS_BASE_URL}/api/v1/user/add` | | CRM 后端 -> OMS | 新增或更新 OMS 项目 | `POST {OMS_BASE_URL}/api/v1/project/add` | | OMS/内部系统 -> CRM 后端 | 按项目编号回写更新 CRM 商机 | `PUT /api/opportunities/integration/update` |