# 接口变更记录 — 2026-04-12 15:38

> **提交** `273bef51` · **作者** wx · **时间** 2026-04-12 15:38:54 +0800
>
> feat(product-v2): Controller层（admin/mp分包，44个接口+完整Swagger文档）

## 变更总览

- **hl-product-service-v2**
  - 🆕 AdminProduct (+28 接口)
  - 🆕 AdminProductLine (+5 接口)
  - 🆕 AdminProductTemplate (+3 接口)
  - 🆕 MpProduct (+6 接口)
  - 🆕 MpProductLine (+2 接口)

---

## hl-product-service-v2

### 🆕 新增: AdminProduct

### 📌 `POST` /admin/product/item — 创建产品

> 创建产品草稿，自动生成产品编号（C/G/D+日期+序号），返回产品ID。

**请求体** `ProductSaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `name` | `String` | 产品名称（≤30字） |
| `productType` | `String` | 产品类型（字典product_type: CORE=核心产品, GROUP=小蒙马跟团游, CUSTOM=私人定制） |
| `lineId` | `Long` | 产品线ID（从 /admin/product/line/simple-list 下拉获取，私人定制可空） |
| `tripDays` | `Integer` | 行程天数（可选，创建后可在Step1修改） |

**响应** `Result<Long>`

### 📌 `PUT` /admin/product/item/{id}/basic — Step1 基础信息保存

> 保存产品基础信息。需传version做乐观锁校验，版本不一致返回错误提示刷新。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ProductBasicSaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID（路径参数自动设入，前端不传） |
| `version` | `Integer` | 乐观锁版本号（从详情接口获取，提交时传回做并发校验） |
| `name` | `String` | 产品名称（≤30字） |
| `subtitle` | `String` | 副标题（一句话卖点，≤128字） |
| `introduction` | `String` | 产品简介（富文本HTML） |
| `lineId` | `Long` | 产品线ID（从产品线下拉接口获取，私人定制可空） |
| `tripDays` | `Integer` | 行程天数（≥1，晚数自动=天数-1） |
| `tripNights` | `Integer` | 行程晚数（通常=天数-1，不传自动计算） |
| `tiers` | `List<TierItem>` | 档位列表（JSON数组，如标准/舒适/豪华3档） |
| `seasons` | `List<String>` | 适用季节（字典product_season: spring=春, summer=夏, autumn=秋, winter=冬） |
| `tags` | `List<String>` | 产品标签（字典product_tag，多选） |
| `coverImageUrl` | `String` | 封面图URL |
| `carouselImages` | `List<String>` | 轮播图URL列表（≤10张） |
| `infantAgeMax` | `Integer` | 幼童年龄上限（默认1岁，幼童≤此值免费或特价） |
| `toddlerAgeMax` | `Integer` | 小童年龄上限（默认3岁，幼童<小童<儿童） |
| `childAgeMin` | `Integer` | 儿童年龄下限（默认4岁） |
| `childAgeMax` | `Integer` | 儿童年龄上限（默认12岁，超过按成人算） |
| `infantDefaultPrice` | `BigDecimal` | 幼童默认价（元，0=免费） |
| `isBooking` | `Boolean` | 是否预约产品（true=订金可不填出行人，false=正常下单） |
| `paymentType` | `String` | 支付方式（字典payment_type: FULL=全款支付, DEPOSIT=订金+尾款） |
| `depositRatio` | `Integer` | 订金比例（%，支付方式=DEPOSIT时使用，与固定额二选一） |
| `depositAmount` | `BigDecimal` | 订金固定额（元，支付方式=DEPOSIT时使用，与比例二选一） |
| `balanceDueDays` | `Integer` | 尾款支付期限（天，出发前N天需付清尾款） |
| `defaultDailyStock` | `Integer` | 每日库存默认值（NULL=不限量，用于价格日历预填） |
| `defaultRoomCount` | `Integer` | 默认房间数（小蒙马跟团游用，每个班期的初始房间数） |
| `minGroupSize` | `Integer` | 最低成团人数（0=不限，小蒙马用） |
| `minAdultPerOrder` | `Integer` | 每单最少成人数 |
| `maxAdultPerOrder` | `Integer` | 每单最多成人数 |
| `maxChildPerOrder` | `Integer` | 每单最多儿童数 |
| `showReview` | `Boolean` | 展示用户评价（true=产品详情页显示评价模块） |
| `showChatGroup` | `Boolean` | 展示群聊入口（true=显示加入群聊按钮） |
| `showTripTime` | `Boolean` | 展示行程时间（true=行程天显示起止时间） |
| `showTripDistance` | `Boolean` | 展示行程距离（true=行程天显示里程数） |
| `creatorAvatarUrl` | `String` | 创建者头像URL（产品详情页展示的领队/设计师头像） |
| `creatorIntro` | `String` | 创建者简介（≤512字） |
| `customizerId` | `Long` | 定制师ID（私人定制产品关联的定制师） |
| `customerName` | `String` | 客户姓名（私人定制的客户） |
| `contactPhone` | `String` | 联系电话（加密存储） |
| `departureDate` | `LocalDate` | 出发日期（私人定制的固定出发日期，格式yyyy-MM-dd） |
| `mchId` | `String` | 商户ID（微信支付商户号，多商户时指定） |
| `safetyItems` | `List<Object>` | 安全保障项（JSON数组 [{icon,title,description}]） |
| `photographyItems` | `List<Object>` | 摄影跟拍项（JSON数组 [{name,description}]） |
| `diningHighlights` | `List<Object>` | 餐饮亮点（JSON数组 [{name,description}]） |
| `tierSeq` | `Integer` | 档位序号（1=标准,2=舒适,3=豪华） |
| `tierName` | `String` | 档位名称 |

**响应** `Result<Void>`

### 📌 `PUT` /admin/product/item/{id}/itinerary — Step2 行程整体保存

> 整体保存行程天+节点+住宿+路线点。支持新增/更新/删除（EntityDiffUtil对比）。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ProductItinerarySaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `version` | `Integer` | 乐观锁版本号 |
| `days` | `List<DayItem>` | 行程天列表 |
| `dayId` | `Long` | 行程天ID(已有则传，新增不传) |
| `dayNumber` | `Integer` | 天序号 |
| `dayTitle` | `String` | 当日标题 |
| `quoteText` | `String` | 当日金句 |
| `description` | `String` | 行程描述 |
| `coverImageUrl` | `String` | 当日封面图 |
| `breakfast` | `String` | 早餐(HOTEL/CAMP/SPECIAL/SELF) |
| `lunch` | `String` | 午餐 |
| `dinner` | `String` | 晚餐 |
| `diningRemark` | `String` | 餐饮备注 |
| `dailyMileage` | `BigDecimal` | 当日里程(km) |
| `mileageManualOverride` | `Boolean` | 里程是否手动覆盖 |
| `photoUrls` | `List<String>` | 照片集 |
| `customFields` | `Object` | 自定义概况 |
| `nodes` | `List<NodeItem>` | 活动节点列表 |
| `hotels` | `List<HotelItem>` | 住宿列表 |
| `routePoints` | `List<RoutePointItem>` | 途经路线点列表 |
| `nodeId` | `Long` | 节点ID(已有则传，新增不传) |
| `sortOrder` | `Integer` | 排序 |
| `nodeType` | `String` | 节点类型(SCENIC/ACTIVITY/RESTAURANT/SERVICE/CUSTOM等) |
| `nodeName` | `String` | 节点名称 |
| `resourceType` | `String` | 资源类型 |
| `resourceId` | `Long` | 资源ID |
| `startTime` | `String` | 开始时间(如10:00) |
| `durationMinutes` | `Integer` | 时长(分钟) |
| `description` | `String` | 描述 |
| `images` | `List<String>` | 图片列表 |
| `emojiIcon` | `String` | 图标 |
| `extraData` | `Object` | 扩展数据 |
| `id` | `Long` | 住宿ID(已有则传，新增不传) |
| `tierSeq` | `Integer` | 档位序号(无档位=1) |
| `hotelId` | `Long` | 酒店ID |
| `roomTypeId` | `Long` | 房型ID(私人定制用) |
| `roomCount` | `Integer` | 房间数(私人定制用) |
| `isDefault` | `Boolean` | 是否默认推荐 |
| `sortOrder` | `Integer` | 排序 |
| `resourceId` | `Long` | 资源ID(可空=自定义地点) |
| `name` | `String` | 地点名称 |
| `longitude` | `BigDecimal` | 经度 |
| `latitude` | `BigDecimal` | 纬度 |
| `sortOrder` | `Integer` | 排序 |

**响应** `Result<Void>`

### 📌 `POST` /admin/product/item/{id}/itinerary/day — Step2 增加一天

> 在行程末尾追加一天，自动编号。返回新天的dayId。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<Long>`

