Files
th-hotel-simple/docs/project/requirements/M010-rooming-list-excel-generation-v1.md
2026-07-24 11:55:29 +07:00

20 KiB
Raw Blame History

M010 Rooming List Excel 生成 V1

项目 内容
文档状态 后端 CP1 已实现CP2 已实现旅游日期派生、Adults 系统计算和目标默认值字段收口CP3 第二种来源名单样式已实现
适用范围 从旅行团名单 Excel 解析护照姓名,并生成酒店 / PMS 可导入的 Rooming List Excel
当前目标 CP1 已支持前端上传来源 Excel、填写分房和目标列默认值后端同步生成目标格式 .xlsxCP2 收口为后端从来源 旅游日期 派生入住 / 离店日期成人数由系统按分房结果计算目标默认值区域只保留付款方式和国籍CP3 兼容 英文姓 + 英文名 第二种名单样式,并由用户补充入住 / 离店日期
依赖能力 登录权限、酒店上下文、Apache POI 或等价 Excel 读写能力

1. 背景

当前系统已经支持 Manual Invoice 生成,但运营中还存在另一类独立工具诉求:旅行社或渠道提供一份名单 Excel里面包含旅客护照姓名酒店侧需要整理成 PMS / 导入模板要求的 Rooming List Excel。

本功能第一版只解决“来源名单到目标 Rooming List”的结构转换和分房填表问题不依赖订单、任务、SuperAgent、AgentBus 或 OPERA。

M002 V4 ROOMING_LIST 任务卡与本工具是两个独立能力V4 卡第一版只做 Rooming List 事项确认,不调用本工具、不解析名单、不生成 Excel、不导入 PMS。

2. 第一阶段定位

CP1 已做到:

  • 前端上传 .xls / .xlsx 来源名单。
  • 后端解析来源名单中的护照姓名字段。
  • 前端输入每间房人数。
  • 前端填写或选择目标 Excel 中除姓名外的字段。
  • 后端按来源名单顺序分组,生成目标格式 Excel。
  • 浏览器直接下载生成后的 .xlsx 文件。

CP2 目标调整:

  • 前端继续上传 .xls / .xlsx 来源名单。
  • 前端继续输入每间房人数和目标 Room Type
  • 后端从来源 Excel 的 旅游日期 列解析入住日期和离店日期,不再要求前端手填 arrival / departure
  • 后端按分房结果计算目标 Adults,不再要求前端手填 adults
  • 目标默认值区域只保留 Payment TypeNationality 两个字段。
  • Payment Type 前端默认为 BTQR,当前可选择 BTQRCA,后端按同一枚举校验。
  • Nationality 前端只允许选择 KRCHN,后端按同一枚举校验。

CP3 目标调整:

  • 继续复用同一个生成接口和同一个前端页面,不新增上传入口。
  • 兼容第二种来源名单样式:表头包含 英文姓英文名,没有 护照全名旅游日期
  • 第二种样式的姓名不再拆分字符串,直接使用 英文姓 写入目标 Name,使用 英文名 写入目标 First Name
  • 第二种样式没有可靠入住 / 离店日期来源,前端需要恢复并提交 arrivaldeparture 两个日期字段;第一种样式仍可不填,由后端从来源 旅游日期 派生。
  • 第二种样式中的领队也进入房表。例如标题行 23+1 应理解为 23 位客人 + 1 位领队,合计 24 位入住人全部参与分房。
  • 来源表中的中文名、性别、出生日期、护照号码、出生地、签发地、签发日期、有效期等字段第一版仍不写入目标 Excel也不在错误响应或日志中回显。

第一阶段不做:

  • 不落业务订单。
  • 不创建任务。
  • 不上传 OSS。
  • 不新增生成记录表。
  • 不做历史查询和重下载。
  • 不接真实 OPERA / OHIP。
  • 不做复杂房型混住、拖拽换房或手工调整入住人顺序。
  • 不从任务详情或订单详情自动预填字段。

