# 2026-02-05 / mall：配送-商家-消费者 数据流统一分析（SQL + 项目规范口径）

## 摘要

本文基于本仓库 **权威 SQL 目录** `docs/sql/`（并参考 `mall_sql/` 中的 `complete_mall_database.sql` 实现细节），结合项目工程与安全规范 `docs/project_spec/AGENT_PROJECT_SPEC.md`、`docs/sql/11_roles_and_permissions_strategy.md`，对 **消费者 / 商家 / 配送员** 三方在电商场景中的数据流运转进行统一分析。

输出内容包括：

- 三方域模型与关键表关系（文字版 ER）
- 端到端事件链路：下单、支付、履约配送、完成、评价
- 订单与配送状态机（字段口径与关键歧义点）
- 读模型（各端典型查询视角）
- 权限边界（RLS / RPC / 前端守卫）在本项目中的统一口径

> 本文为分析文档，不包含代码/SQL 变更。

---

## 动机

- 统一“谁能看/改什么数据”的口径：角色权威字段、RLS 与 RPC 的安全闭环要求必须一致。
- 统一“数据如何落表与流转”的口径：避免前后端/不同模块对订单、配送、商家模型理解不一致。
- 为后续落地（配置页、配送履约闭环、统计看板等）提供可审计的基础说明。

---

## 影响范围

- **数据库层**：`docs/sql/` 中的表结构、触发器、RLS、RPC 策略口径（本文不修改）。
- **前端工程层**：页面必须通过 `services/` 访问数据；admin/analytics 需通过 RPC 获取全局数据（本文不修改）。
- **业务模块**：
  - 消费者端（consumer）订单链路
  - 商家端（merchant）商品与订单履约
  - 配送端（delivery）接单/取货/送达
  - 管理端（admin）配置与全局视角

---

## 变更清单

- **新增文件**：
  - `docs/ops/2026-02-05__mall__delivery-merchant-customer-dataflow-analysis.md`
- **修改文件**：无
- **删除文件**：无

---

## 权威输入（引用口径）

- 数据库与流程说明：
  - `docs/sql/02_relationships_er.md`
  - `docs/sql/03_enums_status_dict.md`
  - `docs/sql/07_business_workflows.md`
  - `docs/sql/11_roles_and_permissions_strategy.md`
- 工程与安全规范：
  - `docs/project_spec/AGENT_PROJECT_SPEC.md`
- 参考实现（非权威口径，但含完整 DDL/触发器/RLS 示例）：
  - `mall_sql/schemas/complete_mall_database.sql`
- 管理端设置页现状（占位）：
  - `pages/mall/admin/system-settings.uvue`

---

## 1. 统一域模型：三方是谁（同一身份体系下的三类角色）

- **统一用户主表**：`public.ak_users`
  - **角色唯一权威字段**：`ak_users.role`
- **商城用户扩展档案**：`public.ml_user_profiles`
  - 关系：`ak_users` 1:1 `ml_user_profiles`（`ml_user_profiles.user_id UNIQUE`）

三方角色定义（业务口径）：

- 消费者：`ak_users.role = 'customer'`
- 商家：`ak_users.role = 'merchant'`
- 配送员：`ak_users.role = 'delivery'`

结论：三方并非三套用户体系，而是“同一 `ak_users` + 不同域表扩展”。

---

## 2. 核心表关系（文字版 ER）

### 2.1 消费者（Customer）

- `ak_users (customer)` 1:N `ml_user_addresses`
- `ak_users (customer)` 1:N `ml_shopping_cart`
- `ak_users (customer)` 1:N `ml_orders`（`ml_orders.user_id`）

### 2.2 商家（Merchant）

- `ak_users (merchant)` 1:1 `ml_shops`（`ml_shops.merchant_id UNIQUE`）
  - 当前模型含义：一商家一店铺
- `ak_users (merchant)` 1:N `ml_products`（`ml_products.merchant_id`）
- `ak_users (merchant)` 1:N `ml_orders`（`ml_orders.merchant_id`）

### 2.3 商品（Product）

- `ml_products` 1:N `ml_product_skus`
- `ml_products` 1:N `ml_product_specs`
- SKU 变更触发汇总：`ml_product_skus` 变更 -> 触发器刷新 `ml_products.total_stock/available_stock`