### 📌 `DELETE` /admin/product/item/{id}/itinerary/day/{dayNumber} — Step2 删除一天

> 删除指定天序号的行程天，级联删除节点+住宿+路线点，后续天序号自动-1。

**参数**

  - `{id}` (`Long`) — 路径参数
  - `{dayNumber}` (`Integer`) — 路径参数

**响应** `Result<Void>`

### 📌 `PUT` /admin/product/item/{id}/route — Step3 路线与备品保存

> 保存路线总览(1:1)+备品清单(1:N)+额外成本项(1:N)。备品和成本项支持增删改。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ProductRouteSaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `version` | `Integer` | 乐观锁版本号 |
| `routeName` | `String` | 路线名称 |
| `routeMapUrl` | `String` | 路线图URL(手动上传) |
| `totalMileage` | `BigDecimal` | 总里程(km) |
| `routeDescription` | `String` | 线路描述 |
| `vehicleModelId` | `Long` | 实际用车车型ID |
| `staffCostIds` | `List<Long>` | 服务人员成本ID列表 |
| `supplies` | `List<SuppliesItem>` | 备品列表 |
| `extraCosts` | `List<FeeItem>` | 额外成本项列表 |
| `id` | `Long` | 备品ID(已有则传，新增不传) |
| `suppliesResourceId` | `Long` | 备品资源ID(可空=自定义) |
| `suppliesName` | `String` | 备品名称 |
| `category` | `String` | 分类 |
| `hasCost` | `Boolean` | 是否涉及成本 |
| `billingType` | `String` | 计费方式(PER_PERSON/PER_QUANTITY) |
| `unitPrice` | `BigDecimal` | 单价 |
| `quantity` | `Integer` | 数量 |
| `sortOrder` | `Integer` | 排序 |
| `id` | `Long` | 费用项ID(已有则传，新增不传) |
| `resourceType` | `String` | 资源类型 |
| `resourceId` | `Long` | 资源ID |
| `costItemId` | `Long` | 费用项ID |
| `name` | `String` | 名称 |
| `description` | `String` | 说明 |
| `unitPrice` | `BigDecimal` | 单价 |
| `quantity` | `Integer` | 数量 |
| `daily` | `Boolean` | 是否按天计费 |
| `sortOrder` | `Integer` | 排序 |

