From 6d2653119807c5060e986c570d7da8d76254c50a Mon Sep 17 00:00:00 2001 From: wx Date: Thu, 30 Apr 2026 16:57:47 +0800 Subject: [PATCH] =?UTF-8?q?2026-04-30:=20fee-deduction=20=E6=8C=89?= =?UTF-8?q?=E5=90=8D=E5=AD=97=E5=8E=BB=E9=87=8D=20+=20name=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=A1=AB=E5=85=B7=E4=BD=93=E5=90=8D=20(PR=20#1577)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...x_product_fee-deduction-dedupe-and-name.md | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 changelogs/2026-04/30_fix_product_fee-deduction-dedupe-and-name.md diff --git a/changelogs/2026-04/30_fix_product_fee-deduction-dedupe-and-name.md b/changelogs/2026-04/30_fix_product_fee-deduction-dedupe-and-name.md new file mode 100644 index 0000000..dc816ee --- /dev/null +++ b/changelogs/2026-04/30_fix_product_fee-deduction-dedupe-and-name.md @@ -0,0 +1,123 @@ +# 修复: 费用包含按名字去重 + name 字段填具体景区/活动/酒店名 + +**类型**: 后端 BUG 修复(PR #1568 follow-up) +**关联**: 工单 #1576 / PR #1577 +**日期**: 2026-04-30 +**前端处理者**: yst / mmg +**影响范围**: 管理后台「产品编辑 → 补充信息 → 费用说明 →『根据行程自动生成』」按钮 + +--- + +## 背景 + +PR #1568 已修复「自动生成」漏景区/活动/酒店的问题(每节点 1 条 + 追加酒店),但用户使用后反馈 2 个新问题: + +### 问题 1: 同名条目重复 + +同一酒店多天住就出现多次 — 产品 `2045018534152478721`「游牧的森林·南线经典」自动生成 35 条结果中: +- 「海拉尔洲季酒店」出现 **11 次**(11 天住同一酒店) +- 「呼伦贝尔香格里拉大酒店」出现 **4 次** +- 「额尔古纳玖成万豪水汇智能酒店」出现 **4 次** +- 27 条住宿真实只有 **9 家不同酒店** + +### 问题 2: `name` 字段无信息量 + +三种类型(景区/活动/酒店)的 `name` 字段都填 `feeType` 标签 — 即: +- 6 条门票项 `name` 都是 `"门票"`(实际景区名是黑山头/白桦林景区/莫日格勒河等) +- 27 条住宿项 `name` 都是 `"住宿"`(实际酒店名各不同) + +前端中间 label 区域显示重复的 chip 文字,无信息量。 + +## 修复 + +`hl-product-service-v2` `ProductValidationService.feeDeductionPreview()`: + +### 1. 按名字去重 + +```java +LinkedHashSet seenNodeNames = new LinkedHashSet<>(); +for (ProductItineraryNodeDO node : sortedNodes) { + String nodeName = node.getNodeName(); + if (nodeName == null || nodeName.isBlank()) continue; + if (!seenNodeNames.add(nodeName)) continue; // ← 按名字去重 + result.add(...); +} + +LinkedHashSet seenHotelNames = new LinkedHashSet<>(); +for (ProductDayHotelDO hotel : sortedHotels) { + String hotelName = hotel.getHotelName(); + if (hotelName == null || hotelName.isBlank()) continue; + if (!seenHotelNames.add(hotelName)) continue; + result.add(...); +} +``` + +`LinkedHashSet` 保留**首次出现**(行程顺序里第一次见到该名字的那条)。 + +### 2. `name` 字段填具体名 + +| 字段 | 改前 | 改后 | +|---|---|---| +| `feeType` | 门票/活动/住宿/餐饮/交通 | **不变**(前端 chip 用) | +| `name` | 同 feeType | **节点名/酒店名** | +| `sourceNodeName` | 节点名/酒店名 | **不变**(与 name 同值) | +| `icon` | null | **不变**(PR #1573 行为) | + +## 影响接口 + +| 方法 | 路径 | 改动 | +|------|------|------| +| GET | `/admin/product/item/{id}/fee-deduction-preview` | 数组**长度变短**(去重),每项 `name` 改为具体名,其他字段语义不变 | + +请求签名完全不变,响应字段不变。 + +## 响应示例 + +```jsonc +GET /admin/product/item/2045018534152478721/fee-deduction-preview +{ + "code": 200, + "data": [ + /* 6 门票 */ + {"feeType": "门票", "name": "黑山头", "source": "AUTO", "sourceNodeName": "黑山头", "icon": null}, + {"feeType": "门票", "name": "白桦林景区", "source": "AUTO", "sourceNodeName": "白桦林景区", "icon": null}, + /* ... 共 6 条 */ + /* 2 活动 */ + {"feeType": "活动", "name": "ATV全地形越野车穿越", "source": "AUTO", "sourceNodeName": "ATV全地形越野车穿越", "icon": null}, + {"feeType": "活动", "name": "驯鹿拉车体验", "source": "AUTO", "sourceNodeName": "驯鹿拉车体验", "icon": null}, + /* 9 住宿(去重后,不再有重复) */ + {"feeType": "住宿", "name": "海拉尔洲季酒店", "source": "AUTO", "sourceNodeName": "海拉尔洲季酒店", "icon": null}, + /* ... 共 9 条 */ + ] +} +``` + +旧返回 35 条,新返回 **17 条**(6 门票 + 2 活动 + 9 住宿)。 + +## 前端要做的改动 + +**0 改动**。前端模板 `{{ item.name }}` 直接渲染就显示具体名字了。 + +`sourceNodeName` 字段值与 `name` 同值,如果前端模板里有 `← {{ item.sourceNodeName }}` 来源标签,会出现"门票 chip + 黑山头 + ← 黑山头"的视觉重复。前端**可选**: +- 保留来源标签作为视觉标识(用户已习惯) +- 或加 `v-if="item.sourceNodeName !== item.name"` 隐藏冗余 + +## 测试覆盖 + +`mvn test -pl hl-product-service-v2`: **1045/1045 全绿**。 + +新增 3 单测: +- `feeDeductionPreview_duplicateNodeNames_dedupedByName` — 5 个同名 SCENIC 节点 → result.size=1 +- `feeDeductionPreview_sameHotelMultipleDays_dedupedByName` — 11 个同酒店多天 → result.size=1 +- `feeDeductionPreview_nullOrBlankName_skipped` — null/空名字跳过 + +测试服 round-trip(产品 `2045018534152478721`)实测: +- 总计 **17 条**(6 + 2 + 9) +- 全部 `name` unique(去重生效) +- 全部 `name` 是具体名字(黑山头/ATV 全地形越野车穿越/海拉尔洲季酒店 等) +- 全部 `icon` null(PR #1573 行为不变) + +## 部署状态 + +- ✅ Deploy Panel 已部署测试服 hl-product-service-v2 双实例(2026-04-30 16:56) +- 正式环境暂未发布(等 dev → main release PR)