# 07 典型业务流程：落表路径与关键字段

本节用“业务步骤 → 涉及表 → 关键字段/约束/触发器”的方式，把核心链路讲清楚，方便新同事快速理解数据如何流动。

---

## 1. 商品发布与上架流程（商家侧）

### 1.1 创建 SPU（商品主表）

- **写入表**：`ml_products`
- **关键字段**：
  - `merchant_id`：商家用户（关联 `ak_users.id`）
  - `category_id/brand_id`
  - `base_price`
  - `status`：初始可为草稿(3)或上架(1)
  - `cid/slug`：对外访问

示例（简化）：

```sql
insert into public.ml_products(
  merchant_id, category_id, product_code, name, base_price, status
)
values (
  '...merchant_uuid...'::uuid,
  '...category_uuid...'::uuid,
  'P20260001',
  '苹果手机',
  4999.00,
  3
)
returning id, cid;
```

### 1.2 定义规格项（可选）

- **写入表**：`ml_product_specs`
- **关键字段**：`spec_name`、`spec_values JSONB`

示例：

```sql
insert into public.ml_product_specs(product_id, spec_name, spec_values)
values
('...product_uuid...'::uuid, '颜色', '["黑","白"]'::jsonb),
('...product_uuid...'::uuid, '容量', '["128G","256G"]'::jsonb);
```

### 1.3 创建 SKU（库存与具体价格）

- **写入表**：`ml_product_skus`
- **关键字段**：`specifications JSONB`、`price`、`stock`、`status`
- **数据库规则**：
  - SKU 变更会触发 `update_product_stock()`，自动汇总刷新 `ml_products.total_stock/available_stock`

示例：

```sql
insert into public.ml_product_skus(product_id, sku_code, specifications, price, stock)
values
(
  '...product_uuid...'::uuid,
  'SKU-001',
  '{"颜色":"黑","容量":"128G"}'::jsonb,
  4999.00,
  10
);

-- 插入 SKU 后，触发器会把商品 total_stock/available_stock 更新为 10
```

### 1.4 上架商品

- **更新表**：`ml_products`
- **关键字段**：`status = 1`、`published_at`（若使用）

示例：

```sql
update public.ml_products
set status = 1, published_at = now()
where id = '...product_uuid...'::uuid;
```

---

## 2. 浏览与收藏（用户侧）

### 2.1 浏览记录

- **写入表**：`ml_browse_history`
- **约束**：`UNIQUE(user_id, product_id)`
- **含义**：倾向记录“最后一次浏览”而不是“浏览流水”。

典型写法：

- 插入失败后转更新（upsert）

```sql
insert into public.ml_browse_history(user_id, product_id, browse_duration)
values ('...user_uuid...'::uuid, '...product_uuid...'::uuid, 20)
on conflict (user_id, product_id)
do update set browse_duration = excluded.browse_duration, updated_at = now();
```

### 2.2 收藏

- **写入表**：`ml_user_favorites`
- **约束**：`UNIQUE(user_id, target_type, target_id)`

```sql
insert into public.ml_user_favorites(user_id, target_type, target_id)
values ('...user_uuid...'::uuid, 1, '...product_uuid...'::uuid)
on conflict do nothing;
```

---

## 3. 加购与结算（用户侧）

### 3.1 加入购物车

- **写入表**：`ml_shopping_cart`
- **约束**：`UNIQUE(user_id, product_id, sku_id)`

常见做法：重复加购时做累加：

```sql
insert into public.ml_shopping_cart(user_id, product_id, sku_id, quantity, selected)
values ('...user_uuid...'::uuid, '...product_uuid...'::uuid, '...sku_uuid...'::uuid, 1, true)
on conflict (user_id, product_id, sku_id)
do update set quantity = public.ml_shopping_cart.quantity + 1, updated_at = now();
```

### 3.2 计算购物车金额

- **读取函数**：`public.calculate_cart_total(p_user_id)`

```sql
select public.calculate_cart_total('...user_uuid...'::uuid);
```

---

## 4. 下单（创建订单 + 明细快照）

### 4.1 订单号生成

- **函数**：`public.generate_order_no()`（基于 `ml_order_seq`）

```sql
select public.generate_order_no();
```

### 4.2 创建订单主表（地址快照）

- **写入表**：`ml_orders`
- **关键字段**：
  - `shipping_address JSONB`：下单时把地址“快照化”写进订单
  - `order_status/payment_status/shipping_status`

示例（简化）：

