履约透明化 · 整体规划设计

义父定调(2026-10-10):「将套餐内容透明化,所有项目和进度能让游客确切知道」
本文所有结论均来自 prod 实测(2026-10-10),非推测。

一、先说结论

后端已经把大部分数据算好了,C 端几乎没用;住宿/套餐链路则连数据都没有。

环节现状判断
买之前·套餐包含什么后端已返回 includes,C 端有渲染能力🟡 接上就能用
买之后·寺供进度后端返回 11 个字段,C 端只用了 2个🟡 接上就能用
买之后·住宿/套餐/餐食进度🔴 数据层没有🔴 要先补数据

⇒ 分三步走:先接已有数据(约 1 天见效),再补缺失状态机(约 1 周)。


二、现状实测(三个数字先摆出来)

2.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),

它们的进度字段根本不存在**。

2.2 明细项状态 —— 只有一个值

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 ⇒ 系统压根没记录"这一项进行到哪一步"。

2.3 但订单主状态是完整的 —— 这是唯一现成的进度主线

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)

💡 这是唯一"每笔订单都一定有"的状态 ⇒ 第一版进度条就用它,不等明细状态机。

2.4 寺供链路的进度 —— 后端已算好 11 个字段

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):

运营把供奉改成「已达成/已发货」,客人端永远看不到。

这次是同一个问题的第二次发生,只是范围更大。

2.5 套餐"包含什么" —— 后端已返回

"includes": [{ id, name, provider("平台自营"/"合作方提供"), description, ... }]
"dailySchedule": []          # 🔴 硬编码空数组

🔴 includes[].amount 硬编码 0、status 硬编码 "pending"

💡 C 端 detail-config.ts 已支持 includes / itinerary 两种块


三、目标:游客应该看到什么

3.1 买之前(套餐详情页)

┌─ 芝达布日 · 两日一晚 ────────────────┐
│  含 4 项:                ✓ 已补齐   │
│   · 住宿 1 晚(札什伦布)  ✓         │
│   · 早餐 × 2                ✓         │
│   · 温泉门票 × 2            ✓         │
│   · 讲解服务(含领队)      ⏳待履约   │
│                                        │
│  行程:                                 │
│   Day1  抵达 → 入住 → 欢迎餐          │
│   Day2  温泉 → 讲解 → 返程            │
└────────────────────────────────────────┘

3.2 买之后(订单详情页)

所有订单统一一个进度条 + 逐项状态:

订单进度
  ✓ 已下单      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
                        凭回执到店取回

四、落地方案(三阶段)

🟢 阶段一:接已有数据(不改数据结构,1 天)

做什么:把后端已经返回、C 端没用的字段显示出来。

改动位置内容
1order-detail.ts + wxml寺供项加:供哪座寺 / 供期至哪天 / 怎么取回 / 三条时间戳
2order-detail.ts顶部加订单主状态进度条(用 orders.status 14 态的前 6 态)
3package-detail.ts启用 includes 块渲染(detail-config.ts 已支持)
4miniapp_products.pyincludes[].amount 改成真实金额(当前硬编码 0)

验收:寺供订单详情能看到完整时间线;任意订单能看到主进度条;套餐页能逐项列出包含什么。

风险:低(只改展示,不动数据)。


🟡 阶段二:补 direct 链路的状态机(1 周)

问题: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 周)
         ↓
之后 ──→ 阶段三(每日行程,等内容来源确定)

理由:阶段一不碰数据结构、零风险、当天就能让游客看到东西;

阶段二才是真正的新建工作,不该和阶段一混在一起做。


附:审计与商户透明(2026-10-10 追加,义父定调「必须要有」)

需求原文

权限记录肯定要有,而且将来也要让商户看到
是哪个员工通过 A 端、什么时候、怎么操作的。

现状:审计设施已有三层,但各缺一块(prod 实测)

表行数已有缺
audit_log24operator_id operator_role action detail🔴 无 merchant_id;operator_id 无外键,人走了记录成孤儿
operation_logs1merchant_id action target🔴 几乎没用;operator 是文本不是 ID
order_intervention_logs0before_status after_status reason operator_id🔴 无 merchant_id;完全没用

💡 audit_log 已经在记供献每一步(offering.confirm-offering / mark-ready / confirm-retrieval),

说明写入侧有现成范式,缺的是「商户维度」和「可靠性」。

补法(迁移 122)

-- ① 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

⇒ 两张表一次事务,不允许只写其一。