3. 与现有模块关系

本功能属于 Reservation 运营工具,不属于 M008 通用文件转换,也不属于 M009 Manual Invoice。

边界说明:

  • M008 负责 Excel 转 PDF面向平台文件转换和调试能力。
  • M009 负责 Proforma Invoice 生成,输出 PDF 并上传 OSS。
  • M010 负责 Rooming List Excel 生成,第一版只输出 .xlsx 文件流。

因此第一版建议在 Reservation 模块内新增独立接口、权限码和服务,不复用发票生成权限,也不让前端调用 M008 调试接口。

4. 来源 Excel 解析规则

基于当前样例来源文件,当前支持两类来源名单格式。后端应先在来源 Excel 中识别表头组合,再选择对应解析器。

4.1 第一种来源样式:护照全名 + 旅游日期

第一种样式识别以下表头:

来源字段 中文说明 第一版用途
护照全名 护照姓名,可能包含 / 或空格 解析为目标 Excel 的 NameFirst Name
旅游日期 旅游团起止日期,例如 2026年5月9日5月14日 CP2 用于派生目标 Excel 的 ArrivalDeparture

CP1 只使用 护照全名 生成目标姓名字段。CP2 额外使用 旅游日期 派生目标 Arrival / Departure。来源文件中的证件号、生日、性别、年龄等字段暂不自动写入目标 Excel除非后续单独确认。

4.1.1 姓名拆分

支持的输入格式:

LI/CHUNHONG
LI CHUNHONG
LIU/JIAYI

规则:

  • 先 trim 首尾空格。
  • 优先按 / 拆分。
  • 如果没有 /,按连续空白拆分。
  • 第一段写入目标 Name
  • 剩余部分合并后写入目标 First Name
  • 如果无法拆出两段,返回明确校验错误,由前端提示用户修正来源 Excel 或后续增加字段映射。

中文说明:

  • 当前目标模板中 Name 更接近姓氏字段,First Name 更接近名字段。
  • 第一版不尝试判断中文、韩文、英文姓名顺序,也不做大小写改写。

4.1.2 旅游日期解析

CP2 起后端应识别来源 Excel 的 旅游日期 表头,并从有名单数据的行中读取日期区间。

已确认样例:

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/09Departure=2026/05/14
  • CP2 暂不支持同一个文件中多批次、多旅游日期混合分房;如后续出现该真实场景,需要先设计按批次拆文件或按旅游日期分组生成多个 Sheet / 多个文件。

4.2 第二种来源样式:英文姓 + 英文名

第二种样式识别以下表头:

来源字段 中文说明 第一版用途
英文姓 护照英文姓氏 直接写入目标 Excel 的 Name
英文名 护照英文名字 直接写入目标 Excel 的 First Name

已确认样例:

Sheet1 第 1 行SYNTHETIC-GROUP-001  23+1  12XX  领队...
Sheet1 第 2 行:序号 / 中文名 / 英文姓 / 英文名 / 性别 / 出生日期 / 护照号码 / ...
Sheet1 第 3 行起:名单数据

解析规则:

  • 在前若干行内查找同一行同时包含 英文姓英文名 的表头。
  • 从表头下一行开始读取名单行。
  • 英文姓英文名 为空的行跳过;如后续需要强校验可单独确认。
  • 底部航班说明、空白行、合计说明等不进入名单。
  • 不读取也不返回中文名、性别、出生日期、护照号码、出生地、签发地、签发日期、有效期。
  • 标题行中的 23+1 只作为人工可读说明,不作为系统人数校验;实际入住人数以后端读取到的有效 英文姓 + 英文名 行数为准。
  • 23+1 业务含义已确认23 位客人 + 1 位领队,领队也要进入目标 Rooming List。
  • 第二种样式没有 旅游日期,后端不得从航班行猜测入住 / 离店日期;必须使用前端提交的 arrival / departure

如果来源文件同时满足第一种和第二种表头组合,后端应优先使用第一种 护照全名 + 旅游日期,保持现有已上线行为稳定。

