data-model.md 8.4 KB

多店结算数据模型(实施版)

日期:2026-10-13。仅适用于服务端新建的多店批次;未关联批次的普通及历史订单保持原语义。金额统一以整数 TWD 存业务快照,渠道需要最小货币单位时只在网关适配层转换,不回写订单金额。

1. 持久化实体

实体 唯一约束/核心字段 职责
order_checkout_config 固定 id=1;enabled、fence_meters、max_stores、confirmation_buffer_minutes、tiers_json、version 平台全局配置专表,固定项用独立列,动态折扣阶梯用 JSON;版本用于报价指纹和后台乐观锁,不接 sys_config。
order_checkout_batch id;parent_dd_id 唯一;(user_id,request_key) 唯一;专用 checkout_status、version、create_time、operation_deadline_at、final_deadline_at、confirmation_buffer_minutes 服务端批次标识、幂等创建、15 分钟发起截止和创建时缓冲快照;专用状态只表示统一履约资格,不代表退款到账。
order_checkout_batch 规则列 anchor_store_id、address_id、same_rider、fence_meters、max_store_count、rule_version、matched_min_stores、discount_ratio、total_amount 创建时冻结本次真实参与集合与计价规则;配置变化不追改。
order_checkout_batch 生效时间(待 T023 实现) activated_at 最后有效成员合法就绪、批次首次原子生效时写入,不能用某子单实收时间或后续 update_time 代替;生效后不因部分取消/重试改写。
order_checkout_member id;pos_order_id 唯一;(batch_id,store_id) 唯一;sort_no、payment_state、pay_type、ready_at、original_freight、discounted_freight、food_amount、amount、coupon_id 每店子单金额和付款/到付确认事实。pay_type 允许未选择;真实实收只来自渠道记录,不能把到付确认当作到账。
order_checkout_payment_attempt id;(channel,merchant_order_no) 唯一;member_id、pay_type、state、requested_at、dispatched_at、gateway_transaction_id、gateway_paid_at、verified_at 保存第 15 分钟前服务端发起的证据及其后缓冲内可信结果。请求预留不是实际发起;回调、查询及重试按原交易号对账。
order_delivery_group id;batch_id 唯一;rider_id、status、version 仅同骑手批次创建;组是一个抢单任务,不是合并收款或强制同步取送状态。
order_delivery_group 开放时间(待 T023 实现) opened_at 首次实际开放给骑手时原子写入并创建通知意图,不能从预约计划时点推算,也不因重试/部分取消改写。无配送组成员使用原 display_time 记录首次实际开放。
order_checkout_outbox id;(event_type,aggregate_id) 唯一;status、next_retry_at、attempts 生效/预约开放后的新任务推送,组按组、非组按子单去重;提交后发送、失败可重试、取消后不可再开放。
order_checkout_refund_intent id;(member_id,reason) 唯一;channel、amount、merchant_order_no、gateway_transaction_id、status、next_retry_at 跟踪 LINE/OMG 原路全额退款、未知结果和人工处理;意图创建不等于到账。到付未收不建网关退款意图。

不在 pos_order 增加多店标志:以 order_checkout_member.pos_order_id 的唯一关联作为不可伪造服务端标识;老订单无关联即沿旧流程。容量统计与组列表通过成员表关联 order_delivery_group,避免为纯读取再加一列。父单 order_parent 沿用现有结构和子单号关联,其 orderStatus 不复用为批次生效或实收状态。

2026-10-16 已确认的时间列是目标模型,当前 Entity/Mapper/SQL 尚待 T023 一批实现;本文更新不代表数据库已有这些列。地址与坐标使用建批时复制到父/子订单的快照;address_id 只保留原地址关联,不在付款、列表、取送时回读地址簿覆盖快照,旧单笔改址入口也不能改组成员。

