14 KiB
14 KiB
【🔧 修改接口·管理后台】Step2 票种规格默认值(#5185)
PR: #5191 | 更新时间: 2026-07-23
1. 接口背景
Step2 门票/游玩项目明细原先可能返回或保存空的 specName,前端无法稳定展示票种/规格。现在查询和保存统一补齐“成人票”默认值,同时保留已有的非空规格。
2. 变更清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | Step2 查询门票核单明细 | GET | /v3/admin/order/{orderId}/settlement/step2 |
修改接口 | 无明确规格的明细统一返回 specName=成人票 |
| 2 | Step2 保存门票核单明细 | PUT | /v3/admin/order/{orderId}/settlement/step2 |
修改接口 | specName 为 null、空串或纯空白时按“成人票”保存 |
3. 接口详情
3.1 Step2 查询门票核单明细
- 使用场景:进入或刷新核单 Step2 时查询门票/游玩项目明细。
- 认证:需要管理后台 JWT。
- 幂等性:是,只读查询。
- 限流:无接口专属限流约定。
- 默认语义:自动生成且无明确规格、或已有明细规格为空时,
specName返回“成人票”。 - 保留语义:已有非空规格原样返回,例如“骑马体验”。
3.2 Step2 保存门票核单明细
- 使用场景:全量保存 Step2 门票/游玩项目明细。
- 认证:需要管理后台 JWT。
- 幂等性:业务数据为全量替换语义;重复提交相同明细得到相同业务内容,行 ID 可能重新生成。
- 限流:无接口专属限流约定。
- 默认语义:
items[].specName为null、""或纯空白时,保存并回读为“成人票”。 - 保留语义:非空规格原样保存,例如“骑马体验”不会被替换。
4. 接口入参
4.1 路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
orderId |
Long / String | 是 | 订单 ID,必须大于 0;19 位 ID 建议按字符串拼入路径 |
GET 无 Query 参数、无请求体。
4.2 PUT 请求体
推荐使用对象形式;接口同时兼容直接提交明细数组。
{
"items": []
}
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|---|---|---|---|---|
items |
Array | 是 | 门票/游玩项目明细,全量替换 | 可为空数组;空数组表示清空已保存草稿 |
4.3 items[] 字段
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|---|---|---|---|---|
id |
Long / String | 否 | 已存在行 ID;新增或自动生成行可为空 | 19 位 ID 建议使用字符串 |
sourceType |
String | 是 | 来源类型 | SCENIC_ASSIGNMENT、ACTIVITY_ASSIGNMENT、CUSTOM_ASSIGNMENT |
sourceTypeName |
String | 否 | 来源类型中文名 | 最长 32 字符 |
scenicAssignmentId |
Long / String | 否 | 来源记录 ID;手动补充行为空 | 19 位 ID 建议使用字符串 |
dayNumber |
Integer | 否 | 行程第几天,从 1 开始;保存时按 dayDate 计算 |
无需前端计算 |
dayDate |
String | 是 | 项目日期 | yyyy-MM-dd,不得早于订单出发日期 |
scenicName |
String | 是 | 景区或游玩项目名称 | 非空,最长 200 字符 |
specName |
String | 否 | 票种/规格名称 | 最长 128 字符;null、空串、纯空白统一为“成人票” |
ticketCount |
Integer | 是 | 实际购票数量 | 套餐含项目可填 0 |
ticketUnitPrice |
Decimal | 否 | 参考单价,单位元 | 自费项目可填;包价项目可为 null |
sellPrice |
Decimal | 否 | 客户成交单价,单位元 | 大于等于 0 |
totalAmount |
Decimal | 否 | 客户成交小计,单位元 | 大于等于 0;为空时按 sellPrice × ticketCount 计算 |
plannedCost |
Decimal | 是 | 计划成本,单位元 | 大于等于 0 |
actualCost |
Decimal | 是 | 实际成本,单位元 | 大于等于 0 |
paymentMethod |
String | 否 | 付款方式 | SIGNED、COMPANY_PAID、CASH_PAID;为空时为 COMPANY_PAID |
paymentMethodName |
String | 否 | 付款方式中文名 | 最长 32 字符 |
voucherUrls |
Array<String> | 否 | 凭证图片 URL 列表 | 可为空数组 |
remark |
String | 否 | 备注 | 最长 500 字符 |
5. 出参(响应)
5.1 统一响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
code |
Integer | 业务状态码,成功为 200 |
message |
String | 响应消息,成功为“成功” |
data |
Object / Array | GET 为明细数组,PUT 为保存结果对象 |
traceId |
String / null | 链路追踪 ID |
success |
Boolean | code=200 时为 true |
5.2 GET data[]
| 字段 | 类型 | 可为空 | 说明 |
|---|---|---|---|
id |
String / null | 是 | 已保存的 19 位行 ID 按字符串返回;未保存的自动生成行可为 null |
sourceType |
String | 否 | 来源类型,取值见 §6.2 |
sourceTypeName |
String | 是 | 来源类型中文名 |
scenicAssignmentId |
String / null | 是 | 19 位来源记录 ID 按字符串返回;手动补充行为空 |
dayNumber |
Integer | 是 | 根据项目日期与订单行程计算的天序 |
dayDate |
String | 否 | 项目日期,格式为 yyyy-MM-dd |
scenicName |
String | 否 | 景区或游玩项目名称 |
specName |
String | 否 | 无明确规格时返回“成人票”;已有非空规格原样返回 |
ticketCount |
Integer | 否 | 实际购票数量 |
ticketUnitPrice |
Decimal | 是 | 参考单价,单位元 |
sellPrice |
Decimal | 是 | 客户成交单价,单位元 |
totalAmount |
Decimal | 是 | 客户成交小计,单位元 |
plannedCost |
Decimal | 否 | 计划成本,单位元 |
actualCost |
Decimal | 否 | 实际成本,单位元 |
paymentMethod |
String | 是 | 付款方式,取值见 §6.3 |
paymentMethodName |
String | 是 | 付款方式中文名 |
voucherUrls |
Array<String> | 是 | 凭证图片 URL 列表 |
remark |
String | 是 | 备注 |
5.3 PUT data
| 字段 | 类型 | 说明 |
|---|---|---|
addedIds |
Array<String> | 本次新增行 ID 列表 |
updatedIds |
Array<String> | 本次更新行 ID 列表 |
deletedIds |
Array<String> | 本次删除行 ID 列表 |
totalActualCost |
String | 保存后实际成本合计,单位元 |
6. 枚举 / 数据字典
6.1 specName(数据字典 settlement_ticket_spec)
所属字段:items[].specName | 类型:String | 必填:否
| 值 | 中文 | 说明 |
|---|---|---|
成人票 |
成人票 | 当前默认票种/规格;字典接口返回的 dictValue |
加载选项使用:
GET /admin/dict/data/settlement_ticket_spec
展示使用字典项 dictLabel,提交使用 dictValue。本次没有新增 specCode 字段。
6.2 sourceType
所属字段:items[].sourceType | 类型:String | 必填:是
| 值 | 中文 | 说明 |
|---|---|---|
SCENIC_ASSIGNMENT |
景区 | 来源于景区项目 |
ACTIVITY_ASSIGNMENT |
游玩项目 | 来源于游玩项目 |
CUSTOM_ASSIGNMENT |
手动补充 | 核单时手动新增 |
6.3 paymentMethod
所属字段:items[].paymentMethod | 类型:String | 必填:否
| 值 | 中文 | 说明 |
|---|---|---|
SIGNED |
签单 | 供应商签单 |
COMPANY_PAID |
公司付款 | 未传付款方式时的默认值 |
CASH_PAID |
现付 | 现场付款,可附凭证 |
7. 错误码
| code | 含义 | 触发场景 |
|---|---|---|
200 |
成功 | 查询或保存成功 |
400 |
请求参数校验失败 | 必填字段为空、枚举值不合法、金额为负数、字段超长或日期格式错误 |
401 |
未认证或认证失效 | 未携带有效管理后台 JWT |
584011 |
当前核单状态不允许录门票核单 | PUT 时订单核单状态不是“待核单”或“核单中” |
584017 |
订单缺出发日期 | PUT 时无法根据 dayDate 计算 dayNumber |
584018 |
项目日期早于订单出发日期 | PUT 的 items[].dayDate 早于订单出发日期 |
500 |
系统异常 | 查询或保存过程发生未预期异常 |
8. 示例
8.1 典型成功:查询自动生成明细
请求:
GET /v3/admin/order/2079454953641836546/settlement/step2
Authorization: Bearer <admin-token>
无请求体。
响应:
{
"code": 200,
"message": "成功",
"data": [
{
"id": null,
"sourceType": "SCENIC_ASSIGNMENT",
"sourceTypeName": "景区",
"scenicAssignmentId": "2079454953641837001",
"dayNumber": 1,
"dayDate": "2026-07-21",
"scenicName": "示例景区",
"specName": "成人票",
"ticketCount": 2,
"ticketUnitPrice": 100.00,
"sellPrice": 120.00,
"totalAmount": 240.00,
"plannedCost": 200.00,
"actualCost": 200.00,
"paymentMethod": "COMPANY_PAID",
"paymentMethodName": "公司付款",
"voucherUrls": [],
"remark": null
}
],
"traceId": "a1b2c3d4-e5f6-7890",
"success": true
}
8.2 典型成功:提交字典选中的“成人票”
请求:
PUT /v3/admin/order/2079454953641836546/settlement/step2
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"items": [
{
"id": null,
"sourceType": "SCENIC_ASSIGNMENT",
"sourceTypeName": "景区",
"scenicAssignmentId": "2079454953641837001",
"dayNumber": 1,
"dayDate": "2026-07-21",
"scenicName": "示例景区",
"specName": "成人票",
"ticketCount": 2,
"ticketUnitPrice": 100.00,
"sellPrice": 120.00,
"totalAmount": 240.00,
"plannedCost": 200.00,
"actualCost": 200.00,
"paymentMethod": "COMPANY_PAID",
"paymentMethodName": "公司付款",
"voucherUrls": [],
"remark": null
}
]
}
响应:
{
"code": 200,
"message": "成功",
"data": {
"addedIds": ["2079454953641840001"],
"updatedIds": [],
"deletedIds": [],
"totalActualCost": "200.00"
},
"traceId": "b2c3d4e5-f6a7-8901",
"success": true
}
8.3 边界情况:空规格归一为“成人票”
保存请求:
PUT /v3/admin/order/2079454953641836546/settlement/step2
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"items": [
{
"sourceType": "ACTIVITY_ASSIGNMENT",
"sourceTypeName": "游玩项目",
"scenicAssignmentId": "2079454953641837002",
"dayDate": "2026-07-22",
"scenicName": "示例游玩项目",
"specName": null,
"ticketCount": 2,
"ticketUnitPrice": null,
"sellPrice": 0,
"totalAmount": 0,
"plannedCost": 0,
"actualCost": 0,
"paymentMethod": "COMPANY_PAID",
"voucherUrls": [],
"remark": null
}
]
}
保存响应:
{
"code": 200,
"message": "成功",
"data": {
"addedIds": ["2079454953641840002"],
"updatedIds": [],
"deletedIds": [],
"totalActualCost": "0"
},
"traceId": "c3d4e5f6-a7b8-9012",
"success": true
}
随后 GET 回读时,该行的关键字段为:
{
"scenicName": "示例游玩项目",
"specName": "成人票"
}
8.4 业务失败:非法来源类型
请求:
PUT /v3/admin/order/2079454953641836546/settlement/step2
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"items": [
{
"sourceType": "UNKNOWN",
"dayDate": "2026-07-21",
"scenicName": "示例项目",
"specName": "成人票",
"ticketCount": 1,
"plannedCost": 0,
"actualCost": 0
}
]
}
响应:
{
"code": 400,
"message": "sourceType 必须是 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / CUSTOM_ASSIGNMENT 之一",
"data": null,
"traceId": "d4e5f6a7-b8c9-0123",
"success": false
}
9. 业务边界
- GET:自动生成明细没有明确规格时,返回
specName=成人票。 - GET:历史已保存明细的
specName为null、空串或纯空白时,也返回“成人票”。 - GET/PUT:已有非空规格保持不变,例如“骑马体验”不会被覆盖。
- PUT:
specName最大 128 字符;空值会归一为“成人票”而不是报错。 - PUT:
items为全量数据;遗漏的旧明细不会继续保留。 - PUT:仅订单核单状态为“待核单”或“核单中”时允许保存。
- 前端从
settlement_ticket_spec字典读取选项,展示dictLabel,提交dictValue到specName。
10. 修改前后对比
10.1 字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
items[].specName |
String,可返回或保存为空 | String;查询和保存的空值统一为“成人票” |
items[].specCode |
不存在 | 仍不存在,本次未新增 |
10.2 行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 自动生成明细没有明确规格 | specName 可能为空或与项目名重复 |
specName=成人票 |
保存 specName=null、空串或纯空白 |
可能按空值保存和回显 | 保存、回读均为“成人票” |
| 保存非空自定义规格 | 原样保存 | 仍原样保存 |
| 接口数量 | GET、PUT 两个既有接口 | 不变,没有新增 Step2 接口 |
11. 影响评估
- 是否破坏向后兼容:否,接口路径、请求结构和响应字段均未改变;只收紧了空规格的返回语义。
- 前端是否必须同步上线:否;前端可逐步接入字典下拉,未接入时也会收到稳定的“成人票”默认值。
12. 注意事项
- 前端如有
specName || "成人票"的临时兜底,可在确认接口已覆盖当前环境后移除。 - 不要新增或提交
specCode;当前契约只使用specName。 - 选择字典项后提交
dictValue,不要提交dictLabel以外的展示元数据或dictDataId。 - 自定义非空规格可以继续提交,接口不会强制替换为字典当前默认项。
13. 关联 / 联系人
13.1 链接
13.2 联系人
- 后端负责人: @yst