hl-api-changelog/changelogs-v2/2026-05/30_3272_资源退费说明CRUD模块_PR3273.md
yaosutu 1c6b38f548 docs(changelog): 资源退费说明 changelog 追加 settleScope 字典化用法说明 (PR #3291)
对应后端 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); 编辑场景拉一个统一字典接口即可。
2026-06-01 10:14:39 +08:00

297 行
10 KiB
Markdown

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

# 二期 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字符串透传雪花|
**返回**
```jsonc
{
"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 #3291settleScope 字典化 + 出参补 settleScopeLabel 中文
后端**已落字典 + 接口出参直接返中文**,前端**不用自己维护英文→中文映射**。两种用法二选一:
#### 用法 A直接用出参的 `settleScopeLabel`(展示场景推荐)
`GET /admin/refund-note` 返回的 `items[]` 每条**新增 `settleScopeLabel` 字段**
```jsonc
"items": [
{
"title": "成人未参加",
"settleScope": "PER_PERSON",
"settleScopeLabel": "按人", // 🆕 后端字典拼装,直接展示
...
}
]
```
**列表 / 详情 UI 直接 `{{ item.settleScopeLabel }}` 渲染**即可,无需自己映射。
#### 用法 B调字典接口拉下拉框选项编辑场景推荐
管理后台编辑退费规则时,需要下拉框让运营选"按人/按团/按车"。调统一字典接口:
```http
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
```jsonc
{
"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` / `ACTIVITY`
- `resourceId`**必须在 scenic_spot / activity 表存在且未软删**,否则返 390805 "关联资源不存在"
- `items`:至少 1 条,最多 50 条
- `items[].amount`>= 0赠送项目填 0
- `items[].settleScope`:必须是上述 3 枚举之一null 时默认 PER_PERSON
- `items[].effectiveFrom` <= `items[].effectiveTo`(同时存在时)
**返回**
```jsonc
{ "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 条规则)
```jsonc
{
"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": "仅儿童"}
]
}
```
### 寻龙诀(按团结算)
```jsonc
{
"resourceType": "ACTIVITY", "resourceId": 99999,
"items": [
{"title": "寻龙诀未参加", "amount": 100, "unitLabel": "/团", "settleScope": "PER_TEAM"}
]
}
```
### 卡丁车(按车结算,文案 /人)
```jsonc
{
"resourceType": "ACTIVITY", "resourceId": 88888,
"items": [
{"title": "卡丁车未骑", "amount": 120, "unitLabel": "/人", "settleScope": "PER_VEHICLE",
"remark": "2 人/辆共享单价"}
]
}
```
### 套娃(时间窗口)
```jsonc
{
"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,完全独立的新页面,老页面不影响。
---
## 注意事项
1. 雪花 ID 透传为字符串(`noteId` / `resourceId`),前端**不要 `Number()`**。
2. 资源软删后的退费说明不会自动清理,运营侧需先删退费说明再删资源(否则成孤儿数据)。
3. 同资源不可有 2 份生效退费说明DB 唯一键约束)。
4. 删除是软删,可同资源重建;不支持物理删除。
---
## 关联
- **Issue**: [#3272](https://git.1814.love:8443/wx/HL/issues/3272)
- **PR**: [#3273](https://git.1814.love:8443/wx/HL/pulls/3273) - feat(resource): 新增资源退费说明 CRUD 模块
- **Commit**: [ffae5ab90](https://git.1814.love:8443/wx/HL/commit/ffae5ab90)
- **后续 PR**(不在本期):
- 产品服务 `ProductDetailVO.NodeItem.refundNote` 注入(参考 #3147 serviceStandard 模式)
- 订单 `OrderProductSnapshotContent` 加 refundNote 反序列化10 行)
- 行程单 PDF 渲染退费表(订单 v3 + PDF 服务)
- 核单 Step 5 录入半结构化对接(可选)