# 保险方案"全程方案"语义对齐 (totalDays 哨兵)

**日期**: 2026-04-27
**PR**: #1454 (Closes #1425)
**影响端**: 管理端 admin

## 背景

保险方案的「段配置」(InsuranceSchemeSegment) 中 `dayOffsetEnd = -1` 是「行程最后一天/全程」的合法哨兵值。

之前 `computeTotalDays` 算法见到 -1 时会跳过该段、错误返回 maxDay=1，导致「全程方案」入库时 `totalDays=1`。`AdminInsuranceSchemeController.listActiveSchemes` 按 `totalDays.equals(s.getTotalDays())` 精确过滤，结果「全程方案」**只在 1 天行程下拉里能查到**，多天行程查不到，与字段语义不符。

## 改动

### 后端
- 新增哨兵常量 `InsuranceConstants.TOTAL_DAYS_FULL_TRIP = -1`
- `computeTotalDays` 见到任一段 `dayOffsetEnd == -1` → 返回哨兵 -1
- `listActiveSchemes` 过滤逻辑同步: `totalDays==-1 || totalDays==请求tripDays` 视为命中

### 接口字段语义变化

**`GET /admin/insurance/scheme/list?totalDays=N`**（启用方案下拉，按行程天数筛选）
- 之前: 只返回 `totalDays == N` 的方案
- 现在: 返回 `totalDays == N` 或 `totalDays == -1`（全程方案）的方案

**`POST /admin/insurance/scheme` / `PUT /admin/insurance/scheme/{id}`**
- request body 行为不变（前端继续配 `dayOffsetEnd: -1` 表示全程段）
- response body 中的 `totalDays` 字段语义变化:
  - 之前: 全程方案返回 `1`（错误语义）
  - 现在: 全程方案返回 `-1`（哨兵, 表示"任意天数都适用"）
  - 普通方案不受影响

## 前端注意

**列表页/详情页展示**: 如果前端 UI 需要展示 `totalDays`, 见到 `-1` 应当展示为「全程」/「任意天数」, 而不是直接显示 "-1天"。

**新增/编辑表单**: `totalDays` 字段是后端计算字段, 前端不需要表单填写, 直接读 response 即可。

## 数据迁移

- 现存所有方案均为非 -1 段配置, `totalDays` 都是正值, 无需迁移
- 本次上线后用户配置全程段时, 入库 `totalDays=-1` 自动生效

## 单测

- `InsuranceManageServiceTest`: +3 case (单段全程哨兵 / 多段含全程 / 全部正常段保持原算法)
- `AdminInsuranceSchemeControllerTest`: +2 case (5 天行程仅命中全程 / 同时命中全程+精确)
- 全部 56 单测绿