5. 分房生成规则

前端需要传入 people_per_room,表示每间房人数。

规则:

  • 按来源 Excel 中旅客出现顺序分组。
  • room_count = ceil(total_people / people_per_room)
  • 每组第一位旅客写入目标 Excel 的 NameFirst Name
  • 同组剩余旅客写入目标 Excel 的 Accompanying Guests
  • 多个陪同人使用英文逗号 , 分隔。
  • 最后一组人数不足 people_per_room 时,仍按一间房生成一行。
  • CP2 起目标 Adults 固定由当前房间实际人数派生;例如最后一间尾房只有 2 人时,该行 Adults=2,不再使用前端传入的统一成人数覆盖。
  • CP2 起目标 Children 第一版固定为 0,不在前端目标默认值区域展示。

示例:

people_per_room = 3

第 1 组:
  Name = LI
  First Name = CHUNHONG
  Accompanying Guests = ZHANG GAILI, LI HONG

第 2 组:
  Name = LIU
  First Name = JIAYI
  Accompanying Guests = ZHU XINGTONG, CHEN YUCHAI

6. 目标 Excel 字段

目标 Excel 按当前样例模板生成以下列:

目标列 第一版来源
Line 后端按生成行号从 1 递增
Name 每间房第一位旅客的姓氏 / 第一段姓名
First Name 每间房第一位旅客的名字 / 剩余姓名
Title CP2 固定为空,后续如有稳定默认值再单独确认
Arrival 第一种样式从来源 Excel 旅游日期 起始日派生;第二种样式使用前端提交的 arrival
Departure 第一种样式从来源 Excel 旅游日期 结束日派生;第二种样式使用前端提交的 departure
Room Type 前端传入
Rate Code CP2 固定为空,后续如需 Rate Code 选择再单独确认
Number of Rooms 第一版每行固定为 1,后续如目标模板变化再调整
Adults CP2 由后端按当前房间实际人数派生
Children CP2 固定为 0
Payment Type 前端选择;当前默认值为 BTQR,允许 BTQRCA
VIP CP2 固定为空,后续如有稳定默认值再单独确认
Accompanying Guests 同房其他旅客姓名,英文逗号分隔
Nationality 前端选择;当前只允许 KRCHN
Email CP2 固定为空,后续如有稳定来源再单独确认
ID Type CP2 固定为空,后续如有稳定来源再单独确认
ID Number CP2 固定为空,后续如有稳定来源再单独确认

中文说明:

  • CP2 目标默认值区域只保留 Payment TypeNationality,不再展示 TitleRate CodeAdultsChildrenVIPEmailID TypeID Number
  • Arrival / Departure 的业务来源按来源样式区分:第一种样式来自来源名单 Excel 的 旅游日期,第二种样式来自前端用户填写。
  • Adults 的业务来源是系统分房结果,前端不再手填。
  • 如后续需要恢复 Rate Code、Title、Email 或证件字段,需要单独确认字段来源、默认值和安全边界,不能直接把 CP1 自由文本框恢复为生产能力。

7. 接口契约

第一版已新增业务接口,不复用 M008 文件转换接口:

POST /api/reservation/rooming-lists/generations
Authorization: Bearer <access_token>
Content-Type: multipart/form-data

表单字段:

字段 类型 必填 中文说明
file file 来源 Excel 文件,仅支持 .xls / .xlsx
hotel_id string 酒店 ID不传时按当前登录用户默认酒店或单酒店上下文解析
people_per_room integer 每间房人数,必须大于 0
room_type string 目标 Excel 的 Room Type
arrival date string 条件必填 CP3 第二种来源样式必填,格式 yyyy-MM-dd;第一种来源样式可不传,后端优先从 旅游日期 派生
departure date string 条件必填 CP3 第二种来源样式必填,格式 yyyy-MM-dd;必须晚于 arrival
payment_type string CP2 目标 Excel 的 Payment Type;为空时后端按 BTQR 处理;当前只允许 BTQRCA
nationality string CP2 目标 Excel 的 Nationality;当前只允许 KRCHN

