# 修复: 上架/完成设计校验跳过最后一天酒店(返程日豁免)

**类型**: 后端 BUG 修复
**关联**: 工单 #1586 / PR #1587
**日期**: 2026-04-30
**前端处理者**: mmg
**影响范围**: 管理后台「完成设计」「提交审核 / 直接上架」按钮的预检逻辑

---

## 现象

用户反馈:产品上架/完成设计预检时,如果**最后一天**没在 `product_day_hotel` 配酒店,就被「Day N 住宿安排为空」拦截无法上架。但**最后一天通常是返程日不需要住宿**,这个校验是错的。

用户原文:「最后一天行程提示也得有住宿安排,实际不需要」

## 根因

`hl-product-service-v2` `ProductValidationService.validateItineraryCompleteness()` 对每一天循环硬要求 `product_day_hotel` 非空,**没有跳过最后一天**。

## 修复

判定最后一天豁免酒店校验:

```java
int totalDays = days.size();
for (int i = 0; i < totalDays; i++) {
    ProductItineraryDayDO day = days.get(i);
    // ... 描述/集合地/解散地/活动校验保持不变 ...

    // 最后一天通常是返程,不强制要求配酒店(2026-04-30 用户反馈)
    boolean isLastDay = (day.getDayNumber() != null)
            ? day.getDayNumber() == totalDays
            : i == totalDays - 1;
    if (!isLastDay) {
        if (dayHotels.isEmpty()) issues.add(dayLabel + " 住宿安排为空");
    }
}
```

判定规则:
- 优先 `day.getDayNumber() == totalDays`
- `dayNumber` 为 null 时回退索引 `i == totalDays - 1`

## 行为变化

| tripDays | 最后一天 | 行为 |
|---|---|---|
| 1 | i=0 是唯一一天即最后一天 | 单天产品没酒店通过 |
| 2 | Day1 校验 / Day2 豁免 | OK |
| 3 | Day1/Day2 校验 / Day3 豁免 | OK |
| dayNumber=null | 列表最后一项兜底 | 跳过 |

## 影响接口

| 方法 | 路径 | 改动 |
|------|------|------|
| POST | `/admin/product/item/{id}/complete-design` | 最后一天没酒店不再返 400 |
| POST | `/admin/product/item/{id}/toggle-publish` (SUBMIT_PUBLISH/DIRECT_PUBLISH) | 同上 |
| PUT  | `/admin/product/item/{id}/supplement` | 不变(草稿允许任意) |

请求/响应签名不变,只是错误条件减少。

## 前端要做的改动

**0 改动**。如果前端之前对「Day N 住宿安排为空」错误做过 UX 引导(比如跳到行程编排页面),保留即可,不会被误触发。

## 测试覆盖

新增 4 单测:
- `validateItinerary_lastDayNoHotel_shouldPass` — 3 天 day3 没酒店,通过
- `validateItinerary_middleDayNoHotel_shouldFail` — 3 天 day2 没酒店,报错;day3 不计入
- `validateItinerary_singleDay_noHotel_shouldPass` — 1 天产品没酒店通过
- `validateItinerary_dayNumberNull_useListIndexFallback` — dayNumber=null 兜底

`mvn test`: ProductValidationServiceTest **53/53 全绿**。

## 部署状态

- ✅ 测试服 hl-product-service-v2 双实例部署完成(2026-04-30 23:40)
- 正式环境暂未发布(等 dev → main release PR)