**响应** `Result<Void>`

### 📌 `GET` /admin/product/item/{id}/price-calendar — Step4 查询价格日历

> 按月份查询价格日历。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<List<ProductPriceCalendarDO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `Long` |  |
| `productId` | `Long` |  |
| `tierSeq` | `Integer` |  |
| `rangeId` | `Long` |  |
| `date` | `LocalDate` |  |
| `priceType` | `String` |  |
| `adultSellPrice` | `BigDecimal` |  |
| `childSellPrice` | `BigDecimal` |  |
| `toddlerDiscount` | `BigDecimal` |  |
| `infantPrice` | `BigDecimal` |  |
| `dailyStock` | `Integer` |  |
| `sold` | `Integer` |  |

### 📌 `GET` /admin/product/item/{id}/schedule/list — Step4 班期列表

> 查询产品下所有班期。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<List<GroupTourBatchDO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `batchId` | `Long` |  |
| `productId` | `Long` |  |
| `batchNo` | `String` |  |
| `batchLabel` | `String` |  |
| `batchName` | `String` |  |
| `productStockLimit` | `Integer` |  |
| `adultPrice` | `BigDecimal` |  |
| `childPrice` | `BigDecimal` |  |
| `toddlerDiscount` | `BigDecimal` |  |
| `infantPrice` | `BigDecimal` |  |
| `departureDate` | `LocalDate` |  |
| `endDate` | `LocalDate` |  |
| `enrollmentDeadline` | `LocalDate` |  |
| `maxParticipants` | `Integer` |  |
| `enrolledCount` | `Integer` |  |
| `maxRooms` | `Integer` |  |
| `bookedRooms` | `Integer` |  |
| `batchStatus` | `String` |  |
| `sortOrder` | `Integer` |  |
| `remark` | `String` |  |
| `version` | `Integer` |  |
| `createBy` | `Long` |  |

### 📌 `POST` /admin/product/item/{id}/price-calendar/batch — Step4 价格日历批量设置

> 按日期范围+档位批量设置价格（UPSERT）。已上架产品用priceVersion，草稿用version。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `PriceCalendarBatchReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `version` | `Integer` | 版本号(草稿用version/已上架用priceVersion) |
| `startDate` | `LocalDate` | 开始日期 |
| `endDate` | `LocalDate` | 结束日期 |
| `tierSeq` | `Integer` | 档位序号(默认1) |
| `priceType` | `String` | 价格类型(NORMAL/PEAK/HOLIDAY/SPECIAL) |
| `adultSellPrice` | `BigDecimal` | 成人售价 |
| `childSellPrice` | `BigDecimal` | 儿童售价 |
| `toddlerDiscount` | `BigDecimal` | 小童优惠额(负数) |
| `infantPrice` | `BigDecimal` | 幼童售价(0=免费) |
| `dailyStock` | `Integer` | 每日库存上限(NULL=不限量) |

**响应** `Result<Void>`

### 📌 `DELETE` /admin/product/item/{id}/price-calendar — Step4 价格日历删除

> 按日期范围+档位删除价格日历（物理删除）。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `PriceCalendarBatchReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `version` | `Integer` | 版本号(草稿用version/已上架用priceVersion) |
| `startDate` | `LocalDate` | 开始日期 |
| `endDate` | `LocalDate` | 结束日期 |
| `tierSeq` | `Integer` | 档位序号(默认1) |
| `priceType` | `String` | 价格类型(NORMAL/PEAK/HOLIDAY/SPECIAL) |
| `adultSellPrice` | `BigDecimal` | 成人售价 |
| `childSellPrice` | `BigDecimal` | 儿童售价 |
| `toddlerDiscount` | `BigDecimal` | 小童优惠额(负数) |
| `infantPrice` | `BigDecimal` | 幼童售价(0=免费) |
| `dailyStock` | `Integer` | 每日库存上限(NULL=不限量) |

**响应** `Result<Void>`

