2026-04-30: fee-deduction 按 feeType 合并为单条 name 拼接 (PR #1579)
这个提交包含在:
父节点
47be9f39d3
当前提交
fd29367a2d
@ -0,0 +1,124 @@
|
||||
# 修复: 费用包含按 feeType 合并为单条 name 拼接
|
||||
|
||||
**类型**: 后端 BUG 修复(PR #1577 follow-up)
|
||||
**关联**: 工单 #1578 / PR #1579
|
||||
**日期**: 2026-04-30
|
||||
**前端处理者**: yst / mmg
|
||||
**影响范围**: 管理后台「产品编辑 → 补充信息 → 费用说明 →『根据行程自动生成』」按钮
|
||||
|
||||
---
|
||||
|
||||
## 演进
|
||||
|
||||
| PR | 行为 | 产品 2045018534152478721 返回数 |
|
||||
|---|---|---|
|
||||
| #1568 | 修复漏景区/活动/酒店,每节点 1 条+追加酒店 | 35 条 |
|
||||
| #1577 | 按名字去重,name 改具体名 | 17 条 |
|
||||
| **#1579 本次** | **按 feeType 合并为单条,name 拼接所有具体名** | **3 条** |
|
||||
|
||||
用户期望:每种 feeType 一条记录,中间 label 显示完整字符串。
|
||||
|
||||
## 用户原文
|
||||
|
||||
> 「门票:黑山头,莫日格勒河 门票是一条数据」
|
||||
> 「其他也是」(指活动/住宿/餐饮/交通也都按 feeType 合并)
|
||||
|
||||
## 设计
|
||||
|
||||
```
|
||||
name = "{feeType}:{name1,name2,...}" (中文「:」前缀 + 中文「,」分隔)
|
||||
```
|
||||
|
||||
| 字段 | 值 |
|
||||
|---|---|
|
||||
| `feeType` | 门票 / 活动 / 住宿 / 餐饮 / 交通(chip 用) |
|
||||
| `name` | `"门票:黑山头,白桦林景区,莫日格勒河,..."` 完整拼接串 |
|
||||
| `sourceNodeName` | **null**(避免重复) |
|
||||
| `icon` | **null**(PR #1573 行为不变) |
|
||||
|
||||
## 影响接口
|
||||
|
||||
| 方法 | 路径 | 改动 |
|
||||
|------|------|------|
|
||||
| GET | `/admin/product/item/{id}/fee-deduction-preview` | 数组**长度从 N 条压缩到 ≤ 5 条**(每 feeType 1 条),`name` 字段值变为完整拼接串,`sourceNodeName` 设为 null |
|
||||
|
||||
请求签名完全不变,响应字段不变。
|
||||
|
||||
## 响应示例
|
||||
|
||||
```jsonc
|
||||
GET /admin/product/item/2045018534152478721/fee-deduction-preview
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"feeType": "门票",
|
||||
"name": "门票:黑山头,白桦林景区,莫日格勒河,莫日格勒河断崖,呼伦湖,阿尔山火车站",
|
||||
"source": "AUTO",
|
||||
"sourceNodeName": null,
|
||||
"icon": null
|
||||
},
|
||||
{
|
||||
"feeType": "活动",
|
||||
"name": "活动:ATV全地形越野车穿越,驯鹿拉车体验",
|
||||
"source": "AUTO",
|
||||
"sourceNodeName": null,
|
||||
"icon": null
|
||||
},
|
||||
{
|
||||
"feeType": "住宿",
|
||||
"name": "住宿:海拉尔洲季酒店,呼伦贝尔香格里拉大酒店,额尔古纳玖成万豪水汇智能酒店,额尔古纳豪星酒店,额尔古纳白桦大酒店,海拉尔海棠酒店,满洲里饭店(百年俄式),敖根栖息地,阿尔善国际维景度假温泉酒店",
|
||||
"source": "AUTO",
|
||||
"sourceNodeName": null,
|
||||
"icon": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 实现要点
|
||||
|
||||
```java
|
||||
// 1. LinkedHashMap<feeType, LinkedHashSet<name>> 双保序去重收集
|
||||
Map<String, LinkedHashSet<String>> feeTypeToNames = new LinkedHashMap<>();
|
||||
// (扫节点 + 扫酒店,按 feeType 分桶)
|
||||
|
||||
// 2. feeIgnoredTypes 在 map 阶段移除 entry(效率优于 result.removeIf)
|
||||
feeTypeToNames.keySet().removeAll(ignored);
|
||||
|
||||
// 3. 排序(门票/活动/住宿/餐饮/交通)+ 拼接 + 输出
|
||||
return feeTypeToNames.entrySet().stream()
|
||||
.filter(e -> !e.getValue().isEmpty())
|
||||
.sorted(byFeeTypeOrder)
|
||||
.map(e -> FeeDeductionPreviewVO.builder()
|
||||
.feeType(e.getKey())
|
||||
.name(e.getKey() + ":" + String.join(",", e.getValue()))
|
||||
.source(SOURCE_AUTO)
|
||||
.sourceNodeName(null)
|
||||
.build())
|
||||
.toList();
|
||||
```
|
||||
|
||||
## 前端要做的改动
|
||||
|
||||
**0 改动**。前端模板 `{{ item.name }}` 直接渲染就显示 `"门票:黑山头,白桦林景区,..."` 完整字符串。
|
||||
|
||||
`sourceNodeName=null` 后,前端 `v-if="item.sourceNodeName"` 来源标签自动不渲染,UI 自然更清爽。
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
`mvn test -pl hl-product-service-v2`: **1046/1046 全绿**。
|
||||
|
||||
10 现有断言更新(从「按 name 多条」改为「按 feeType 一条」)+ 1 新增 `feeDeductionPreview_mixedTypes_groupedByFeeType` 混合类型分组测试。
|
||||
|
||||
测试服 round-trip(产品 `2045018534152478721`)实测:
|
||||
- 总计 **3 条**(门票/活动/住宿 各一)
|
||||
- 每条 `name` 是 `"{feeType}:{name1,name2,...}"` 完整串
|
||||
- `sourceNodeName` 全 null
|
||||
- `feeType` 排序正确(门票 → 活动 → 住宿)
|
||||
- `icon` 全 null
|
||||
|
||||
## 部署状态
|
||||
|
||||
- ✅ Deploy Panel 已部署测试服 hl-product-service-v2 双实例(2026-04-30 17:11)
|
||||
- 正式环境暂未发布(等 dev → main release PR)
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户