所有检测均成功
changelog-filename-gate / validate (push) Successful in 2s
修改原因:hl-ui changelog loop 需要把前端消费进度回写到契约源,避免领取、实现状态与实际交付脱节。 修改内容:将 frontend_status 与已有 legacy frontend 同步为 implemented,记录负责人 hl-ui-codex,并关联 mmg/hl-ui@5a155c42395a7abd66c78789b225d6af86bb7fbd;发布和验收字段保持不变。 实际验证:回写器已校验目标文件、状态单调性、提交范围和 Front Matter 内容,提交只包含当前 changelog。 Frontend-Status: tools/mcp-api-sync/.changelog-repo/changelogs-v2/2026-07/25_5238_核单门票来源类型统一-修改接口-管理后台.md
15 KiB
15 KiB
frontend_status, frontend_owner, frontend_ref, updated_at
| frontend_status | frontend_owner | frontend_ref | updated_at |
|---|---|---|---|
| implemented | hl-ui-codex | mmg/hl-ui@5a155c4239 | 2026-07-25T03:42:03.625Z |
【修改接口·管理后台】核单门票来源类型统一 (#5238)
PR: #5242 | 服务: hl-order-service-v3 | 更新时间: 2026-07-25 10:03
1. 接口背景
核单 Step2 门票/游玩项目页签中,手工补充的门票行此前在查询出参中使用 CUSTOM_ASSIGNMENT。为避免前端按不同 Tab 或来源类型做额外分支,本次将查询出参的手工门票来源统一为 MANUAL,中文名统一为 手工项目;保存接口同步允许直接提交 MANUAL。
2. 变更清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | Step 2 查询门票核单明细 | GET | /v3/admin/order/{orderId}/settlement/step2 |
修改接口 | 手工/自定义门票行的 sourceType 统一返回 MANUAL,sourceTypeName 返回 手工项目 |
| 2 | Step 2 录门票核单明细 | PUT | /v3/admin/order/{orderId}/settlement/step2 |
修改接口 | items[].sourceType 新增允许 MANUAL;旧 CUSTOM_ASSIGNMENT 入参继续兼容 |
3. 接口详情
3.1 Step 2 查询门票核单明细
- 方法:GET
- 路径:
/v3/admin/order/{orderId}/settlement/step2 - 接口名:
listTicket - ApiOperation:Step 2 查询门票核单明细
- 使用场景:进入核单 Step2 门票/游玩项目页签,或保存成功后回读页面明细。
- 认证:需要管理后台 JWT。
- 幂等性:幂等,只读查询。
- 限流:无单接口额外限流。
- 响应结构:
data为TicketItemVO[]。
3.2 Step 2 录门票核单明细
- 方法:PUT
- 路径:
/v3/admin/order/{orderId}/settlement/step2 - 接口名:
saveTicket - ApiOperation:Step 2 录门票核单明细
- 使用场景:保存核单 Step2 门票/游玩项目明细,包含派生门票行和手工补充门票行。
- 认证:需要管理后台 JWT。
- 幂等性:全量替换保存;同一份
items重复提交后,以最后一次提交结果为准。 - 限流:无单接口额外限流。
- 请求体兼容:推荐使用
{ "items": [...] };历史数组 body[...]仍兼容。 - 响应结构:
data为SettlementTicketSaveRespVO。
4. 接口入参
4.1 路径参数 / Query 参数
| 接口 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| GET / PUT | orderId |
string | 是 | 订单 ID,长整型字符串 |
两个接口均无 Query 参数。
4.2 GET 请求体字段
GET 无请求体。
4.3 PUT 请求体字段
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|---|---|---|---|---|
items |
array | 是 | 门票/游玩项目明细行数组,全量替换保存 | 不允许为 null |
items[].id |
string | 否 | 已存在行 ID;新增行可不传 | 长整型字符串 |
items[].sourceType |
string | 是 | 来源类型;手工门票推荐传 MANUAL |
SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MANUAL / CUSTOM_ASSIGNMENT |
items[].sourceTypeName |
string | 否 | 来源类型中文名,仅展示字段;保存时可不传 | 最大 32 字符 |
items[].scenicAssignmentId |
string/null | 否 | 来源 assignment ID;手工项目传 null |
长整型字符串或 null |
items[].dayNumber |
integer/null | 否 | 行程第几天;保存后以回读值为准 | 从 1 开始 |
items[].dayDate |
string | 是 | 行程日期 | yyyy-MM-dd |
items[].scenicName |
string | 是 | 景区/游玩项目名称 | 1-200 字符 |
items[].specName |
string/null | 否 | 规格/票型名称 | 最大 128 字符 |
items[].ticketCount |
integer | 是 | 实际购票数量;套餐含门票但无额外成本时可填 0 | 整数 |
items[].ticketUnitPrice |
number/null | 否 | 参考成本单价,单位元 | 小数 |
items[].sellPrice |
number/null | 否 | 客户成交单价,单位元 | >= 0 |
items[].totalAmount |
number/null | 否 | 客户成交小计,单位元 | >= 0 |
items[].plannedCost |
number | 是 | 计划成本,单位元 | >= 0 |
items[].actualCost |
number | 是 | 实际成本,单位元 | >= 0 |
items[].paymentMethod |
string | 否 | 付款方式;不传时按公司付款处理 | SIGNED / COMPANY_PAID / CASH_PAID |
items[].paymentMethodName |
string | 否 | 付款方式中文名,仅展示字段;保存时可不传 | 最大 32 字符 |
items[].voucherUrls |
array | 否 | 凭证图片 URL 数组 | 字符串数组 |
items[].remark |
string/null | 否 | 备注 | 最大 500 字符 |
5. 出参字段
5.1 GET 响应字段:TicketItemVO[]
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 业务状态码,成功为 200 |
message |
string | 响应消息 |
success |
boolean | 是否成功 |
data |
array | 门票/游玩项目明细行数组 |
data[].id |
string/null | 核单明细行 ID;未持久化派生行可能为 null |
data[].sourceType |
string | 来源类型;手工/自定义门票行本次统一返回 MANUAL |
data[].sourceTypeName |
string/null | 来源类型中文名;MANUAL 返回 手工项目 |
data[].scenicAssignmentId |
string/null | 来源 assignment ID;手工项目为 null |
data[].dayNumber |
integer/null | 行程第几天 |
data[].dayDate |
string | 行程日期,yyyy-MM-dd |
data[].scenicName |
string | 景区/游玩项目名称 |
data[].specName |
string/null | 规格/票型名称 |
data[].ticketCount |
integer | 实际购票数量 |
data[].ticketUnitPrice |
number/null | 参考成本单价,单位元 |
data[].sellPrice |
number/null | 客户成交单价,单位元 |
data[].totalAmount |
number/null | 客户成交小计,单位元 |
data[].plannedCost |
number | 计划成本,单位元 |
data[].actualCost |
number | 实际成本,单位元 |
data[].paymentMethod |
string/null | 付款方式 |
data[].paymentMethodName |
string/null | 付款方式中文名 |
data[].voucherUrls |
array | 凭证图片 URL 数组 |
data[].remark |
string/null | 备注 |
5.2 PUT 响应字段:SettlementTicketSaveRespVO
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 业务状态码,成功为 200 |
message |
string | 响应消息 |
success |
boolean | 是否成功 |
data.addedIds |
string[] | 本次保存新增的核单明细行 ID 列表 |
data.updatedIds |
string[] | 本次保存更新的核单明细行 ID 列表;当前全量替换语义下通常为空数组 |
data.deletedIds |
string[] | 本次保存删除的核单明细行 ID 列表;当前返回通常为空数组 |
data.totalActualCost |
string | 保存后 Step2 实际成本合计,单位元 |
6. 枚举 / 数据字典
6.1 sourceType
所属字段:items[].sourceType、data[].sourceType | 类型:String | PUT 必填:是 | GET 必返:是
| 值 | 中文 | 说明 |
|---|---|---|
SCENIC_ASSIGNMENT |
景区 | 景区派生来源行;查询和保存语义不变 |
ACTIVITY_ASSIGNMENT |
游玩项目 | 游玩项目派生来源行;查询和保存语义不变 |
MANUAL |
手工项目 | 本次推荐值;查询手工/自定义门票行统一返回该值,保存接口也允许提交该值 |
CUSTOM_ASSIGNMENT |
手工项目(旧入参兼容) | 仅用于兼容旧保存请求;查询响应不再返回该值 |
6.2 sourceTypeName
所属字段:items[].sourceTypeName、data[].sourceTypeName | 类型:String | 必填:否
| sourceType | sourceTypeName | 说明 |
|---|---|---|
SCENIC_ASSIGNMENT |
景区 |
景区派生来源行 |
ACTIVITY_ASSIGNMENT |
游玩项目 |
游玩项目派生来源行 |
MANUAL |
手工项目 |
手工/自定义门票行统一展示名 |
CUSTOM_ASSIGNMENT |
手工项目 |
旧保存请求兼容;保存成功后回读为 MANUAL / 手工项目 |
null / 未知值 |
null |
查询行为不变,不新增兜底文案 |
6.3 paymentMethod
所属字段:items[].paymentMethod、data[].paymentMethod | 类型:String | 必填:否
| 值 | 中文 | 说明 |
|---|---|---|
SIGNED |
签单 | 现场签单 |
COMPANY_PAID |
公司付款 | 公司统一付款;未传 paymentMethod 时按该值处理 |
CASH_PAID |
现付 | 现场现金/线下现付 |
7. 错误码
| HTTP 状态 / code | 含义 | 触发场景 |
|---|---|---|
200 / 200 |
成功 | GET 查询成功或 PUT 保存成功 |
200 / 401 |
未授权 | 缺少有效的管理后台 Authorization 头 |
400 / 400 |
请求参数非法 | sourceType 不在 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MANUAL / CUSTOM_ASSIGNMENT 内,或请求体结构不符合要求 |
200 / 584011 |
当前核单状态不允许录门票核单 | PUT 保存时订单不是可录门票核单的状态 |
7.1 错误结构
{
"code": 400,
"message": "sourceType 必须是 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MANUAL / CUSTOM_ASSIGNMENT 之一",
"data": null,
"success": false
}
8. 示例(3 组:典型 / 边界 / 异常)
8.1 典型成功:GET 返回手工项目为 MANUAL
请求:
GET /v3/admin/order/2079576729147338754/settlement/step2
Authorization: Bearer {token}
GET 无请求体。
响应:
{
"code": 200,
"message": "成功",
"success": true,
"data": [
{
"id": "2080186487600025601",
"sourceType": "MANUAL",
"sourceTypeName": "手工项目",
"scenicAssignmentId": null,
"dayNumber": 2,
"dayDate": "2026-07-22",
"scenicName": "临时补充门票",
"specName": "成人票",
"ticketCount": 2,
"ticketUnitPrice": 30.00,
"sellPrice": 50.00,
"totalAmount": 100.00,
"plannedCost": 60.00,
"actualCost": 60.00,
"paymentMethod": "COMPANY_PAID",
"paymentMethodName": "公司付款",
"voucherUrls": [],
"remark": "现场补充"
}
]
}
8.2 边界成功:查询结果原样 PUT
场景说明:前端可把 GET 回来的 MANUAL 行原样放入 items 后提交;保存成功后再次 GET 仍返回 MANUAL / 手工项目。
请求:
PUT /v3/admin/order/2079576729147338754/settlement/step2
Authorization: Bearer {token}
Content-Type: application/json
{
"items": [
{
"id": "2080186487600025601",
"sourceType": "MANUAL",
"sourceTypeName": "手工项目",
"scenicAssignmentId": null,
"dayNumber": 2,
"dayDate": "2026-07-22",
"scenicName": "临时补充门票",
"specName": "成人票",
"ticketCount": 2,
"ticketUnitPrice": 30.00,
"sellPrice": 50.00,
"totalAmount": 100.00,
"plannedCost": 60.00,
"actualCost": 60.00,
"paymentMethod": "COMPANY_PAID",
"paymentMethodName": "公司付款",
"voucherUrls": [],
"remark": "现场补充"
}
]
}
响应:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"addedIds": ["2080186500000000001"],
"updatedIds": [],
"deletedIds": [],
"totalActualCost": "60.00"
}
}
8.3 业务失败:非法 sourceType
场景说明:items[].sourceType 传入未定义值时仍按参数非法处理。
请求:
PUT /v3/admin/order/2079576729147338754/settlement/step2
Authorization: Bearer {token}
Content-Type: application/json
{
"items": [
{
"sourceType": "TAB_MANUAL",
"scenicAssignmentId": null,
"dayDate": "2026-07-22",
"scenicName": "临时补充门票",
"specName": "成人票",
"ticketCount": 1,
"ticketUnitPrice": 0,
"sellPrice": 0,
"totalAmount": 0,
"plannedCost": 0,
"actualCost": 0,
"paymentMethod": "COMPANY_PAID",
"voucherUrls": [],
"remark": null
}
]
}
响应:
{
"code": 400,
"message": "sourceType 必须是 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MANUAL / CUSTOM_ASSIGNMENT 之一",
"data": null,
"success": false
}
9. 业务边界
- 适用场景:核单 Step2 门票/游玩项目页签查询、保存门票明细时使用。
- 手工项目保存:新增或编辑手工门票行时,
items[].sourceType推荐传MANUAL,scenicAssignmentId可传null。 - 旧入参兼容:旧页面继续传
CUSTOM_ASSIGNMENT仍可保存;保存成功后再次查询会返回MANUAL。 - 查询结果原样提交:GET 返回的
MANUAL行可原样进入 PUT 的items。 - 未变化范围:
SCENIC_ASSIGNMENT、ACTIVITY_ASSIGNMENT的查询和保存语义不变;null/ 未知来源的查询兜底行为不变。 - 不适用场景:人员费用、住宿、餐食、其他支出接口没有本次契约变化。
10. 修改前后对比
10.1 字段级对比
| 字段 | 修改前 | 修改后 |
|---|---|---|
GET data[].sourceType |
手工/自定义门票行返回 CUSTOM_ASSIGNMENT |
手工/自定义门票行统一返回 MANUAL |
GET data[].sourceTypeName |
手工/自定义门票行可能按旧来源展示 | 手工/自定义门票行统一返回 手工项目 |
PUT items[].sourceType |
允许 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / CUSTOM_ASSIGNMENT |
允许 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MANUAL / CUSTOM_ASSIGNMENT |
10.2 行为级对比
| 行为 | 修改前 | 修改后 |
|---|---|---|
| 查询手工门票行 | 前端需要识别 CUSTOM_ASSIGNMENT |
前端按 MANUAL 识别手工项目 |
| 保存手工门票行 | 前端需要把手工 Tab 转成 CUSTOM_ASSIGNMENT |
前端可直接提交 MANUAL |
| 查询结果原样保存 | GET 的旧来源值与页面手工 Tab 值可能不一致 | GET 结果可原样 PUT |
| 旧请求兼容 | 旧 CUSTOM_ASSIGNMENT 入参可保存 |
继续可保存,回读统一为 MANUAL |
11. 影响评估 / 回滚
11.1 影响评估
- 是否破坏向后兼容:否。PUT 继续兼容旧
CUSTOM_ASSIGNMENT入参;GET 只统一手工门票来源的展示值。 - 前端是否必须同步上线:否。旧保存请求仍可用;但前端可清理
MANUAL与CUSTOM_ASSIGNMENT互转逻辑。 - 影响已有数据:不需要前端处理历史数据;页面以后端返回的
MANUAL为准。
11.2 回滚方案
- 如接口回滚,前端需恢复兼容 GET 返回
CUSTOM_ASSIGNMENT的判断。 - 回滚后不要把 GET 查询结果中的
sourceType假定为一定可原样提交。
12. 注意事项
- 前端不要再按 Tab 名称把手工项目强制转换成
CUSTOM_ASSIGNMENT;新增手工行可以直接传MANUAL。 - 前端如有
sourceType === "CUSTOM_ASSIGNMENT"才展示手工项目的判断,需要同步兼容或改为判断MANUAL。 CUSTOM_ASSIGNMENT仅作为旧保存请求兼容值保留,不应再作为新页面查询展示值。sourceTypeName是展示字段,保存时可不传;保存后以再次查询结果为准。- 非法
sourceType仍会返回参数非法,不新增兜底保存。
13. 关联 / 联系人
13.1 链接
13.2 联系人
- 后端负责人: @yst