### 📌 `POST` /admin/product/item/{id}/schedule — Step4 创建班期

> 创建新班期，batchId不传。自动生成班期编号、计算结束日期和报名截止日。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ScheduleSaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `batchId` | `Long` | 班期ID(修改时传) |
| `batchName` | `String` | 班期名称 |
| `departureDate` | `LocalDate` | 出发日期 |
| `enrollmentDeadline` | `LocalDate` | 报名截止日(默认出发前1天) |
| `adultPrice` | `BigDecimal` | 成人价 |
| `childPrice` | `BigDecimal` | 儿童价 |
| `toddlerDiscount` | `BigDecimal` | 小童优惠额(负数) |
| `infantPrice` | `BigDecimal` | 幼童价 |
| `maxParticipants` | `Integer` | 最大参与人数 |
| `maxRooms` | `Integer` | 总房间数(0=不限) |
| `productStockLimit` | `Integer` | 产品库存上限 |
| `remark` | `String` | 备注 |

**响应** `Result<Long>`

### 📌 `DELETE` /admin/product/item/{id}/schedule/{scheduleId} — Step4 删除班期

> 删除班期（已有报名订单不允许删除）。

**参数**

  - `{id}` (`Long`) — 路径参数
  - `{scheduleId}` (`Long`) — 路径参数

**响应** `Result<Void>`

### 📌 `PUT` /admin/product/item/{id}/schedule — Step4 修改班期

> 修改已有班期，batchId在body中传。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ScheduleSaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `batchId` | `Long` | 班期ID(修改时传) |
| `batchName` | `String` | 班期名称 |
| `departureDate` | `LocalDate` | 出发日期 |
| `enrollmentDeadline` | `LocalDate` | 报名截止日(默认出发前1天) |
| `adultPrice` | `BigDecimal` | 成人价 |
| `childPrice` | `BigDecimal` | 儿童价 |
| `toddlerDiscount` | `BigDecimal` | 小童优惠额(负数) |
| `infantPrice` | `BigDecimal` | 幼童价 |
| `maxParticipants` | `Integer` | 最大参与人数 |
| `maxRooms` | `Integer` | 总房间数(0=不限) |
| `productStockLimit` | `Integer` | 产品库存上限 |
| `remark` | `String` | 备注 |

**响应** `Result<Long>`

### 📌 `POST` /admin/product/item/{id}/schedule/batch-create — Step4 班期批量创建

> 按重复模式批量创建班期（最多52个，跨度≤12个月）。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ScheduleBatchCreateReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `startDate` | `LocalDate` | 开始日期 |
| `endDate` | `LocalDate` | 结束日期 |
| `repeatMode` | `String` | 重复模式(WEEKLY/BIWEEKLY/MONTHLY) |
| `dayOfWeek` | `Integer` | 星期几(1=周一...7=周日) |
| `adultPrice` | `BigDecimal` | 成人价 |
| `childPrice` | `BigDecimal` | 儿童价 |
| `maxParticipants` | `Integer` | 最大参与人数 |
| `maxRooms` | `Integer` | 总房间数 |

**响应** `Result<Integer>`

### 📌 `POST` /admin/product/item/{id}/schedule/{scheduleId}/cancel — Step4 取消班期

> 取消班期（有报名订单则标记为CANCELLING触发退款，无订单直接CANCELLED）。

**参数**

  - `{id}` (`Long`) — 路径参数
  - `{scheduleId}` (`Long`) — 路径参数

**响应** `Result<Void>`

### 📌 `GET` /admin/product/item/{id}/schedule/team — 班期团队查询

> 查询指定班期的团队成员列表。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<List<GroupBatchStaffDO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `Long` |  |
| `batchId` | `Long` |  |
| `productId` | `Long` |  |
| `staffId` | `Long` |  |
| `staffRole` | `String` |  |
| `staffName` | `String` |  |
| `staffPhone` | `String` |  |
| `remark` | `String` |  |
| `sortOrder` | `Integer` |  |

### 📌 `PUT` /admin/product/item/{id}/schedule/team — 班期团队保存

> 全量保存班期团队成员（全删全插策略，每次传完整列表）。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ScheduleTeamSaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `staffList` | `List<StaffItem>` | 团队成员列表 |
| `staffId` | `Long` | 员工ID |
| `staffRole` | `String` | 员工角色 |
| `staffName` | `String` | 员工名称 |
| `staffPhone` | `String` | 员工电话 |
| `remark` | `String` | 备注 |
| `sortOrder` | `Integer` | 排序 |

**响应** `Result<Void>`

### 📌 `POST` /admin/product/item/{id}/quote — 报价计算

> 根据出发日期+人数+档位计算总价，返回每日明细。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `QuoteReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `departureDate` | `LocalDate` | 出发日期 |
| `adultCount` | `Integer` | 成人数 |
| `batchId` | `Long` | 班期ID(小蒙马用) |

**响应** `Result<QuoteResultVO>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `totalAdultPrice` | `BigDecimal` | 成人总价 |
| `totalChildPrice` | `BigDecimal` | 儿童总价 |
| `grandTotal` | `BigDecimal` | 总价合计 |
| `dailyDetails` | `List<DailyDetail>` | 每日价格明细 |
| `date` | `String` | 日期 |
| `adultPrice` | `BigDecimal` | 成人价 |
| `childPrice` | `BigDecimal` | 儿童价 |

