# 收银系统(POS)设计文档 > **设计日期**: 2026-06-03 > **版本**: v2.0 > **状态**: 设计中 > **目标**: 为棋牌室、酒吧、KTV 等服务业态构建多端收银系统,支持多门店连锁运营 --- ## 一、背景与目标 ### 1.1 现状分析 目前运营 100+ 棋牌室门店,采用总部 → 代理商 → 门店三级管理模式。各门店急需一套收银系统来管理包间/桌台的开台、计费、结账流程。现有系统空缺,依赖人工记账,效率低、易出错、无法统一管理。 ### 1.2 目标定义 1. **建立标准化收银流程**:支持开台、计时/计费、结账、退款的完整闭环 2. **多门店统一管理**:总部可查看所有门店数据,代理商管理名下门店,门店独立运营 3. **多端覆盖**:管理后台(Web)、收银台(APP/小程序),后续扩展收银机、PC 端 4. **灵活计费模式**:支持按时计费、包时段套餐、按局/圈等混合计费方式 5. **复杂结算场景**:预付款/后付款、小程序下单、小商品分离结算 6. **预留扩展能力**:会员钱包、优惠券/团购券、支付通道、第三方平台对接、智能设备控制 --- ## 二、详细设计 ### 2.1 项目仓库 | 仓库 | 地址 | 说明 | |------|------|------| | 前端 | `ssh://git@git.dev.vifo.cc:222/rui/rui-frontend.git` | admin-ui + 移动端 | | 后端 | `ssh://git@git.dev.vifo.cc:222/rui/rui-cashier.git` | 收银系统独立后端服务 | | 文档 | `ssh://git@git.dev.vifo.cc:222/rui/rui-docs.git` | 共享文档中心 | > ⚠️ **注意**:收银系统相关 Issue 应提交到 `rui/rui-cashier` 仓库,不是 `spring-ai` ### 2.2 整体架构 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 前端层 │ ├──────────────┬──────────────┬────────────────┬─────────────────┤ │ 管理后台 │ 收银台 APP │ 顾客小程序 │ 第三方平台 │ │ (admin-ui) │ (uni-app) │ (独立uni-app) │ (美团等) │ │ Vue3+Element│ Vue3语法 │ 二期开发 │ 三期对接 │ └──────────────┴──────────────┴────────────────┴─────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 网关层 (rui-gateway) │ │ 路由转发 │ 鉴权 │ 限流 │ 负载均衡 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 业务服务层 (app/rui-cashier/) │ ├──────────────┬──────────────┬────────────────┬─────────────────┤ │ rui-cashier-store│ rui-cashier-order│ rui-cashier-product│ rui-cashier-report │ │ 门店+包间服务 │ 订单服务 │ 商品服务 │ 报表服务 │ │ │ │ │ │ │ • 组织架构 │ • 开台/结账 │ • 商品档案 │ • 营业日报 │ │ • 门店管理 │ • 计费规则 │ • 服务分类 │ • 数据统计 │ │ • 包间/桌台 │ • 子订单管理 │ • 定价管理 │ • 趋势分析 │ │ • 套餐策略 │ • 退款处理 │ │ │ │ • 设备管理 │ │ │ │ └──────────────┴──────────────┴────────────────┴─────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 基础设施层 │ │ MySQL 8.0 │ Redis │ Nacos │ MQ(RabbitMQ) │ Seata │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.2 服务职责划分 #### rui-cashier-store(门店+包间服务) **职责**:组织架构、门店管理、包间/桌台管理、套餐策略、设备管理 **核心实体**: - `Store`(门店):基本信息、所属代理商、营业状态、配置 - `StoreConfig`(门店配置):预付款/后付款、余额支付商品开关等 - `Agent`(代理商):基本信息、管理的门店列表 - `RoomType`(包间类型):名称、容纳人数 - `Room`(包间/桌台):所属门店、类型、状态、设备绑定 - `PricingStrategy`(定价策略):包间类型关联的套餐集合 - `PricingPackage`(套餐):具体的价格套餐,支持时段/日期限制 - `Device`(设备):网关、门锁、电源、音箱、打印机、锁球器等 **套餐策略设计**(核心变更): ```java // 计费方式枚举 public enum BillingType { HOURLY(1, "按时计费"), // 按小时计费,可设最低消费时长 FIXED_PERIOD(2, "包时段"), // 固定时段买断,如包下午场 PER_GAME(3, "按局计费"), // 按局/圈计费 MIXED(4, "混合模式"); // 组合计费 } // 定价策略(一个包间类型对应一个策略,包含多条套餐) @Data public class PricingStrategy { private Long id; private Long roomTypeId; // 包间类型ID private String strategyName; // 策略名称,如"标准计费" private Integer status; // 状态:0-禁用 1-启用 } // 套餐(策略中的具体套餐项) @Data public class PricingPackage { private Long id; private Long strategyId; // 所属策略ID private String packageName; // 套餐名称,如"3小时套餐" private Integer billingType; // 计费方式 private BigDecimal price; // 套餐价格 private Integer duration; // 时长(分钟),包时段时有效 private Integer sort; // 排序,第一条是默认/fallback // 限制条件(JSON格式) private String restrictions; // { // "weekdays": [1,2,3,4,5], // 工作日可用 // "weekends": [6,7], // 周末可用 // "timeRanges": [ // 时段范围 // {"start": "08:00", "end": "12:00"} // ], // "holidays": { // 节假日设置 // "mode": "EXCLUDE", // EXCLUDE-不可用 INCLUDE-仅可用 // "dates": ["2026-10-01", "2026-10-02"] // } // } private Integer isDefault; // 是否默认套餐(1-是 0-否),默认套餐不可配置限制条件 private Integer status; // 状态:0-禁用 1-启用 } ``` **套餐匹配逻辑**: ```java public PricingPackage matchPackage(Long roomTypeId, LocalDateTime startTime) { // 1. 获取包间类型的定价策略 PricingStrategy strategy = strategyService.getByRoomTypeId(roomTypeId); // 2. 获取策略下的所有套餐,按 sort 排序 List packages = packageService.getByStrategyId(strategy.getId()); // 3. 依次匹配套餐限制条件 for (PricingPackage pkg : packages) { if (pkg.getIsDefault() == 1) { continue; // 默认套餐跳过,作为fallback } if (matchRestrictions(pkg.getRestrictions(), startTime)) { return pkg; // 找到匹配的套餐 } } // 4. 没有匹配的,返回默认套餐(第一条) return packages.stream() .filter(p -> p.getIsDefault() == 1) .findFirst() .orElseThrow(() -> new BizException("无可用套餐")); } ``` **包间状态机**: ``` 空闲 ──[开台]──► 使用中 ──[结账]──► 待清洁 ──[清洁完成]──► 空闲 ▲ │ │ │ │ [挂单] │ │ ▼ │ │ 已挂单 ──[恢复]───────────────────┘ │ │ │ │ [预约](二期) │ ▼ │ 已预约 ──[开台]──► 使用中 │ └──[取消预约]─── 已预约 ``` #### rui-cashier-order(订单服务) **职责**:开台、计费计算、结账、退款、订单生命周期管理 **核心实体**: - `Order`(主订单):订单整体信息 - `RoomSubOrder`(包间子订单):包间消费、计时计费 - `ProductSubOrder`(商品子订单):小商品消费 - `OrderTimeline`(订单时间线):开台、结账等关键时间点 **订单体系设计**(核心变更): ```java // 主订单 @Data public class Order { private Long id; private String orderNo; // 订单编号 private Long storeId; // 门店ID private Long roomId; // 包间ID(包间订单时) private Integer orderType; // 订单类型:1-包间订单 2-纯商品订单 private String customerName; // 顾客姓名 private String customerPhone; // 顾客电话 // 金额汇总 private BigDecimal roomAmount; // 包间费用 private BigDecimal productAmount; // 商品费用 private BigDecimal discountAmount;// 优惠金额 private BigDecimal totalAmount; // 订单总金额 private BigDecimal payAmount; // 实付金额 // 支付信息 private Integer payStatus; // 支付状态:0-未支付 1-部分支付 2-已支付 private Integer payType; // 支付方式 private LocalDateTime payTime; // 支付时间 // 状态 private Integer status; // 订单状态 private String remark; // 备注 private Long cashierId; // 收银员ID } // 包间子订单 @Data public class RoomSubOrder { private Long id; private Long orderId; // 主订单ID private Long roomId; // 包间ID private Long packageId; // 使用的套餐ID private Integer billingType; // 计费方式 private BigDecimal price; // 套餐价格/单价 private LocalDateTime startTime; // 开台时间 private LocalDateTime endTime; // 结账时间 private Integer duration; // 使用时长(分钟) private Integer gameCount; // 局数 private BigDecimal amount; // 包间费用 private Integer status; // 状态 } // 商品子订单 @Data public class ProductSubOrder { private Long id; private Long orderId; // 主订单ID private Long productId; // 商品ID private String productName; // 商品名称 private BigDecimal productPrice; // 商品单价 private Integer quantity; // 数量 private BigDecimal amount; // 小计金额 private Integer payStatus; // 支付状态(单独控制) private Integer source; // 来源:1-收银台 2-顾客小程序 private LocalDateTime createTime; // 创建时间 } ``` **结算场景支持**: ```java // 门店配置 @Data public class StoreConfig { private Long storeId; private Integer productPayMode; // 商品支付模式:1-先付款 2-后付款 private Integer balancePayProduct;// 余额是否可支付商品:0-否 1-是 private Integer notifyOnOrder; // 下单是否通知:0-否 1-是(喇叭+打印) } // 结算逻辑 public void checkout(Long orderId) { Order order = orderService.getById(orderId); StoreConfig config = storeService.getConfig(order.getStoreId()); // 1. 计算包间费用 RoomSubOrder roomOrder = roomSubOrderService.getByOrderId(orderId); BigDecimal roomAmount = calculateRoomAmount(roomOrder); // 2. 计算商品费用 List products = productSubOrderService.getByOrderId(orderId); BigDecimal productAmount = products.stream() .map(ProductSubOrder::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); // 3. 计算优惠(预留) BigDecimal discountAmount = discountService.calculate(orderId); // 4. 计算应付金额 BigDecimal totalAmount = roomAmount.add(productAmount).subtract(discountAmount); // 5. 如果商品是先付款模式,商品费用已从实付中扣除 if (config.getProductPayMode() == 1) { BigDecimal paidProductAmount = products.stream() .filter(p -> p.getPayStatus() == 2) .map(ProductSubOrder::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); totalAmount = totalAmount.subtract(paidProductAmount); } // 6. 更新订单 order.setRoomAmount(roomAmount); order.setProductAmount(productAmount); order.setDiscountAmount(discountAmount); order.setTotalAmount(totalAmount); order.setStatus(2); // 待支付 orderService.update(order); } ``` **开台流程**(支持小程序预下单): ``` 场景1:收银台直接开台 1. 收银员选择包间 → 检查包间状态"空闲" 2. 选择套餐(系统自动匹配当前时段可用套餐) 3. 确认顾客信息 4. 创建主订单 + 包间子订单 5. 包间状态变更为"使用中" 场景2:顾客小程序预订包间 1. 顾客选择门店、包间类型、时间段 2. 系统匹配可用套餐,显示价格 3. 顾客支付包间费用(预付款) 4. 创建主订单 + 包间子订单(状态:已支付) 5. 顾客到店后,收银员确认开台,包间状态变更为"使用中" 场景3:顾客小程序购买小商品 1. 顾客扫描包间二维码 2. 浏览商品,下单 3. 如果门店配置"先付款":在线支付 → 创建商品子订单 4. 如果门店配置"后付款":创建商品子订单(未支付) 5. 触发智能喇叭通知、打印小票(如配置) 6. 后付款的商品可在收银台统一结算 ``` #### rui-cashier-product(商品服务) **职责**:商品/服务档案管理、分类管理、价格管理 **核心实体**: - `Product`(商品/服务):名称、分类、价格、状态 - `ProductCategory`(分类):名称、层级、排序 - `StoreProduct`(门店商品):门店可售商品及自定义价格 **设计要点**: - 商品分为"普通商品"(饮料、零食)和"服务商品"(加时、换桌等) - 支持总部统一配置,门店可自定义价格和上下架 #### rui-cashier-report(报表服务) **职责**:营业数据统计、报表生成、数据看板 **核心功能**: - 营业日报:按门店、按日期统计营业额、订单数、客单价 - 包间利用率:各包间使用时长/空闲时长占比 - 时段分析:高峰时段、低谷时段统计 - 代理商汇总:名下门店汇总数据 - 商品销售统计:各商品销量、销售额 **技术要点**: - 报表数据定时从 order 服务同步,避免实时查询拖垮核心服务 - 支持按门店、代理商、时间维度筛选 ### 2.3 数据流 #### 开台-结账核心流程 ``` 收银员(APP)/ 顾客(小程序) │ ▼ [选择包间/商品] ──► rui-cashier-store(检查包间状态/商品库存) │ ▼ [确认开台/下单] ──► rui-cashier-order(创建订单) │ │ │ ├─ 创建主订单 │ ├─ 创建包间子订单(或商品子订单) │ ▼ │ 包间状态变更为"使用中" │ │ ▼ ▼ [追加商品] ──► rui-cashier-product(查询商品) │ │ │ ▼ │ 创建商品子订单 │ 触发硬件通知(喇叭+打印,如配置) ▼ [结账] ─────► rui-cashier-order(计算费用) │ │ │ ├─ 计算包间费用(按时/套餐) │ ├─ 汇总商品费用(扣除已支付的) │ ├─ 计算优惠(预留) │ ▼ │ 生成应付金额 │ │ ▼ ▼ [支付完成] ──► rui-cashier-order(更新订单状态) │ │ │ ▼ │ 包间状态变更为"待清洁" ▼ [清洁完成] ──► rui-cashier-store(包间状态变更为"空闲") ``` ### 2.4 接口设计 #### rui-cashier-store 核心接口 ```yaml # 门店管理 POST /cashier/admin/store # 创建门店 PUT /cashier/admin/store/{id} # 更新门店 GET /cashier/admin/store/{id} # 门店详情 GET /cashier/admin/store/list # 门店列表(按代理商筛选) DELETE /cashier/admin/store/{id} # 删除门店 # 门店配置 GET /cashier/admin/store/{id}/config # 获取门店配置 PUT /cashier/admin/store/{id}/config # 更新门店配置 # 包间类型管理 POST /cashier/admin/room-type # 创建包间类型 PUT /cashier/admin/room-type/{id} # 更新包间类型 GET /cashier/admin/room-type/list # 包间类型列表 # 包间管理 POST /cashier/admin/room # 创建包间 PUT /cashier/admin/room/{id} # 更新包间 GET /cashier/admin/room/list # 包间列表(按门店筛选) GET /cashier/admin/room/{id}/status # 包间实时状态 # 定价策略管理 POST /cashier/admin/pricing-strategy # 创建定价策略 PUT /cashier/admin/pricing-strategy/{id} # 更新定价策略 GET /cashier/admin/pricing-strategy/list # 策略列表 # 套餐管理 POST /cashier/admin/pricing-package # 创建套餐 PUT /cashier/admin/pricing-package/{id} # 更新套餐 GET /cashier/admin/pricing-package/list # 套餐列表(按策略筛选) # 设备管理 POST /cashier/admin/device # 注册设备 PUT /cashier/admin/device/{id} # 更新设备 GET /cashier/admin/device/list # 设备列表 POST /cashier/admin/device/{id}/control # 设备控制(预留) ``` #### rui-cashier-order 核心接口 ```yaml # 开台 POST /cashier/admin/order/open # 开台(创建订单) body: { "roomId": 1, "packageId": 1, # 选择的套餐ID "customerName": "张三", // 可选 "customerPhone": "138...", // 可选 "remark": "..." // 备注 } # 追加商品(收银台) POST /cashier/admin/order/{id}/product # 追加商品到订单 body: { "productId": 1, "quantity": 2, "price": 15.00 } # 顾客小程序下单商品 POST /cashier/app/order/{id}/product # 顾客下单商品 body: { "productId": 1, "quantity": 2, "source": 2 // 来源:2-顾客小程序 } # 结账 POST /cashier/admin/order/{id}/checkout # 结账 response: { "orderId": 1, "roomAmount": 120.00, // 包间费用 "productAmount": 30.00, // 商品费用(未支付部分) "paidProductAmount": 20.00, // 已支付商品金额 "discountAmount": 0.00, // 优惠金额(预留) "totalAmount": 130.00, // 应付金额 "duration": 120, // 使用时长(分钟) "startTime": "2026-06-03T10:00:00", "endTime": "2026-06-03T12:00:00" } # 确认支付 POST /cashier/admin/order/{id}/pay # 确认支付(预留支付通道) body: { "payType": "CASH", // CASH-现金, WECHAT-微信, ALIPAY-支付宝, BALANCE-余额 "amount": 130.00, "remark": "..." } # 商品子订单支付(小程序先付款) POST /cashier/app/product-order/{id}/pay # 商品子订单支付 body: { "payType": "WECHAT", "amount": 20.00 } # 退款 POST /cashier/admin/order/{id}/refund # 订单退款 body: { "refundAmount": 130.00, "reason": "..." } # 订单查询 GET /cashier/admin/order/{id} # 订单详情 GET /cashier/admin/order/list # 订单列表(按门店、日期筛选) GET /cashier/admin/order/{id}/timeline # 订单时间线 # 包间状态操作 POST /cashier/admin/room/{id}/clean # 清洁完成(包间恢复空闲) ``` ### 2.5 数据库设计 #### 门店表(rui_cashier_store) ```sql CREATE TABLE #prefix#cashier_store ( id BIGINT PRIMARY KEY COMMENT '门店ID', agent_id BIGINT NOT NULL COMMENT '所属代理商ID', store_name VARCHAR(100) NOT NULL COMMENT '门店名称', store_code VARCHAR(50) NOT NULL COMMENT '门店编码', contact_name VARCHAR(50) COMMENT '联系人', contact_phone VARCHAR(20) COMMENT '联系电话', address VARCHAR(200) COMMENT '地址', business_hours VARCHAR(50) COMMENT '营业时间', status TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', remark VARCHAR(500) COMMENT '备注', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_by BIGINT COMMENT '创建人', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_by BIGINT COMMENT '更新人', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', UNIQUE KEY uk_store_code (store_code), KEY idx_agent_id (agent_id) ) COMMENT='门店表'; ``` #### 门店配置表(rui_cashier_store_config) ```sql CREATE TABLE #prefix#cashier_store_config ( id BIGINT PRIMARY KEY COMMENT '配置ID', store_id BIGINT NOT NULL COMMENT '门店ID', config JSON NOT NULL COMMENT '配置内容:{ \"productPayMode\": 2, // 商品支付模式:1-先付款 2-后付款 \"balancePayProduct\": 0, // 余额是否可支付商品:0-否 1-是 \"notifyOnOrder\": 1, // 下单是否通知:0-否 1-是 \"autoCleanTime\": 10, // 自动清洁时间(分钟) \"minDeposit\": 50.00, // 最低押金(可选) \"overtimeRule\": { // 超时规则(可选) \"type\": 1, // 1-按小时收费 2-按套餐收费 \"price\": 20.00 } }', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', UNIQUE KEY uk_store_id (store_id) ) COMMENT='门店配置表'; ``` #### 包间类型表(rui_cashier_room_type) ```sql CREATE TABLE #prefix#cashier_room_type ( id BIGINT PRIMARY KEY COMMENT '类型ID', store_id BIGINT NOT NULL COMMENT '所属门店ID', type_name VARCHAR(50) NOT NULL COMMENT '类型名称', capacity INT DEFAULT 4 COMMENT '容纳人数', icon VARCHAR(200) COMMENT '图标', sort INT DEFAULT 0 COMMENT '排序', status TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', KEY idx_store_id (store_id) ) COMMENT='包间类型表'; ``` #### 包间表(rui_cashier_room) ```sql CREATE TABLE #prefix#cashier_room ( id BIGINT PRIMARY KEY COMMENT '包间ID', store_id BIGINT NOT NULL COMMENT '所属门店ID', room_type_id BIGINT NOT NULL COMMENT '包间类型ID', room_name VARCHAR(50) NOT NULL COMMENT '包间名称', room_no VARCHAR(20) NOT NULL COMMENT '包间编号', room_status TINYINT DEFAULT 0 COMMENT '状态:0-空闲 1-使用中 2-待清洁 3-已挂单 4-已预约', current_order_id BIGINT COMMENT '当前订单ID', device_config JSON COMMENT '设备配置(关联的设备ID列表)', sort INT DEFAULT 0 COMMENT '排序', enabled TINYINT DEFAULT 1 COMMENT '启用状态:0-禁用 1-启用', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', UNIQUE KEY uk_room_no (store_id, room_no), KEY idx_store_id (store_id), KEY idx_room_status (room_status) ) COMMENT='包间表'; ``` #### 定价策略表(rui_cashier_pricing_strategy) ```sql CREATE TABLE #prefix#cashier_pricing_strategy ( id BIGINT PRIMARY KEY COMMENT '策略ID', room_type_id BIGINT NOT NULL COMMENT '包间类型ID', strategy_name VARCHAR(50) NOT NULL COMMENT '策略名称', status TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', UNIQUE KEY uk_room_type_id (room_type_id), KEY idx_status (status) ) COMMENT='定价策略表'; ``` #### 套餐表(rui_cashier_pricing_package) ```sql CREATE TABLE #prefix#cashier_pricing_package ( id BIGINT PRIMARY KEY COMMENT '套餐ID', strategy_id BIGINT NOT NULL COMMENT '策略ID', package_name VARCHAR(50) NOT NULL COMMENT '套餐名称', billing_type TINYINT NOT NULL COMMENT '计费方式:1-按时 2-包时段 3-按局', price DECIMAL(19,4) NOT NULL COMMENT '套餐价格', duration INT COMMENT '时长(分钟),包时段时有效', min_duration INT DEFAULT 60 COMMENT '最低消费时长(分钟),按时计费时有效', restrictions JSON COMMENT '限制条件(时段、工作日、节假日)', is_default TINYINT DEFAULT 0 COMMENT '是否默认套餐:0-否 1-是', sort INT DEFAULT 0 COMMENT '排序', status TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', KEY idx_strategy_id (strategy_id), KEY idx_status (status) ) COMMENT='套餐表'; ``` #### 设备表(rui_cashier_device) ```sql CREATE TABLE #prefix#cashier_device ( id BIGINT PRIMARY KEY COMMENT '设备ID', store_id BIGINT NOT NULL COMMENT '所属门店ID', room_id BIGINT COMMENT '关联包间ID(设备可在公共区域,此时为空)', location VARCHAR(100) COMMENT '设备位置:前台、茶水间、大厅等(设备在公共区域时填写)', device_name VARCHAR(50) NOT NULL COMMENT '设备名称', device_code VARCHAR(50) NOT NULL COMMENT '设备编号(唯一标识)', device_type TINYINT NOT NULL COMMENT '设备类型:1-网关 2-门锁 3-电源 4-音箱 5-打印机 6-锁球器', parent_id BIGINT COMMENT '父设备ID(门锁/电源等关联网关)', online_status TINYINT DEFAULT 0 COMMENT '在线状态:0-离线 1-在线', last_heartbeat DATETIME(3) COMMENT '最后心跳时间', -- 4G设备信息 imei VARCHAR(20) COMMENT 'IMEI号(4G设备)', sim_card_no VARCHAR(20) COMMENT '流量卡号(4G设备)', sim_operator VARCHAR(20) COMMENT '运营商(移动/联通/电信)', sim_expire_date DATE COMMENT '流量卡到期日期', -- 设备功能配置 functions JSON COMMENT '设备功能配置:{ \"speaker\": { // 音箱功能(电源控制器自带音箱时) \"enabled\": true, \"type\": \"EXTERNAL\", // EXTERNAL-外置 INTERNAL-内置 \"volume\": 80 // 音量 0-100 }, \"power\": { // 电源功能 \"channels\": 4, // 通道数 \"channelNames\": [\"照明\", \"空调\", \"插座\", \"备用\"] } }', config JSON COMMENT '扩展配置', sort INT DEFAULT 0 COMMENT '排序', status TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', UNIQUE KEY uk_device_code (device_code), KEY idx_store_id (store_id), KEY idx_room_id (room_id), KEY idx_device_type (device_type), KEY idx_imei (imei) ) COMMENT='设备表'; ``` #### 主订单表(rui_cashier_order) ```sql CREATE TABLE #prefix#cashier_order ( id BIGINT PRIMARY KEY COMMENT '订单ID', order_no VARCHAR(32) NOT NULL COMMENT '订单编号', store_id BIGINT NOT NULL COMMENT '门店ID', room_id BIGINT COMMENT '包间ID(包间订单时)', order_type TINYINT DEFAULT 1 COMMENT '订单类型:1-包间订单 2-纯商品订单', customer_name VARCHAR(50) COMMENT '顾客姓名', customer_phone VARCHAR(20) COMMENT '顾客电话', room_amount DECIMAL(19,4) DEFAULT 0 COMMENT '包间费用', product_amount DECIMAL(19,4) DEFAULT 0 COMMENT '商品费用', discount_amount DECIMAL(19,4) DEFAULT 0 COMMENT '优惠金额', total_amount DECIMAL(19,4) DEFAULT 0 COMMENT '订单总金额', pay_amount DECIMAL(19,4) DEFAULT 0 COMMENT '实付金额', pay_status TINYINT DEFAULT 0 COMMENT '支付状态:0-未支付 1-部分支付 2-已支付', pay_type VARCHAR(20) COMMENT '支付方式:CASH/WECHAT/ALIPAY/BALANCE', pay_time DATETIME(3) COMMENT '支付时间', status TINYINT DEFAULT 0 COMMENT '状态:0-开台中 1-已挂单 2-待支付 3-已完成 4-已退款', remark VARCHAR(500) COMMENT '备注', refund_amount DECIMAL(19,4) DEFAULT 0 COMMENT '退款金额', refund_reason VARCHAR(500) COMMENT '退款原因', refund_time DATETIME(3) COMMENT '退款时间', cashier_id BIGINT COMMENT '收银员ID', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', UNIQUE KEY uk_order_no (order_no), KEY idx_store_id (store_id), KEY idx_room_id (room_id), KEY idx_status (status), KEY idx_create_time (create_time) ) COMMENT='主订单表'; ``` #### 包间子订单表(rui_cashier_room_sub_order) ```sql CREATE TABLE #prefix#cashier_room_sub_order ( id BIGINT PRIMARY KEY COMMENT '子订单ID', order_id BIGINT NOT NULL COMMENT '主订单ID', room_id BIGINT NOT NULL COMMENT '包间ID', package_id BIGINT COMMENT '套餐ID', billing_type TINYINT NOT NULL COMMENT '计费方式:1-按时 2-包时段 3-按局', price DECIMAL(19,4) NOT NULL COMMENT '套餐价格/单价', start_time DATETIME(3) COMMENT '开台时间', end_time DATETIME(3) COMMENT '结账时间', duration INT COMMENT '使用时长(分钟)', game_count INT DEFAULT 0 COMMENT '局数', amount DECIMAL(19,4) DEFAULT 0 COMMENT '包间费用', status TINYINT DEFAULT 0 COMMENT '状态:0-进行中 1-已完成 2-已取消', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', UNIQUE KEY uk_order_id (order_id), KEY idx_room_id (room_id) ) COMMENT='包间子订单表'; ``` #### 商品子订单表(rui_cashier_product_sub_order) ```sql CREATE TABLE #prefix#cashier_product_sub_order ( id BIGINT PRIMARY KEY COMMENT '子订单ID', order_id BIGINT NOT NULL COMMENT '主订单ID', product_id BIGINT NOT NULL COMMENT '商品ID', product_name VARCHAR(100) NOT NULL COMMENT '商品名称', product_price DECIMAL(19,4) NOT NULL COMMENT '商品单价', quantity INT NOT NULL COMMENT '数量', amount DECIMAL(19,4) NOT NULL COMMENT '小计金额', pay_status TINYINT DEFAULT 0 COMMENT '支付状态:0-未支付 1-部分支付 2-已支付', source TINYINT DEFAULT 1 COMMENT '来源:1-收银台 2-顾客小程序', status TINYINT DEFAULT 0 COMMENT '状态:0-待处理 1-已确认 2-已取消', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', KEY idx_order_id (order_id), KEY idx_product_id (product_id) ) COMMENT='商品子订单表'; ``` #### 商品表(rui_cashier_product) ```sql CREATE TABLE #prefix#cashier_product ( id BIGINT PRIMARY KEY COMMENT '商品ID', category_id BIGINT COMMENT '分类ID', product_name VARCHAR(100) NOT NULL COMMENT '商品名称', product_code VARCHAR(50) COMMENT '商品编码', product_type TINYINT DEFAULT 1 COMMENT '类型:1-普通商品 2-服务商品', unit VARCHAR(20) COMMENT '单位', sale_price DECIMAL(19,4) NOT NULL COMMENT '售价', cost_price DECIMAL(19,4) COMMENT '成本价', status TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', sort INT DEFAULT 0 COMMENT '排序', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', deleted TINYINT DEFAULT 0 COMMENT '删除标志', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', KEY idx_category_id (category_id) ) COMMENT='商品表'; ``` ### 2.6 错误码设计 收银系统错误码区间:**5000-5999** | 错误码 | 含义 | 场景 | |--------|------|------| | 5000 | 门店不存在 | 操作不存在的门店 | | 5001 | 门店已禁用 | 门店状态为禁用 | | 5010 | 包间不存在 | 操作不存在的包间 | | 5011 | 包间已占用 | 开台时包间不是空闲状态 | | 5012 | 包间已禁用 | 包间状态为禁用 | | 5020 | 订单不存在 | 操作不存在的订单 | | 5021 | 订单状态错误 | 操作与当前状态不符 | | 5022 | 订单已支付 | 重复支付 | | 5023 | 退款金额超过订单金额 | 退款金额校验失败 | | 5030 | 商品不存在 | 添加商品时商品不存在或已下架 | | 5040 | 定价策略不存在 | 包间类型没有配置定价策略 | | 5041 | 无可用套餐 | 当前时段没有匹配的套餐 | | 5050 | 设备不存在 | 操作不存在的设备 | | 5051 | 设备离线 | 设备控制时设备离线 | | 5060 | 门店配置不存在 | 门店未配置 | --- ## 三、验收标准 ### MVP 阶段验收标准 - [ ] **门店管理**:可创建、编辑、禁用门店,支持按代理商筛选 - [ ] **门店配置**:可配置商品支付模式、余额支付开关、下单通知开关 - [ ] **包间管理**:可创建包间类型和包间,配置设备绑定 - [ ] **套餐策略**:可创建定价策略,添加多个套餐,支持排序和默认套餐 - [ ] **开台流程**:收银员可选择空闲包间开台,系统自动匹配当前时段可用套餐 - [ ] **计费计算**:按时计费正确计算时长和费用,支持最低消费时长 - [ ] **商品管理**:可添加商品,支持收银台追加到订单 - [ ] **结账流程**:可正常结账,生成订单,支持预付款/后付款模式 - [ ] **退款功能**:可对已支付订单进行部分或全额退款 - [ ] **营业报表**:可查看门店营业日报(营业额、订单数、包间利用率) - [ ] **权限控制**:不同角色(总部/代理商/店长/收银员)看到不同数据和功能 ### 性能标准 - [ ] 包间状态查询响应时间 < 200ms - [ ] 套餐匹配响应时间 < 300ms - [ ] 开台操作响应时间 < 500ms - [ ] 结账计算响应时间 < 500ms - [ ] 报表查询响应时间 < 2s(支持日期范围筛选) --- ## 四、风险与依赖 | 风险 | 影响 | 缓解措施 | |------|------|----------| | **多门店数据量大** | 中 | 报表服务独立,定时同步数据;数据库按租户分表 | | **套餐策略复杂** | 中 | 策略+套餐两级设计,限制条件JSON化;充分测试边界场景 | | **支付 SDK 延迟** | 高 | 先实现现金支付,预留支付接口;等 SDK 就绪后对接 | | **uni-app 性能** | 低 | 收银场景简单,无复杂动画;如性能不足可考虑原生小程序 | | **智能设备对接** | 高 | 预留设备控制接口;等 IOT 平台就绪后对接;APP 端先集成插件 | | **并发开台** | 中 | 包间状态变更加分布式锁;订单号使用雪花算法 | | **第三方平台对接** | 中 | 预留开放平台 API;三期按需对接美团等平台 | | **子订单分离** | 中 | 商品子订单独立支付状态,需保证数据一致性 | --- ## 五、预留扩展设计 ### 5.1 会员钱包预留 ```sql -- 会员钱包表(二期) CREATE TABLE #prefix#cashier_member_wallet ( id BIGINT PRIMARY KEY COMMENT '钱包ID', member_id BIGINT NOT NULL COMMENT '会员ID', real_balance DECIMAL(19,4) DEFAULT 0 COMMENT '实际金额', gift_balance DECIMAL(19,4) DEFAULT 0 COMMENT '赠送金额', total_balance DECIMAL(19,4) DEFAULT 0 COMMENT '总余额', status TINYINT DEFAULT 1 COMMENT '状态', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', UNIQUE KEY uk_member_id (member_id) ) COMMENT='会员钱包表'; -- 钱包流水表(二期) CREATE TABLE #prefix#cashier_wallet_transaction ( id BIGINT PRIMARY KEY COMMENT '流水ID', wallet_id BIGINT NOT NULL COMMENT '钱包ID', transaction_type TINYINT NOT NULL COMMENT '类型:1-充值 2-消费 3-退款', amount DECIMAL(19,4) NOT NULL COMMENT '金额', real_amount DECIMAL(19,4) COMMENT '实际金额变动', gift_amount DECIMAL(19,4) COMMENT '赠送金额变动', order_no VARCHAR(32) COMMENT '关联订单号', remark VARCHAR(500) COMMENT '备注', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', KEY idx_wallet_id (wallet_id) ) COMMENT='钱包流水表'; ``` ```java // 余额支付接口(预留) public interface BalancePaymentService { // 检查余额是否足够 boolean checkBalance(Long memberId, BigDecimal amount); // 扣款(先扣赠送金额,再扣实际金额) PaymentResult deduct(Long memberId, BigDecimal amount, String orderNo); // 退款(退回实际金额) PaymentResult refund(Long memberId, BigDecimal amount, String orderNo); } // 门店配置余额支付范围 // StoreConfig.balance_pay_product:0-余额不可支付商品 1-余额可支付商品 ``` ### 5.2 优惠券/团购券预留 ```sql -- 优惠券表(二期) CREATE TABLE #prefix#cashier_coupon ( id BIGINT PRIMARY KEY COMMENT '优惠券ID', coupon_name VARCHAR(100) NOT NULL COMMENT '优惠券名称', coupon_type TINYINT NOT NULL COMMENT '类型:1-满减 2-折扣 3-立减', face_value DECIMAL(19,4) NOT NULL COMMENT '面值', min_amount DECIMAL(19,4) DEFAULT 0 COMMENT '最低消费金额', valid_days INT COMMENT '有效天数', status TINYINT DEFAULT 1 COMMENT '状态', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间' ) COMMENT='优惠券表'; -- 第三方团购券表(二期) CREATE TABLE #prefix#cashier_third_party_voucher ( id BIGINT PRIMARY KEY COMMENT '券ID', platform VARCHAR(20) NOT NULL COMMENT '平台:MEITUAN/DIANPING', voucher_code VARCHAR(50) NOT NULL COMMENT '券码', voucher_name VARCHAR(100) COMMENT '券名称', face_value DECIMAL(19,4) NOT NULL COMMENT '面值', status TINYINT DEFAULT 0 COMMENT '状态:0-未使用 1-已使用 2-已过期', use_time DATETIME(3) COMMENT '使用时间', order_no VARCHAR(32) COMMENT '关联订单号', tenant_id BIGINT DEFAULT 0 COMMENT '租户ID', create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', UNIQUE KEY uk_voucher_code (voucher_code) ) COMMENT='第三方团购券表'; ``` ```java // 优惠计算接口(预留) public interface DiscountService { // 计算订单可使用的优惠 List calculateAvailableDiscounts(Long orderId, Long memberId); // 应用优惠 DiscountResult applyDiscount(Long orderId, List couponIds, List voucherCodes); // 核销团购券 VoucherResult verifyVoucher(String voucherCode); } ``` ### 5.3 支付通道预留 ```java // 支付接口(预留) public interface PaymentService { // 发起支付 PaymentResult pay(Long orderId, String payType, BigDecimal amount); // 查询支付状态 PaymentStatus queryStatus(String payOrderNo); // 退款 RefundResult refund(Long orderId, BigDecimal amount, String reason); } // 当前实现:现金支付 @Component public class CashPaymentService implements PaymentService { // 仅记录支付流水,不调用第三方 } // 后续扩展:微信支付、支付宝、余额支付等 @Component public class WechatPaymentService implements PaymentService { // 对接支付平台 SDK } ``` ### 5.4 第三方平台对接预留 ```java // 开放平台接口(预留) @RestController @RequestMapping("/cashier/open") public class OpenPlatformController { // 查询包间可用性 @GetMapping("/room/availability") public Result> queryAvailability( @RequestParam Long storeId, @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) { // 返回各包间在指定时段的可用状态 } // 查询套餐价格 @GetMapping("/room/{roomTypeId}/packages") public Result> queryPackages( @PathVariable Long roomTypeId, @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime) { // 返回当前时段可用套餐 } // 第三方下单 @PostMapping("/order/create") public Result createOrder(@RequestBody @Valid OpenOrderDTO dto) { // 创建订单,返回订单信息 } // 订单状态同步 @GetMapping("/order/{orderNo}/status") public Result queryStatus(@PathVariable String orderNo) { // 返回订单当前状态 } // 退款 @PostMapping("/order/{orderNo}/refund") public Result refund(@PathVariable String orderNo, @RequestBody RefundDTO dto) { // 处理退款 } } ``` ### 5.5 智能设备控制预留 ```java // 设备控制服务(预留) public interface DeviceControlService { // 开锁 Result unlock(Long roomId); // 关锁 Result lock(Long roomId); // 通电 Result powerOn(Long roomId); // 断电 Result powerOff(Long roomId); // 语音播报 Result playVoice(Long roomId, String content); // 打印小票 Result printTicket(Long roomId, String content); } // 当前实现:空实现(记录日志) @Component public class DummyDeviceControlService implements DeviceControlService { // 仅记录操作日志,等 IOT 平台对接后替换 } ``` --- ## 六、模块结构 ``` app/rui-cashier/ ├── pom.xml # 聚合 POM ├── rui-cashier-store/ # 门店+包间服务 │ ├── rui-cashier-store-common/ # DTO、枚举、常量 │ ├── rui-cashier-store-core/ # Entity、Mapper、Service │ └── rui-cashier-store-api/ # REST API ├── rui-cashier-order/ # 订单服务 │ ├── rui-cashier-order-common/ │ ├── rui-cashier-order-core/ │ └── rui-cashier-order-api/ ├── rui-cashier-product/ # 商品服务 │ ├── rui-cashier-product-common/ │ ├── rui-cashier-product-core/ │ └── rui-cashier-product-api/ └── rui-cashier-report/ # 报表服务 ├── rui-cashier-report-common/ ├── rui-cashier-report-core/ └── rui-cashier-report-api/ ``` --- ## 七、实施阶段规划 ### 阶段一:MVP(6-8 周) - 搭建项目结构 - 实现 rui-cashier-store(门店+包间+套餐策略+设备管理) - 实现 rui-cashier-order(开台、计费、结账、退款、子订单) - 实现 rui-cashier-product(商品管理) - 实现基础报表 - 开发管理后台页面(admin-ui) - 开发收银台 APP(uni-app) ### 阶段二:会员+营销+顾客小程序(4-6 周) - 会员系统(注册、积分、储值) - 会员钱包(实际金额+赠送金额) - 优惠券、营销活动 - 顾客小程序(预约、扫码开台、在线支付) ### 阶段三:生态对接(4-6 周) - 支付通道对接(微信、支付宝、余额) - 第三方平台对接(美团等) - 第三方团购券对接 - IOT 设备对接(通过 IOT 平台) - 收银机/PC 端开发 --- *文档结束*