unis_crm/docs/OMS接口整理.md

467 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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<Long>`,成功时 `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=<CRM当前用户username>
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<Long>`,成功时 `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` |