```sql
insert into public.ml_orders(
  order_no, user_id, merchant_id,
  product_amount, discount_amount, shipping_fee, total_amount,
  shipping_address,
  order_status, payment_status, shipping_status
)
values (
  public.generate_order_no(),
  '...user_uuid...'::uuid,
  '...merchant_uuid...'::uuid,
  4999.00, 0.00, 10.00, 5009.00,
  '{"receiver":"张三","phone":"138...","province":"广东","city":"深圳","district":"南山","detail":"xxx"}'::jsonb,
  1, 1, 1
)
returning id;
```

### 4.3 创建订单明细（商品快照）

- **写入表**：`ml_order_items`
- **关键点**：把 `product_name/sku_name/specifications/image_url/price` 等写入明细，防止商品后改影响历史。

```sql
insert into public.ml_order_items(
  order_id, product_id, sku_id,
  product_name, sku_name, specifications, image_url,
  price, quantity, total_amount
)
values (
  '...order_uuid...'::uuid,
  '...product_uuid...'::uuid,
  '...sku_uuid...'::uuid,
  '苹果手机',
  '黑/128G',
  '{"颜色":"黑","容量":"128G"}'::jsonb,
  'https://.../1.png',
  4999.00,
  1,
  4999.00
);
```

> 注意：当前可见 SQL 未体现“扣库存/冻结库存”动作，通常需要在同一事务中由应用层或额外 DB 函数完成（详见 `08_data_consistency_boundaries.md`）。

---

## 5. 支付、发货、完成（状态流转）

### 5.1 支付完成

- **更新表**：`ml_orders`
- **预期**：`order_status: 1 -> 2`，并写 `paid_amount/payment_status`
- **数据库副作用（complete 脚本）**：触发器 `handle_order_status_change()` 自动写 `paid_at`

```sql
update public.ml_orders
set order_status = 2,
    payment_status = 2,
    paid_amount = total_amount
where id = '...order_uuid...'::uuid;
```

### 5.2 发货

```sql
update public.ml_orders
set order_status = 3,
    shipping_status = 2
where id = '...order_uuid...'::uuid;

-- complete 脚本的触发器会在 2->3 时写 shipped_at
```

### 5.3 收货完成

```sql
update public.ml_orders
set order_status = 4,
    shipping_status = 4
where id = '...order_uuid...'::uuid;

-- complete 脚本触发器在 3->4 时写 delivered_at/completed_at，并累计商品 sale_count
```

---

## 6. 评价

- **写入表**：`ml_product_reviews`
- **约束设计**：强绑定 `order_id` 与 `order_item_id`，保证评价来自真实订单。

```sql
insert into public.ml_product_reviews(
  order_id, order_item_id, user_id, product_id, merchant_id,
  rating, content, images
)
values (
  '...order_uuid...'::uuid,
  '...order_item_uuid...'::uuid,
  '...user_uuid...'::uuid,
  '...product_uuid...'::uuid,
  '...merchant_uuid...'::uuid,
  5,
  '很好用',
  '["https://.../a.png"]'::jsonb
);
```

---

## 7. 优惠券：发放与使用

### 7.1 领券

- **写入表**：`ml_user_coupons`
- **券码**：可使用 `generate_coupon_code()` 生成

```sql
insert into public.ml_user_coupons(user_id, template_id, coupon_code, status, expire_at)
values (
  '...user_uuid...'::uuid,
  '...template_uuid...'::uuid,
  public.generate_coupon_code(),
  1,
  now() + interval '30 days'
);
```

### 7.2 用券归因

```sql
update public.ml_user_coupons
set status = 2,
    used_at = now(),
    order_id = '...order_uuid...'::uuid
where id = '...user_coupon_uuid...'::uuid
  and status = 1;
```

---

## 8. 订阅：开通/续费/到期

### 8.1 创建套餐

- `ml_subscription_plans`

```sql
insert into public.ml_subscription_plans(plan_code, name, price, billing_period)
values ('PRO_MONTH', '专业版（月付）', 99.00, 'monthly');
```

### 8.2 用户订阅

- `ml_user_subscriptions`

```sql
insert into public.ml_user_subscriptions(user_id, plan_id, status, start_date, next_billing_date)
values (
  '...user_uuid...'::uuid,
  '...plan_uuid...'::uuid,
  'trial',
  now(),
  now() + interval '30 days'
);
```

> 说明：订阅模块脚本未与 `ml_orders` 建立外键关联，支付/对账链路通常在应用层或另一个交易子系统实现。
