# 架构决策:真实预约容量模型 日期:2026-08-01 状态:Accepted 范围:单店 SaaS 真实试点 ## 决策 1. 服务项目配置 `duration_minutes`,范围 30~480 分钟且按 30 分钟递增。 2. 预约保存 `duration_minutes` 快照,后续修改服务项目不会改变已承诺号源。 3. 门店配置 `booking_capacity`(1~10),表示同一半小时容量桶可并行服务的顾客数。 4. 可约性必须检查服务从开始到结束覆盖的每个半小时桶;有效预约和 `walk_in` 各消耗一个名额,`blocked` 关闭覆盖范围内全部名额。 5. 创建预约或手动占用时,对门店行使用悲观写锁,在同一事务内重新读取占用并判定,避免两个并发请求同时通过可用性检查。 6. `booking_last_slot_start` 保留兼容语义:它是最后一个 30 分钟容量桶的开始时间,营业容量结束时刻为其后 30 分钟;服务必须在结束时刻前完成。 ## 为什么先做门店容量 现有预约在开始服务时才绑定 `assigned_user_id`,宠主下单也不选择技师。在这种数据结构下宣称已经具备“技师排班”会制造假精度。试点阶段先由老板配置门店可真实兑现的并发接待数,足以阻止长服务跨档冲突和同店并发超卖。 个人技师日历延后到预约前可分配技师、员工可维护可工作时段、服务项目可声明技师技能之后再引入。届时门店容量仍作为总上限,不会推翻本决策。 ## 兼容与迁移 - 旧接口未传 `serviceType` 查询号源时采用 60 分钟安全默认值;创建预约必须解析到本店有效服务项目。 - 历史系统默认与预约按稳定名称映射回填:洗澡 60、美容 120、洗澡+美容 150、剪指甲/驱虫 30,未知服务 60 分钟。 - 旧手动占用回填为 30 分钟;旧门店并发接待数回填为 1。 - 生产不自动执行迁移,发布前按 `backend/db/migrations/README.md` 备份、测试和人工执行。 ## 验收 - 120 分钟服务会占用连续四个半小时桶。 - 容量为 2 时,同一桶允许两个有效占用,第三个被拒绝。 - `walk_in` 消耗一个名额;`blocked` 覆盖处全部不可约。 - 候选服务结束晚于营业容量结束时刻时不可约。 - 并发创建不会突破门店容量。