### 2.4 订单（Order）

- `ml_orders` 1:N `ml_order_items`
- `ml_orders` 1:1 `ml_delivery_tasks`（`ml_delivery_tasks.order_id UNIQUE`）
  - 当前模型含义：一订单最多一个配送任务

### 2.5 配送（Delivery）

- `ak_users (delivery)` 1:1 `ml_delivery_drivers`（`ml_delivery_drivers.user_id UNIQUE`）
- `ml_delivery_drivers` 1:N `ml_delivery_tasks`（通过 `ml_delivery_tasks.driver_id` 关联）

---

## 3. 三方端到端数据流（事件 → 落表 → 状态变化）

### 3.1 消费者链路（浏览→加购→下单→支付→收货→评价）

#### 事件 A：维护地址

- 写表：`ml_user_addresses`
- 约束：同一用户最多一个默认地址
  - 由触发器 `ensure_single_default_address()` 保证

#### 事件 B：加购/改购

- 写表：`ml_shopping_cart`
- 约束：`UNIQUE(user_id, product_id, sku_id)`（便于 upsert 累加）

#### 事件 C：下单（创建订单 + 明细快照）

- 写表：
  - `ml_orders`（订单主表）
  - `ml_order_items`（订单明细快照）
- 关键设计点：
  - `ml_orders.shipping_address` 为 JSONB 地址快照，避免订单与地址簿耦合
  - `ml_order_items` 快照化写入商品名/规格/图/价格，避免商品变更影响历史

> 一致性提示（现状）：当前权威文档指出未体现“扣库存/冻结库存”落地，库存一致性需由应用层事务或后续 DB/RPC 补齐。

#### 事件 D：支付成功

- 更新：`ml_orders`
  - `order_status: 1 -> 2`
  - `payment_status: 1 -> 2`
  - `paid_amount = total_amount`
- 触发器副作用：`handle_order_status_change()` 在 `1->2` 写 `paid_at`

#### 事件 E：收货完成/订单完成

- 更新：`ml_orders`
  - `order_status: 3 -> 4`
  - `shipping_status -> 4`
- 触发器副作用：写 `delivered_at/completed_at`，并累计更新 `ml_products.sale_count`

#### 事件 F：评价

- 写表：`ml_product_reviews`
- 强绑定来源：`order_id + order_item_id`

---

### 3.1.1 退款/取消在当前模型中的最小表达（现状与缺口）

当前数据库没有独立的退款申请表或售后流程表，只能通过 `ml_orders` 的状态组合表达：

- **退款分支**：
  - `payment_status: 3（部分退款）` / `4（全额退款）`
  - `order_status` 通常进入 `6（退款中）` -> `7（已退款）`
- **取消分支**：
  - `order_status: 5`（歧义点，见第 4.2 节）
  - `cancel_reason`（文本字段）可用于记录取消原因

> **缺口提示**：
> - 缺少退款申请流水、审批流、支付对账表，复杂售后需后续扩表或接支付系统。
> - 缺少 `cancelled_at`、`refunded_at` 等审计时间字段。
> - 配送已取货/在途/已送达时的退款/取消口径未在当前模型体现（需业务层定义）。

---

### 3.2 商家链路（供给→接单→履约）

#### 事件 A：创建/编辑商品

- 写表：`ml_products`、`ml_product_skus`、`ml_product_specs`
- 库存汇总：SKU 触发器刷新 SPU 汇总库存字段

#### 事件 B：发货/履约推进

- 更新：`ml_orders`
  - `order_status: 2 -> 3`
  - `shipping_status: 1 -> 2`
- 触发器副作用：`2->3` 时写 `shipped_at`

---

### 3.2.1 单商家订单模型的限制（需求提醒）

当前订单主表 `ml_orders` 直接持有 `merchant_id`，且未见“主单/子单”结构，因此该模型天然对应：

- **一笔订单仅归属一个商家/店铺**（单商家订单）

> 若产品需求为“一单多商家（多店铺）”，通常需要：
> - 引入主/子订单拆分（例如主订单聚合支付与收货信息，子订单按商家拆分履约与结算），或
> - 在下单阶段强制拆单（多个 `ml_orders`）并建立父子关联字段。

---

### 3.3 配送链路（派单/接单→取货→配送→送达）

