diff --git a/docs/project/frontend-backend/backend-to-frontend-notes.md b/docs/project/frontend-backend/backend-to-frontend-notes.md index 05ad4a5..b635bae 100644 --- a/docs/project/frontend-backend/backend-to-frontend-notes.md +++ b/docs/project/frontend-backend/backend-to-frontend-notes.md @@ -485,7 +485,7 @@ RESERVATION_ROOMING_LIST_GENERATE - 姓名支持 `LI/CHUNHONG` 和 `LI CHUNHONG` 两类格式;后端会把第一段写入目标 `Name`,剩余部分写入目标 `First Name`。 - 前端必须让用户输入 `people_per_room` 和目标 `Room Type`,后端按名单顺序分组,并用 `ceil(total_people / people_per_room)` 生成房间行。 - 每组第一位旅客写入 `Name` / `First Name`,同组剩余旅客写入 `Accompanying Guests`,多人用英文逗号分隔。 -- CP2 起前端不再展示或提交 `Arrival` / `Departure`,后端从来源 Excel 的 `旅游日期` 派生。样例 `2026年5月9日-5月14日` 应生成 `Arrival=2026-05-09`、`Departure=2026-05-14`。 +- CP2 起前端不再展示或提交 `Arrival` / `Departure`,后端从来源 Excel 的 `旅游日期` 派生。样例 `2026年5月9日-5月14日` 应生成 `Arrival=2026/05/09`、`Departure=2026/05/14`。 - CP2 起前端不再展示或提交 `Adults`,后端按每个分房行的实际人数派生;尾房不足满房人数时按尾房实际人数写入。`Children` 第一版固定为 `0`。 - CP2 起目标默认值区域只保留 `Payment Type` 和 `Nationality`:`Payment Type` 默认 `BTQR`,当前允许 `BTQR`、`CA`;`Nationality` 只允许 `KR`、`CHN`。 - CP2 页面不再展示或提交 `Title`、`Rate Code`、`VIP`、`Email`、`ID Type`、`ID Number`,这些目标列第一版固定为空。 diff --git a/docs/project/frontend-backend/frontend-to-backend-api-requests.md b/docs/project/frontend-backend/frontend-to-backend-api-requests.md index 11fd5a4..52a0413 100644 --- a/docs/project/frontend-backend/frontend-to-backend-api-requests.md +++ b/docs/project/frontend-backend/frontend-to-backend-api-requests.md @@ -1221,7 +1221,7 @@ id_number 后端派生规则: -- 从来源 Excel `旅游日期` 列解析入住和离店日期;样例 `2026年5月9日-5月14日` 派生 `Arrival=2026-05-09`、`Departure=2026-05-14`。 +- 从来源 Excel `旅游日期` 列解析入住和离店日期;样例 `2026年5月9日-5月14日` 派生 `Arrival=2026/05/09`、`Departure=2026/05/14`。 - `Adults` 按当前分房行实际人数计算;尾房人数不足时按实际人数写入。 - `Children` 固定为 `0`。 - `Title`、`Rate Code`、`VIP`、`Email`、`ID Type`、`ID Number` 第一版固定为空。 diff --git a/docs/project/requirements/M010-rooming-list-excel-generation-v1.md b/docs/project/requirements/M010-rooming-list-excel-generation-v1.md index 843ff65..d8e0700 100644 --- a/docs/project/requirements/M010-rooming-list-excel-generation-v1.md +++ b/docs/project/requirements/M010-rooming-list-excel-generation-v1.md @@ -110,15 +110,15 @@ CP2 起后端应识别来源 Excel 的 `旅游日期` 表头,并从有名单 - 右侧结束日期可以省略年份;省略年份时继承起始日期年份。 - 右侧结束日期可以省略月份;省略月份时继承起始日期月份。 - 支持常见区间分隔符,例如 `-`、`-`、`–`、`—`、`至`、`到`。 -- `Arrival` 使用起始日期,格式化为 `yyyy-MM-dd`。 -- `Departure` 使用结束日期,格式化为 `yyyy-MM-dd`。 +- `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`。 +- 当前样表第 2 行表头中包含 `旅游日期`,第 3 行起数据为 `2026年5月9日-5月14日`,可派生 `Arrival=2026/05/09`、`Departure=2026/05/14`。 - CP2 暂不支持同一个文件中多批次、多旅游日期混合分房;如后续出现该真实场景,需要先设计按批次拆文件或按旅游日期分组生成多个 Sheet / 多个文件。 ## 5. 分房生成规则 @@ -328,7 +328,7 @@ RESERVATION_ROOMING_LIST_GENERATE 后端测试建议覆盖: - 能识别 `护照全名` 表头。 -- 能识别 `旅游日期` 表头,并把 `2026年5月9日-5月14日` 解析为 `Arrival=2026-05-09`、`Departure=2026-05-14`。 +- 能识别 `旅游日期` 表头,并把 `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。 diff --git a/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/RoomingListExcelRenderer.java b/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/RoomingListExcelRenderer.java index b4fa20f..21477ee 100644 --- a/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/RoomingListExcelRenderer.java +++ b/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/RoomingListExcelRenderer.java @@ -21,6 +21,8 @@ import org.springframework.stereotype.Component; @Component public class RoomingListExcelRenderer { + private static final DateTimeFormatter TARGET_DATE_FORMATTER = + DateTimeFormatter.ofPattern("yyyy/MM/dd"); private static final String[] HEADERS = { "Line", "Name", @@ -98,8 +100,8 @@ public class RoomingListExcelRenderer { row.createCell(1).setCellValue(room.primaryGuest().lastName()); row.createCell(2).setCellValue(room.primaryGuest().firstName()); row.createCell(3).setCellValue(""); - row.createCell(4).setCellValue(arrival.format(DateTimeFormatter.ISO_LOCAL_DATE)); - row.createCell(5).setCellValue(departure.format(DateTimeFormatter.ISO_LOCAL_DATE)); + row.createCell(4).setCellValue(arrival.format(TARGET_DATE_FORMATTER)); + row.createCell(5).setCellValue(departure.format(TARGET_DATE_FORMATTER)); row.createCell(6).setCellValue(defaultString(request.roomType())); row.createCell(7).setCellValue(""); row.createCell(8).setCellValue(1); diff --git a/server/src/test/java/cn/nianxx/thhotel/workflows/reservation/control/ReservationRoomingListGenerationControllerTest.java b/server/src/test/java/cn/nianxx/thhotel/workflows/reservation/control/ReservationRoomingListGenerationControllerTest.java index a602a98..4b47495 100644 --- a/server/src/test/java/cn/nianxx/thhotel/workflows/reservation/control/ReservationRoomingListGenerationControllerTest.java +++ b/server/src/test/java/cn/nianxx/thhotel/workflows/reservation/control/ReservationRoomingListGenerationControllerTest.java @@ -101,9 +101,9 @@ class ReservationRoomingListGenerationControllerTest { .andExpect(result -> assertThat(workbookCell(result.getResponse().getContentAsByteArray(), 1, 3)) .isEmpty()) .andExpect(result -> assertThat(workbookCell(result.getResponse().getContentAsByteArray(), 1, 4)) - .isEqualTo("2026-05-09")) + .isEqualTo("2026/05/09")) .andExpect(result -> assertThat(workbookCell(result.getResponse().getContentAsByteArray(), 1, 5)) - .isEqualTo("2026-05-14")) + .isEqualTo("2026/05/14")) .andExpect(result -> assertThat(workbookCell(result.getResponse().getContentAsByteArray(), 1, 9)) .isEqualTo("2")) .andExpect(result -> assertThat(workbookCell(result.getResponse().getContentAsByteArray(), 1, 10)) diff --git a/server/src/test/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationRoomingListGenerationServiceImplTest.java b/server/src/test/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationRoomingListGenerationServiceImplTest.java index 071b35c..f91cd43 100644 --- a/server/src/test/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationRoomingListGenerationServiceImplTest.java +++ b/server/src/test/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationRoomingListGenerationServiceImplTest.java @@ -60,8 +60,8 @@ class ReservationRoomingListGenerationServiceImplTest { assertThat(cellText(firstRoom.getCell(1))).isEqualTo("LI"); assertThat(cellText(firstRoom.getCell(2))).isEqualTo("CHUNHONG"); assertThat(cellText(firstRoom.getCell(3))).isEmpty(); - assertThat(cellText(firstRoom.getCell(4))).isEqualTo("2026-05-09"); - assertThat(cellText(firstRoom.getCell(5))).isEqualTo("2026-05-14"); + assertThat(cellText(firstRoom.getCell(4))).isEqualTo("2026/05/09"); + assertThat(cellText(firstRoom.getCell(5))).isEqualTo("2026/05/14"); assertThat(cellText(firstRoom.getCell(6))).isEqualTo("UG1"); assertThat(cellText(firstRoom.getCell(7))).isEmpty(); assertThat(cellText(firstRoom.getCell(8))).isEqualTo("1"); @@ -119,8 +119,8 @@ class ReservationRoomingListGenerationServiceImplTest { try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(generatedFile.content()))) { Row firstRoom = workbook.getSheetAt(0).getRow(1); - assertThat(cellText(firstRoom.getCell(4))).isEqualTo("2026-05-09"); - assertThat(cellText(firstRoom.getCell(5))).isEqualTo("2026-05-14"); + assertThat(cellText(firstRoom.getCell(4))).isEqualTo("2026/05/09"); + assertThat(cellText(firstRoom.getCell(5))).isEqualTo("2026/05/14"); } }