义父定调(2026-10-10):「将套餐内容透明化,所有项目和进度能让游客确切知道」
本文所有结论均来自 prod 实测(2026-10-10),非推测。
后端已经把大部分数据算好了,C 端几乎没用;住宿/套餐链路则连数据都没有。
| 环节 | 现状 | 判断 |
|---|---|---|
| 买之前·套餐包含什么 | 后端已返回 includes,C 端有渲染能力 | 🟡 接上就能用 |
| 买之后·寺供进度 | 后端返回 11 个字段,C 端只用了 2个 | 🟡 接上就能用 |
| 买之后·住宿/套餐/餐食进度 | 🔴 数据层没有 | 🔴 要先补数据 |
⇒ 分三步走:先接已有数据(约 1 天见效),再补缺失状态机(约 1 周)。
fulfillment_status 非空 0 / 12
condition_achieved_at 非空 0 / 12
shipped_at 非空 0 / 12
offering_deadline 非空 0 / 12
offering_done_at 非空 0 / 12
retrieved_at 非空 0 / 12
partner_confirmed_at 非空 0 / 9
⚠️ 这不是"没写"的问题,而是当前 3 笔订单全是 auto_cancelled(自动取消),
以及 —— 更关键 —— **item_type 有四类(hotel/package/meal/driver),
它们的进度字段根本不存在**。
order_items.status : active × 12 ← 只有这一个
order_items.fulfillment_mode: direct × 12
order_items.item_type : hotel 3 / package 3 / meal 3 / driver 3
🔴 status 永远只有 active ⇒ 系统压根没记录"这一项进行到哪一步"。
orders.status CHECK (... 14 态 ...)
draft → booked / pending_payment → part_paid → paid
→ dispatching → pending_confirmation → confirmed → in_service
→ partially_redeemed → completed
(+ cancelled / refunding / refunded / partial_refunded)
💡 这是唯一"每笔订单都一定有"的状态 ⇒ 第一版进度条就用它,不等明细状态机。
miniapp_orders.py::_map_service 返回:
offeringTemple 供哪座寺 · offeringChannel 怎么供 · retrievalChannel 怎么拿回
offeringDurationDays 供期几天 · deadline · achievedAt · shippedAt
offeringDoneAt · offeringReadyAt · retrievedAt
C 端实际用量:
| 字段 | C 端 |
|---|---|
status / deadline | ✅ |
shippedAt | ✅ 1 处 |
achievedAt / offeringDoneAt / retrievedAt | 🔴 0 处 |
offeringTemple / offeringChannel / retrievalChannel / offeringDurationDays | 🔴 0 处 |
⚠️ 后端注释里记着一次同类修复(2026-09-30):
运营把供奉改成「已达成/已发货」,客人端永远看不到。
这次是同一个问题的第二次发生,只是范围更大。
"includes": [{ id, name, provider("平台自营"/"合作方提供"), description, ... }]
"dailySchedule": [] # 🔴 硬编码空数组
🔴 includes[].amount 硬编码 0、status 硬编码 "pending"
💡 C 端 detail-config.ts 已支持 includes / itinerary 两种块
┌─ 芝达布日 · 两日一晚 ────────────────┐
│ 含 4 项: ✓ 已补齐 │
│ · 住宿 1 晚(札什伦布) ✓ │
│ · 早餐 × 2 ✓ │
│ · 温泉门票 × 2 ✓ │
│ · 讲解服务(含领队) ⏳待履约 │
│ │
│ 行程: │
│ Day1 抵达 → 入住 → 欢迎餐 │
│ Day2 温泉 → 讲解 → 返程 │
└────────────────────────────────────────┘
所有订单统一一个进度条 + 逐项状态:
订单进度
✓ 已下单 10-02 14:23
✓ 已支付 10-02 14:25
✓ 已确认 10-03 09:10
○ 履约中 进行中
○ 已完成
逐项
▸ 住宿 · 1 晚 ✓ 已入住 10-03 15:00 · 10-04 12:00 退房
▸ 温泉 × 2 ⏳ 待核销
▸ 讲解服务 ⏳ 待安排
寺供单(已有数据,直接接):
▸ 风物 · 铜铃 ✓ 已供献 10-08 14:20
芝达布日寺 · 供期至 10-15
凭回执到店取回
做什么:把后端已经返回、C 端没用的字段显示出来。
| 改动 | 位置 | 内容 |
|---|---|---|
| 1 | order-detail.ts + wxml | 寺供项加:供哪座寺 / 供期至哪天 / 怎么取回 / 三条时间戳 |
| 2 | order-detail.ts | 顶部加订单主状态进度条(用 orders.status 14 态的前 6 态) |
| 3 | package-detail.ts | 启用 includes 块渲染(detail-config.ts 已支持) |
| 4 | miniapp_products.py | includes[].amount 改成真实金额(当前硬编码 0) |
验收:寺供订单详情能看到完整时间线;任意订单能看到主进度条;套餐页能逐项列出包含什么。
风险:低(只改展示,不动数据)。
问题:hotel / package / meal / driver 四类没有任何进度字段。
做法:给 order_items 加一张履约进度表(比在 order_items 上加列更干净):
CREATE TABLE item_fulfillment_steps (
id serial PRIMARY KEY,
order_item_id bigint NOT NULL REFERENCES order_items(id) ON DELETE CASCADE,
step_code text NOT NULL, -- confirmed / check_in / check_out / served / fulfilled
step_label text NOT NULL, -- 游客看到的中文,如"已入住"
happened_at timestamptz,
operator text,
UNIQUE (order_item_id, step_code)
);
⚠️ 为什么用表不用列:order_items 已有 29 列,再加 4 个类型各自的进度列会变成"宽表空洞";
用表则新增一种业务类型不用改表结构。
四类各自的步骤定义:
| item_type | 步骤 |
|---|---|
hotel | 已确认 → 已入住 → 已离房 |
package | 已确认 → 进行中 → 已完成 |
meal | 已确认 → 已用餐 |
driver | 已确认 → 已接单 → 行程中 → 已完成 |
A 端要有推进入口:谁把这步标完成?(item_type=driver 的"已接单"该是司机自己点,还是平台代点?这条要定)
🔴 dailySchedule 现在是硬编码空数组,packages 表也没有"第 N 天做什么"的结构。
要补:新表 package_daily_plan(package_id / day_no / title / description / meals)
C 端:itinerary 块已经支持,只等数据。
⚠️ 这块工作量取决于内容谁来写(平台?还是领队?)。
① 住宿/餐食这几步,是运营在 A 端标,还是相关方(B 端)自己标?
· 影响阶段二要不要动 B 端(工作量差一倍)
② 客人能不能自己"确认收货"?
· 寺供有 partner_confirmed_at(寺方确认)⇒ 客人侧要不要对称的"我已拿到"
· 影响是否要加 user_confirmed_at
③ orders.status 的 14 态,游客该看到几个?
· 我的建议:只露 6 个(已下单/已支付/已确认/履约中/已完成/已取消)
· part_paid、dispatching、refunding 这类是内部状态,露出来反而让人困惑
今天 ──→ 阶段一(接已有数据,1 天,立即能看到效果)
↓
本周 ──→ 定①②③ → 阶段二(补状态机,1 周)
↓
之后 ──→ 阶段三(每日行程,等内容来源确定)
理由:阶段一不碰数据结构、零风险、当天就能让游客看到东西;
阶段二才是真正的新建工作,不该和阶段一混在一起做。
权限记录肯定要有,而且将来也要让商户看到
是哪个员工通过 A 端、什么时候、怎么操作的。
| 表 | 行数 | 已有 | 缺 |
|---|---|---|---|
audit_log | 24 | operator_id operator_role action detail | 🔴 无 merchant_id;operator_id 无外键,人走了记录成孤儿 |
operation_logs | 1 | merchant_id action target | 🔴 几乎没用;operator 是文本不是 ID |
order_intervention_logs | 0 | before_status after_status reason operator_id | 🔴 无 merchant_id;完全没用 |
💡 audit_log 已经在记供献每一步(offering.confirm-offering / mark-ready / confirm-retrieval),
说明写入侧有现成范式,缺的是「商户维度」和「可靠性」。
-- ① audit_log 补商户维度与外键
ALTER TABLE audit_log
ADD COLUMN IF NOT EXISTS merchant_id bigint REFERENCES merchants(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS operator_name text, -- 冗余存名字:员工离职后仍可读
ADD COLUMN IF NOT EXISTS ip inet, -- 「谁在哪个网络操作」
ADD COLUMN IF NOT EXISTS request_id text; -- 与后端日志对得上
CREATE INDEX IF NOT EXISTS idx_audit_log_merchant
ON audit_log (merchant_id, created_at DESC);
-- ② operation_logs 补回值快照(「怎么操作的」要能看到改了前后)
ALTER TABLE operation_logs
ADD COLUMN IF NOT EXISTS operator_id bigint,
ADD COLUMN IF NOT EXISTS before_value jsonb,
ADD COLUMN IF NOT EXISTS after_value jsonb;
⚠️ operator_name 冗余存名字是刻意的 —— 只存 ID 的话,员工离职后
商户看到的会是一串无意义数字,审计就失去了意义。
以下操作强制写 audit_log(不靠"记得写",而是加统一中间件):
| 动作 | 为什么必须 |
|---|---|
| 代商户改订单状态 | 直接影响客人权益 |
| 代商户标履约进度 | 客人会看,事后要能追责 |
| 改价 / 退款 / 免除 | 涉及钱 |
| 指派领队 / 寺方 | 决定谁负责 |
| 开通 / 停用从业者身份 | 权限变更 |
⚠️ 落地方式:写一个 @audit(table, record_id, action) 装饰器,
在关键写端点上标注 —— 比"要求开发记得写"可靠。
B 端 / 商户页 → 操作记录
10-05 14:23 运营张三 把订单 #50225 标记为「供奉已达成」
员工工号 EMP-003 · 来自平台代操作 · IP 1.2.3.4
10-05 15:01 运营张三 标记为「可取回」
⚠️ 接口与A 端共用一套后端,只是 B 端加了 merchant_id 过滤 ⇒ 不用重写。
进度打勾(item_fulfillment_steps)的每次写入必须同时写 audit_log
⇒ 两张表一次事务,不允许只写其一。