对应后端 PR #3291 (Issue #3290): settleScope 字段补字典化, 出参补 settleScopeLabel。 在原 30_3272_资源退费说明CRUD模块_PR3273.md 的 settleScope 枚举说明后追加: - 用法 A: 直接用出参 settleScopeLabel (展示场景) - 用法 B: 调字典接口 GET /internal/dict/data?dictType=order_refund_settle_scope 拉下拉框选项 (编辑场景) - 容错说明 + 字典元信息 前端零代码改动也能用 (用法 A); 编辑场景拉一个统一字典接口即可。
10 KiB
二期 v3:资源退费说明 CRUD 模块(给司机行程单展示用)
服务: hl-resource-service PR: #3273 Issue: #3272 日期: 2026-05-30 影响: 🟢 新增能力。给运营在景区/活动详情下"维护退费说明"提供 CRUD 接口;最终用于行程单 PDF 渲染 + 核单 Step 5 录入对账。本期仅交付资源服务端,后续 PR 接入产品快照 + PDF + 核单。
总览(前端 mmg 必读)
每个景区(SCENIC)和活动(ACTIVITY)资源都可以挂一份"退费说明",包含资源级备注 + 多条退费明细(标题/金额/单位/备注/生效期)。
界面:景区 / 活动列表行加一个按钮 → 弹窗里调本期 3 个接口做 CRUD。
接口前缀:/admin/refund-note(无 /v3/ 前缀,因 resource-service 沿用一期路径风格;消费方仍是二期 v3 管理后台)。
本期不做:行程单 PDF 渲染、产品 ProductDetailVO 注入 refundNote、订单 OrderProductSnapshotContent 反序列化、核单 Step 5 录入对接。这些留后续 PR。
接口清单(3 个)
1. 查询单资源的退费说明
GET /admin/refund-note?resourceType=SCENIC&resourceId=12345
入参(query)
| 字段 | 类型 | 必填 | 枚举 | 说明 |
|---|---|---|---|---|
resourceType |
string | ✅ | SCENIC / ACTIVITY |
资源类型 |
resourceId |
long | ✅ | - | 资源 ID(字符串透传雪花) |
返回
{
"code": 200,
"data": { // 未配置时直接返 null,前端按 null 隐藏弹窗内容
"noteId": "20596378108368322580", // 雪花 ID 字符串透传
"resourceType": "SCENIC",
"resourceId": "12345",
"intro": "苔藓为赠送项目,不退费",
"items": [
{
"title": "成人未参加",
"amount": 44.00,
"unitLabel": "/人", // 展示文案,给人看
"settleScope": "PER_PERSON", // 结算粒度枚举,给规则引擎用
"remark": "",
"effectiveFrom": null, // yyyy-MM-dd
"effectiveTo": null
},
...
],
"createTime": "2026-05-30 10:15:00",
"updateTime": "2026-05-30 10:15:00"
}
}
枚举
settleScope:PER_PERSON按人结算(默认)PER_TEAM按团结算(如寻龙诀 100/团 整团一次性)PER_VEHICLE按车辆结算(如卡丁车 2 人/辆,按辆数乘单价)
注意:unitLabel("/人" / "/团" / "/辆")和 settleScope 职责不同:
- 卡丁车场景:
unitLabel="/人"(给客户看是 120/人)+settleScope=PER_VEHICLE(引擎按车辆数乘单价) - 两个字段独立维护,前端展示用
unitLabel,未来规则引擎用settleScope。
⚡ 更新(2026-06-01 PR #3291):settleScope 字典化 + 出参补 settleScopeLabel 中文
后端已落字典 + 接口出参直接返中文,前端不用自己维护英文→中文映射。两种用法二选一:
用法 A:直接用出参的 settleScopeLabel(展示场景推荐)
GET /admin/refund-note 返回的 items[] 每条新增 settleScopeLabel 字段:
"items": [
{
"title": "成人未参加",
"settleScope": "PER_PERSON",
"settleScopeLabel": "按人", // 🆕 后端字典拼装,直接展示
...
}
]
列表 / 详情 UI 直接 {{ item.settleScopeLabel }} 渲染即可,无需自己映射。
用法 B:调字典接口拉下拉框选项(编辑场景推荐)
管理后台编辑退费规则时,需要下拉框让运营选"按人/按团/按车"。调统一字典接口:
GET /internal/dict/data?dictType=order_refund_settle_scope
Authorization: Bearer <admin-token>
→ Result<List<SysDictDataDTO>>:
[
{ "dictValue": "PER_PERSON", "dictLabel": "按人", "sortOrder": 10 },
{ "dictValue": "PER_TEAM", "dictLabel": "按团", "sortOrder": 20 },
{ "dictValue": "PER_VEHICLE", "dictLabel": "按车", "sortOrder": 30 }
]
- 下拉框
option.label = dictLabel,option.value = dictValue - 提交时
req.items[].settleScope = dictValue(提交settleScopeLabel后端会忽略,label 由后端拼) - 建议前端全局缓存字典 5-10 分钟避免高频请求;如果有统一字典组件直接复用
容错(前端可忽略)
- 字典服务异常 / 字典数据缺失 →
settleScopeLabel = null,前端展示 fallback 用settleScope英文原值即可 - 字典文案后续改了(如"按人" → "按人头")→ 运维改 sys_dict_data + 清 Redis 缓存即生效,后端无需重启,前端无需发版
字典元信息
| 项 | 值 |
|---|---|
dict_type |
order_refund_settle_scope |
dict_name |
退费明细结算粒度 |
| 字典数据 | PER_PERSON→按人 / PER_TEAM→按团 / PER_VEHICLE→按车 |
| 字典存储 | hl_user_service.sys_dict_data |
| 缓存 key | cache:dict:data:order_refund_settle_scope |
2. 保存(upsert 整块)
PUT /admin/refund-note
入参(body)
{
"resourceType": "SCENIC",
"resourceId": 12345,
"intro": "苔藓为赠送项目,不退费",
"items": [
{
"title": "成人未参加", // 必填
"amount": 44.00, // 必填,>= 0(赠送项填 0)
"unitLabel": "/人", // 默认 "/人",可省略
"settleScope": "PER_PERSON", // 默认 "PER_PERSON",可省略
"remark": "",
"effectiveFrom": null,
"effectiveTo": null
}
]
}
约束
resourceType:必须是SCENIC/ACTIVITYresourceId:必须在 scenic_spot / activity 表存在且未软删,否则返 390805 "关联资源不存在"items:至少 1 条,最多 50 条items[].amount:>= 0(赠送项目填 0)items[].settleScope:必须是上述 3 枚举之一(null 时默认 PER_PERSON)items[].effectiveFrom<=items[].effectiveTo(同时存在时)
返回
{ "code": 200, "data": "20596378108368322580" } // 落库后的 noteId(字符串透传)
语义:upsert——按 (resourceType, resourceId) 找现有记录,有则整块覆盖(不做 item 级 diff),无则 insert。
3. 软删整份
DELETE /admin/refund-note?resourceType=SCENIC&resourceId=12345
入参:同 GET。
返回:{ "code": 200, "data": true }(不存在或已软删返 false,不报错)。
软删用主键自身写 deleted_at,UNIQUE KEY 永不撞键,支持同资源无限次"删→重建"。
错误码(段位 39080x)
| code | 错误信息 |
|---|---|
| 390801 | 退费明细金额必须 ≥ 0 |
| 390802 | 退费明细生效起日不能晚于止日 |
| 390803 | 退费明细结算粒度非法: {0} |
| 390804 | 退费说明资源类型非法: {0} |
| 390805 | 关联资源不存在: type={0}, id={1} |
非业务错误(参数校验失败)走通用 400。
业务边界
- 资源类型:一期仅
SCENIC+ACTIVITY,其他资源(餐饮/酒店/物资等)有各自退订/退款政策,不复用本结构。 - 一份生效:同资源同时刻只有一份生效的退费说明(DB 唯一键
(resource_type, resource_id, deleted_at)保证)。 - 关联校验:保存时校验 resourceId 在主资源表存在,防孤儿数据。
- 资源软删后:本退费说明仍存在但孤儿(查不到对应资源),不主动清理;运营侧需手动删除。
数据示例(典型)
白桦林(4 条规则)
{
"resourceType": "SCENIC", "resourceId": 12345,
"intro": "苔藓为赠送项目, 不退费",
"items": [
{"title": "儿童/学生/无证件未参加", "amount": 15, "unitLabel": "/人", "settleScope": "PER_PERSON"},
{"title": "免票座电瓶车", "amount": 30, "unitLabel": "/人", "settleScope": "PER_PERSON"},
{"title": "白桦林未参加", "amount": 44, "unitLabel": "/人", "settleScope": "PER_PERSON"},
{"title": "桦树皮画未参加", "amount": 50, "unitLabel": "/人", "settleScope": "PER_PERSON", "remark": "仅儿童"}
]
}
寻龙诀(按团结算)
{
"resourceType": "ACTIVITY", "resourceId": 99999,
"items": [
{"title": "寻龙诀未参加", "amount": 100, "unitLabel": "/团", "settleScope": "PER_TEAM"}
]
}
卡丁车(按车结算,文案 /人)
{
"resourceType": "ACTIVITY", "resourceId": 88888,
"items": [
{"title": "卡丁车未骑", "amount": 120, "unitLabel": "/人", "settleScope": "PER_VEHICLE",
"remark": "2 人/辆共享单价"}
]
}
套娃(时间窗口)
{
"resourceType": "SCENIC", "resourceId": 77777,
"items": [
{"title": "老人只看大马戏", "amount": 65, "settleScope": "PER_PERSON"},
{"title": "6/25 后没去或免票", "amount": 165, "settleScope": "PER_PERSON",
"effectiveFrom": "2026-06-25", "effectiveTo": null}
]
}
影响评估
- 后端:仅 hl-resource-service 新增 1 张表
resource_refund_note+ 3 个 admin 接口,对其他业务零影响。 - DB:Flyway
V20260529_002__create_resource_refund_note.sql自动建表,重启服务后生效。 - 前端:新增弹窗 UI(建议在景区 / 活动详情页加按钮 → 弹窗 CRUD),完全独立的新页面,老页面不影响。
注意事项
- 雪花 ID 透传为字符串(
noteId/resourceId),前端不要Number()。 - 资源软删后的退费说明不会自动清理,运营侧需先删退费说明再删资源(否则成孤儿数据)。
- 同资源不可有 2 份生效退费说明(DB 唯一键约束)。
- 删除是软删,可同资源重建;不支持物理删除。