### 📌 `PUT` /admin/product/item/{id}/supplement — Step5 补充信息保存

> 保存费用包含/不含/自定义项+人群优惠+补充配置（退改/车辆/保险/卖点等）。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ProductSupplementSaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `version` | `Integer` | 乐观锁版本号 |
| `includedFees` | `List<FeeItem>` | 费用包含列表 |
| `excludedFees` | `List<FeeItem>` | 费用不含列表 |
| `customFees` | `List<FeeItem>` | 自定义费用列表 |
| `feeIgnoredTypes` | `List<String>` | 费用推导已忽略类型 |
| `childTicket` | `String` | 儿童门票优惠(FREE/HALF_PRICE/FULL_PRICE) |
| `childAccommodation` | `String` | 儿童住宿(BED/NO_BED) |
| `childMeal` | `String` | 儿童餐饮(FULL/HALF) |
| `elderTicket` | `String` | 老人门票优惠 |
| `elderAccommodation` | `String` | 老人住宿 |
| `crowdBenefitTips` | `String` | 人群优惠温馨提示 |
| `vehicleModelIds` | `List<Long>` | 展示车型ID列表 |
| `vehicleRuleText` | `String` | 用车规则说明 |
| `refundPolicyIds` | `Object` | 退改政策映射 |
| `bookingTermsId` | `Long` | 预订条款ID |
| `warmTipsId` | `Long` | 温馨提示ID |
| `equipmentAdvice` | `String` | 装备建议(富文本) |
| `insuranceNotice` | `String` | 保险告知(INCLUDED/EXCLUDED/OPTIONAL) |
| `insuranceSchemeId` | `Long` | 保险方案ID |
| `contractSchemeId` | `Long` | 合同方案ID |
| `quickUnderstand` | `Object` | 快速理解 |
| `childExperience` | `Object` | 孩子经历 |
| `growthGains` | `List<Object>` | 成长收获 |
| `id` | `Long` | 费用项ID(已有则传) |
| `feeType` | `String` | 费用类型 |
| `source` | `String` | 来源(AUTO/MANUAL) |
| `name` | `String` | 名称 |
| `description` | `String` | 说明 |
| `unitPrice` | `BigDecimal` | 单价(自定义费用) |
| `unit` | `String` | 单位(自定义费用) |
| `sortOrder` | `Integer` | 排序 |

**响应** `Result<Void>`

### 📌 `GET` /admin/product/item/{id} — 产品详情

> 聚合全部5步数据（主表+基础+行程+路线+价格+补充），用于编辑页回显。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<ProductDetailRespVO>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `productNo` | `String` | 产品编号 |
| `productType` | `String` | 产品类型 |
| `status` | `String` | 产品状态 |
| `version` | `Integer` | 乐观锁版本号 |
| `priceVersion` | `Integer` | 调价版本号 |
| `createBy` | `Long` | 创建人ID |
| `publishedAt` | `LocalDateTime` | 上架时间 |
| `createTime` | `LocalDateTime` | 创建时间 |
| `name` | `String` | 产品名称 |
| `subtitle` | `String` | 副标题 |
| `introduction` | `String` | 产品简介 |
| `lineId` | `Long` | 产品线ID |
| `tripDays` | `Integer` | 行程天数 |
| `tripNights` | `Integer` | 行程晚数 |
| `coverImageUrl` | `String` | 封面图 |
| `tiers` | `Object` | 档位列表 |
| `seasons` | `Object` | 产品季节 |
| `tags` | `Object` | 产品标签 |
| `carouselImages` | `Object` | 轮播图 |
| `infantAgeMax` | `Integer` | 幼童年龄上限 |
| `toddlerAgeMax` | `Integer` | 小童年龄上限 |
| `childAgeMin` | `Integer` | 儿童年龄下限 |
| `childAgeMax` | `Integer` | 儿童年龄上限 |
| `isBooking` | `Boolean` | 是否预约产品 |
| `paymentType` | `String` | 支付方式 |
| `depositRatio` | `Integer` | 订金比例 |
| `depositAmount` | `BigDecimal` | 订金固定额 |
| `showReview` | `Boolean` | 展示评价 |
| `creatorAvatarUrl` | `String` | 创建者头像 |
| `creatorIntro` | `String` | 创建者简介 |
| `customizerId` | `Long` | 定制师ID |
| `customerName` | `String` | 客户姓名 |
| `departureDate` | `LocalDate` | 出发日期(定制) |
| `mchId` | `String` | 商户ID |
| `itinerary` | `List<Object>` | 行程天列表 |
| `routeInfo` | `ProductRouteInfoDO` | 路线信息 |
| `supplies` | `List<ProductSuppliesDO>` | 备品列表 |
| `extraCosts` | `List<ProductFeeItemDO>` | 额外成本项 |
| `schedules` | `List<GroupTourBatchDO>` | 班期列表 |
| `includedFees` | `List<ProductFeeItemDO>` | 费用包含 |
| `excludedFees` | `List<ProductFeeItemDO>` | 费用不含 |
| `customFees` | `List<ProductFeeItemDO>` | 自定义费用 |
| `crowdBenefit` | `ProductCrowdBenefitDO` | 人群优惠 |
| `supplement` | `ProductSupplementDO` | 补充配置 |

