467 lines
16 KiB
Markdown
467 lines
16 KiB
Markdown
# 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` |
|