> **关联文档**:`需求说明书.html`(需求基线 v1.0,2026-07-26,状态:需求确认中)|`酒店模块需求缺口清单.md`(需求对照 + P0/P1 缺口台账 + 补齐排期)
> **本文档职责**:代码地图与实现进度,回答「**代码里有什么**」。需求视角的缺口、验收对照与排期不在本文档维护,避免双份状态互相失真。
### 1.3 Forge 已有代码
```
forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/
├── controller/
│ ├── HotelQrCodeController.java ✅ 二维码管理 + 在住会话操作(入住/退房/打扫/维护/入住记录)
│ ├── HotelBusinessHoursController.java ✅ 营业时段管理(含 check-current 当前营业状态)
│ ├── HotelDishController.java ✅ 菜品管理(分页/详情/增删改/上下架/售罄/恢复/批量/销量排行)
│ ├── HotelDishCategoryController.java ✅ 菜品分类管理(分页/详情/增删改/启禁用)
│ ├── HotelDishSpecController.java ✅ 菜品规格管理(规格组 + 选项)
│ └── open/ ✅ 免登录开放接口(SaTokenConfig 已排除 /hotel/open/** 的登录与 API 权限校验)
│ ├── HotelOpenController.java ✅ 扫码查询 GET /hotel/open/scan
│ ├── HotelCustomerController.java ✅ 顾客端 10 接口(categoryEnabled/dishPage/dishDetail/orderCreate/orderDetail/orderPage/customerRefund/abandonOrder/businessHours + SSE订阅)
│ ├── HotelPayController.java ✅ 支付 5 接口(create/notify/status/alipayQuery/mockSuccess)
│ └── AlipayAuthController.java ✅ 支付宝小程序用户授权 getUserInfo(姓名 + 手机号)
├── domain/
│ ├── HotelQrCode.java ✅ 二维码实体
│ ├── HotelQrBindLog.java ✅ 绑定日志实体
│ ├── HotelRoom.java ✅ 房间实体(⚠️ 无「启停点餐」字段)
│ ├── HotelRoomType.java ✅ 房型实体
│ ├── HotelRoomStay.java ✅ 在住会话实体(@TableLogic(value="0", delval="id"))
│ ├── HotelBusinessHours.java ✅ 营业时段实体
│ ├── HotelDish.java ✅ 菜品实体(image 主图 + images 附加图)
│ ├── HotelDishCategory.java ✅ 菜品分类实体
│ ├── HotelDishSpecGroup.java ✅ 规格组实体(挂 dishId)
│ ├── HotelDishSpecOption.java ✅ 规格选项实体(priceExtra 加价)
│ ├── HotelDishAddition.java ✅ 加料实体(extraPrice 加价)
│ └── HotelDishLog.java ⚠️ 菜品操作日志实体(表已建,**零业务代码**:无写入、无查询)
├── dto/
│ ├── QrCodeBatchGenerateDTO.java ✅
│ ├── QrCodeBindDTO.java ✅
│ ├── QrCodeQueryDTO.java ✅
│ ├── HotelRoomDTO.java ✅
│ ├── HotelRoomTypeDTO.java ✅
│ ├── HotelDishDTO.java ✅ 含 specGroups + additions 嵌套
│ ├── HotelDishCategoryDTO.java ✅
│ └── DishBatchOperationDTO.java ✅ 批量上下架/售罄
├── vo/
│ ├── HotelQrCodeVO.java ✅
│ ├── HotelQrBindLogVO.java ✅
│ ├── QrCodeScanVO.java ✅ RoomInfo 含 roomStatus/stayId/guestName/guestPhone
│ ├── HotelRoomVO.java ✅ 含 stayId/guestName/guestPhone/checkInTime
│ ├── HotelRoomTypeVO.java ✅
│ ├── HotelRoomStayVO.java ✅
│ ├── RoomCheckOutVO.java ✅ needConfirm/message/activeOrders/checkedOut
│ ├── HotelDishVO.java ✅ 含 specGroups + additions
│ ├── HotelDishCategoryVO.java ✅
│ └── BusinessHoursStatusVO.java ✅ 营业状态推导(configured/open/todayPeriods/nextText 等)
├── mapper/
│ ├── HotelQrCodeMapper.java ✅
│ ├── HotelQrBindLogMapper.java ✅
│ ├── HotelRoomMapper.java ✅ XML 含 JoinActiveStay
│ ├── HotelRoomTypeMapper.java ✅
│ ├── HotelRoomStayMapper.java ✅ XML 显式 AND del_flag = 0
│ ├── HotelBusinessHoursMapper.java ✅
│ ├── HotelDishMapper.java ✅ XML 含 selectSalesRanking 销量排行
│ ├── HotelDishCategoryMapper.java ✅
│ ├── HotelDishSpecGroupMapper.java ✅
│ ├── HotelDishSpecOptionMapper.java ✅
│ ├── HotelDishAdditionMapper.java ✅
│ └── HotelDishLogMapper.java ⚠️ 仅接口,无调用方
├── service/
│ ├── HotelQrCodeService.java ✅
│ ├── HotelBusinessHoursService.java ✅ 含 checkCurrentBusinessHours() + currentStatus()
│ ├── HotelRoomStayService.java ✅ 入住/退房/打扫/维护/入住记录/联系人回写
│ ├── HotelDishService.java ✅ 含 dishSalesRanking(limit)
│ ├── HotelDishCategoryService.java ✅
│ ├── HotelDishSpecGroupService.java ✅
│ ├── HotelDishSpecOptionService.java ✅
│ └── impl/ ✅ 7 个实现类与接口一一对应
├── constant/
│ └── HotelQrConstants.java ✅ 含 STAY_ACTIVE / STAY_CHECKED_OUT;`QR_BASE_PATH` 已降为「未配置时的回退默认值」(2026-09-07)
├── utils/
│ └── QrCodeUtils.java ✅ `buildQrCodeUrl` 已拆三参(委派,兼容既有调用方)+ 四参(主实现,`basePath` 空串回退 `QR_BASE_PATH`)
└── notification/ ✅ 实时通知子包
├── controller/
│ └── NotificationController.java ✅ SSE 端点(subscribe/unsubscribe/connections/remote/notifyNewOrder)
└── manager/
└── SseEmitterManager.java ✅ 长连接管理 + 广播(NEW_ORDER / ORDER_READY / REFUND_REQUEST / CUSTOMER_ORDER_STATUS 四类事件)
> ⚠️ 文档修正:原记录的 `OrderNotificationService.java` **不存在**,通知广播能力由 `SseEmitterManager` 直接承担。
order/ ✅ 订单管理子包(独立于已有代码)
├── constant/
│ └── HotelOrderConstants.java ✅ 订单状态常量 0~11 + 支付状态 0~3 + paySource + PAY_TIMEOUT_MINUTES=15 + 退单发起方 + refund_status 0~2
├── domain/
│ ├── HotelOrder.java ✅ 订单实体(含 payStatus/payExpireTime/paySource)
│ └── HotelOrderItem.java ✅ 订单明细实体
├── dto/
│ └── OrderCreateDTO.java ✅ 创建订单请求(roomNo/roomId/contactName/contactPhone/deliveryFee/deliveryTimeType/customDeliveryTime/note/items;已移除 payMethod,**无 paySource 字段**,paySource 在创建支付时传参)
├── vo/
│ ├── HotelOrderVO.java ✅ 订单视图(含内部类 OrderItemVO)
│ └── OrderDashboardVO.java ✅ 看板统计视图
├── mapper/
│ ├── HotelOrderMapper.java ✅
│ └── HotelOrderItemMapper.java ✅
├── service/
│ ├── HotelOrderService.java ✅ 15 个方法接口(查询/看板/创建/5 步流转/拒单/3 类退单/审核/放弃);支付与超时取消在 pay 子包的 `HotelPayService`
│ └── impl/
│ └── HotelOrderServiceImpl.java ✅ 完整状态机 + 退单流程 + 支付超时处理
├── controller/
│ └── HotelOrderController.java ✅ 基路径 `/hotel/order`,15 个 REST 接口(`/page` `/detail` `/dashboard` `POST /` `/accept` `/reject` `/prepare` `/ready` `/deliver` `/complete` `/customerRefund` `/hotelRefund` `/refundApprove` `/refundReject` `/refund`)
pay/ ✅ 支付系统子包(支付宝沙箱 H5 WAP Pay + 安全加固 + 超时取消 + 退款 + Mock 开关)
├── config/
│ ├── AlipayConfig.java ✅ 支付宝配置(appId/密钥/网关/回调地址,@ConfigurationProperties)
│ └── HotelPayConfig.java ✅ 支付安全开关(`hotel.pay.mock-enabled`,**代码级默认 `false`**;`@PostConstruct` 打印开关状态,开启时 WARN 级资金安全告警)
├── domain/
│ └── HotelPayLog.java ✅ 支付流水实体(outTradeNo/tradeNo/payAmount/payStatus/payChannel/paySource)
├── dto/
│ └── AlipayAuthDTO.java ✅ 支付宝授权请求(authCode + phoneResponse)
├── vo/
│ ├── PayResultVO.java ✅ 支付结果视图(payStatus/payForm/tradeNo)
│ └── AlipayAuthVO.java ✅ 授权结果(userId/userName/phone)
├── mapper/
│ └── HotelPayLogMapper.java ✅ @InterceptorIgnore + 幂等更新 + 按 orderId 查最新流水
│ (XML:resources/mapper/business/hotel/pay/HotelPayLogMapper.xml,幂等 UPDATE WHERE pay_status != 1)
├── service/
│ ├── HotelPayService.java ✅ createPay/handlePayCallback/queryAlipayTradeStatus/refundOrder/cancelTimeoutOrder
│ ├── AlipayAuthService.java ✅ 支付宝用户信息 + 手机号解密
│ └── impl/
│ ├── HotelPayServiceImpl.java ✅ WAP Pay + 回调 + 退款 + 5 层安全加固 + `resolvePayChannel`/`mockOrReject` 渠道单一收口
│ └── AlipayAuthServiceImpl.java ✅
└── task/
└── PayTimeoutTask.java ✅ @Scheduled(fixedDelay=120000) 每 2 分钟跨租户扫描超时未支付订单并取消(状态 10)
```
> ⚠️ **文档修正(3 处)**:
> 1. 原记录的 `pay/constant/HotelPayConstants.java` **不存在**,payStatus / paySource 常量统一放在 `order/constant/HotelOrderConstants.java`。
> 2. 原记录的 `pay/dto/PayCreateDTO.java` **不存在**,创建支付为 `createPay(Long orderId, String paySource)` 直接传参。
> 3. `HotelPayController` 与 `AlipayAuthController` 实际位于 `controller/open/` 包下,**非** `pay/controller/open/`。
```
forge-admin-ui/src/views/hotel/
├── board.vue ✅ 房态看板(房间卡片网格 + 状态 Tab + 入住/退房/打扫/维护/入住记录,V1.0.107 建菜单)
├── qrcode.vue ✅ 二维码管理(AiCrudPage + 批量生成/绑定/解绑/批量下载 ZIP)
├── room.vue ✅ 房间管理(AiCrudPage 纯 CRUD;⚠️ 在住业务操作已拆到 board.vue,编辑表单不含状态字段)
├── roomType.vue ✅ 房型管理(AiCrudPage)
├── dish.vue ✅ 菜品管理(AiCrudPage,图片预览/上下架/售罄;⚠️ 无加料与规格绑定编辑入口)
├── dishCategory.vue ✅ 菜品分类管理(AiCrudPage,含启禁用)
├── dishSpec.vue ✅ 菜品规格管理(AiCrudPage + 选项管理弹窗,规格组按 dishId 挂载)
├── businessHours.vue ✅ 营业时段管理(AiCrudPage,时间段下拉选择)
└── order.vue ✅ 订单管理(AiCrudPage + 看板统计 + 流程操作 + 详情弹窗 + 退单审核 + 退款状态列 + 手动退款 + SSE 通知)
└── restaurantOrder.vue ✅ 餐厅前台订单管理(三 Tab:全部订单/订单看板/营业时间;SSE 实时通知;API 复用 hotel 模块接口;✅ 订单详情弹窗 + 按状态显示操作按钮)
└── kitchenOrder.vue ✅ 厨房订单页面(日期筛选 + 统计卡片 + 分组展示:待备餐/制作中/已完成 + 详情弹窗 + 出餐操作 + SSE 自动刷新 + TTS 语音播报菜品名+房号)
forge-admin-ui/src/api/
└── hotel.js ✅ 酒店模块 API(房间/房型/二维码/菜品/分类/规格/营业时段/订单/退款/在住会话/销量排行/厨房订单列表+统计)
forge-admin-ui/src/composables/
└── useOrderNotification.js ✅ SSE 订阅 + MP3 提示音 + 桌面通知 + 防抖合并 + 首次交互补播
forge-h5-ui/src/pages/hotel/
├── scan-bind.vue ✅ 【员工 H5】扫码绑定房间(**双职责页**,见下方说明)
├── qr-scanner.vue ✅ 【员工 H5】扫码页(html5-qrcode,依赖 DOM + 摄像头)
├── staff-order.vue ✅ 【员工 H5/PAD】餐厅订单管理(2026-09-09 新增)
│ 三 Tab:全部订单(统计卡片+分组列表+操作按钮)/ 订单看板(6 列卡片横向滚动+自适应列宽)/ 营业时间(时段管理+暂停接单)
│ 10 秒轮询实时刷新(替代 SSE);首页快捷入口已添加;返回键 switchTab 回首页
├── hotel-order.vue ✅ 【员工 H5/PAD】宾馆前台 · 订单总览(2026-09-09 新增)
│ 3 张统计卡片(今日订单/进行中/退单)+ 房间号搜索 + 状态下拉筛选 + 卡片式订单列表 + 底部 TabBar(订单总览/退单管理)
├── hotel-order-refund.vue ✅ 【员工 H5/PAD】宾馆前台 · 退单管理/订单详情(2026-09-09 新增)
│ 列表模式(退单审核中+已退单)+ 详情模式(订单信息卡片+菜品明细+退单操作区+状态时间线);支持同意/驳回退单
└── customer/ 【顾客小程序】8 页(小程序化时仅保留这 8 页;`pages.json` L67-87 已用 `// #ifdef H5` 裁掉员工两页)
├── room-confirm.vue ✅ 房号确认入口页(`resolveScanParams` 三端取参 + 扫码验证/房间卡片/开始点餐 + 🕐 非营业时段整页拦截)
├── menu.vue ✅ 点餐菜单页 Tab1(配送信息栏/Banner/搜索/分类/菜品列表/购物车浮层 + 🕐 非营业菜品区拦截)
├── dish-detail.vue ✅ 菜品详情页(大图/规格选择/加料/备注/数量/加入购物车)
├── cart.vue ✅ 购物车页 Tab3(商品列表/数量控制/备注/费用汇总/结算)
├── order-confirm.vue ✅ 确认下单页(配送信息/送达时间/明细/提交 + 🕐 提交前营业状态刷新阻断,已移除硬编码支付方式)
├── pay.vue ✅ 支付确认页(按后端 `payChannel` 分流 + 支付宝 H5 WAP Pay + 轮询 + 主动查询兜底 + 倒计时;else 已拆为显式报错,不再兜底调 Mock)
├── order-status.vue ✅ 订单状态页 Tab2(进度条/状态卡片/SSE 实时推送 + 30秒兜底轮询/自定义退单弹窗)
└── orders.vue ✅ 订单列表页 Tab2(订单卡片列表/分页加载/状态标签/待支付去支付按钮)
forge-h5-ui/src/components/hotel/
└── HotelTabBar.vue ✅ 酒店顾客端 3-tab TabBar(点餐/订单/购物车);已按 `#ifdef H5` / `#ifdef MP` 拆双分支(146 行)
forge-h5-ui/src/styles/
└── hotel-theme.css ✅ 亚朵深墨绿主题 CSS 变量(--h-pri/--h-bg/--h-txt 等)
forge-h5-ui/src/store/modules/
└── hotel-order.js ✅ Pinia 购物车 Store(roomInfo/cartItems/orderNote/businessHours,持久化不含营业状态)
forge-h5-ui/src/api/
└── index.js ✅ 顾客端 API + 员工侧 API(2026-09-09 新增 13 个餐厅员工接口 + 4 个酒店前台接口)
```
> ⚠️ **`scan-bind.vue` 是双职责页**(共 812 行):用 `authStore.isLogin` 分流员工/顾客。
> - **未登录**(顾客):`loadScanInfo()` L233-278 调 `/hotel/open/scan`,已绑定则 `uni.redirectTo` 到 `customer/room-confirm`,未绑定提示「二维码未启用」
> - **已登录**(员工):展示绑定/解绑界面,调 `hotelRoomListAll()` / `hotelQrBind()` / `hotelQrUnbind()`(均需登录,操作前走 `ensureLogin`)
>
> 员工扫码绑定房间已确认为**员工登录 H5 后操作,不属于小程序**(功能已完成)。小程序化时 `scan-bind.vue` + `qr-scanner.vue` 用 `#ifdef H5` 整页排除,其独占的 `AiIcon`/`AiButton`/`AiResult`/`html5-qrcode` 自动不进小程序包。**顾客小程序的扫码入口页是 `customer/room-confirm.vue`**,不是 `scan-bind.vue`。
---
## 二、模块迁移状态总览
| # | 模块 | 后端 | 数据库 | 前端 PC | 前端 H5 | 状态 |
|---|------|------|--------|--------|--------|------|
| 1 | room(房间/房型管理) | ✅ | ✅ | ✅ | ✅ | **已完成** |
| 2 | qrcode(二维码管理) | ✅ | ✅ | ✅ | ✅ | **已完成**;✅ 二维码 URL 路径已配置化(`hotel.qr.path`:dev 保持旧 hash 值、生产默认 `/r/`,三层回退保证既有码 URL 逐字符不变,2026-09-07) |
| 3 | open(开放扫码接口) | ✅ | — | — | ✅ | **已完成** |
| 4 | dish-spec(菜品规格管理) | ✅ | ✅ | ✅ | — | **已完成** |
| 5 | business-hours(营业时段) | ✅ | ✅ | ✅ | ✅ | **已完成**(后端 currentStatus 推导 + 开放接口 + H5 三页接入 + 下单校验) |
| 6 | hotelconfig(酒店基础配置) | ❌ | ❌ | ❌ | — | **暂缓**(暂不需要,配送费 + 预计配送时长待讨论) |
| 7 | dish(菜品管理) | ✅ | ✅ | ⚠️ | — | **核心已完成**(加料后端已通但 **PC 无编辑入口**;操作日志 **仅实体+Mapper,零业务代码**) |
| 8 | order(订单管理) | ✅ | ✅ | ✅ | ✅ | **主链路完成**(后端+PC管理端+H5顾客端+支付系统);✅ 金额重算已计入规格/加料加价(2026-09-04);✅ **餐厅前台订单管理页** `restaurantOrder.vue` 已完成(三 Tab:全部订单/订单看板/营业时间,看板含已拒单列,支持全日期筛选,V1.0.117 建菜单);✅ **餐厅 PAD 移动端** `staff-order.vue` 已完成(2026-09-09,10 秒轮询+看板自适应列宽);✅ **宾馆前台移动端** `hotel-order.vue` + `hotel-order-refund.vue` 已完成(2026-09-09,订单总览+退单管理+详情时间线) |
| 8.1 | pay(支付系统) | ⚠️ | ✅ | — | ✅ | **支付宝 H5 完成**(沙箱 WAP Pay + 5 层安全加固 + 轮询兜底 + 退款 + 超时取消);✅ **Mock 免付款资金红线已收口**(2026-09-07:`hotel.pay.mock-enabled` 开关 + `resolvePayChannel`/`mockOrReject` 单一收口 + `paySource` 必传 + `refundOrder` 渠道显式判定 + `pay.vue` else 拆分);🔴 **AGENTS.md 5.9 合规未闭环**(代码先行、Spec 未补、人工审查未做);🔴 **支付宝小程序支付 100% 不可用**(无 `ALIPAY_MP` 渠道、`my.tradePay` 拿到 `undefined` tradeNo);⏸️ 微信支付暂缓,见缺口清单 3.3 |
| 8.2 | customer-open(顾客端开放接口) | ✅ | — | — | ✅ | **已完成**(10 个免登录接口,`requireTenantId()` 统一校验);⏳ `stayId` 归属校验(R2)随小程序化收口,见缺口清单 3.2 |
| 8.3 | alipay-auth(支付宝授权) | ✅ | — | — | ✅ | **已完成且已调用**:`getUserInfo`(authCode → 姓名,加密响应 → 手机号),`room-confirm.vue` L376-396 已接入授权成功/取消/失败处理;✅ 手机号日志已脱敏(2026-09-07,符合 AGENTS.md 5.9) |
| 9 | notification(实时通知) | ⚠️ | — | ✅ | — | **已完成**(SSE + MP3 声音 + 桌面通知 + 防抖合并 + 跨服务转发);仅 NEW_ORDER / REFUND_REQUEST 两类事件,**无接单/出餐通知** |
| 9.1 | stay(在住会话 + 订单退款) | ✅ | ✅ | ✅ | ✅ | **全部完成**(入住/退房/打扫/维护 + 退房联动取消 + 自动/手动退款,见 3.10) |
| 9.2 | board(房态看板) | ✅ | ✅ | ✅ | — | **已完成**(独立页面承接入住/退房/打扫/维护操作,V1.0.107 建菜单) |
| 10 | kitchen(厨房端) | ✅ | — | ⚠️ | ❌ | **部分完成**(厨房订单页:日期筛选+分组展示+统计+详情弹窗+出餐操作;⚠️ 无语音播报/无小票打印) |
> 🔴 标记项与全部未完成项的**需求依据、证据链、修复方向与补齐排期**统一记在 `酒店模块需求缺口清单.md`,本表只保留实现状态。
---
## 三、各模块详细任务清单
### 3.1 hotelconfig — 酒店基础配置 ⏸️ 暂缓
> **状态**: 暂缓开发。暂时用不到,其中**配送费**和**预计配送时长**功能待后续讨论后再实现。
#### 后端(待开发)
- [ ] 实体:`HotelConfig.java` — 酒店基础配置(名称/主题/配送费/预计配送时长)
- [ ] Mapper: `HotelConfigMapper.java` + XML
- [ ] Service: `HotelConfigService.java` — 获取/保存配置
- [ ] Controller: `HotelConfigController.java` — 配置接口
#### 数据库(待开发)
- [ ] 建表:`hotel_config`(IF NOT EXISTS)
- [ ] 菜单:酒店配置管理(sys_resource)
#### 前端(待开发)
- [ ] 页面:`forge-admin-ui/src/views/hotel/config.vue` — 酒店基础配置表单
#### ⚠️ 待讨论项
- **配送费规则**:按距离/按区域/固定费用?是否支持阶梯定价?
- **预计配送时长**:固定值还是动态计算?单位分钟?
- 这两项功能需要与业务方确认具体需求后再开发
#### 源框架参考
| 文件 | 说明 |
|------|------|
| `hotelconfig/domain/HotelConfig.java` | 配置实体 |
| `hotelconfig/domain/HotelBusinessHours.java` | 营业时段实体 |
| `hotelconfig/controller/HotelConfigController.java` | 全部接口 |
| `hotelconfig/service/HotelConfigService.java` | 全部业务逻辑 |
| `hotelconfig/mapper/xml/HotelConfigMapper.xml` | 配置查询 SQL |
| 前端:`views/hotel/admin/HotelConfigManagement.vue` | 配置管理页面 |
| 前端:`views/hotel/admin/BusinessHoursManagement.vue` | 营业时段页面 |
| 前端:`views/hotel/admin/BusinessHoursForm.vue` | 营业时段表单 |
| 前端:`api/hotel/HotelConfigService.js` | 配置 API |
| 前端:`api/hotel/HotelBusinessHoursService.js` | 营业时段 API |
---
### 3.2 dish — 菜品管理 ⭐⭐⭐
#### 后端
- [x] 实体:`HotelDish.java` — 菜品主表
- [x] 实体:`HotelDishCategory.java` — 菜品分类
- [x] 实体:`HotelDishSpecGroup.java` — 规格组(源框架同时存在 spec 和 spec_group,Forge 统一用 spec_group)
- [x] 实体:`HotelDishSpecOption.java` — 规格选项
- [x] 实体:`HotelDishAddition.java` — 加料
- [x] 实体:`HotelDishLog.java` — 操作日志
- [x] DTO: `HotelDishDTO.java` — 菜品传输对象
- [x] DTO: `HotelDishCategoryDTO.java` — 分类传输对象
- [x] DTO: `DishBatchOperationDTO.java` — 批量操作传输对象
- [x] VO: `HotelDishVO.java` / `HotelDishCategoryVO.java` — 视图对象
- [x] Mapper: `HotelDishMapper.java` + XML(含管理后台分页查询)
- [x] Mapper: `HotelDishCategoryMapper.java` + XML(含分页查询)
- [x] Mapper: `HotelDishSpecGroupMapper.java`
- [x] Mapper: `HotelDishSpecOptionMapper.java`
- [x] Mapper: `HotelDishAdditionMapper.java` + XML
- [x] Mapper: `HotelDishLogMapper.java`
- [x] Service: `HotelDishService.java` — 菜品 CRUD + 上下架 + 售罄/恢复 + 删除
- [x] Service: `HotelDishCategoryService.java` — 分类 CRUD + 启用/禁用
- [x] Service: `HotelDishSpecGroupService.java` — 规格组 CRUD + 级联删除选项
- [x] Service: `HotelDishSpecOptionService.java` — 规格选项 CRUD
- [x] Controller: `HotelDishController.java` — 菜品接口(分页/详情/新增/修改/删除/上下架/售罄/恢复)
- [x] Controller: `HotelDishCategoryController.java` — 分类接口(分页/新增/修改/删除/启禁用)
- [x] Controller: `HotelDishSpecController.java` — 规格组 + 规格选项统一接口
- [x] 加料:随菜品新增/修改一并保存(`HotelDishDTO.additions` → `saveAdditions()`),菜品详情回传 `additions`,H5 详情页可选加料并计算加价
- [ ] ⚠️ **PC 端无加料编辑入口**:`dish.vue` 表单不含加料与规格绑定项,等于加料数据无维护入口
- [ ] ⚠️ **菜品操作日志未落地**:`hotel_dish_log` 表 + `HotelDishLog` 实体 + `HotelDishLogMapper` 已备,但**无任何写入与查询代码**(无需独立 Controller,应在 `HotelDishServiceImpl` 各操作埋点)
#### 数据库
- [x] 建表:`hotel_dish`(IF NOT EXISTS)— 含 image(主图)+ images(附加图片)字段
- [x] 建表:`hotel_dish_category`(IF NOT EXISTS)
- [x] 建表:`hotel_dish_spec_group`(IF NOT EXISTS)
- [x] 建表:`hotel_dish_spec_option`(IF NOT EXISTS)
- [x] 建表:`hotel_dish_addition`(IF NOT EXISTS)
- [x] 建表:`hotel_dish_log`(IF NOT EXISTS)
- [x] 字典:`hotel_dish_status`(ON_SALE/OFF_SHELF)
- [x] 字典:`hotel_dish_sold_out`(0=正常/1=售罄)
- [x] 菜单:菜品管理 + 菜品分类管理(sys_resource)
- [x] 菜单权限:菜品增删改查按钮权限
- 迁移脚本:`V1.0.101__add_hotel_dish_module_tables.sql`
#### 前端
- [x] 页面:`forge-admin-ui/src/views/hotel/dish.vue` — 菜品管理(AiCrudPage)
- 序号列、主图缩略图(AuthImage)、全屏图片预览(主图 + 副图左右切换)
- 编辑按钮(仅下架可编辑)、上架/下架、售罄/恢复、删除
- 编辑表单:主厨推荐 (Switch)、状态 (Radio)、价格/制作时长/排序 (inputNumber)、主图/附加图片 (imageUpload)
- [x] 页面:`forge-admin-ui/src/views/hotel/dishCategory.vue` — 菜品分类管理(AiCrudPage)
- 编辑按钮、启用/禁用操作
- [x] API: `forge-admin-ui/src/api/hotel.js` 中追加菜品/分类相关接口
#### 源框架参考
| 文件 | 说明 |
|------|------|
| `dish/domain/HotelDish.java` | 菜品实体 |
| `dish/domain/HotelDishCategory.java` | 分类实体 |
| `dish/domain/HotelDishSpecGroup.java` | 规格组实体 |
| `dish/domain/HotelDishSpecOption.java` | 规格选项实体 |
| `dish/domain/HotelDishAddition.java` | 加料实体 |
| `dish/domain/HotelDishLog.java` | 日志实体 |
| `dish/controller/HotelDishController.java` | 菜品全部接口 |
| `dish/controller/HotelDishCategoryController.java` | 分类接口 |
| `dish/controller/HotelDishSpecGroupController.java` | 规格组接口 |
| `dish/controller/HotelDishSpecOptionController.java` | 规格选项接口 |
| `dish/service/HotelDishService.java` | 菜品业务逻辑(417 行) |
| `dish/service/HotelDishCategoryService.java` | 分类业务逻辑 |
| `dish/mapper/xml/HotelDishMapper.xml` | 菜品 SQL |
| `dto/DishBatchOperationDTO.java` | 批量操作 DTO |
| 前端:`views/hotel/dish/DishManagement.vue` | 菜品管理页面 |
| 前端:`views/hotel/dish/DishForm.vue` | 菜品表单 |
| 前端:`views/hotel/dish/CategoryManagement.vue` | 分类管理 |
| 前端:`views/hotel/dish/CategoryForm.vue` | 分类表单 |
| 前端:`api/hotel/HotelDishService.js` | 菜品 API |
| 前端:`api/hotel/HotelDishSpecService.js` | 规格 API |
#### 注意事项
- 源框架同时存在 `hotel_dish_spec` 和 `hotel_dish_spec_group` 两张表,Forge 统一使用 `hotel_dish_spec_group`
- 源框架删除菜品时校验未完成订单 → 需要依赖 order 模块(可先预留校验接口,order 完成后再联通)
- 菜品价格使用 `BigDecimal`,金额单位与源框架保持一致(元)
---
### 3.3 order — 订单管理 ⭐⭐⭐⭐(最复杂)
> **状态**: 全部完成。后端 + 前端 PC 管理端 + H5 顾客点餐端 + 支付系统均已完成。
#### 后端
- [x] 实体:`HotelOrder.java` — 订单主表(Integer status,0~10 数字状态,含 payStatus/payExpireTime/paySource)
- [x] 实体:`HotelOrderItem.java` — 订单明细(冗余 dishName/specDesc/additionsDesc)
- [x] DTO: `OrderCreateDTO.java` — 创建订单请求(已移除 payMethod,改用 paySource)
- [x] VO: `HotelOrderVO.java` — 订单视图对象(含内部类 OrderItemVO + itemCount)
- [x] VO: `OrderDashboardVO.java` — 看板统计响应
- [x] Mapper: `HotelOrderMapper.java` + XML(含看板统计/今日营收/订单号生成,6 个自定义 SQL)
- [x] Mapper: `HotelOrderItemMapper.java` + XML(按订单ID查询 + 批量插入)
- [x] Service: `HotelOrderService.java` — 14+ 个方法接口(含完整 Javadoc + 支付/超时取消)
- [x] Service: `HotelOrderServiceImpl.java` — 完整状态机 + 退单流程 + 菜品销量累加 + 支付超时处理
- [x] Controller: `HotelOrderController.java` — 15+ 个 REST 接口(查询3 + 操作6 + 退单4 + 创建1 + 看板1 + 支付相关)
- [x] 常量:`HotelOrderConstants.java` — 状态 0~10 + 退单发起方
#### 数据库
- [x] 建表:`hotel_order`(IF NOT EXISTS)— 23+ 个字段 + 4 个索引(V1.0.104 建表 + V1.0.105 支付字段扩展)
- [x] 建表:`hotel_order_item`(IF NOT EXISTS)— 13 个字段 + 1 个索引
- [x] 字典:`hotel_order_status`(0~10 数字状态,对应中文标签 + list_class 颜色,V1.0.104 + V1.0.105 补充 0/10)
- [x] 字典:`hotel_pay_method`(MOCK/CASH/ONLINE/CREDIT)
- [x] 字典:`hotel_delivery_time_type`(ASAP/CUSTOM)
- [x] 菜单:订单管理(sys_resource)
- [x] 菜单权限:订单查询/接单/拒单/备餐/配送/退单审核 6 个按钮权限
- 迁移脚本:`V1.0.104__add_hotel_order_module.sql`(建表 + 字典 + 菜单)
- 迁移脚本:`V1.0.105__add_hotel_order_payment_fields.sql`(支付字段 + 状态 0/10 + MySQL 8 兼容)
- 迁移脚本:`V1.0.117__add_restaurant_order_menu.sql`(新增一级菜单「餐厅管理」及子菜单「餐厅订单」,按钮权限 `restaurant:order:*`,复用 hotel 模块后端接口)
#### 前端
- [x] 页面:`forge-admin-ui/src/views/hotel/order.vue` — 订单管理(AiCrudPage + 看板统计 + 流程操作 + 详情弹窗)
- 顶部看板统计卡片(待接单/备餐中/配送中/今日订单/今日营收)
- 搜索筛选(房间号/订单状态/联系人/支付方式)
- 操作列按状态动态显示(接单/拒单/备餐/出餐/配送/完成/同意退单/驳回/前台退单)
- 订单详情弹窗(基本信息 + 金额 + 时间记录 + 菜品明细表格)
- 原因输入弹窗(拒单/退单审核共用)
- [x] API: `forge-admin-ui/src/api/hotel.js` 中追加 14 个订单接口函数
- [x] 页面:`forge-admin-ui/src/views/hotel/restaurantOrder.vue` — **餐厅前台订单管理**(三 Tab 布局,SSE 实时通知,API 复用 hotel 模块接口)
- Tab1「全部订单」:统计卡片(待接单/进行中/今日完成/已拒单)+ 分组订单列表(待接单含接单/拒单按钮,进行中含备餐/出餐/配送/完成按钮)+ 拒单记录(含拒单原因)
- Tab2「订单看板」:6 列卡片式看板(待接单→备餐中→待配送→配送中→已结单→已拒单),参考 board.vue 房态看板样式
- Tab3「营业时间」:营业时段卡片列表(编辑/启用禁用/删除)+ 紧急控制区(暂停接单开关),弹窗表单编辑
- [x] 后端修复:`queryDate` 参数类型 `LocalDate` → `Date`(5 个文件),Controller + 实体均加 `@DateTimeFormat(pattern = "yyyy-MM-dd")`
- [x] 后端修复:`selectDashboard` SQL 按日期过滤 pendingCount/preparingCount/deliveringCount(`DATE(o.create_time) = IFNULL(#{queryDate}, CURDATE())`)
- [x] 餐厅订单详情弹窗:`restaurantOrder.vue` 新增订单详情弹窗(小票样式,深蓝色渐变标题栏 + 虚线分割 + 菜品明细表格 + 底部操作按钮);全部订单和订单看板卡片均可点击打开;按状态显示操作按钮(待接单→接单/拒单,制作中→提示等待,已出餐→开始配送,配送中→完成配送,已完成/已拒单→状态标签);4 个 FromDetail 处理器(handleAcceptFromDetail/handleRejectFromDetail/handleDeliverFromDetail/handleCompleteFromDetail)操作成功后自动关闭弹窗并刷新列表;⚠️ **修复**:`useNotification` 回退为全局 `$notification`(父级无 `NNotificationProvider` 会导致 `useNotification()` 返回 null 崩溃)
#### 支付系统(pay 子包)⭐⭐⭐
> **状态**: 支付宝 **H5 WAP Pay** 全链路完成(沙箱)+ 5 层安全加固 + 前端轮询兜底。
> ✅ **Mock 免付款资金红线已收口**(2026-09-07):`hotel.pay.mock-enabled` 开关(**代码级默认 `false`**,yml 整块缺失也不会误开)+ `resolvePayChannel`/`mockOrReject` 单一收口(全仓禁止裸 `return "MOCK"`)+ `paySource` 去 `defaultValue` + `refundOrder` 渠道显式判定 + `pay.vue` else 拆分。dev 环境 `application-dev.yml` 覆盖为 `true`,**既有 H5 调试行为零变化**。
> ⚠️ **AGENTS.md 5.9 合规状态:Spec 已补写、人工审查待执行**(2026-09-07):本节属资金类变更,**代码先于 Spec 落地**,属流程倒置。已补写 `code-copilot/changes/hotel-pay-mock-hardening/spec.md`(13 节结构,`status: review`)+ `tasks.md`(8 Task 全标已执行);第 8.1 节摊开三个待审查资金风险点、第 10.1 节登记与原方案三处差异、第 11 节回填实际改动文件。**第 13 节 HARD-GATE 的确认时间/确认人留空,只能由需求方本人手填**,签署前不得归档。
> 🔴 **支付宝小程序支付尚未打通**(无 `ALIPAY_MP` 渠道,见下方「小程序支付缺口」);⏸️ **微信支付暂缓**(渠道已显式阻断,不再静默降级为 MOCK)。
> ✅ **【已决策接受】支付宝沙箱密钥明文入库**:`application-dev.yml` 含支付宝沙箱 `private-key` / `alipay-public-key` **明文**,已随 commit `65508d9` 推到 `origin/master`。**用户决策**:git 是**内网私有仓库**(192.168.8.4),不在外网;密钥是沙箱密钥;**后期如有必要,会直接不提交生产的密钥**。本变更关闭(`hotel-alipay-secret-externalize` 不再推进)。
> ✅ **【已撤销】生产 profile 启动崩溃 — 判断错误,当前不会崩**:`AlipayConfig` L22-34 有 **5 个 `@Value` 完全无默认值**,而**两份生产 `application.yml` 完全没有 `hotel.alipay` 块**。**原判断**:生产启动即崩(P0)。**更正**:`application.yml` L42-43 `spring.profiles.active: dev`,且 docker-compose.yml / .env.example **无任何 profile 覆盖**,项目也**没有** `application-prod.yml`。因此当前启动(包括 docker 部署)都会加载 `application-dev.yml`,`hotel.alipay.*` 全部有值,**不会崩**。我错在把 `application.yml` 当成「生产专用配置」,忽略了它 L43 默认激活 dev profile。
> 需求视角的缺口与修复排期见 `酒店模块需求缺口清单.md` 3.3 / 3.6.5。
##### 后端
- [x] 配置:`AlipayConfig.java` — 支付宝配置(appId/privateKey/alipayPublicKey/gatewayUrl/notifyUrl/returnUrl/sandbox)
- ⚠️ **文档修正**:原记「`@ConfigurationProperties` 注入」**错误**,实为 **7 个独立 `@Value` 字段注入**(L22-41)
- ✅ **【已撤销】P0 隐患**:其中 5 个 `@Value` 无默认值,但 `application.yml` L42-43 `spring.profiles.active: dev`,当前启动会加载 `application-dev.yml`,`hotel.alipay.*` 全部有值,**不会崩**。原判断错误(把 `application.yml` 当生产专用)
- `alipayClient()` L50-54 — `@Bean` 无条件创建 `DefaultAlipayClient`,密钥为空时**不会启动报错但调用时验签失败**,错误信息晦涩
- ✅ **【已决策接受】密钥明文入库**:L25 `private-key` 的实际值写在 `application-dev.yml`,已进远端历史。用户决策:内网私仓 + 沙箱密钥,可接受;后期生产密钥不提交
- [x] 配置:`pay/config/HotelPayConfig.java` — **支付安全开关**(2026-09-07 新增):`@Value("${hotel.pay.mock-enabled:false}")` **代码级默认 `false`**;`@PostConstruct logMockSwitch()` 启动即打印开关状态,开启时用 WARN 输出「【资金安全告警】」,避免误配到生产无人察觉
- [x] 常量:`HotelPayConstants.java` — 支付状态常量(0=待支付/1=成功/3=超时)+ 支付渠道(MOCK/WECHAT/ALIPAY)
- [x] 实体:`HotelPayLog.java` — 支付流水(outTradeNo/tradeNo/payAmount/payStatus/payTime/notifyContent)
- [x] DTO: `PayCreateDTO.java` — 创建支付请求(orderId + paySource)
- [x] VO: `PayResultVO.java` — 支付结果(payStatus/payForm/tradeNo)
- [x] Mapper: `HotelPayLogMapper.java` + XML
- `@InterceptorIgnore(tenantLine = "true")` 绕过租户拦截(回调无登录态)
- `updatePayStatusSuccessByIdempotent` — 数据库级幂等更新(`UPDATE WHERE pay_status != 1`)
- `selectLatestByOrderId` — 按订单 ID 查最新支付流水(主动查询兜底用)
- [x] Service: `HotelPayService.java` — 支付接口(createPay/handlePayCallback/queryAlipayTradeStatus/queryPayStatus)
- [x] Service: `HotelPayServiceImpl.java` — 完整支付实现
- `resolvePayChannel(paySource)` L821-836 — **渠道解析单一收口点**(2026-09-07 新增):`ALIPAY`/`ALIPAY_MP` → `ALIPAY`;其余全部分支(WECHAT/WECHAT_MP/空值/MOCK/H5/未知)一律走 `mockOrReject(reason)`
- ⚠️ **语义陷阱**:`resolvePayChannel("ALIPAY_MP")` 返回 `"ALIPAY"`(两者归为同一渠道),因此后续 `trade.create` 分支**必须用原始 `paySource` 分流**,`payChannel` 已无法区分
- `mockOrReject(reason)` L846-851 — **全仓唯一允许返回 `"MOCK"` 的位置**:`if (hotelPayConfig.isMockEnabled()) return "MOCK"; throw new BusinessException(reason);`。改造前有两处静默降级裸 `return "MOCK"`,正是免付款漏洞成因
- `createPay()` — **渠道解析已前置**(L117-119):非白名单渠道在建流水前即被拒,杜绝静默降级;后续分流用已解析的 `payChannel`(L138-140),**禁止用原始 `paySource` 判断**(否则 `ALIPAY_MP` 会漏进 else 被置为 MOCK)
- ALIPAY → `AlipayTradeWapPayRequest.pageExecute()` → 返回 `payForm` HTML
- ⚠️ `pageExecute()` 是**本地签名即成功、不调网关**,用户实际付款前不创建交易,因此**没有 `tradeNo`**
- 🔴 **小程序支付缺口**:`my.tradePay({ tradeNO })` 只能配 `alipay.trade.create`。需新增 `ALIPAY_MP` 渠道走 `AlipayTradeCreateRequest` 返回 `tradeNo`,`buyer_id` 由 `AlipayAuthController#getUserInfo` 拿到的支付宝 `userId` 提供
- `handlePayCallback()` — 支付宝异步回调处理(拆分两步:查 payLog 获取 tenantId → TenantContextHolder.executeWithTenant 包裹业务处理)
- `queryAlipayTradeStatus()` — 主动查询支付宝侧订单状态(回调丢失时的兜底机制)
- [x] Controller: `controller/open/HotelPayController.java` — 5 个接口(❗ 位于 `controller/open/`,非 `pay/controller/`)
- `POST /hotel/open/pay/create` — 创建支付(返回 payForm/tradeNo)
- ✅ L56-58 `@RequestParam Long tenantId, @RequestParam Long orderId, @RequestParam String paySource` —— **已去掉 `required = false` + `defaultValue = "MOCK"`**,不传即 400(2026-09-07)
- `POST /hotel/open/pay/notify` — 支付宝异步回调(签名验证 + 金额校验 + app_id 校验 + 幂等更新 + 异常返回 fail 触发重试)
- `GET /hotel/open/pay/status` — 查询支付状态(前端轮询用)
- `GET /hotel/open/pay/alipayQuery` — 主动查询支付宝侧订单状态(兜底机制)
- `POST /hotel/open/pay/mockSuccess` — Mock 支付(仅开发调试用)
- ✅ **资金红线已收口**(2026-09-07):`HotelPayController.mockPaySuccess()` L83-86 方法首行拦截 —— `if (!hotelPayConfig.isMockEnabled()) { log.warn(...); throw new BusinessException("模拟支付已禁用"); }`。接口仍位于 `/hotel/open/**` 白名单(`SaTokenConfig` L79/L104 整体放行,**免登录属性未变**),但开关默认 `false` 后生产环境调用一律报错
- 💡 未采用「给接口加鉴权」方案:开放接口无法携带登录态(顾客未登录),改用环境开关 + 渠道白名单双层防护
- ⚠️ 文档修正:原文记录的 `payNotify` 实为 `notify`;`returnUrl` 接口**不存在**(`AlipayConfig.returnUrl` 仅作为支付宝同步跳转地址配置,回到 H5 后由前端轮询检测状态)
- [x] Controller: `controller/open/AlipayAuthController.java` — `POST /hotel/open/alipay-auth/getUserInfo`(authCode → 姓名,加密响应 → 手机号)
- ✅ **已被前端调用**(纠正旧记录「当前 H5 未调用」):`room-confirm.vue` L376-396 已实现授权成功/取消/失败三分支处理
- ✅ **日志脱敏已做**(2026-09-07):手机号不再明文入日志,符合 AGENTS.md 5.9「禁止在日志中打印手机号」
- 💡 小程序化时该接口是 `buyer_id` / `user_id → stayId` 绑定的数据来源,是收口 R2 的关键链路
- [x] Task: `pay/task/PayTimeoutTask.java` — 每 2 分钟跨租户扫描超时未支付订单(`selectExpiredUnpaidOrderIds` → `cancelTimeoutOrder`,状态 10)
- [x] 退款:`HotelPayService.refundOrder()` L564-582 — 支付宝 `AlipayTradeRefundRequest` 全额退款,失败标记 `refund_status=2` 可重试
- ✅ **渠道显式判定已落地**(2026-09-07,防资损):`paySource=MOCK` **或** 流水 `pay_channel=MOCK` **或** 无流水 → 仅回写退款状态(`markRefundSuccess`);`ALIPAY` → 走真实退款;其余渠道 → `log.error` + 抛「该支付渠道暂不支持在线退款,请联系前台人工处理」
- 🔴 改造前用 `!"ALIPAY".equals(payChannel)` 兜底判 MOCK —— **未来接入微信后会造成真实资损**(微信订单退款被误判为无资金流而直接标记成功),已改为显式判定 MOCK
##### 数据库
- [x] 建表:`hotel_pay_log`(IF NOT EXISTS)— 支付流水表(outTradeNo/tradeNo/payAmount/payStatus/payTime/notifyTime/notifyContent)
- [x] 迁移脚本:`V1.0.106__add_hotel_pay_log_table.sql`
##### 安全加固(5 层保障)
| # | 保障层 | 说明 |
|---|--------|------|
| 1 | RSA2 签名验证 | `AlipaySignature.rsaCheckV1` 验证回调签名,防伪造 |
| 2 | app_id 校验 | 验证回调中的 app_id 与配置一致,防跨应用攻击 |
| 3 | 金额校验 | 回调金额与订单金额比对,不一致则拒绝 |
| 4 | 数据库级幂等 | `UPDATE WHERE pay_status != 1` 利用行锁保证原子性,防并发重复处理 |
| 5 | 主动查询兜底 | 前端轮询超时后调用 `/alipayQuery` 主动查支付宝侧状态,补偿丢失的回调 |
##### 异常场景覆盖
| 场景 | 防护措施 |
|------|----------|
| 事务处理中断电 | `@Transactional` 保证原子性,全部回滚 |
| 回调时网络中断 | 返回 "fail",支付宝 24h 内自动重试(约 8 次) |
| 并发重复回调 | 数据库 `UPDATE WHERE pay_status != 1` 行锁幂等 |
| 回调全部丢失 | 前端 `/alipayQuery` 主动查支付宝侧订单状态 |
| 服务器长时间宕机 | 支付宝重试 + 恢复后自动处理 |
##### 前端(pay.vue)
- [x] 支付宝 H5 WAP Pay:后端返回 payForm(HTML 表单),前端渲染后自动提交跳转支付宝收银台
- ⚠️ **支付环境「自动识别」实为前端判断,非服务端环境判断**:条件编译(MP-WEIXIN/MP-ALIPAY/H5)+ UA 检测(`micromessenger`)得出 `paySource`,由**前端传参**给后端,未做服务端校验(需求 7.安全「防支付方式注入」未满足)
- 🔴 **支付宝小程序支付 100% 不可用**(纠正旧记录):后端只 put 了 `payForm`,`tradeNo` 为 **`undefined`** → `my.tradePay({ tradeNO })` 必然 fail → 提示「支付取消或失败」、`paying` 复位。**不是免付款下单**
- ✅ 分流依据已从 `paySource` 改为**后端返回的 `createData.payChannel`**(L204-286,2026-09-07):因为 `resolvePayChannel("ALIPAY_MP")` 返回 `"ALIPAY"`,用 `paySource` 判断会让小程序分支永远进不了支付宝分支
- ✅ **Mock 免付款兜底已拆除**(2026-09-07):原 L258-276 else 分支直接调 `hotelPayMockSuccess`(显示「支付成功」但无真实扣款)。**采取「拆分」而非「删除」**:
- 新增独立分支 `else if (createData.payChannel === 'MOCK')` 才调 `hotelPayMockSuccess` —— 后端开关为 `true` 时才会返回 `payChannel=MOCK`,因此该分支**生产环境永远不会命中**,既有 H5 调试行为保持不变
- 兜底 else 改为 `uni.showModal`「暂无法在线支付」+ `paying` 复位,**禁止在此兜底调 Mock**(那等于绕过资金校验免付款)
- [x] 递归 setTimeout 轮询:首次延迟 3 秒,间隔 2 秒,最多 60 次(120 秒),检测支付状态
- [x] 轮询超时兜底:超时后调用 `/alipayQuery` 主动查询支付宝侧订单状态
- [x] 页面加载自动轮询:从支付宝返回后自动检测支付状态(无需用户操作)
- [x] 已支付直接跳转:订单 payStatus=1 时直接跳转订单状态页
#### H5 顾客端(forge-h5-ui)
- [x] 基础设施:
- `api/index.js` — 新增 12+ 个顾客端 API 函数(含支付/支付宝查询)
- `store/modules/hotel-order.js` — Pinia 购物车 Store(roomInfo/cartItems/orderNote,persist 持久化)
- `styles/hotel-theme.css` — 亚朵深墨绿主题 CSS 变量(`--h-pri: #2D5016` 等)
- `components/hotel/HotelTabBar.vue` — 3-tab 自定义 TabBar(点餐/订单/购物车),使用 `uni.reLaunch` 导航(已移除"我的"tab,避免与系统 tabBar 冲突)
- ✅ 已按 `#ifdef H5` / `#ifdef MP` 拆双分支(146 行,2026-09-07):H5 保留原 `WebkitMask` + 本地 SVG;小程序侧改原生 ``
- 🔴 **小程序图标无法换色**:lucide 风格 SVG 的 `stroke="currentColor"` 在小程序 `` 中**解析为黑色且无法换色** → 激活态只能用 `opacity: 1 / 0.45` 区分;已排除 `?raw` + data-URI 方案(全仓 grep 零命中,无先例)
- `pages.json` — 注册 8 个顾客端页面路由
- ✅ L67-87 已用 `// #ifdef H5` / `// #endif` 裁掉员工两页(`pages-hotel-scan-bind` / `pages-hotel-qr-scanner`,2026-09-07)。已用 `pnpm build:h5` 产物验证:H5 仍含这两个 chunk,**dev 零影响**
- 💡 页面被 `pages.json` 裁掉后**不参与编译**,`.vue` 内部无需再加 `#ifdef`;真正要保护的是**其它页面对它的入口引用**
- ✅ **首页入口引用已同步**(`git diff` 核实在位):`pages/index/index.vue` 的 5 处 `#ifdef H5` —— `scanBindItem` 常量 L245-254 / `SCAN_BIND_PERM` L274-276 / `menuItems` 的 `prefix` 逻辑 L282-288 / `isRegisteredH5Route` 白名单 L412-427 / `handleShortcut` 跳转 —— 全部在位 → 小程序构建时首页不渲染该入口,不会 `navigateTo` 到未注册页面。**H5 dev 零变化**
- 🔴 **`` 不支持小程序**(实测推翻原判断):`pnpm build:mp-alipay` **2.4s 内失败**于 `AiAvatarCropper.vue` 的 ``(`[vite:vue] not supported: Teleport`)。根因:`unplugin-vue-components` 会为**任何被引用到的**组件生成注册代码,与「顾客 8 页是否引用」无关 → **页面裁剪挡不住**
- ✅ **`.env.mp-alipay` 已生效**:`package.json` L9/L25 的 `--mode mp-alipay` 在位(`git diff` 核实)。⚠️ 该 `--mode` **不可省** —— 实测 `@dcloudio` 包内**零 `loadEnv(` 调用**,uni-app/Vite 只按 mode 加载 `.env.[mode]`,**不会按平台加载 `.env.[platform]`**,去掉 `--mode` 会让该文件静默失效。`manifest.json` L61-64 的 `mp-alipay.appid` 占位亦在位(值为空串,待资质),三项构成完整链条、**无遗留不一致**
- [x] 页面:`forge-h5-ui/src/pages/hotel/customer/room-confirm.vue` — 房号确认入口页
- ✅ `resolveScanParams(options)` L185-232 — **三端取参已落地**(2026-09-07):H5 直开路径读 `c`/`s`(**齐备即返回,不走正则,dev 零变化**);小程序扫码从 `query.q`(微信)/ `query.qrCode`(支付宝)`decodeURIComponent` 后正则提取;取参源为 `getCurrentPages()` 末页的 `$page.options || options`
- ⚠️ 与早期方案稿的两处实质差异:① 返回 `{shortCode, sign}` 而非 `{c,t,s}`,`tenantId` 由后端按 shortCode 反查后经 `data.tenantId` 回传(L270);② 显式参数优先
- 扫码验证房间(调用 `/hotel/open/scan` 免登录接口)
- 房间卡片展示(房间号 + 房型)、验证成功动画、开始点餐按钮
- [x] 页面:`forge-h5-ui/src/pages/hotel/customer/menu.vue` — 点餐菜单页(Tab1)
- 配送信息栏(房间号/房型)、Banner、搜索框
- 分类标签横向滚动、菜品列表(图片/价格/加入购物车)
- 购物车浮层(数量角标/总价/去结算)
- [x] 页面:`forge-h5-ui/src/pages/hotel/customer/dish-detail.vue` — 菜品详情页
- 大图展示、规格选择(必选/可选标签)、加料选择(多选)
- 特殊要求备注、数量控制、加入购物车(含规格合并逻辑)
- [x] 页面:`forge-h5-ui/src/pages/hotel/customer/cart.vue` — 购物车页(Tab3)
- 商品列表(图片/规格描述/数量控制)、订单备注
- 费用汇总(菜品小计 + 配送费)、结算按钮
- **已修复**: 结算按钮添加 `box-sizing: border-box` 防止越界
- [x] 页面:`forge-h5-ui/src/pages/hotel/customer/order-confirm.vue` — 确认下单页
- 配送信息(房间号/联系人/联系电话)、送达时间选择(立即/30分钟/1小时)
- 订单明细、提交订单(调用 `/hotel/order` 创建)
- **已修复**: `unitPrice` 字段从 `item.price`(基础价)改为 `item.unitPrice`(含规格/加料加价)
- **已清理**: 移除硬编码的"支付方式"卡片(非小程序自动适配)
- **已修复**: 提交按钮添加 `box-sizing: border-box` 防止越界
- [x] 页面:`forge-h5-ui/src/pages/hotel/customer/pay.vue` — 支付确认页
- 金额展示(从后端 API 获取真实金额,非 URL 参数)
- 支付宝 H5 WAP Pay(payForm 渲染 + 自动提交跳转收银台)
- ⚠️ 支付环境识别为**前端判断**(条件编译 + UA 检测);🔴 MP-ALIPAY 分支拿到 `undefined` tradeNo 必然失败,待新增 `ALIPAY_MP` 渠道后拆清分支(见上方「小程序支付缺口」)
- 递归 setTimeout 轮询(首次 3 秒延迟,间隔 2 秒,最多 120 秒)
- 轮询超时兜底(主动查询支付宝侧订单状态)
- 返回修改按钮(redirectTo 到菜单页,因购物车已清空)
- loading 状态、支付倒计时
- **已清理**: 移除"支付方式"卡片(非小程序自动适配)
- **已修复**: 支付按钮添加 `box-sizing: border-box` 防止越界
- [x] 页面:`forge-h5-ui/src/pages/hotel/customer/order-status.vue` — 订单状态页(Tab2)
- 状态大卡片(图标 + 文本 + 提示,渐变色按状态区分)
- 水平进度条(已下单→已接单→备餐中→已出餐→配送中→已送达)
- 退单/拒单通知卡片(显示原因)
- 配送信息 + 订单明细 + 订单信息三个区块
- **10 秒轮询**自动刷新(未完成订单)
- 退单申请自定义弹窗(标题 + 提示文案 + 原因输入框 + 取消/确定按钮,替代 uni.showModal)
- [x] 页面:`forge-h5-ui/src/pages/hotel/customer/orders.vue` — 订单列表页(Tab2)
- 订单卡片列表(订单号/状态标签/菜品摘要/时间/金额)
- 分页加载(`pageNum` + `pageSize`)、退单原因显示
- 待支付订单(status=0)显示"去支付"按钮
- 点击待支付订单跳转支付页,其他订单跳转订单状态详情
#### 源框架参考
| 文件 | 说明 |
|------|------|
| `order/domain/HotelOrder.java` | 订单实体 |
| `order/domain/HotelOrderItem.java` | 订单明细实体 |
| `order/controller/HotelOrderController.java` | 全部接口(430 行) |
| `order/service/HotelOrderService.java` | 订单业务逻辑(749 行) |
| `order/mapper/xml/HotelOrderMapper.xml` | 订单 SQL |
| `order/mapper/xml/HotelOrderItemMapper.xml` | 明细 SQL |
| `dto/OrderCreateDTO.java` | 创建订单 DTO |
| `dto/OrderDashboardDTO.java` | 看板 DTO |
| 前端:`views/hotel/hotelfront/HotelFrontOrders.vue` | 前台订单页面 |
| 前端:`views/hotel/kitchen/KitchenOrders.vue` | 厨房订单页面 |
| 前端:`views/hotel/restaurant/RestaurantOrders.vue` | 餐厅订单页面 |
| 前端:`views/hotel/customer/CustomerMenu.vue` | 顾客点餐页面 |
| 前端:`api/hotel/HotelOrderService.js` | 订单 API |
#### 订单状态机(数字编号 1~9)
```
0-PENDING_PAY(待支付)
└── → 超时自动取消 → 10-TIMEOUT_CANCELLED(超时取消)
└── → 支付成功 → 1-PLACED(待接单)
1-PLACED(待接单)
├── → 2-ACCEPTED(已接单)→ 3-PREPARING(备餐中)→ 4-READY(已出餐)→ 5-DELIVERING(配送中)→ 6-COMPLETED(已完成)
├── → 7-REJECTED(已拒单)
└── → 9-REFUNDED(已退单,未接单时直接退)
2-ACCEPTED(已接单)
└── → 8-REFUND_REQUESTED(退单待审核)
├── → 9-REFUNDED(审核通过)
── → 2-ACCEPTED(审核驳回,恢复)
```
#### 注意事项
- ~~源框架使用 `synchronized` 防止并发接单~~ → Forge 当前版本未加锁,后续高并发场景需补充 Redisson 分布式锁
- ~~🔴 **金额重算不完整(资金缺陷,待修)**~~ ✅ **已修复**(2026-09-04):`hotel_order_item` 已增 `spec_option_ids`、`addition_ids` 字段(迁移脚本 **V1.0.109**),`order-confirm.vue` **L248-249** 提交时携带 ID,`orderCreate()` **L198-216** 按 ID 从数据库查规格/加料价格重算 `unitPrice`。验证:支付页金额 = 下单页金额 = 购物车金额。验收结论见缺口清单 3.1。⚠️ 原记录的 `V1.0.104`、`order-confirm.vue` L141-148、`orderCreate()` L158-175 与不存在的变更目录 `code-copilot/changes/hotel-order-amount-fix/` 均已更正/删除
- ~~🔴 **配送费信任前端传值**~~ ✅ **已关闭**(2026-09-04):已从 `OrderCreateDTO` 删除 `deliveryFee` 字段(前端传值入口彻底消失),改由 `hotel_config.delivery_fee` 服务端读取;负数归零、超 `999.00` 截断并告警;V1.0.112 播种缺省 `0.00`
- ~~🔴 **Mock 支付免登录暴露(资金红线,待修)**~~ ✅ **已收口**(2026-09-07):`hotel.pay.mock-enabled` 开关(代码级默认 `false`)+ `HotelPayController.mockPaySuccess()` L83-86 首行拦截 + `resolvePayChannel`/`mockOrReject` 单一收口 + `paySource` 去 `defaultValue` + `refundOrder` L564-582 渠道显式判定 + `pay.vue` L204-286 else 拆分。dev 环境覆盖为 `true`,**H5 调试行为零变化**
- ⚠️ **AGENTS.md 5.9 合规:Spec 已补写、审查待执行**(代码先行属流程倒置)。`code-copilot/changes/hotel-pay-mock-hardening/` 的 `spec.md`(13 节,`status: review`)+ `tasks.md` 已于 2026-09-07 补齐,**HARD-GATE 确认时间/确认人留空待需求方本人手填**,签署前本项不算真正关闭。详见缺口清单 3.3 / 3.3.1
- 🔴 **支付宝小程序支付不可用(待修)**:无 `ALIPAY_MP` 渠道,`my.tradePay` 拿到 `undefined` tradeNo 必然 fail。详见缺口清单 3.6.5,第 3 批修复
- ⚠️ 修复时注意:`resolvePayChannel("ALIPAY_MP")` 已归并返回 `"ALIPAY"`,新增 `trade.create` 分支**必须用原始 `paySource` 分流**,用 `payChannel` 无法区分 H5 与小程序
- ✅ 源框架订单完成后自动累加菜品销量 → 已实现(`incrementDishSales`);销量排行接口 `GET /hotel/dish/ranking` 与前端 `getDishSalesRanking()` 已就绪,**但无页面调用**
- ✅ 源框架退单流程较复杂(客户退单 + 前台退单 + 审核退单)→ 已完整迁移
- 订单状态使用数字 0~11(0=待支付, 1~9 原有状态, 10=超时取消, **11=退房联动取消**),非英文字符串常量
- 金额使用 `BigDecimal` + 元(与 dish 模块一致),编码规范要求 bigint + 分但为模块一致性保持 DECIMAL
---
### 3.4 notification — 实时通知 ⭐⭐
> **状态**: 已完成。后端 SSE 通知 + 前端声音提醒 + 桌面通知 + 防抖合并。
#### 后端
- [x] Manager: `SseEmitterManager.java` — SSE 长连接管理 + 广播(ConcurrentHashMap + AtomicInteger)+ 远程接收(receiveNewOrderFromRemote),**直接承担通知广播职责**
- [x] Controller: `NotificationController.java` — SSE 端点(subscribe/unsubscribe/connections/remote/notifyNewOrder)
- ⚠️ 文档修正:原记录的 `OrderNotificationService.java`(notifyNewOrder + notifyRefundRequest)**不存在**,该能力已内聚到 `SseEmitterManager`
- ⚠️ 事件类型仅 `NEW_ORDER`、`REFUND_REQUEST` 两类;**无接单/出餐通知事件**(需求 4.2「出餐通知接收」未满足)
- [x] 集成: `HotelPayServiceImpl.java` — 支付成功后发布 NEW_ORDER 通知(支持跨服务 HTTP 调用)
- [x] 集成: `HotelOrderServiceImpl.java` — 客户申请退单时发布 REFUND_REQUEST 通知
- [x] 跨服务通信: 8583(AppServer)支付成功后通过 HTTP POST 调用 8580(AdminServer)`/hotel/notification/remote/notifyNewOrder` 接口,解决双服务 SSE 内存不共享问题
- [x] Sa-Token 白名单: 添加 `/hotel/notification/remote/**` 到免登录路径(服务间调用无登录态)
#### 数据库
- 无需新建表(直接通过 SSE 推送,不使用 Redis Pub/Sub,单 JVM 足够)
#### 前端
- [x] Composable: `useOrderNotification.js` — SSE 订阅 + MP3 声音 + 桌面通知 + 防抖合并 + Chrome 自动播放策略绕过
- [x] 集成: `order.vue` — 接入 SSE 通知(声音提醒 + Naive UI 通知 + 看板/列表自动刷新 + 连接状态指示器 + 页面刷新自动检测待接单订单)
- [x] 音频文件: `forge-admin-ui/public/女生-订单提醒.mp3` — MP3 语音提醒文件
#### 技术决策
- 不使用 Redis Pub/Sub(单 JVM 部署,直接调用 SseEmitterManager 即可,省去中间层)
- SSE 端点使用 `@SaIgnore` 跳过登录校验(EventSource 无法携带 Authorization Header)
- 声音使用 MP3 文件(`/女生-订单提醒.mp3`),通过 Audio 对象播放
- 3 秒防抖窗口合并多个同时到达的通知(避免弹窗叠加)
- 绿色呼吸灯指示 SSE 连接状态(看板工具栏右侧)
- **跨服务通信**:PC端(8580 AdminServer)和移动端(8583 AppServer)是独立 JVM 进程,SSE 连接存储在各自内存中。8583 支付成功后通过 HTTP POST 调用 8580 的 `/hotel/notification/remote/notifyNewOrder` 接口,由 8580 推送 SSE 事件给 PC 端
- **服务类型判断**:通过 `@Value("${server.type:admin}")` 注入自定义配置属性判断当前服务类型(`admin` 或 `app`),在两个服务的 `application.yml` 中分别配置 `server.type: admin/app`
- **Chrome 自动播放策略**:页面加载时用户未交互,`audio.play()` 会被阻止。通过监听首次 `click/keydown` 事件,将播放失败的声音加入待播放队列,用户首次交互后自动补播
- **页面刷新触发语音**:通过 `watch(connected, ...)` 监听 SSE 连接状态,连接成功后自动查询待接单订单(status=1)并触发语音提醒。重置按钮不触发
---
### 3.10 stay — 在住会话机制 + 订单退款 ⭐⭐⭐⭐(已完成)
> **背景**:系统与亚朵PMS不互通,房间入住/退房由前台在本系统手动操作。核心诉求:入住到退房期间房间客人不变;移动端订单/购物车与当前住客绑定;退房后新客人扫码进来订单和购物车必须是全新的。
> 方案:引入“在住会话(hotel_room_stay)”——入住=开新会话,退房=关闭会话;订单挂载到会话(stay_id);H5 按会话隔离订单可见性与购物车。
#### 后端(已完成)
- [x] 实体:`HotelRoomStay.java`(roomId/guestName/guestPhone/checkInTime/checkOutTime/status/remark,`@TableLogic(value="0", delval="id")`)
- [x] Mapper:`HotelRoomStayMapper.java` + XML(selectActiveByRoomId/selectStayPage/selectAllActive,均显式 `AND del_flag = 0`)
- [x] VO:`HotelRoomStayVO.java`、`RoomCheckOutVO.java`(needConfirm/message/activeOrders/checkedOut)
- [x] Service:`HotelRoomStayService(Impl).java` — roomCheckIn/roomCheckOut(两步交互)/roomCleaningDone/roomMaintenanceToggle/stayPage/backfillGuest
- [x] Controller:`HotelQrCodeController` 新增 5 接口(/room/checkIn、/room/checkOut?force、/room/cleaningDone、/room/maintenanceToggle、/room/stayPage)
- [x] 房间列表带在住客人:`HotelRoomMapper.xml` JoinActiveStay + `HotelRoomVO` 增 stayId/guestName/guestPhone/checkInTime
- [x] 扫码增强:`QrCodeScanVO.RoomInfo` 增 roomStatus/stayId(H5 拦截与隔离依据)
- [x] 房间删除保护:在住中房间禁止删除;`roomUpdate` 禁止直接改状态(状态仅通过操作流转)
- [x] 订单绑定会话:`orderCreate` 写 stay_id + 未入住拦截 + 首单联系人仅空字段回写会话 + 房号以数据库为准;分页支持 stayId 过滤
- [x] 退房联动:`cancelPendingByStayId` 批量取消待支付订单(状态 11),进行中订单需二次确认(force)
- [x] 退款:`HotelPayService.refundOrder`(全额退款,`AlipayTradeRefundRequest`,失败标记可重试);4 个自动触发点(拒单/顾客退单/前台退单/审核通过);手动退款接口 `POST /hotel/order/refund`
- [x] 回调竞态防护:已取消订单(7/9/10/11)收到支付回调不复活,标记异常流水,由前台手动退款
- [x] 常量:`HotelOrderConstants` 新增 11=退房联动已取消 + 退款状态 0/1/2;`HotelQrConstants` 新增 STAY_ACTIVE/STAY_CHECKED_OUT
#### 前端 PC(已完成)
- [x] `board.vue`(房态看板):房间卡片网格 + 状态 Tab + 房间号/房型/楼层筛选 + 入住登记弹窗(住客姓名/电话/备注)+ 退房二次确认弹窗(进行中订单清单)+ 历史入住记录弹窗;菜单由 V1.0.107 写入
- [x] `room.vue`:回归为 AiCrudPage **纯 CRUD**(房间号/房型/楼层/状态列/排序/备注);在住业务操作已拆到看板页,编辑表单不含状态字段
- [x] `order.vue`:退款状态列(DictTag hotel_refund_status)+ 手动退款按钮(已支付未退款且已取消/退款失败场景)
- [x] `api/hotel.js`:新增 6 个接口(roomCheckIn/roomCheckOut/roomCleaningDone/roomMaintenanceToggle/getRoomStayPage/orderRefund)
- ⚠️ `board.vue` 与 `room.vue` 的房间状态标签/颜色为前端硬编码映射(`statusLabelMap`/`statusColorMap`),与 AGENTS.md 5.7「字典禁止硬编码」不一致,待补 `hotel_room_status` 字典后改 DictTag
#### 前端 H5(已完成)
- [x] `store/hotel-order.js`:roomInfo 增 stayId/roomStatus;setRoomInfo 检测会话变更自动清空购物车(退房后新客人全新购物车)
- [x] `room-confirm.vue`:非入住中房间拦截点餐(“该房间暂未入住,如需点餐请联系前台”)
- [x] `orders.vue`:订单列表改为按 stayId 过滤(同会话共享可见,退房后隔离),无会话时退回按房间号兼容存量数据;状态映射补 11=已取消
- [x] `order-status.vue`:状态 11 文案/图标/提示补齐(“房间已退房,该订单已自动取消”)
- [x] `order-confirm.vue`:下单已传 roomId,后端负责会话查找与拦截(双重保险)
#### 房间状态流转规则(仅通过操作流转,禁止编辑表单直接改)
```
空闲/打扫中 --入住--> 入住中 --退房--> 打扫中 --打扫完成--> 空闲;空闲 <--> 维护中。
退房时:待支付订单自动取消(11);进行中订单(2/3/4/5/8)需前台二次确认后强制退房。
```
#### 技术决策(本模块)
- 入住不采集客人信息,首单联系人仅空字段回写会话(多人同住以第一位下单客人作为在住客人)
- 退房两步交互:后端返回 needConfirm + 订单清单,前端弹窗确认后 force=true 重调,避免误操作丢失进行中订单
- 退款仅全额退款;退款失败不阻断订单状态流转(内部持久化 refund_status=2),PC 订单页可手动重试
- 退房竞态(退房瞬间顾客支付成功):回调不复活已取消订单,流水标记异常,由前台在订单页手动退款
---
## 四、框架适配速查表
| 适配项 | jeeplus(源) | Forge(目标) |
|--------|--------------|--------------|
| 实体基类 | `BaseEntity` | `TenantEntity`(含 tenantId + 审计字段) |
| ID 生成 | `UUID.randomUUID()` | `@TableId(type = IdType.ASSIGN_ID)` 雪花算法 |
| 响应体 | `ResponseEntity` / `ResponseUtil` | `RespInfo.success(data)` / `RespInfo.error(msg)` |
| 注入方式 | `@Resource` | **`@Autowired` 字段注入**(实地统计:forge-hotel 共 **57 处 `@Autowired`、0 处 `@RequiredArgsConstructor`**) |
| 权限注解 | `@PreAuthorize("hasAuthority('xxx')")` | `@SaCheckPermission("xxx")` |
| 日志注解 | `@ApiLog` | `@OperationLog` |
| SQL 位置 | Service 层 `QueryWrapper` | Mapper XML(DataScopeInterceptor 要求) |
| 逻辑删除 | 手动 `setDelFlag(1)` + `updateById` | `@TableLogic(value="0", delval="id")` 自动 |
| 租户隔离 | 无 | `TenantLineInnerInterceptor` 自动追加 `tenant_id` |
| 字典 | 硬编码状态值 | `DictSelect` / `DictTag` / `useDict()` |
| 前端列表 | 手写 NInput/NSelect | `AiCrudPage` 配置化 |
| 分页参数 | `Page` | `pageNum` + `pageSize`(`@RequestParam`) |
| 分布式锁 | `synchronized` | Redisson(`@Idempotent` 或手动) |
---
## 五、建议开发顺序
```
✅ 1. room(房间/房型管理) ← 已完成
✅ 2. qrcode(二维码管理) ← 已完成
✅ 3. open(开放扫码接口) ← 已完成
✅ 4. dish-spec(菜品规格管理) ← 已完成
✅ 5. business-hours(营业时段)← 已完成
⏸️ 6. hotelconfig(酒店配置) ← 暂缓(配送费/配送时长待讨论)
7. dish(加料/日志) ← 待开发
✅ 8. order(订单管理) ← 全部完成(后端+PC管理端+H5顾客端+支付系统)
✅ 9. notification(实时通知) ← 已完成(SSE + 声音提醒 + 桌面通知)
✅ 9.1 stay(在住会话+退款) ← 全部完成(入住/退房/打扫/维护 + 退房联动 + 自动/手动退款)
✅ 9.3 restaurantOrder(餐厅前台订单管理) ← 已完成(三 Tab:全部订单/订单看板/营业时间,V1.0.117 建菜单)
✅ 9.4 dashboard(营业数据看板) ← 已完成(KPI 卡片 + ECharts 趋势图 + 热门菜品 Top5 + 餐段分布,V1.0.116 建菜单)
️ 10. kitchen(厨房端) ← 部分完成(厨房订单页:日期筛选+分组展示+统计+详情+出餐;⚠️ 无语音播报/无小票打印)
```
---
## 六、数据库表汇总
### 已完成(Forge 已有)
| 表名 | 说明 |
|------|------|
| `hotel_qr_code` | 二维码表 |
| `hotel_qr_bind_log` | 二维码绑定日志表 |
| `hotel_room` | 房间表 |
| `hotel_room_type` | 房型表 |
### 已完成(order 模块)
| 表名 | 说明 | 迁移脚本 |
|------|------|--------|
| `hotel_order` | 订单主表(23+字段 + 4索引,V1.0.105 新增 payStatus/payExpireTime/paySource) | V1.0.104 + V1.0.105 |
| `hotel_order_item` | 订单明细表(含 `spec_option_ids` / `addition_ids` 作为服务端重算加价的依据) | V1.0.104(建表)+ **V1.0.109**(增两列 ID) |
### 已完成(pay 模块)
| 表名 | 说明 | 迁移脚本 |
|------|------|--------|
| `hotel_pay_log` | 支付流水表(outTradeNo/tradeNo/payAmount/payStatus/payTime/notifyContent) | V1.0.106 |
| `hotel_room_stay` | 房间在住会话表(roomId/guestName/guestPhone/checkInTime/checkOutTime/status 0=在住|1=已退房) | V1.0.106 |
**V1.0.107__add_hotel_room_board_menu.sql**:无建表,仅新增「房态看板」菜单(`/hotel/board` → `hotel/board`,sort=9,动态查找 `酒店管理` 父菜单 + `NOT EXISTS` 防重)
**hotel_order 扩展字段(V1.0.106)**:`stay_id`(在住会话ID + 索引)、`refund_amount`(实际退款金额)、`refund_trade_no`(支付宝退款交易号)、`refund_status`(0=未退款/1=成功/2=失败可重试)
### 待创建
| 表名 | 说明 | 所属模块 |
|------|------|--------|
| `hotel_config` | 酒店基础配置 | hotelconfig(暂缓) |
### 已完成(dish + dish-spec + business-hours 模块)
| 表名 | 说明 | 迁移脚本 |
|------|------|--------|
| `hotel_dish` | 菜品(含主图/附加图片/主厨推荐/售罄/上下架) | V1.0.101 |
| `hotel_dish_category` | 菜品分类 | V1.0.101 |
| `hotel_dish_spec_group` | 菜品规格组 | V1.0.101(建表)+ V1.0.102(菜单) |
| `hotel_dish_spec_option` | 规格选项 | V1.0.101(建表) |
| `hotel_dish_addition` | 加料 | V1.0.101 |
| `hotel_dish_log` | 菜品操作日志 | V1.0.101 |
| `hotel_business_hours` | 营业时段 | V1.0.103(建表 + 菜单 + 字典) |
| **数据看板菜单** | 营业数据看板(KPI/趋势/热门菜/餐段) | **V1.0.116__add_hotel_dashboard_menu.sql** |
### 已完成字典
| 字典类型 | 说明 | 迁移脚本 |
|---------|------|--------|
| `hotel_dish_status` | 菜品状态(在售/下架) | V1.0.101 |
| `hotel_dish_sold_out` | 售罄状态(正常/售罄) | V1.0.101 |
| `hotel_meal_type` | 餐段类型(早餐/午餐/晚餐) | V1.0.103 |
| `hotel_business_hours_status` | 营业时段状态(启用/禁用) | V1.0.103 |
### 已完成字典(order 模块)
| 字典类型 | 说明 | 迁移脚本 |
|---------|------|--------|
| `hotel_order_status` | 订单状态(0~11,含待支付/超时取消/11=退房联动已取消) | V1.0.104 + V1.0.105 + V1.0.106 |
| `hotel_pay_method` | 支付方式(MOCK/CASH/ONLINE/CREDIT) | V1.0.104 |
| `hotel_delivery_time_type` | 配送时间类型(ASAP/CUSTOM) | V1.0.104 |
| `hotel_room_stay_status` | 在住会话状态(0=在住/1=已退房) | V1.0.106 |
| `hotel_refund_status` | 订单退款状态(0=未退款/1=退款成功/2=退款失败可重试) | V1.0.106 |
### 待新增字典
| 字典类型 | 说明 | 所属模块 |
|---------|------|--------|
| (暂无) | | |
---
## 七、前端页面汇总
### 已完成
| 页面 | 路径 | 组件 | 说明 |
|------|------|------|------|
| 二维码管理 | `views/hotel/qrcode.vue` | AiCrudPage | 批量生成/绑定/解绑/批量下载 ZIP |
| 房态看板 | `views/hotel/board.vue` | 自定义卡片网格 | 房间状态 Tab + 筛选 + 入住/退房/打扫/维护 + 退房二次确认 + 入住记录弹窗 |
| 房间管理 | `views/hotel/room.vue` | AiCrudPage | 房间纯 CRUD(业务操作已拆到房态看板) |
| 房型管理 | `views/hotel/roomType.vue` | AiCrudPage | 房型 CRUD |
| 菜品管理 | `views/hotel/dish.vue` | AiCrudPage | 图片预览/上下架/售罄/主厨推荐 |
| 菜品分类 | `views/hotel/dishCategory.vue` | AiCrudPage | 分类 CRUD + 启禁用 |
| 菜品规格 | `views/hotel/dishSpec.vue` | AiCrudPage | 规格组列表 + 选项管理弹窗 |
| 营业时段 | `views/hotel/businessHours.vue` | AiCrudPage | 时段列表 + 时间段下拉选择 + 启禁用 |
| **营业数据看板** | `views/hotel/dashboard.vue` | ECharts + NCard | KPI 卡片(今日订单/营收/环比)+ 7 天趋势图 + 热门菜品 Top5 + 餐段分布;V1.0.116 建菜单 |
| 扫码绑定 (H5) | `forge-h5-ui/src/pages/hotel/scan-bind.vue` | UniApp | 【员工 H5】**双职责页**:`authStore.isLogin` 分流 —— 未登录转顾客 `room-confirm`,已登录展示绑定/解绑。小程序化时 `#ifdef H5` 排除 |
| 扫码页 (H5) | `forge-h5-ui/src/pages/hotel/qr-scanner.vue` | UniApp | 【员工 H5】html5-qrcode 相机扫码(依赖 DOM + `getUserMedia`,需 HTTPS 安全上下文)。小程序化时 `#ifdef H5` 排除 |
### 已完成(order 模块 — PC 管理端)
| 页面 | 路径 | 组件 | 说明 |
|------|------|------|------|
| 订单管理 | `views/hotel/order.vue` | AiCrudPage | 看板统计 + 流程操作 + **详情弹窗(NTimeline 时间线)** + 退单审核 + 退款状态列 + 手动退款 |
| 餐厅前台订单 | `views/hotel/restaurantOrder.vue` | 自定义三 Tab | 全部订单(统计+分组列表+拒单记录)+ 订单看板(6列卡片+拒单列)+ 营业时间(时段管理+暂停接单);V1.0.117 建菜单;**详情弹窗含 NTimeline 时间线** |
### 已完成(order 模块 — H5 顾客端)
| 页面 | 路径 | Tab | 说明 |
|------|------|-----|------|
| 房号确认 | `forge-h5-ui/src/pages/hotel/customer/room-confirm.vue` | 入口 | 扫码验证房间、非入住拦截、房间卡片、开始点餐 |
| 点餐菜单 | `forge-h5-ui/src/pages/hotel/customer/menu.vue` | Tab1 | 分类导航、菜品列表、搜索、购物车浮层 |
| 菜品详情 | `forge-h5-ui/src/pages/hotel/customer/dish-detail.vue` | 子页 | 大图、规格选择、加料、备注、加入购物车 |
| 购物车 | `forge-h5-ui/src/pages/hotel/customer/cart.vue` | Tab3 | 商品列表、数量控制、备注、费用汇总、结算 |
| 确认下单 | `forge-h5-ui/src/pages/hotel/customer/order-confirm.vue` | 子页 | 配送信息、送达时间、明细、提交订单(已移除硬编码支付方式) |
| 支付确认 | `forge-h5-ui/src/pages/hotel/customer/pay.vue` | 子页 | 按后端 `payChannel` 分流 + 支付宝 H5 WAP Pay + 轮询 + 主动查询兜底 + 倒计时。✅ Mock 免付款兜底已拆为独立分支 + 显式报错(2026-09-07);🔴 MP-ALIPAY 分支支付仍不可用 |
| 订单状态 | `forge-h5-ui/src/pages/hotel/customer/order-status.vue` | Tab2 | 进度条、状态卡片、10秒轮询、自定义退单弹窗 |
| 历史订单 | `forge-h5-ui/src/pages/hotel/customer/orders.vue` | Tab2 | 订单卡片列表(按在住会话 stayId 过滤)、分页加载、状态标签、待支付"去支付"按钮 |
### 待开发
| 页面 | 路径 | 说明 | 源框架参考 |
|------|------|------|-----------|
| 酒店配置 | `views/hotel/config.vue` | 单例配置表单(暂缓) | `HotelConfigManagement.vue` |
| 厨房待备餐大屏 | 待定 | 待备餐列表 + 出餐 + 语音播报 + 小票打印(需求 4.3,未开发) | `KitchenOrders.vue` |
| 数据看板 | 待定 | 热门菜品/平均送达时长/趋势图(需求 4.5,现有 5 指标集成在 order.vue) | 无直接参考 |
| ~~订单看板~~ | ~~`views/hotel/orderDashboard.vue`~~ | ~~已集成到 order.vue 工具栏,无需单独页面~~ → ✅ **已独立为 `restaurantOrder.vue`**(三 Tab + 6 列看板含拒单列 + 全日期筛选,V1.0.117 建菜单) | 无直接参考 |
---
## 八、关键决策记录
### 8.1 暂缓开发项
| 模块 | 原因 | 待讨论点 |
|------|------|--------|
| hotelconfig(酒店基础配置) | 暂不需要 | 配送费规则(按距离/区域/固定?)、预计配送时长(固定/动态?) |
### 8.2 技术决策
| 决策 | 说明 |
|------|------|
| 规格组统一命名 | 源框架同时存在 `hotel_dish_spec` 和 `hotel_dish_spec_group`,Forge 统一使用 `hotel_dish_spec_group` |
| 营业时段时间选择 | 使用下拉选择(09:00~23:30,每 30 分钟一档),而非时间选择器 |
| 餐段类型 | 仅保留早餐/午餐/晚餐三种,移除夜宵 |
| 前端组件规范 | 统一使用 AiCrudPage 配置化页面,保持分页/筛选/操作列格式一致 |
| 订单状态值 | 使用数字 0~11 连续编号(0=待支付, 1~9 原有状态, 10=超时取消, 11=退房联动取消),非英文字符串常量 |
| 订单金额 | 使用 `BigDecimal` + 元(`DECIMAL(10,2)`),与 dish 模块保持一致 |
| 订单模块包结构 | 独立 `order/` 子包,与已有 hotel 代码分离 |
| 看板统计集成 | 订单看板统计集成到 AiCrudPage 工具栏 `#toolbar-start` slot,不单独建页面 |
| H5 顾客端框架 | Vue 3 `