### 📌 `GET` /admin/product/item/list — 产品分页列表

> 支持按名称关键词/产品类型/产品线/状态筛选。

**响应** `Result<PageResult<ProductListRespVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `productNo` | `String` | 产品编号 |
| `productType` | `String` | 产品类型 |
| `name` | `String` | 产品名称 |
| `coverImageUrl` | `String` | 封面图 |
| `tripDays` | `Integer` | 行程天数 |
| `status` | `String` | 产品状态 |
| `version` | `Integer` | 版本号 |
| `createBy` | `Long` | 创建人ID |
| `publishedAt` | `LocalDateTime` | 上架时间 |
| `createTime` | `LocalDateTime` | 创建时间 |
| `updateTime` | `LocalDateTime` | 更新时间 |

### 📌 `DELETE` /admin/product/item/{id} — 删除产品

> 级联删除产品+全部子表。仅草稿/已驳回/已下架/已完成状态可删，已上架/待审核需先下架。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<Void>`

### 📌 `GET` /admin/product/item/{id}/operation-logs — 产品操作记录

> 查询产品的全部操作记录（按时间倒序），审计保留不随产品删除。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<List<ProductOperationLogRespVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `Long` | 记录ID |
| `action` | `String` | 操作类型 |
| `detail` | `String` | 详情/驳回原因 |
| `step` | `String` | 步骤号 |
| `operatorId` | `Long` | 操作人ID |
| `operatorName` | `String` | 操作人名称 |
| `createTime` | `LocalDateTime` | 操作时间 |

### 📌 `POST` /admin/product/item/{id}/action — 产品状态变更

> 通过COLA状态机驱动状态流转。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ProductActionReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `action` | `String` | 操作动作(SUBMIT_PUBLISH/WITHDRAW/DIRECT_PUBLISH/UNPUBLISH/FORCE_UNPUBLISH/COMPLETE) |
| `remark` | `String` | 备注/驳回原因(可选) |

**响应** `Result<Void>`

### 📌 `POST` /admin/product/item/{id}/copy — 复制产品

> 深拷贝产品+全部子表（主表+6张1:1/1:N子表+行程数据），新产品为草稿状态。返回新产品ID。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<Long>`

### 📌 `PUT` /admin/product/item/{id}/transfer — 转让产品

> 更换产品创建人（仅超管可操作）。

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `ProductTransferReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `newCreatorId` | `Long` | 新创建人ID |

**响应** `Result<Void>`

### 📌 `GET` /admin/product/item/{id}/validate-publish — 上架预检

> 5级校验：L1基础字段→L2内容完整→L3定价→L4条款→L5资源。返回问题列表，空=全部通过。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<List<String>>`

### 📌 `GET` /admin/product/item/{id}/fee-deduction-preview — 费用推导预览

> 根据行程节点类型自动推导费用包含项（景区→门票，酒店→住宿等），排除已忽略类型。

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<List<FeeDeductionPreviewVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `feeType` | `String` | 费用类型标签 |
| `name` | `String` | 费用名称 |
| `source` | `String` | 来源（AUTO=自动推导） |
| `sourceNodeName` | `String` | 来源节点名称 |

### 🆕 新增: AdminProductLine

### 📌 `POST` /admin/product/line — 创建/修改产品线

**请求体** `ProductLineSaveReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `lineId` | `Long` | 产品线ID(修改时传) |
| `name` | `String` | 名称 |
| `productType` | `String` | 绑定产品类型 |
| `description` | `String` | 描述 |
| `seasons` | `List<String>` | 适用季节 |
| `tags` | `List<String>` | 标签 |
| `coverImageUrl` | `String` | 封面图 |
| `sortOrder` | `Integer` | 排序 |
| `status` | `String` | 状态(ACTIVE/INACTIVE) |

**响应** `Result<Long>`

### 📌 `GET` /admin/product/line/{id} — 产品线详情

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<ProductLineRespVO>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `lineId` | `Long` | 产品线ID |
| `name` | `String` | 产品线名称 |
| `productType` | `String` | 绑定产品类型 |
| `description` | `String` | 描述 |
| `seasons` | `Object` | 适用季节 |
| `tags` | `Object` | 产品线标签 |
| `coverImageUrl` | `String` | 封面图 |
| `sortOrder` | `Integer` | 排序 |
| `status` | `String` | 状态 |
| `createBy` | `Long` | 创建人ID |
| `createTime` | `LocalDateTime` | 创建时间 |

### 📌 `GET` /admin/product/line/list — 产品线分页列表

