diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 23e55ad..7bbf447 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -34,7 +34,7 @@ - 现有历史文档暂不按 AI-NSES 目录大搬迁,先通过索引和采用说明建立映射关系。 - `docs/import/` 下按日期导入的资料是输入材料,不等同于当前权威开发契约;当前开发应优先看 `docs/project/README.md` 标记为当前有效或权威契约的文档。 - 后续每完成一个 Feature 或 Checkpoint,需要更新本文件,避免项目状态继续沉淀在聊天记录里。 -- M010 Rooming List Excel 生成后端 CP1 和前端 V1 已实现:前端 `/reservation/rooming-lists/new` 上传来源名单并下载后端同步生成的 `.xlsx`,第一版不落库、不上传 OSS。CP2 已实现:来源 Excel `旅游日期` 派生 Arrival / Departure,Adults 由系统按分房结果计算,目标默认值区域只保留 Payment Type / Nationality,Payment Type 默认 / 当前唯一允许值为 `CA`,Nationality 只允许 `KR` / `CHN`。 +- M010 Rooming List Excel 生成后端 CP1 和前端 V1 已实现:前端 `/reservation/rooming-lists/new` 上传来源名单并下载后端同步生成的 `.xlsx`,第一版不落库、不上传 OSS。CP2 已实现:来源 Excel `旅游日期` 派生 Arrival / Departure,Adults 由系统按分房结果计算,目标默认值区域只保留 Payment Type / Nationality,Payment Type 默认 `BTQR` 且当前允许 `BTQR` / `CA`,Nationality 只允许 `KR` / `CHN`。 - M011 Booking Excel 附件预处理 CP1/CP2/CP3 已实现:后端可排除人员名单类 Excel,按最近 6 个月候选窗口选择实际存在的最新 3 个业务月,抽取 Booking Update / 附加费表高亮行业务 JSON;Debug EML 和 AgentBus dispatch 在各自 include 开关与总开关同时启用时,会在调用 SuperAgent 前追加 `attachment_extractions[]`。测试机 AgentBus 增强已开启;生产链路仍默认关闭,生产开启需单独确认。 - M002 V4 CP1 当前已完成入站解析和现有任务链路过渡适配;M002 V4 CP2 已完成订单任务与多卡领域模型设计;M002 V4 CP3 已完成 V4 订单任务、多卡和 S10/S99 来源通知表结构与 Repository 基线;M002 V4 CP4 已完成入站写入新模型;M002 V4 CP5 已完成前端查询接口并补齐订单详情 `v4_order_tasks[]` 时间线;M002 V4 CP6 已完成普通卡片确认和 S10/S99 来源通知 ack;M002 V4 CP7 已完成 `REVIEW_REQUIRED` 卡复核解阻和复核场景订单归属确认;M002 V4 CP8 已完成目录校验、V4 卡片 `fields[]` 字段白名单、确认写入白名单收口和嵌套业务字段目录校验;M002 V4 CP11 已完成数据库目录、初始化种子、启动补种子、Account / Room Type / Rate Code lookup API,并把 V4 入站、确认、复核目录校验切换到当前酒店数据库目录;M002 V4 CP12 已完成前端 lookup 接入第一版和 V4 订单任务时间线消费;M002 V4 CP13 目录管理后台 CP1 已完成前后端列表、新增、启用 / 停用闭环;M002 V4 CP14 已完成订单列表 V4 继续处理入口字段和前端入口消费,`GET /api/reservation/orders` 返回 V4 下一步订单任务、卡片、动作类型、动作状态、V4 open 数和统一展示计数 `open_work_item_count`,前端按 V4 优先跳转,并按 `open_work_item_count` 展示待处理数量;V4 业务审计查询已补齐订单任务审计和来源通知 ack 审计两个只读接口;订单详情已补齐并完成前端接入 V4 `order_overview`、`next_v4_action`、`related_source_messages[]` 和 `v4_order_tasks[].cards[]`;V4 普通业务入站已停止双写旧 `workflow_reservation_task`;Room Information 后端展示模型和前端业务化展示第一版已完成;Rooming List 确认触发 Group Booking Status 自动置 `DEF` 已完成,前端轻量事项确认卡也已完成;Payment 附件安全摘要后端和前端预览接入均已完成。OWNER RATE Room Type / Rate Code 目录口径已落文档;真实 PMS 同步和 SuperAgent 目录机器接口仍未完成。 - M002 V4 CP2 已确认:V4 工作台统一列表草案为 `/api/reservation/workbench-items`,业务订单任务接口新开 `/api/reservation/order-tasks/**`,S10/S99 来源通知详情草案为 `/api/reservation/source-notifications/{notificationId}`;S10/S99 使用来源通知模型,不再挂隐藏技术订单;`FIT + BOOKING_CODE` 不建 ACTIVE 唯一约束,匹配多条进人工复核;Basic Information 必须先确认;Rooming List 卡第一版只做事项确认;Account 通过数据库目录选择,Market / Source 可默认来自目录并允许前端人工覆盖提交;旧 V2/V3 任务详情和草稿确认接口后续可逐步废弃。 diff --git a/client/src/i18n/locales/en-US.ts b/client/src/i18n/locales/en-US.ts index 4d81c00..91da88f 100644 --- a/client/src/i18n/locales/en-US.ts +++ b/client/src/i18n/locales/en-US.ts @@ -416,6 +416,7 @@ export default { adults: 'Adults', children: 'Children', paymentType: 'Payment Type', + paymentTypeBtqr: 'BTQR', paymentTypeCa: 'CA', vip: 'VIP', nationality: 'Nationality', @@ -442,7 +443,7 @@ export default { departureAfterArrival: 'Departure must be later than arrival.', roomTypeRequired: 'Enter room type.', paymentTypeRequired: 'Select payment type.', - paymentTypeAllowed: 'Only CA is currently allowed.', + paymentTypeAllowed: 'Only BTQR or CA are currently allowed.', nationalityRequired: 'Select nationality.', nationalityAllowed: 'Only KR or CHN are currently allowed.', invalidEmail: 'Invalid email.', diff --git a/client/src/i18n/locales/th-TH.ts b/client/src/i18n/locales/th-TH.ts index 765fd5a..4aafe64 100644 --- a/client/src/i18n/locales/th-TH.ts +++ b/client/src/i18n/locales/th-TH.ts @@ -416,6 +416,7 @@ export default { adults: 'Adults', children: 'Children', paymentType: 'Payment Type', + paymentTypeBtqr: 'BTQR', paymentTypeCa: 'CA', vip: 'VIP', nationality: 'Nationality', @@ -442,7 +443,7 @@ export default { departureAfterArrival: 'Departure ต้องอยู่หลัง Arrival', roomTypeRequired: 'กรุณากรอก Room Type', paymentTypeRequired: 'กรุณาเลือก Payment Type', - paymentTypeAllowed: 'รองรับเฉพาะ CA ในตอนนี้', + paymentTypeAllowed: 'รองรับเฉพาะ BTQR หรือ CA ในตอนนี้', nationalityRequired: 'กรุณาเลือก Nationality', nationalityAllowed: 'รองรับเฉพาะ KR หรือ CHN ในตอนนี้', invalidEmail: 'รูปแบบ Email ไม่ถูกต้อง', diff --git a/client/src/i18n/locales/zh-CN.ts b/client/src/i18n/locales/zh-CN.ts index b4c1d55..27d08fb 100644 --- a/client/src/i18n/locales/zh-CN.ts +++ b/client/src/i18n/locales/zh-CN.ts @@ -415,6 +415,7 @@ export default { adults: '成人数(Adults)', children: '儿童数(Children)', paymentType: '付款方式(Payment Type)', + paymentTypeBtqr: 'BTQR', paymentTypeCa: 'CA', vip: 'VIP', nationality: '国籍(Nationality)', @@ -441,7 +442,7 @@ export default { departureAfterArrival: '离店日期必须晚于入住日期。', roomTypeRequired: '请填写房型。', paymentTypeRequired: '请选择付款方式。', - paymentTypeAllowed: '付款方式当前只允许 CA。', + paymentTypeAllowed: '付款方式当前只允许 BTQR 或 CA。', nationalityRequired: '请选择国籍。', nationalityAllowed: '国籍当前只允许 KR 或 CHN。', invalidEmail: 'Email 格式不正确。', diff --git a/client/src/tests/roomingListView.spec.ts b/client/src/tests/roomingListView.spec.ts index 3cfca07..00045ef 100644 --- a/client/src/tests/roomingListView.spec.ts +++ b/client/src/tests/roomingListView.spec.ts @@ -168,7 +168,7 @@ describe('ReservationRoomingListGenerationView', () => { hotelId: 'HOTEL-TEST', peoplePerRoom: 2, roomType: 'DLX', - paymentType: 'CA', + paymentType: 'BTQR', nationality: 'CHN', }) expect(URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob)) @@ -176,6 +176,27 @@ describe('ReservationRoomingListGenerationView', () => { expect(wrapper.text()).toContain('rooming-list-HOTEL-TEST.xlsx') }) + it('allows submitting CA payment type when selected', async () => { + vi.mocked(generateReservationRoomingListExcel).mockResolvedValue({ + blob: new Blob(['xlsx-binary'], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }), + fileName: 'rooming-list-HOTEL-TEST.xlsx', + }) + const wrapper = mountView() + await fillRequiredFields(wrapper) + await wrapper.find('[data-testid="rooming-list-payment-type"]').setValue('CA') + + await wrapper.find('[data-testid="rooming-list-submit"]').trigger('click') + await flushPromises() + + expect(generateReservationRoomingListExcel).toHaveBeenCalledWith( + expect.objectContaining({ + paymentType: 'CA', + }), + ) + }) + it('does not submit the same source file again while generation is pending', async () => { let resolveGeneration!: (value: { blob: Blob; fileName: string }) => void vi.mocked(generateReservationRoomingListExcel).mockImplementation( diff --git a/client/src/views/reservation/ReservationRoomingListGenerationView.vue b/client/src/views/reservation/ReservationRoomingListGenerationView.vue index 1845ed8..0369ebe 100644 --- a/client/src/views/reservation/ReservationRoomingListGenerationView.vue +++ b/client/src/views/reservation/ReservationRoomingListGenerationView.vue @@ -141,6 +141,9 @@ :class="validationClass('paymentType')" v-bind="validationAria('paymentType')" > + @@ -265,7 +268,7 @@ type RoomingListFieldKey = | 'paymentType' | 'nationality' -const allowedPaymentTypes = new Set(['CA']) +const allowedPaymentTypes = new Set(['BTQR', 'CA']) const allowedNationalities = new Set(['KR', 'CHN']) const { t, te } = useI18n() @@ -275,7 +278,7 @@ const form = reactive({ file: null as File | null, peoplePerRoom: '', roomType: '', - paymentType: 'CA', + paymentType: 'BTQR', nationality: '', }) diff --git a/docs/project/frontend-backend/backend-to-frontend-notes.md b/docs/project/frontend-backend/backend-to-frontend-notes.md index f41eb79..05ad4a5 100644 --- a/docs/project/frontend-backend/backend-to-frontend-notes.md +++ b/docs/project/frontend-backend/backend-to-frontend-notes.md @@ -487,7 +487,7 @@ RESERVATION_ROOMING_LIST_GENERATE - 每组第一位旅客写入 `Name` / `First Name`,同组剩余旅客写入 `Accompanying Guests`,多人用英文逗号分隔。 - 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` 默认且当前唯一允许值为 `CA`;`Nationality` 只允许 `KR`、`CHN`。 +- CP2 起目标默认值区域只保留 `Payment Type` 和 `Nationality`:`Payment Type` 默认 `BTQR`,当前允许 `BTQR`、`CA`;`Nationality` 只允许 `KR`、`CHN`。 - CP2 页面不再展示或提交 `Title`、`Rate Code`、`VIP`、`Email`、`ID Type`、`ID Number`,这些目标列第一版固定为空。 - 第一版接口成功后直接返回 Excel 文件流,前端应按 Blob 下载处理,不要期待 JSON 里的 URL。 - 失败时返回统一 JSON 错误,例如 `ROOMING_LIST_VALIDATION_FAILED`、`ROOMING_LIST_SOURCE_FILE_INVALID`、`HOTEL_ACCESS_DENIED` 或 `FRONTEND_PERMISSION_DENIED`。 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 4254ed5..11fd5a4 100644 --- a/docs/project/frontend-backend/frontend-to-backend-api-requests.md +++ b/docs/project/frontend-backend/frontend-to-backend-api-requests.md @@ -1201,7 +1201,7 @@ CP2 前端提交字段: | `hotel_id` | 否 | 当前选择酒店;不传时由后端按登录用户酒店上下文解析。 | | `people_per_room` | 是 | 每间房人数,正整数。 | | `room_type` | 是 | 目标 Excel 的 Room Type。 | -| `payment_type` | 否 | 目标 Excel 的 Payment Type;默认和当前唯一允许值为 `CA`。 | +| `payment_type` | 否 | 目标 Excel 的 Payment Type;默认 `BTQR`,当前允许 `BTQR`、`CA`。 | | `nationality` | 是 | 目标 Excel 的 Nationality;只允许 `KR`、`CHN`。 | CP2 前端不再提交: @@ -1231,7 +1231,7 @@ id_number - 页面第一部分保留上传来源 Excel、每房人数、Room Type。 - 目标默认值区域只保留 Payment Type 和 Nationality 两个下拉。 -- Payment Type 默认选中 `CA`,当前不开放其他选项。 +- Payment Type 默认选中 `BTQR`,当前提供 `BTQR`、`CA` 两个选项。 - Nationality 只提供 `KR` 和 `CHN`。 - 前端不读取完整 Excel 作为权威解析结果;旅游日期和成人数以后端生成结果为准。 - 页面不要把上传文件内容、客人名单、生成文件内容写入浏览器日志、埋点、错误上报、URL 或 localStorage。 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 7764a9b..843ff65 100644 --- a/docs/project/requirements/M010-rooming-list-excel-generation-v1.md +++ b/docs/project/requirements/M010-rooming-list-excel-generation-v1.md @@ -33,7 +33,7 @@ CP2 目标调整: - 后端从来源 Excel 的 `旅游日期` 列解析入住日期和离店日期,不再要求前端手填 `arrival` / `departure`。 - 后端按分房结果计算目标 `Adults`,不再要求前端手填 `adults`。 - 目标默认值区域只保留 `Payment Type` 和 `Nationality` 两个字段。 -- `Payment Type` 前端默认为 `CA`,后端也按 `CA` 作为当前唯一允许值校验。 +- `Payment Type` 前端默认为 `BTQR`,当前可选择 `BTQR` 或 `CA`,后端按同一枚举校验。 - `Nationality` 前端只允许选择 `KR` 或 `CHN`,后端按同一枚举校验。 第一阶段不做: @@ -169,7 +169,7 @@ people_per_room = 3 | `Number of Rooms` | 第一版每行固定为 `1`,后续如目标模板变化再调整 | | `Adults` | CP2 由后端按当前房间实际人数派生 | | `Children` | CP2 固定为 `0` | -| `Payment Type` | 前端选择;当前默认和唯一允许值为 `CA` | +| `Payment Type` | 前端选择;当前默认值为 `BTQR`,允许 `BTQR`、`CA` | | `VIP` | CP2 固定为空,后续如有稳定默认值再单独确认 | | `Accompanying Guests` | 同房其他旅客姓名,英文逗号分隔 | | `Nationality` | 前端选择;当前只允许 `KR`、`CHN` | @@ -202,7 +202,7 @@ Content-Type: multipart/form-data | `hotel_id` | string | 否 | 酒店 ID;不传时按当前登录用户默认酒店或单酒店上下文解析 | | `people_per_room` | integer | 是 | 每间房人数,必须大于 0 | | `room_type` | string | 是 | 目标 Excel 的 `Room Type` | -| `payment_type` | string | 否 | CP2 目标 Excel 的 `Payment Type`;为空时后端按 `CA` 处理;当前只允许 `CA` | +| `payment_type` | string | 否 | CP2 目标 Excel 的 `Payment Type`;为空时后端按 `BTQR` 处理;当前只允许 `BTQR`、`CA` | | `nationality` | string | 是 | CP2 目标 Excel 的 `Nationality`;当前只允许 `KR`、`CHN` | CP2 不再接收以下前端业务字段作为生成依据: @@ -244,7 +244,7 @@ Content-Disposition: attachment; filename="rooming-list-.xlsx" - multipart 必填字段缺失,返回 `ROOMING_LIST_VALIDATION_FAILED`。 - `people_per_room` 数字格式错误,返回 `ROOMING_LIST_VALIDATION_FAILED`。 -- `payment_type` 非 `CA`,返回 `ROOMING_LIST_VALIDATION_FAILED`。 +- `payment_type` 非 `BTQR` / `CA`,返回 `ROOMING_LIST_VALIDATION_FAILED`。 - `nationality` 非 `KR` / `CHN`,返回 `ROOMING_LIST_VALIDATION_FAILED`。 - 来源文件缺失、非 `.xls` / `.xlsx`、无法读取、缺少 `护照全名` 表头或缺少 / 无法解析 `旅游日期` 表头,返回 `ROOMING_LIST_SOURCE_FILE_INVALID`。 - 同一来源文件出现多个不同旅游日期区间,CP2 返回受控错误,不生成 Excel。 @@ -309,7 +309,7 @@ RESERVATION_ROOMING_LIST_GENERATE - 填写每间房人数。 - 填写目标 `Room Type`。 - 目标默认值区域只保留 `Payment Type` 和 `Nationality`。 -- `Payment Type` 使用下拉或固定选择,默认 `CA`,当前不开放其他值。 +- `Payment Type` 使用下拉选择,默认 `BTQR`,当前可选择 `BTQR` 或 `CA`。 - `Nationality` 使用下拉,只允许 `KR`、`CHN`。 - 展示解析预览:总人数、预计房间数、前几行分房结果。 - 点击生成后下载 `.xlsx`。 @@ -336,7 +336,7 @@ RESERVATION_ROOMING_LIST_GENERATE - 尾房人数不足时仍生成一行。 - `Adults` 按每个房间实际人数派生,尾房人数不足时不使用满房人数。 - `Children` 默认为 `0`。 -- `payment_type` 为空时按 `CA` 处理,非 `CA` 返回受控错误。 +- `payment_type` 为空时按 `BTQR` 处理,非 `BTQR` / `CA` 返回受控错误。 - `nationality=KR` / `CHN` 成功,其他值返回受控错误。 - 来源文件中多个不同旅游日期区间返回受控错误。 - 缺少 `旅游日期` 表头或旅游日期格式无法解析时返回受控错误。 diff --git a/docs/project/security-access-control-boundary.md b/docs/project/security-access-control-boundary.md index 0dd9647..60381ff 100644 --- a/docs/project/security-access-control-boundary.md +++ b/docs/project/security-access-control-boundary.md @@ -67,7 +67,7 @@ | `POST /api/reservation/tasks/{taskId}/opera-operations/{operationId}/retry` | `FRONTEND_USER` | 当前为 OPERA 模拟 | 登录 + `RESERVATION_OPERA_SIM_EXECUTE` + 酒店访问权 | 必须写业务审计和 attempt | | `GET /api/reservation/tasks/{taskId}/audits` | `FRONTEND_USER` | 已强制 Bearer 登录 + `RESERVATION_AUDIT_READ`;按任务实际所属酒店校验访问权 | 保持登录 + `RESERVATION_AUDIT_READ` + 酒店访问权 | 查询审计不再写审计 | | `POST /api/reservation/invoices/manual-generations` | `FRONTEND_USER` | 已实现 M009 CP2;强制 Bearer 登录 + `RESERVATION_INVOICE_GENERATE` + 酒店访问权;`task_id` / `order_id` 可为空,传入时反查对象所属酒店 | 保持登录 + `RESERVATION_INVOICE_GENERATE` + 酒店访问权;后续如增加历史列表或预填接口需单独登记权限 | 写业务审计,记录来源类型、模板版本、生成结果摘要;生成失败写入生成记录安全错误摘要 | -| `POST /api/reservation/rooming-lists/generations` | `FRONTEND_USER` | 已实现 M010 CP1 / CP2;multipart 上传来源名单并直接下载 `.xlsx`;已在 multipart 参数绑定前前置校验登录和生成权限 | 登录 + `RESERVATION_ROOMING_LIST_GENERATE` + 酒店访问权;后端从来源 Excel `旅游日期` 派生 Arrival / Departure,Adults 按分房结果计算,目标默认值区域只保留 Payment Type / Nationality;第一版仍不落库、不上传 OSS | CP1 / CP2 不落生成记录表;错误响应不得记录完整名单、证件信息、源文件内容或生成文件内容;后续若增加历史记录再补业务审计 | +| `POST /api/reservation/rooming-lists/generations` | `FRONTEND_USER` | 已实现 M010 CP1 / CP2;multipart 上传来源名单并直接下载 `.xlsx`;已在 multipart 参数绑定前前置校验登录和生成权限 | 登录 + `RESERVATION_ROOMING_LIST_GENERATE` + 酒店访问权;后端从来源 Excel `旅游日期` 派生 Arrival / Departure,Adults 按分房结果计算,目标默认值区域只保留 Payment Type / Nationality;Payment Type 默认 `BTQR` 且仅允许 `BTQR` / `CA`;第一版仍不落库、不上传 OSS | CP1 / CP2 不落生成记录表;错误响应不得记录完整名单、证件信息、源文件内容或生成文件内容;后续若增加历史记录再补业务审计 | ### 3.2.1 V4 工作台、订单详情和任务详情页展示层技术信息边界 diff --git a/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/common/request/ReservationRoomingListGenerationRequest.java b/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/common/request/ReservationRoomingListGenerationRequest.java index 94a33b4..998a837 100644 --- a/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/common/request/ReservationRoomingListGenerationRequest.java +++ b/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/common/request/ReservationRoomingListGenerationRequest.java @@ -6,7 +6,7 @@ package cn.nianxx.thhotel.workflows.reservation.common.request; * @param hotelId 酒店 ID;为空时按当前登录用户默认酒店解析 * @param peoplePerRoom 每间房人数 * @param roomType 目标 Excel 的 Room Type - * @param paymentType 目标 Excel 的 Payment Type;当前默认和唯一允许值为 CA + * @param paymentType 目标 Excel 的 Payment Type;默认 BTQR,当前允许 BTQR 或 CA * @param nationality 目标 Excel 的 Nationality;当前只允许 KR 或 CHN */ public record ReservationRoomingListGenerationRequest( diff --git a/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationRoomingListGenerationServiceImpl.java b/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationRoomingListGenerationServiceImpl.java index 98d47f9..96fabcf 100644 --- a/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationRoomingListGenerationServiceImpl.java +++ b/server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationRoomingListGenerationServiceImpl.java @@ -27,7 +27,9 @@ public class ReservationRoomingListGenerationServiceImpl implements ReservationR private static final long MAX_SOURCE_FILE_BYTES = 20L * 1024L * 1024L; private static final DateTimeFormatter FILE_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); - private static final String DEFAULT_PAYMENT_TYPE = "CA"; + private static final String PAYMENT_TYPE_BTQR = "BTQR"; + private static final String PAYMENT_TYPE_CA = "CA"; + private static final String DEFAULT_PAYMENT_TYPE = PAYMENT_TYPE_BTQR; private static final String NATIONALITY_KR = "KR"; private static final String NATIONALITY_CHN = "CHN"; @@ -132,8 +134,8 @@ public class ReservationRoomingListGenerationServiceImpl implements ReservationR return DEFAULT_PAYMENT_TYPE; } String code = normalized.toUpperCase(java.util.Locale.ROOT); - if (!DEFAULT_PAYMENT_TYPE.equals(code)) { - errors.add("payment_type: 当前只允许 CA。"); + if (!PAYMENT_TYPE_BTQR.equals(code) && !PAYMENT_TYPE_CA.equals(code)) { + errors.add("payment_type: 当前只允许 BTQR 或 CA。"); } return code; } 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 033cfef..a602a98 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 @@ -89,7 +89,7 @@ class ReservationRoomingListGenerationControllerTest { .param("hotel_id", "HOTEL-TEST") .param("people_per_room", "2") .param("room_type", "UG1") - .param("payment_type", "CA") + .param("payment_type", "BTQR") .param("nationality", "CHN")) .andExpect(status().isOk()) .andExpect(content().contentType( @@ -109,7 +109,7 @@ class ReservationRoomingListGenerationControllerTest { .andExpect(result -> assertThat(workbookCell(result.getResponse().getContentAsByteArray(), 1, 10)) .isEqualTo("0")) .andExpect(result -> assertThat(workbookCell(result.getResponse().getContentAsByteArray(), 1, 11)) - .isEqualTo("CA")) + .isEqualTo("BTQR")) .andExpect(result -> assertThat(workbookCell(result.getResponse().getContentAsByteArray(), 1, 14)) .isEqualTo("CHN")) .andExpect(content().string(not(containsString("P123456")))); 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 cbdcb79..071b35c 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 @@ -67,7 +67,7 @@ class ReservationRoomingListGenerationServiceImplTest { assertThat(cellText(firstRoom.getCell(8))).isEqualTo("1"); assertThat(cellText(firstRoom.getCell(9))).isEqualTo("3"); assertThat(cellText(firstRoom.getCell(10))).isEqualTo("0"); - assertThat(cellText(firstRoom.getCell(11))).isEqualTo("CA"); + assertThat(cellText(firstRoom.getCell(11))).isEqualTo("BTQR"); assertThat(cellText(firstRoom.getCell(13))).isEqualTo("ZHANG GAILI, LI HONG"); assertThat(cellText(firstRoom.getCell(14))).isEqualTo("CHN"); assertThat(cellText(firstRoom.getCell(15))).isEmpty(); @@ -88,6 +88,26 @@ class ReservationRoomingListGenerationServiceImplTest { } } + @Test + void shouldAcceptCaPaymentTypeForRoomingListWorkbook() throws Exception { + when(hotelContextService.requireAccessibleHotel("HOTEL-TEST")).thenReturn("HOTEL-TEST"); + + ReservationRoomingListGeneratedFile generatedFile = service.generate( + sourceFileWithTravelDate("2026-05-09 - 05-14", List.of("LI/CHUNHONG")), + new ReservationRoomingListGenerationRequest( + "HOTEL-TEST", + 1, + "UG1", + "ca", + "CHN"), + actor()); + + try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(generatedFile.content()))) { + Row firstRoom = workbook.getSheetAt(0).getRow(1); + assertThat(cellText(firstRoom.getCell(11))).isEqualTo("CA"); + } + } + @Test void shouldParseTravelDateWithNumericHyphenRange() throws Exception { when(hotelContextService.requireAccessibleHotel("HOTEL-TEST")).thenReturn("HOTEL-TEST"); @@ -156,7 +176,7 @@ class ReservationRoomingListGenerationServiceImplTest { assertThat(exception.getErrorCode()).isEqualTo("ROOMING_LIST_VALIDATION_FAILED"); assertThat(exception.getDetails()) - .contains("payment_type: 当前只允许 CA。", "nationality: 当前只允许 KR 或 CHN。"); + .contains("payment_type: 当前只允许 BTQR 或 CA。", "nationality: 当前只允许 KR 或 CHN。"); } @Test