16 KiB
16 KiB
OMS 接口整理
整理日期:2026-06-10
本文档基于当前后端代码整理,覆盖两类接口:
- CRM 主动推送/同步项目到 OMS。
- OMS 或内部系统回写更新 CRM 项目/商机。
相关代码位置:
backend/src/main/java/com/unis/crm/controller/OpportunityController.javabackend/src/main/java/com/unis/crm/controller/OpportunityIntegrationController.javabackend/src/main/java/com/unis/crm/service/OmsClient.javabackend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.javabackend/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 售前人员,可传:
{
"preSalesId": 123,
"preSalesName": "张三"
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
preSalesId |
number | 否 | OMS 售前用户 ID。优先按该字段匹配 OMS 售前人员。 |
preSalesName |
string | 否 | OMS 售前姓名。未传 preSalesId 时按姓名匹配。 |
成功示例:
{
"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 返回示例:
{
"code": "0",
"msg": "success",
"data": [
{
"userId": 123,
"loginName": "zhangsan",
"userName": "张三"
}
]
}
4. CRM 确保当前用户存在于 OMS
推送项目时,CRM 会把当前登录用户作为 OMS 创建人来源:
- 先调用
GET {OMS_BASE_URL}{OMS_USER_INFO_PATH}查询用户:
userCode=<CRM当前用户username>
roleName=
- 如果 OMS 中不存在该用户,则调用
POST {OMS_BASE_URL}{OMS_USER_ADD_PATH}新增用户。
默认新增用户路径:/api/v1/user/add
新增用户请求体:
{
"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 |
认证级别。 |
请求示例:
{
"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 兼容:
0, 200, success, SUCCESS
code 为空也按成功处理。
项目编号提取规则:
- 如果
data是字符串/数字,直接作为项目编号。 - 如果
data是对象或数组,优先查找以下字段:project_codeprojectCodeprojectNoomsProjectCodecodeprojectIdid
- 如果仍未找到,会递归查找字段名包含
code、no或以id结尾的标量字段。
若 OMS 返回成功但无法提取项目编号,后端抛出:
OMS返回成功,但未返回项目编号
成功返回示例:
{
"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. 请求示例
{
"opportunityCode": "V001234",
"stage": "won",
"status": "won",
"confidencePct": "A",
"amount": 2800000,
"expectedCloseDate": "2026-06-30",
"pushedToOms": true,
"description": "OMS回写成交结果"
}
成功返回:
{
"code": "0",
"msg": "success",
"data": 10001
}
失败返回:
{
"code": "-1",
"msg": "商机不存在",
"data": null
}
curl 示例:
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 |