2. 状态和不变量

  • 批次:PENDING → ACTIVE 或 PENDING → CANCELLED;CANCELLED 永不重开。到最终确认截止仍未全部合法就绪时关批次,截止后收到真实付款也只进入退款/对账,不转 ACTIVE。
  • 成员:UNSELECTED → ONLINE_PENDING → ONLINE_PAID,或 UNSELECTED → COD_CONFIRMED;渠道失败/未知不得标记就绪。未决在线尝试不能改选到付。ONLINE_PAID 与 COD_CONFIRMED 均计就绪,但仅前者计已实收。
  • 发起线上尝试、重试、换方式、到付确认均要求 now < operation_deadline_at;已有实际发起且可核实的在线尝试允许在 now <= final_deadline_at 完成。缓冲内不新建尝试。
  • 同一父批次读写必须以批次行锁或状态版本 CAS 保护;激活/取消必须在同一事务内重新读取全部成员事实。外部网关 HTTP 不持有数据库事务锁。
  • 组接单:骑手锁 → 组行锁 → 成员子单按 ID 加锁;资格、现有任务容量、组门店上限和全部成员可接状态在锁内校验;全部分配或全部失败。单笔接单入口发现组成员必须拒绝拆抢。
  • 子单创建时写当前业务字段快照,但 isDisplay 不等于批次准入;骑手列表、直接接单、商家操作及新单通知均校验批次已 ACTIVE 和预约旧时点已开放。普通订单不套批次期限。
  • 组任务只有全部有效成员都履约结束或合法取消后释放。独立取餐、独立送达、独立结算/通知按子单进行,部分取消不重算余单折扣。
  • 已确认接入 037:无人接单基准为 max(activated_at, opened_at, 预约开始)(即时忽略预约);部分子单先付款不启动计时,部分取消不重置。超时与接组在统一锁/事务规则下互斥,未承接组取消剩余有效成员并关联现有渠道退款事实;无组成员使用父批次生效与本人实际开放时间按独立订单判断。
  • 尚未生效预约批次在激活时已到/超过原结束时间则转 CANCELLED 并退款,不自动改即时;恢复扫描补处理。已经 ACTIVE 的任务不因预约结束由此路径取消,继续依履约及 037 无人接单规则处理。

3. 平台配置和金额

独立 order_checkout_config 单行保存全局开关、围栏距离米(默认 1000)、最多店数(默认 5)、缓冲分钟(默认 5)及 JSON 动态折扣阶梯(初始 {2:0.9,3:0.8}),配置保存时校验:围栏/最多店数/缓冲为正整数,门槛为不小于 2 的唯一整数,系数 0 < ratio <= 1 且最多四位小数,与批次快照 DECIMAL(7,4) 一致。开关/围栏/门店上限/阶梯缺失或非法时保守关闭新合并报价;缓冲异常回退 5 分钟,不变为无限。配置版本在同一行 CAS 增加并纳入报价指纹;创建时重算与报价版本不一致则要求重报。配置表缺行时读取显示关闭态、保存时提示执行 036 手动迁移,不能偷偷初始化为启用。

2026-10-16 已确认:降低 max_stores 前检查未结束、未承接组的有效成员数,存在超出新值者拒绝保存且不增加配置版本,包含尚未生效的已建组;已接组继续履约。检查与建批的配置读取/版本重验以及组取消/承接必须具有一致并发保护,不能检查后又按旧配置建出超限未接组。

首店门店坐标为圆心、对每个后续门店作球面直线距离,米精度比较 distance <= fence_meters;不以道路/用户地址距离代替。圈外店不进入本批次父/子单或金额;创建成员集合必须与确认报价一致,否则拒绝。

每店原运费按现有有效规则权威计算;同骑手批次取最高满足门槛档位,对每店分别执行 original_freight × ratio 后 HALF_UP 到整数 TWD。子单 amount = food_amount - valid_promotion_and_coupon_reduction + discounted_freight,父单总额为子单实付之和。用户到付代收该子单 amount,给商家 amount - discounted_freight,商家给骑手折后运费;不存在跨商家补差。取消余单金额不重算。

4. 迁移与兼容

DDL 只登记到 updatesql/sql.md,不自动执行。迁移前不得开启新入口。已有父/子单不回填批次关联;老单店转账、自取、夜市自配送、已有 LINE/OMG/到付均沿旧流程。普通平台配送预约只补旧规则时点扫描,不套批次付款截止。