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