**响应** `Result<PageResult<ProductLineRespVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `lineId` | `Long` | 产品线ID |
| `name` | `String` | 产品线名称 |
| `productType` | `String` | 绑定产品类型 |
| `description` | `String` | 描述 |
| `seasons` | `Object` | 适用季节 |
| `tags` | `Object` | 产品线标签 |
| `coverImageUrl` | `String` | 封面图 |
| `sortOrder` | `Integer` | 排序 |
| `status` | `String` | 状态 |
| `createBy` | `Long` | 创建人ID |
| `createTime` | `LocalDateTime` | 创建时间 |

### 📌 `DELETE` /admin/product/line/{id} — 删除产品线

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<Void>`

### 📌 `GET` /admin/product/line/simple-list — 产品线下拉选项

**响应** `Result<List<ProductLineDropdownVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `lineId` | `Long` | 产品线ID |
| `name` | `String` | 产品线名称 |

### 🆕 新增: AdminProductTemplate

### 📌 `GET` /admin/product/booking-terms/enabled — 预订条款下拉

**响应** `Result<List<BookingTermsVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `termsId` | `Long` | 条款ID |
| `termsName` | `String` | 条款名称 |

### 📌 `GET` /admin/product/warm-tips/enabled — 温馨提示下拉

**响应** `Result<List<WarmTipsVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `tipsId` | `Long` | 提示ID |
| `tipsName` | `String` | 提示名称 |

### 📌 `GET` /admin/product/safety-template/enabled — 安全保障模板下拉

**响应** `Result<List<SafetyTemplateVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `templateId` | `Long` | 模板ID |
| `name` | `String` | 模板名称 |
| `items` | `String` | 具体条目（JSON） |

### 🆕 新增: MpProduct

### 📌 `GET` /mp/product/{id} — 产品详情

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<MpProductDetailRespVO>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `productType` | `String` | 产品类型 |
| `name` | `String` | 产品名称 |
| `subtitle` | `String` | 副标题 |
| `introduction` | `String` | 产品简介 |
| `coverImageUrl` | `String` | 封面图 |
| `carouselImages` | `List<String>` | 轮播图 |
| `tripDays` | `Integer` | 行程天数 |
| `tripNights` | `Integer` | 行程晚数 |
| `itinerary` | `List<DayInfo>` | 行程天列表 |
| `includedFees` | `List<FeeInfo>` | 费用包含 |
| `excludedFees` | `List<FeeInfo>` | 费用不含 |
| `crowdBenefit` | `CrowdBenefitInfo` | 人群优惠 |
| `routeMapUrl` | `String` | 路线图URL |
| `creatorAvatarUrl` | `String` | 创建者头像 |
| `creatorIntro` | `String` | 创建者简介 |
| `dayNumber` | `Integer` | 天序号 |
| `dayTitle` | `String` | 标题 |
| `description` | `String` | 描述 |
| `coverImageUrl` | `String` | 封面图 |
| `breakfast` | `String` | 早餐 |
| `lunch` | `String` | 午餐 |
| `dinner` | `String` | 晚餐 |
| `nodes` | `List<NodeInfo>` | 节点列表 |
| `nodeType` | `String` | 节点类型 |
| `nodeName` | `String` | 节点名称 |
| `startTime` | `String` | 开始时间 |
| `durationMinutes` | `Integer` | 时长(分钟) |
| `description` | `String` | 描述 |
| `childTicket` | `String` | 儿童门票 |
| `childAccommodation` | `String` | 儿童住宿 |
| `childMeal` | `String` | 儿童餐饮 |
| `elderTicket` | `String` | 老人门票 |
| `tips` | `String` | 温馨提示 |
| `name` | `String` | 名称 |
| `description` | `String` | 说明 |

### 📌 `GET` /mp/product/{id}/price-calendar — 价格日历

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<MpPriceCalendarRespVO>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `days` | `List<CalendarDay>` | 日历列表 |
| `date` | `String` | 日期(yyyy-MM-dd) |
| `priceType` | `String` | 价格类型 |
| `adultPrice` | `BigDecimal` | 成人售价 |
| `childPrice` | `BigDecimal` | 儿童售价 |
| `isSelectable` | `Boolean` | 是否可选(有库存且未过期) |
| `remainStock` | `Integer` | 剩余库存(NULL=不限量) |

### 📌 `GET` /mp/product/{id}/schedules — 班期列表

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<List<GroupTourBatchDO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `batchId` | `Long` |  |
| `productId` | `Long` |  |
| `batchNo` | `String` |  |
| `batchLabel` | `String` |  |
| `batchName` | `String` |  |
| `productStockLimit` | `Integer` |  |
| `adultPrice` | `BigDecimal` |  |
| `childPrice` | `BigDecimal` |  |
| `toddlerDiscount` | `BigDecimal` |  |
| `infantPrice` | `BigDecimal` |  |
| `departureDate` | `LocalDate` |  |
| `endDate` | `LocalDate` |  |
| `enrollmentDeadline` | `LocalDate` |  |
| `maxParticipants` | `Integer` |  |
| `enrolledCount` | `Integer` |  |
| `maxRooms` | `Integer` |  |
| `bookedRooms` | `Integer` |  |
| `batchStatus` | `String` |  |
| `sortOrder` | `Integer` |  |
| `remark` | `String` |  |
| `version` | `Integer` |  |
| `createBy` | `Long` |  |