CP3 后仍不接收以下前端业务字段作为生成依据:

字段 处理口径
title 固定为空
rate_code 固定为空
adults 后端按当前房间实际人数派生
children 固定为 0
vip 固定为空
email 固定为空
id_type 固定为空
id_number 固定为空

成功响应:

HTTP 200
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="rooming-list-<timestamp>.xlsx"

错误响应沿用后端统一错误模型,例如:

{
  "error_code": "ROOMING_LIST_SOURCE_FILE_INVALID",
  "message": "来源名单文件不合法。",
  "details": [
    "未找到表头:护照全名"
  ]
}

已收口的第一版错误场景:

  • multipart 必填字段缺失,返回 ROOMING_LIST_VALIDATION_FAILED
  • people_per_room 数字格式错误,返回 ROOMING_LIST_VALIDATION_FAILED
  • payment_typeBTQR / CA,返回 ROOMING_LIST_VALIDATION_FAILED
  • nationalityKR / CHN,返回 ROOMING_LIST_VALIDATION_FAILED
  • 第二种来源样式缺少 arrival / departure、日期格式错误或 departure 不晚于 arrival,返回 ROOMING_LIST_VALIDATION_FAILED
  • 来源文件缺失、非 .xls / .xlsx、无法读取、既不能识别第一种 护照全名 + 旅游日期,也不能识别第二种 英文姓 + 英文名,返回 ROOMING_LIST_SOURCE_FILE_INVALID
  • 第一种来源样式缺少 / 无法解析 旅游日期 表头,返回 ROOMING_LIST_SOURCE_FILE_INVALID
  • 同一来源文件出现多个不同旅游日期区间CP2 返回受控错误,不生成 Excel。
  • 错误详情只返回字段名或行号,不回显完整名单、证件号或源文件内容。

8. 权限、安全和审计

接口分类:FRONTEND_USER

已新增权限码:

RESERVATION_ROOMING_LIST_GENERATE

管控要求:

  • 必须登录。
  • 必须拥有 RESERVATION_ROOMING_LIST_GENERATE 权限。
  • 上传接口在 multipart 参数绑定前执行登录和权限前置校验,未登录或无权限请求不会进入来源文件解析。
  • 必须校验用户对 hotel_id 的访问权。
  • 第一版直接下载、不落库,因此不写生成记录表。
  • 当前 CP1 不新增持久化审计表;后续如增加生成记录或历史下载,再补业务审计落库。
  • 日志和错误响应不得输出完整名单、证件号、源文件内容或生成文件内容。

9. 后端实现拆分

已实现类职责:

类 / 能力 中文职责
ReservationRoomingListGenerationController 接收前端上传请求,做登录、权限和酒店上下文校验
ReservationRoomingListGenerationService 编排来源解析、分房、目标 Excel 渲染和文件响应
ReservationRoomingListGenerationServiceImpl 具体业务实现
RoomingListSourceExcelParser CP1 解析来源 Excel提取护照姓名CP2 已扩展为同时提取并校验统一旅游日期区间CP3 已扩展第二种 英文姓 + 英文名 样式,并在该样式下要求用户补日期
RoomingListNameParser 拆分 / 或空格格式的护照姓名
RoomingListGroupingService 按每房人数分组并生成目标行模型
RoomingListExcelRenderer 生成目标格式 .xlsx
ReservationRoomingListGenerationRequest multipart 外的业务参数请求模型
ReservationRoomingListGeneratedFile 生成后的下载文件字节和文件名
ReservationRoomingListGuestDto 解析后的旅客姓名结构
ReservationRoomingListRoomDto 分房后的目标行结构