#### 事件 A：创建配送任务

- 写表：`ml_delivery_tasks`
- 关键字段：
  - `order_id UNIQUE`
  - `driver_id` 可空（先生成后指派）
  - `pickup_address`（取货点）
  - `delivery_address`（配送点，建议源自订单地址快照）

#### 事件 B：接单/指派

- 更新：`ml_delivery_tasks`
  - `status: 1 -> 2`
  - `assigned_at` 写入
  - 绑定 `driver_id`

#### 事件 C：取货→配送→送达（或失败）

- 更新：`ml_delivery_tasks`
  - `2 -> 3`（写 `picked_at`）
  - `3 -> 4`
  - `4 -> 5`（写 `delivered_at`）
- 失败：`status = 6` + `failure_reason`

> 一致性提示（现状）：未看到 DB 侧“配送送达 → 自动联动订单状态”的权威触发器/RPC 设计，需在 service/RPC 层承担联动更新职责。

---

### 3.3.1 配送状态 → 订单状态的建议映射（统一口径）

为避免三方对“配送进展如何反映到订单状态”理解不一致，建议采用以下最小映射（可在 service/RPC 事务中实现）：

| 配送任务 `status` | 建议同步的 `ml_orders.shipping_status` | 建议同步的 `ml_orders.order_status`                   | 说明                      |
| ----------------- | -------------------------------------- | ----------------------------------------------------- | ------------------------- |
| 1 待接单          | 1 未发货                               | 2 待发货（已支付）                                    | 任务生成但未接单          |
| 2 已接单          | 2 已发货                               | 2 待发货                                              | 商家/平台已指派，但未取货 |
| 3 取货中          | 3 运输中                               | 3 待收货                                              | 骑手已取货，在途          |
| 4 配送中          | 3 运输中                               | 3 待收货                                              | 仍在途                    |
| 5 已送达          | 4 已送达                               | 3 待收货（或直接 4 已完成，取决于产品是否需用户确认） | 骑手确认送达              |
| 6 配送失败        | 1 未发货（或自定义状态）               | 2 待发货（或进入异常分支）                            | 需人工介入                |

> **关键决策点**：`status=5` 时，`order_status` 是“待收货”还是直接“已完成”，取决于产品是否需要用户“确认收货”这一步。若不需要，可直接 `4`。

---

### 3.3.2 派单依据（任务池可见性）的字段来源

`ml_delivery_drivers` 与 `ml_delivery_tasks` 中的以下字段是“任务池筛选/派单算法”的数据基础：

- **服务区域**：`ml_delivery_drivers.service_areas`（JSONB），用于按区域过滤可接单骑手
- **实时位置**：`current_lat/current_lng`，用于距离排序/就近派单
- **工作状态**：`work_status`（在线/忙碌/离线），用于过滤可用骑手
- **任务状态**：`ml_delivery_tasks.status=1`，用于构建“待接单任务池”

> 权限提示：若任务池需对骑手可见，需配合 RLS 或 RPC 实现“仅可看同区域且状态=1 的任务”。

---

## 4. 状态机统一口径（必须统一的单一真相）

### 4.1 订单并行状态字段

`ml_orders` 存在三条并行状态线：

- `order_status`：订单流程
- `payment_status`：支付/退款
- `shipping_status`：发货/物流

推荐统一驱动关系：

- 支付成功驱动 `order_status` 进入“待发货”阶段
- 发货/配送接管驱动 `order_status` 进入“待收货”阶段
- 签收/确认收货驱动 `order_status` 完成
- 退款以 `payment_status` 为主线，同时影响 `order_status` 分支

### 4.2 `order_status = 5` 的歧义

文档指出 `5` 在不同脚本存在“已取消/已取货”表述差异。

- 无自提流程：建议统一为“已取消”
- 有自提流程：建议拆出独立状态值（避免取消与自提完成混用）

---

## 5. 读模型（各端典型查询视角）

> 本节描述“应该怎么查”，不涉及具体实现。

### 5.1 消费者端

- 商品列表/详情：优先基于商品详情视图（若存在）或 `ml_products`（上架商品）
- 我的订单列表：`ml_orders.user_id = 当前用户` + 时间倒序
- 订单详情：`ml_orders` + `ml_order_items`
- 配送进度：若需展示，则读取 `ml_delivery_tasks`（需权限策略或 RPC 支持）

