实现 Rooming List CP2 字段收口

This commit is contained in:
andy
2026-07-22 15:29:33 +07:00
parent 71191a10b0
commit 1236df86cf
24 changed files with 704 additions and 481 deletions

View File

@@ -2,9 +2,9 @@
| 项目 | 内容 |
| --- | --- |
| 文档状态 | 后端 CP1 已实现,第一版直接下载 `.xlsx`,不落库、不上传 OSS |
| 文档状态 | 后端 CP1 已实现CP2 已实现旅游日期派生、Adults 系统计算和目标默认值字段收口 |
| 适用范围 | 从旅行团名单 Excel 解析护照姓名,并生成酒店 / PMS 可导入的 Rooming List Excel |
| 当前目标 | 第一阶段已支持前端上传来源 Excel、填写分房和目标列默认值后端同步生成目标格式 `.xlsx` 供浏览器下载 |
| 当前目标 | CP1 已支持前端上传来源 Excel、填写分房和目标列默认值后端同步生成目标格式 `.xlsx`CP2 收口为后端从来源 `旅游日期` 派生入住 / 离店日期,成人数由系统按分房结果计算,目标默认值区域只保留付款方式和国籍 |
| 依赖能力 | 登录权限、酒店上下文、Apache POI 或等价 Excel 读写能力 |
## 1. 背景
@@ -17,7 +17,7 @@ M002 V4 `ROOMING_LIST` 任务卡与本工具是两个独立能力V4 卡第一
## 2. 第一阶段定位
第一阶段应做到:
CP1 已做到:
- 前端上传 `.xls` / `.xlsx` 来源名单。
- 后端解析来源名单中的护照姓名字段。
@@ -26,6 +26,16 @@ M002 V4 `ROOMING_LIST` 任务卡与本工具是两个独立能力V4 卡第一
- 后端按来源名单顺序分组,生成目标格式 Excel。
- 浏览器直接下载生成后的 `.xlsx` 文件。
CP2 目标调整:
- 前端继续上传 `.xls` / `.xlsx` 来源名单。
- 前端继续输入每间房人数和目标 `Room Type`
- 后端从来源 Excel 的 `旅游日期` 列解析入住日期和离店日期,不再要求前端手填 `arrival` / `departure`
- 后端按分房结果计算目标 `Adults`,不再要求前端手填 `adults`
- 目标默认值区域只保留 `Payment Type``Nationality` 两个字段。
- `Payment Type` 前端默认为 `CA`,后端也按 `CA` 作为当前唯一允许值校验。
- `Nationality` 前端只允许选择 `KR``CHN`,后端按同一枚举校验。
第一阶段不做:
- 不落业务订单。
@@ -56,8 +66,9 @@ M002 V4 `ROOMING_LIST` 任务卡与本工具是两个独立能力V4 卡第一
| 来源字段 | 中文说明 | 第一版用途 |
| --- | --- | --- |
| `护照全名` | 护照姓名,可能包含 `/` 或空格 | 解析为目标 Excel 的 `Name``First Name` |
| `旅游日期` | 旅游团起止日期,例如 `2026年5月9日5月14日` | CP2 用于派生目标 Excel 的 `Arrival``Departure` |
第一版只使用 `护照全名` 生成目标姓名字段。来源文件中的证件号、生日、性别、年龄等字段暂不自动写入目标 Excel除非后续单独确认。
CP1 只使用 `护照全名` 生成目标姓名字段。CP2 额外使用 `旅游日期` 派生目标 `Arrival` / `Departure`来源文件中的证件号、生日、性别、年龄等字段暂不自动写入目标 Excel除非后续单独确认。
### 4.1 姓名拆分
@@ -83,6 +94,33 @@ LIU/JIAYI
- 当前目标模板中 `Name` 更接近姓氏字段,`First Name` 更接近名字段。
- 第一版不尝试判断中文、韩文、英文姓名顺序,也不做大小写改写。
### 4.2 旅游日期解析
CP2 起后端应识别来源 Excel 的 `旅游日期` 表头,并从有名单数据的行中读取日期区间。
已确认样例:
```text
2026年5月9日5月14日
```
解析规则:
- 左侧起始日期必须能解析出年、月、日。
- 右侧结束日期可以省略年份;省略年份时继承起始日期年份。
- 右侧结束日期可以省略月份;省略月份时继承起始日期月份。
- 支持常见区间分隔符,例如 ```-````—``至``到`
- `Arrival` 使用起始日期,格式化为 `yyyy-MM-dd`
- `Departure` 使用结束日期,格式化为 `yyyy-MM-dd`
- 旅游日期属于酒店本地业务日期,不按 UTC 时间点换算。
- 同一来源文件中参与生成的名单行必须解析出同一个旅游日期区间如果出现多个不同区间CP2 先返回受控错误,不自动混合生成。
- 如果缺少 `旅游日期` 表头、旅游日期为空、格式无法解析或结束日期不晚于起始日期,返回 `ROOMING_LIST_SOURCE_FILE_INVALID` 或统一受控业务错误,不应生成半成品 Excel。
中文说明:
- 当前样表第 2 行表头中包含 `旅游日期`,第 3 行起数据为 `2026年5月9日5月14日`,可派生 `Arrival=2026-05-09``Departure=2026-05-14`
- CP2 暂不支持同一个文件中多批次、多旅游日期混合分房;如后续出现该真实场景,需要先设计按批次拆文件或按旅游日期分组生成多个 Sheet / 多个文件。
## 5. 分房生成规则
前端需要传入 `people_per_room`,表示每间房人数。
@@ -95,6 +133,8 @@ LIU/JIAYI
- 同组剩余旅客写入目标 Excel 的 `Accompanying Guests`
- 多个陪同人使用英文逗号 `,` 分隔。
- 最后一组人数不足 `people_per_room` 时,仍按一间房生成一行。
- CP2 起目标 `Adults` 固定由当前房间实际人数派生;例如最后一间尾房只有 2 人时,该行 `Adults=2`,不再使用前端传入的统一成人数覆盖。
- CP2 起目标 `Children` 第一版固定为 `0`,不在前端目标默认值区域展示。
示例:
@@ -114,33 +154,35 @@ people_per_room = 3
## 6. 目标 Excel 字段
目标 Excel 第一版按当前样例模板生成以下列:
目标 Excel 按当前样例模板生成以下列:
| 目标列 | 第一版来源 |
| --- | --- |
| `Line` | 后端按生成行号从 1 递增 |
| `Name` | 每间房第一位旅客的姓氏 / 第一段姓名 |
| `First Name` | 每间房第一位旅客的名字 / 剩余姓名 |
| `Title` | 前端传入,第一版可为空 |
| `Arrival` | 前端传入 |
| `Departure` | 前端传入 |
| `Title` | CP2 固定为空,后续如有稳定默认值再单独确认 |
| `Arrival` | CP2 从来源 Excel `旅游日期` 起始日派生 |
| `Departure` | CP2 从来源 Excel `旅游日期` 结束日派生 |
| `Room Type` | 前端传入 |
| `Rate Code` | 前端传入 |
| `Rate Code` | CP2 固定为空,后续如需 Rate Code 选择再单独确认 |
| `Number of Rooms` | 第一版每行固定为 `1`,后续如目标模板变化再调整 |
| `Adults` | 前端传入;为空时可由后端按当前分组人数派生 |
| `Children` | 前端传入;第一版默认可`0` |
| `Payment Type` | 前端传入 |
| `VIP` | 前端传入,第一版可为空 |
| `Adults` | CP2 由后端按当前房间实际人数派生 |
| `Children` | CP2 固定`0` |
| `Payment Type` | 前端选择;当前默认和唯一允许值为 `CA` |
| `VIP` | CP2 固定为空,后续如有稳定默认值再单独确认 |
| `Accompanying Guests` | 同房其他旅客姓名,英文逗号分隔 |
| `Nationality` | 前端传入 |
| `Email` | 前端传入,第一版可为空 |
| `ID Type` | 前端传入,第一版可为空 |
| `ID Number` | 前端传入,第一版可为空 |
| `Nationality` | 前端选择;当前只允许 `KR``CHN` |
| `Email` | CP2 固定为空,后续如有稳定来源再单独确认 |
| `ID Type` | CP2 固定为空,后续如有稳定来源再单独确认 |
| `ID Number` | CP2 固定为空,后续如有稳定来源再单独确认 |
中文说明:
- `Line``Name``First Name``Number of Rooms``Accompanying Guests` 外,第一版都由前端输入或选择
- 如果前端希望 `Adults` 自动等于当前房间人数,后端可以提供默认规则;但请求体仍建议显式保留覆盖字段,方便后续处理儿童、领队或特殊房间
- CP2 目标默认值区域只保留 `Payment Type``Nationality`,不再展示 `Title``Rate Code``Adults``Children``VIP``Email``ID Type``ID Number`
- `Arrival` / `Departure` 的业务来源是来源名单 Excel 的 `旅游日期`,前端不再手填
- `Adults` 的业务来源是系统分房结果,前端不再手填。
- 如后续需要恢复 Rate Code、Title、Email 或证件字段,需要单独确认字段来源、默认值和安全边界,不能直接把 CP1 自由文本框恢复为生产能力。
## 7. 接口契约
@@ -159,19 +201,24 @@ Content-Type: multipart/form-data
| `file` | file | 是 | 来源 Excel 文件,仅支持 `.xls` / `.xlsx` |
| `hotel_id` | string | 否 | 酒店 ID不传时按当前登录用户默认酒店或单酒店上下文解析 |
| `people_per_room` | integer | 是 | 每间房人数,必须大于 0 |
| `arrival` | string | 是 | 入住日期,酒店本地业务日期,格式 `yyyy-MM-dd` |
| `departure` | string | 是 | 离店日期,酒店本地业务日期,格式 `yyyy-MM-dd` |
| `room_type` | string | 是 | 目标 Excel 的 `Room Type` |
| `title` | string | 否 | 目标 Excel 的 `Title` |
| `rate_code` | string | | 目标 Excel 的 `Rate Code` |
| `adults` | integer | 否 | 目标 Excel 的 `Adults`;为空时可由后端按分组人数派生 |
| `children` | integer | 否 | 目标 Excel 的 `Children`;为空时默认 `0` |
| `payment_type` | string | 否 | 目标 Excel 的 `Payment Type` |
| `vip` | string | 否 | 目标 Excel 的 `VIP` |
| `nationality` | string | 否 | 目标 Excel 的 `Nationality` |
| `email` | string | 否 | 目标 Excel 的 `Email` |
| `id_type` | string | 否 | 目标 Excel 的 `ID Type` |
| `id_number` | string | 否 | 目标 Excel 的 `ID Number` |
| `payment_type` | string | 否 | CP2 目标 Excel 的 `Payment Type`;为空时后端按 `CA` 处理;当前只允许 `CA` |
| `nationality` | string | | CP2 目标 Excel 的 `Nationality`;当前只允许 `KR``CHN` |
CP2 不再接收以下前端业务字段作为生成依据:
| 字段 | CP2 处理口径 |
| --- | --- |
| `arrival` | 不由前端提交;后端从来源 Excel `旅游日期` 派生 |
| `departure` | 不由前端提交;后端从来源 Excel `旅游日期` 派生 |
| `title` | 固定为空 |
| `rate_code` | 固定为空 |
| `adults` | 后端按当前房间实际人数派生 |
| `children` | 固定为 `0` |
| `vip` | 固定为空 |
| `email` | 固定为空 |
| `id_type` | 固定为空 |
| `id_number` | 固定为空 |
成功响应:
@@ -196,9 +243,11 @@ Content-Disposition: attachment; filename="rooming-list-<timestamp>.xlsx"
已收口的第一版错误场景:
- multipart 必填字段缺失,返回 `ROOMING_LIST_VALIDATION_FAILED`
- `arrival` / `departure` 日期格式错误,返回 `ROOMING_LIST_VALIDATION_FAILED`
- `people_per_room``adults``children` 数字格式错误,返回 `ROOMING_LIST_VALIDATION_FAILED`
- 来源文件缺失、非 `.xls` / `.xlsx`、无法读取或缺少 `护照全名` 表头,返回 `ROOMING_LIST_SOURCE_FILE_INVALID`
- `people_per_room` 数字格式错误,返回 `ROOMING_LIST_VALIDATION_FAILED`
- `payment_type``CA`,返回 `ROOMING_LIST_VALIDATION_FAILED`
- `nationality` `KR` / `CHN`,返回 `ROOMING_LIST_VALIDATION_FAILED`
- 来源文件缺失、非 `.xls` / `.xlsx`、无法读取、缺少 `护照全名` 表头或缺少 / 无法解析 `旅游日期` 表头,返回 `ROOMING_LIST_SOURCE_FILE_INVALID`
- 同一来源文件出现多个不同旅游日期区间CP2 返回受控错误,不生成 Excel。
- 错误详情只返回字段名或行号,不回显完整名单、证件号或源文件内容。
## 8. 权限、安全和审计
@@ -230,7 +279,7 @@ RESERVATION_ROOMING_LIST_GENERATE
| `ReservationRoomingListGenerationController` | 接收前端上传请求,做登录、权限和酒店上下文校验 |
| `ReservationRoomingListGenerationService` | 编排来源解析、分房、目标 Excel 渲染和文件响应 |
| `ReservationRoomingListGenerationServiceImpl` | 具体业务实现 |
| `RoomingListSourceExcelParser` | 解析来源 Excel提取护照姓名 |
| `RoomingListSourceExcelParser` | CP1 解析来源 Excel提取护照姓名CP2 需扩展为同时提取并校验统一旅游日期区间 |
| `RoomingListNameParser` | 拆分 `/` 或空格格式的护照姓名 |
| `RoomingListGroupingService` | 按每房人数分组并生成目标行模型 |
| `RoomingListExcelRenderer` | 生成目标格式 `.xlsx` |
@@ -258,7 +307,10 @@ RESERVATION_ROOMING_LIST_GENERATE
- 上传来源 Excel。
- 填写每间房人数。
- 填写或选择 Arrival、Departure、Room Type、Rate Code、Payment Type、Nationality 等字段
- 填写目标 `Room Type`
- 目标默认值区域只保留 `Payment Type``Nationality`
- `Payment Type` 使用下拉或固定选择,默认 `CA`,当前不开放其他值。
- `Nationality` 使用下拉,只允许 `KR``CHN`
- 展示解析预览:总人数、预计房间数、前几行分房结果。
- 点击生成后下载 `.xlsx`
@@ -266,6 +318,9 @@ RESERVATION_ROOMING_LIST_GENERATE
- 不要在前端解析完整 Excel 作为权威结果;前端预览可以后置为后端 preview 接口。
- 第一版如果没有 preview 接口,可以只展示用户输入和文件名,生成成功后直接下载。
- CP2 页面不再展示或提交 `arrival``departure``title``rate_code``adults``children``vip``email``id_type``id_number`
- `arrival` / `departure` 由后端从来源 Excel `旅游日期` 解析后写入目标 Excel前端不要自行读取 Excel 并推导日期。
- `Adults` 由后端按分房结果计算;前端不要让用户覆盖。
- 不要把上传文件内容、客人名单或生成文件内容写进浏览器日志、错误上报、URL 或 localStorage。
## 11. 测试范围
@@ -273,11 +328,18 @@ RESERVATION_ROOMING_LIST_GENERATE
后端测试建议覆盖:
- 能识别 `护照全名` 表头。
- 能识别 `旅游日期` 表头,并把 `2026年5月9日5月14日` 解析为 `Arrival=2026-05-09``Departure=2026-05-14`
- `LI/CHUNHONG` 能拆成 `Name=LI``First Name=CHUNHONG`
- `LI CHUNHONG` 能拆成 `Name=LI``First Name=CHUNHONG`
- 姓名首尾空格会 trim。
- `people_per_room=3` 时 32 人生成 11 行。
- 尾房人数不足时仍生成一行。
- `Adults` 按每个房间实际人数派生,尾房人数不足时不使用满房人数。
- `Children` 默认为 `0`
- `payment_type` 为空时按 `CA` 处理,非 `CA` 返回受控错误。
- `nationality=KR` / `CHN` 成功,其他值返回受控错误。
- 来源文件中多个不同旅游日期区间返回受控错误。
- 缺少 `旅游日期` 表头或旅游日期格式无法解析时返回受控错误。
- `Accompanying Guests` 使用英文逗号拼接。
- 未找到表头时返回明确错误。
- 非 Excel 文件、空文件、超大文件返回明确错误。
@@ -294,4 +356,4 @@ RESERVATION_ROOMING_LIST_GENERATE
- 支持不同房间不同人数。
- 支持字段映射配置,允许用户选择来源 Excel 中的姓名列。
- 支持从订单、任务或 SourceMessage 附件自动带入名单。
- 支持从任务详情订单详情预填 Arrival、Departure、Room Type、Rate Code 等字段。
- 支持从任务详情订单详情或来源附件预填 `Room Type``Payment Type``Nationality`,以及后续经确认恢复的目标字段。