### 📌 `POST` /mp/product/{id}/quote — C端报价

**参数**

  - `{id}` (`Long`) — 路径参数

**请求体** `QuoteReqVO`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `departureDate` | `LocalDate` | 出发日期 |
| `adultCount` | `Integer` | 成人数 |
| `batchId` | `Long` | 班期ID(小蒙马用) |

**响应** `Result<QuoteResultVO>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `totalAdultPrice` | `BigDecimal` | 成人总价 |
| `totalChildPrice` | `BigDecimal` | 儿童总价 |
| `grandTotal` | `BigDecimal` | 总价合计 |
| `dailyDetails` | `List<DailyDetail>` | 每日价格明细 |
| `date` | `String` | 日期 |
| `adultPrice` | `BigDecimal` | 成人价 |
| `childPrice` | `BigDecimal` | 儿童价 |

### 📌 `GET` /mp/product/compare — 产品对比

**参数**

  - `ids` (`String`) — 查询参数

**响应** `Result<List<MpProductDetailRespVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `productType` | `String` | 产品类型 |
| `name` | `String` | 产品名称 |
| `subtitle` | `String` | 副标题 |
| `introduction` | `String` | 产品简介 |
| `coverImageUrl` | `String` | 封面图 |
| `carouselImages` | `List<String>` | 轮播图 |
| `tripDays` | `Integer` | 行程天数 |
| `tripNights` | `Integer` | 行程晚数 |
| `itinerary` | `List<DayInfo>` | 行程天列表 |
| `includedFees` | `List<FeeInfo>` | 费用包含 |
| `excludedFees` | `List<FeeInfo>` | 费用不含 |
| `crowdBenefit` | `CrowdBenefitInfo` | 人群优惠 |
| `routeMapUrl` | `String` | 路线图URL |
| `creatorAvatarUrl` | `String` | 创建者头像 |
| `creatorIntro` | `String` | 创建者简介 |
| `dayNumber` | `Integer` | 天序号 |
| `dayTitle` | `String` | 标题 |
| `description` | `String` | 描述 |
| `coverImageUrl` | `String` | 封面图 |
| `breakfast` | `String` | 早餐 |
| `lunch` | `String` | 午餐 |
| `dinner` | `String` | 晚餐 |
| `nodes` | `List<NodeInfo>` | 节点列表 |
| `nodeType` | `String` | 节点类型 |
| `nodeName` | `String` | 节点名称 |
| `startTime` | `String` | 开始时间 |
| `durationMinutes` | `Integer` | 时长(分钟) |
| `description` | `String` | 描述 |
| `childTicket` | `String` | 儿童门票 |
| `childAccommodation` | `String` | 儿童住宿 |
| `childMeal` | `String` | 儿童餐饮 |
| `elderTicket` | `String` | 老人门票 |
| `tips` | `String` | 温馨提示 |
| `name` | `String` | 名称 |
| `description` | `String` | 说明 |

### 📌 `GET` /mp/product/{id}/tier-compare — 档位对比

**参数**

  - `{id}` (`Long`) — 路径参数

**响应** `Result<Map<Integer, List<ProductDayHotelDO>>>`

### 🆕 新增: MpProductLine

### 📌 `GET` /mp/product-line/list — 产品线列表

**参数**

  - `season` (`String`) — 查询参数

**响应** `Result<List<ProductLineDropdownVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `lineId` | `Long` | 产品线ID |
| `name` | `String` | 产品线名称 |

### 📌 `GET` /mp/product-line/{lineId}/products — 产品线下产品列表

**参数**

  - `{lineId}` (`Long`) — 路径参数

**响应** `Result<List<MpProductListRespVO>>`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `productId` | `Long` | 产品ID |
| `name` | `String` | 产品名称 |
| `subtitle` | `String` | 副标题 |
| `coverImageUrl` | `String` | 封面图 |
| `tripDays` | `Integer` | 行程天数 |
| `productType` | `String` | 产品类型 |

---

<details>
<summary>📁 全部变更文件 (点击展开)</summary>

```
A	hl-product-service-v2/src/main/java/com/hulalv/product/controller/admin/AdminProductController.java
A	hl-product-service-v2/src/main/java/com/hulalv/product/controller/admin/AdminProductLineController.java
A	hl-product-service-v2/src/main/java/com/hulalv/product/controller/admin/AdminProductTemplateController.java
A	hl-product-service-v2/src/main/java/com/hulalv/product/controller/mp/MpProductController.java
A	hl-product-service-v2/src/main/java/com/hulalv/product/controller/mp/MpProductLineController.java
A	hl-product-service-v2/src/main/java/com/hulalv/product/interceptor/AdminAuthInterceptor.java
```
</details>