### 5.2 商家端

- 我的商品：`ml_products.merchant_id = 当前商家`
  - 注意：若 `ml_products` 的 RLS select 仅允许 `status=1`，商家后台查看草稿/下架需额外策略或 RPC
- 店铺订单：`ml_orders.merchant_id = 当前商家` + `ml_order_items`
- 配送任务看板：按订单关联 `ml_delivery_tasks`

> **现状风险（RLS）**：`ml_products_select_policy` 仅允许 `status=1`，会导致商家后台直查表时看不到自己的草稿/下架商品。建议扩展 RLS 或为商家后台提供专用 RPC。

### 5.3 配送端

- 待接单任务池：`ml_delivery_tasks.status = 1`（通常需按服务区域过滤）
- 我的任务：`ml_delivery_tasks.driver_id = 当前配送员`
- 任务详情：最小化暴露订单/个人敏感信息（原则层面）

---

## 6. 权限边界（RLS / RPC / 前端守卫的闭环）

### 6.1 前端工程约束

- 数据访问唯一入口：必须通过 `services/`
- 客户端路由守卫用于快速失败，但最终权限以数据库为准

### 6.2 admin / analytics 的全局数据访问

按项目规范与策略文档：

- 不建议对 admin/analytics 直接开放业务表全局 `SELECT`
- 应通过 RPC（`SECURITY DEFINER` + 固定 `search_path` + 入口 `get_current_user_role()` 鉴权）返回最小必要字段/聚合数据

---

## 7. 金额字段的归因与缺口（运费/优惠/抽佣）

| 金额字段                    | 数据来源                                              | 归因说明                                          | 现状缺口                                |
| --------------------------- | ----------------------------------------------------- | ------------------------------------------------- | --------------------------------------- |
| `ml_orders.shipping_fee`    | `ml_system_configs.shipping_fee`（或按距离/重量计算） | 配送费，可配置默认值或按规则计算                  | 缺少运费规则表（如按区域/重量/距离）    |
| `ml_orders.discount_amount` | 优惠券/活动/满减                                      | 优惠金额，使用券时关联 `ml_user_coupons.order_id` | 缺少活动/满减规则表，复杂优惠需扩表     |
| `ml_orders.total_amount`    | 商品金额 + 运费 - 优惠                                | 订单最终应付                                      | —                                       |
| `platform_commission`       | `ml_system_configs.platform_commission`（比例）       | 平台抽佣，通常在结算时计算                        | 缺少结算单/分账流水表，抽佣未落结算流水 |

> **结论**：金额归因在“配置 → 落表”层面基本可表达，但缺少结算/对账/复杂优惠的支撑表。

---

## 8. 系统配置（system-settings）与“配置驱动流程”的现状说明

- DB 已存在：`ml_system_configs`，并在参考脚本中初始化了配置项（如 `shipping_fee`、`platform_commission`、`order_auto_confirm_days`）。
- 管理端页面现状：`pages/mall/admin/system-settings.uvue` 目前为占位页，仅展示 query 参数，未接入配置读写。

结论：配置承载表存在，但尚未形成“配置 → 业务流程”的实际驱动闭环。

---

## 兼容性与风险

- **状态机风险**：三条并行状态字段若缺少统一约束，易出现互相矛盾的状态组合。
- **一致性风险**：库存扣减/冻结、配送送达与订单联动等关键一致性未在当前权威 SQL 中体现，需在应用层或后续 RPC/触发器补齐。
- **权限风险**：配送域 RLS/RPC 若未补齐，会导致配送端无法形成可运转的“接单→更新状态”闭环。

---

## 回滚方案

本文仅新增文档，无业务变更；删除该 markdown 文件即可回滚。

---

## 验证方式

- 打开本文档，核对引用路径均存在：
  - `docs/sql/*`、`docs/project_spec/*`
- 与现有页面目录结构、角色字段口径（`ak_users.role`）一致。

---

## 关联文档

- `docs/project_spec/AGENT_PROJECT_SPEC.md`
- `docs/sql/02_relationships_er.md`
- `docs/sql/03_enums_status_dict.md`
- `docs/sql/07_business_workflows.md`
- `docs/sql/11_roles_and_permissions_strategy.md`