中文说明:

  • 第一版不需要新增 Entity、Mapper 和 Repository。
  • 如果后续要生成记录、OSS 上传、历史查询或重下载,再新增 workflow_reservation_rooming_list_generation 表。
  • 解析和渲染能力应和 Controller 解耦,便于单元测试。
  • 目标 Excel 固定列宽,不调用 POI autoSizeColumn,避免后端运行环境依赖本机字体或图形环境。

10. 前端实现建议

第一版页面可以放在 Reservation 工具入口,例如:

/reservation/rooming-lists/new

页面能力:

  • 上传来源 Excel。
  • 填写每间房人数。
  • 填写目标 Room Type
  • 填写 Arrival / Departure:第一种来源样式可不填,第二种来源样式必填;页面文案需说明“当来源名单没有旅游日期时请填写”。
  • 目标默认值区域只保留 Payment TypeNationality
  • Payment Type 使用下拉选择,默认 BTQR,当前可选择 BTQRCA
  • Nationality 使用下拉,只允许 KRCHN
  • 展示解析预览:总人数、预计房间数、前几行分房结果。
  • 点击生成后下载 .xlsx

前端注意:

  • 不要在前端解析完整 Excel 作为权威结果;前端预览可以后置为后端 preview 接口。
  • 第一版如果没有 preview 接口,可以只展示用户输入和文件名,生成成功后直接下载。
  • CP3 页面重新展示并提交可选 arrivaldeparture;后端仅在第二种来源样式下要求这两个字段必填,第一种来源样式仍优先使用来源 Excel 旅游日期
  • 前端不要自行读取 Excel 并推导日期;如果用户上传第二种没有 旅游日期 的名单,由用户手工填写 arrival / departure
  • Adults 由后端按分房结果计算;前端不要让用户覆盖。
  • 不要把上传文件内容、客人名单或生成文件内容写进浏览器日志、错误上报、URL 或 localStorage。

11. 测试范围

后端测试建议覆盖:

  • 能识别 护照全名 表头。
  • 能识别 旅游日期 表头,并把 2026年5月9日5月14日 解析为 Arrival=2026/05/09Departure=2026/05/14
  • 能识别第二种来源样式 英文姓 / 英文名 表头。
  • 第二种来源样式下 英文姓=FENG英文名=MINNA 能生成 Name=FENGFirst Name=MINNA
  • 第二种来源样式下领队也进入房表,实际人数以后端读取到的有效名单行数为准。
  • 第二种来源样式使用前端提交的 arrival / departure 写入目标 Excel。
  • 第二种来源样式缺少 arrival / departure 返回受控错误。
  • LI/CHUNHONG 能拆成 Name=LIFirst Name=CHUNHONG
  • LI CHUNHONG 能拆成 Name=LIFirst Name=CHUNHONG
  • 姓名首尾空格会 trim。
  • people_per_room=3 时 32 人生成 11 行。
  • 尾房人数不足时仍生成一行。
  • Adults 按每个房间实际人数派生,尾房人数不足时不使用满房人数。
  • Children 默认为 0
  • payment_type 为空时按 BTQR 处理,非 BTQR / CA 返回受控错误。
  • nationality=KR / CHN 成功,其他值返回受控错误。
  • 来源文件中多个不同旅游日期区间返回受控错误。
  • 缺少 旅游日期 表头或旅游日期格式无法解析时返回受控错误。
  • Accompanying Guests 使用英文逗号拼接。
  • 未找到表头时返回明确错误。
  • 非 Excel 文件、空文件、超大文件返回明确错误。
  • 无 token、无权限、无酒店访问权被拦截。

12. 后续版本候选

后续可以继续扩展:

  • 生成记录表和历史下载。
  • 生成文件上传 OSS。
  • 后端预览接口。
  • 支持前端手工调整分房顺序。
  • 支持不同房间不同人数。
  • 支持字段映射配置,允许用户选择来源 Excel 中的姓名列。
  • 支持从订单、任务或 SourceMessage 附件自动带入名单。
  • 支持从任务详情、订单详情或来源附件预填 Room TypePayment TypeNationality,以及后续经确认恢复的目标字段。