|
|
@@ -0,0 +1,453 @@
|
|
|
+# 商品配送与履约系统设计
|
|
|
+
|
|
|
+> 需求:用户下单时可选配送方式(快递/上门自取/线上服务)。自取需供应商录入地址+时间段;线上服务可选人(规划师/营养师,每人不同价格)或不可选人(一人一商品);订单页展示地址/联系人/电话;供应商确认已发出已收货后用户评价。
|
|
|
+
|
|
|
+## 一、配送方式扩展
|
|
|
+
|
|
|
+### 1.1 配送方式枚举
|
|
|
+
|
|
|
+`delivery_method` 字段从 `TINYINT` 扩展为多选位掩码 + 新增 `service_mode` 区分线上子类型:
|
|
|
+
|
|
|
+| 值 | 含义 | 说明 |
|
|
|
+|----|------|------|
|
|
|
+| 1 | 快递 | 供应商发货,用户填收货地址 |
|
|
|
+| 2 | 上门自取 | 供应商录入自取点+时间段,用户选地点+时间段 |
|
|
|
+| 3 | 两者皆可 | 快递或自取(兼容旧值) |
|
|
|
+| 4 | 线上服务-可选人 | 多个服务者(规划师/营养师),每人不同价格+时间段 |
|
|
|
+| 5 | 线上服务-单人 | 一人一商品,仅需选时间段 |
|
|
|
+
|
|
|
+> **设计决策**:保留旧 `delivery_method` 枚举(1/2/3 兼容),新增值 4/5 给线上服务。这样不影响存量商品/订单。
|
|
|
+
|
|
|
+### 1.2 Product 实体新增字段
|
|
|
+
|
|
|
+```java
|
|
|
+// Product.java
|
|
|
+private Integer deliveryMethod; // 1=快递 2=自提 3=两者皆可 4=线上-可选人 5=线上-单人
|
|
|
+private Integer serviceMode; // 线上服务子类型: 1=可选人 2=单人 (deliveryMethod=4/5时必填, 其余null)
|
|
|
+```
|
|
|
+
|
|
|
+不对 — `serviceMode` 与 `deliveryMethod` 重复。简化:`deliveryMethod` 唯一决定模式,去掉 `serviceMode`。
|
|
|
+
|
|
|
+```sql
|
|
|
+-- schema.sql: products 表
|
|
|
+-- delivery_method 注释更新
|
|
|
+delivery_method TINYINT DEFAULT 1 COMMENT '配送方式: 1=快递 2=自提 3=两者皆可 4=线上-可选人 5=线上-单人',
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二、自取地址(pickup_locations)
|
|
|
+
|
|
|
+### 2.1 新建表:`product_pickup_locations`
|
|
|
+
|
|
|
+供应商在创建/编辑商品时录入多个自取地址,每个地址挂多个时间段模板。
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS product_pickup_locations (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
|
|
+ product_id BIGINT NOT NULL COMMENT '所属商品ID',
|
|
|
+ name VARCHAR(100) NOT NULL COMMENT '地点名称(如"亿图腾科技-3楼会议室")',
|
|
|
+ address VARCHAR(500) NOT NULL COMMENT '详细地址',
|
|
|
+ longitude DECIMAL(10,7) COMMENT '经度',
|
|
|
+ latitude DECIMAL(10,7) COMMENT '纬度',
|
|
|
+ contact_name VARCHAR(50) COMMENT '联系人',
|
|
|
+ contact_phone VARCHAR(20) COMMENT '联系电话',
|
|
|
+ sort_order INT DEFAULT 0 COMMENT '排序',
|
|
|
+ enabled TINYINT DEFAULT 1 COMMENT '启用: 0/1',
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ INDEX idx_product_id (product_id)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品自取地点';
|
|
|
+```
|
|
|
+
|
|
|
+### 2.2 新建表:`product_pickup_time_slots`
|
|
|
+
|
|
|
+每个地点的可选时间段模板(按周循环 or 具体日期)。
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS product_pickup_time_slots (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
|
|
+ location_id BIGINT NOT NULL COMMENT '所属地点ID',
|
|
|
+ start_time TIME NOT NULL COMMENT '开始时间 HH:MM',
|
|
|
+ end_time TIME NOT NULL COMMENT '结束时间 HH:MM',
|
|
|
+ -- 循环模式
|
|
|
+ weekday TINYINT COMMENT '周几(1-7), null=按具体日期',
|
|
|
+ specific_date DATE COMMENT '具体日期, null=按周循环',
|
|
|
+ -- 容量
|
|
|
+ max_capacity INT DEFAULT 1 COMMENT '该时段最大接待人数',
|
|
|
+ booked_count INT DEFAULT 0 COMMENT '已预订数',
|
|
|
+ enabled TINYINT DEFAULT 1,
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ INDEX idx_location_id (location_id),
|
|
|
+ INDEX idx_specific_date (specific_date)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自取时间段模板';
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 三、线上服务-可选人(service_persons)
|
|
|
+
|
|
|
+### 3.1 新建表:`product_service_persons`
|
|
|
+
|
|
|
+每个商品可挂多个服务者(规划师/营养师/老师),每人独立价格+独立时间段。
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS product_service_persons (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
|
|
+ product_id BIGINT NOT NULL COMMENT '所属商品ID',
|
|
|
+ user_id BIGINT NOT NULL COMMENT '服务者用户ID(关联users表)',
|
|
|
+ name VARCHAR(50) NOT NULL COMMENT '展示名称',
|
|
|
+ title VARCHAR(100) COMMENT '头衔(如"高级营养师")',
|
|
|
+ intro TEXT COMMENT '个人介绍',
|
|
|
+ avatar VARCHAR(500) COMMENT '头像URL',
|
|
|
+ price INT NOT NULL COMMENT '该人专属价格(分)',
|
|
|
+ original_price INT COMMENT '原价(分)',
|
|
|
+ sort_order INT DEFAULT 0,
|
|
|
+ enabled TINYINT DEFAULT 1,
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ INDEX idx_product_id (product_id),
|
|
|
+ INDEX idx_user_id (user_id)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品线上服务者';
|
|
|
+```
|
|
|
+
|
|
|
+### 3.2 新建表:`product_service_person_slots`
|
|
|
+
|
|
|
+每个服务者的可预约时间段。
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS product_service_person_slots (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
|
|
+ person_id BIGINT NOT NULL COMMENT '所属服务者ID',
|
|
|
+ start_time DATETIME NOT NULL COMMENT '开始时间',
|
|
|
+ end_time DATETIME NOT NULL COMMENT '结束时间',
|
|
|
+ max_capacity INT DEFAULT 1 COMMENT '容量',
|
|
|
+ booked_count INT DEFAULT 0 COMMENT '已预订数',
|
|
|
+ enabled TINYINT DEFAULT 1,
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ INDEX idx_person_id (person_id),
|
|
|
+ INDEX idx_start_time (start_time)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务者可预约时间段';
|
|
|
+```
|
|
|
+
|
|
|
+### 3.3 单人线上服务(deliveryMethod=5)
|
|
|
+
|
|
|
+`deliveryMethod=5` 时商品本身即一人,无需 `product_service_persons` 记录,但仍需时间段表:
|
|
|
+
|
|
|
+复用 `product_service_person_slots` 但 `person_id` 关联到一个**自动创建的占位 person**(productId 关联的单条 person 记录),或用单独的表 `product_online_slots`:
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS product_online_slots (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ product_id BIGINT NOT NULL COMMENT '所属商品ID',
|
|
|
+ start_time DATETIME NOT NULL,
|
|
|
+ end_time DATETIME NOT NULL,
|
|
|
+ max_capacity INT DEFAULT 1,
|
|
|
+ booked_count INT DEFAULT 0,
|
|
|
+ enabled TINYINT DEFAULT 1,
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ INDEX idx_product_id (product_id),
|
|
|
+ INDEX idx_start_time (start_time)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单人线上服务时间段';
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、订单扩展
|
|
|
+
|
|
|
+### 4.1 ProductOrder 新增字段
|
|
|
+
|
|
|
+```sql
|
|
|
+ALTER TABLE product_orders
|
|
|
+ ADD COLUMN pickup_location_id BIGINT COMMENT '自取地点ID',
|
|
|
+ ADD COLUMN pickup_location_snapshot JSON COMMENT '自取地点快照(JSON: name/address/contact/phone)',
|
|
|
+ ADD COLUMN time_slot_id BIGINT COMMENT '所选时间段ID',
|
|
|
+ ADD COLUMN time_slot_snapshot JSON COMMENT '时间段快照(JSON: start/end)',
|
|
|
+ ADD COLUMN service_person_id BIGINT COMMENT '所选服务者ID(deliveryMethod=4)',
|
|
|
+ ADD COLUMN service_person_snapshot JSON COMMENT '服务者快照(JSON: name/title/avatar)',
|
|
|
+ ADD COLUMN fulfill_status VARCHAR(32) DEFAULT 'pending' COMMENT '履约状态: pending/confirmed_by_vendor/received_by_user/completed',
|
|
|
+ ADD COLUMN fulfill_confirmed_at DATETIME COMMENT '供应商确认时间',
|
|
|
+ ADD COLUMN received_at DATETIME COMMENT '用户确认收货时间';
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 履约状态机
|
|
|
+
|
|
|
+```
|
|
|
+pending(待支付)
|
|
|
+ ↓ 支付成功
|
|
|
+paid(已支付)
|
|
|
+ ↓ 供应商确认发出(自取:用户到场; 线上:服务完成)
|
|
|
+fulfill_status: confirmed_by_vendor
|
|
|
+ ↓ 用户确认收货/完成
|
|
|
+fulfill_status: received_by_user
|
|
|
+ ↓ 用户评价
|
|
|
+completed
|
|
|
+```
|
|
|
+
|
|
|
+> `status` 字段保持现状(pending/paid/completed/cancelled/refunded),`fulfill_status` 新增控制履约子流程。
|
|
|
+
|
|
|
+### 4.3 CreateProductOrderDTO 扩展
|
|
|
+
|
|
|
+```java
|
|
|
+@Data
|
|
|
+public class CreateProductOrderDTO {
|
|
|
+ // 现有字段...
|
|
|
+ private Long pickupLocationId; // 自取地点ID
|
|
|
+ private Long timeSlotId; // 时间段ID(自取/线上)
|
|
|
+ private Long servicePersonId; // 服务者ID(线上-可选人)
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+`addressSnapshot` 字段语义扩展:
|
|
|
+- 快递:用户收货地址(现有)
|
|
|
+- 自取:供应商录入的自取地点快照
|
|
|
+- 线上:服务者信息快照
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、快照策略
|
|
|
+
|
|
|
+下单时把地点/时间/服务者信息**序列化为 JSON 存快照**到订单,避免后续商品信息修改影响历史订单展示。
|
|
|
+
|
|
|
+| 字段 | 快照内容 |
|
|
|
+|------|---------|
|
|
|
+| `pickup_location_snapshot` | `{name, address, contactName, contactPhone, longitude, latitude}` |
|
|
|
+| `time_slot_snapshot` | `{startTime, endTime}` |
|
|
|
+| `service_person_snapshot` | `{name, title, intro, avatar, price}` |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 六、API 设计
|
|
|
+
|
|
|
+### 6.1 商品管理(供应商端)
|
|
|
+
|
|
|
+#### 录入自取地点+时间段
|
|
|
+```
|
|
|
+POST /api/product/pickup-location/save
|
|
|
+{
|
|
|
+ "productId": 123,
|
|
|
+ "locations": [
|
|
|
+ {
|
|
|
+ "name": "亿图腾科技-3楼",
|
|
|
+ "address": "北京市朝阳区xxx",
|
|
|
+ "contactName": "张三",
|
|
|
+ "contactPhone": "13800138000",
|
|
|
+ "timeSlots": [
|
|
|
+ {"weekday": 1, "startTime": "09:00", "endTime": "12:00", "maxCapacity": 5},
|
|
|
+ {"weekday": 1, "startTime": "14:00", "endTime": "17:00", "maxCapacity": 5}
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 录入线上服务者+时间段
|
|
|
+```
|
|
|
+POST /api/product/service-person/save
|
|
|
+{
|
|
|
+ "productId": 123,
|
|
|
+ "persons": [
|
|
|
+ {
|
|
|
+ "userId": 456,
|
|
|
+ "name": "李老师",
|
|
|
+ "title": "高级营养师",
|
|
|
+ "intro": "10年营养指导经验",
|
|
|
+ "price": 29900,
|
|
|
+ "slots": [
|
|
|
+ {"startTime": "2026-08-10 09:00:00", "endTime": "2026-08-10 10:00:00"},
|
|
|
+ {"startTime": "2026-08-10 10:00:00", "endTime": "2026-08-10 11:00:00"}
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 录入单人线上时间段
|
|
|
+```
|
|
|
+POST /api/product/online-slot/save
|
|
|
+{
|
|
|
+ "productId": 123,
|
|
|
+ "slots": [
|
|
|
+ {"startTime": "2026-08-10 09:00:00", "endTime": "2026-08-10 10:00:00", "maxCapacity": 1}
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 6.2 商品详情(用户端)
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/product/detail
|
|
|
+→ 返回商品信息 + 根据deliveryMethod附带:
|
|
|
+ - deliveryMethod=2/3: pickupLocations[] (含timeSlots[])
|
|
|
+ - deliveryMethod=4: servicePersons[] (含 slots[])
|
|
|
+ - deliveryMethod=5: onlineSlots[]
|
|
|
+```
|
|
|
+
|
|
|
+### 6.3 下单
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/product/order/create
|
|
|
+{
|
|
|
+ "productId": 123,
|
|
|
+ "deliveryMethod": 2,
|
|
|
+ "pickupLocationId": 10,
|
|
|
+ "timeSlotId": 20,
|
|
|
+ "consigneeId": null, // 自取不需要收货人
|
|
|
+ "paymentMethod": "wechat",
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+线上-可选人还需 `servicePersonId`。
|
|
|
+
|
|
|
+### 6.4 履约流程
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/product/order/vendor-confirm // 供应商确认已发出/服务完成
|
|
|
+ {orderNo} → fulfill_status = confirmed_by_vendor
|
|
|
+
|
|
|
+POST /api/product/order/confirm // 用户确认收货(现有,扩展)
|
|
|
+ {orderNo} → fulfill_status = received_by_user, status = completed
|
|
|
+
|
|
|
+POST /api/product/review/submit // 用户评价(新增 or 复用现有评价系统)
|
|
|
+```
|
|
|
+
|
|
|
+### 6.5 订单详情展示
|
|
|
+
|
|
|
+订单详情返回(用户端 + 供应商端):
|
|
|
+```json
|
|
|
+{
|
|
|
+ "orderNo": "...",
|
|
|
+ "deliveryMethod": 2,
|
|
|
+ "pickupLocationSnapshot": {
|
|
|
+ "name": "亿图腾科技-3楼",
|
|
|
+ "address": "北京市朝阳区xxx",
|
|
|
+ "contactName": "张三",
|
|
|
+ "contactPhone": "13800138000"
|
|
|
+ },
|
|
|
+ "timeSlotSnapshot": {
|
|
|
+ "startTime": "09:00",
|
|
|
+ "endTime": "12:00"
|
|
|
+ },
|
|
|
+ "fulfillStatus": "confirmed_by_vendor",
|
|
|
+ "canReview": false
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、数据库迁移(DatabaseInitializer)
|
|
|
+
|
|
|
+```java
|
|
|
+// 迁移N: 商品配送与履约系统
|
|
|
+// 1. products 表更新 delivery_method 注释(无需ALTER,注释仅元数据)
|
|
|
+
|
|
|
+// 2. product_orders 表新增字段
|
|
|
+ensureColumn("product_orders", "pickup_location_id", "BIGINT COMMENT '自取地点ID'");
|
|
|
+ensureColumn("product_orders", "pickup_location_snapshot", "JSON COMMENT '自取地点快照'");
|
|
|
+ensureColumn("product_orders", "time_slot_id", "BIGINT COMMENT '时间段ID'");
|
|
|
+ensureColumn("product_orders", "time_slot_snapshot", "JSON COMMENT '时间段快照'");
|
|
|
+ensureColumn("product_orders", "service_person_id", "BIGINT COMMENT '服务者ID'");
|
|
|
+ensureColumn("product_orders", "service_person_snapshot", "JSON COMMENT '服务者快照'");
|
|
|
+ensureColumn("product_orders", "fulfill_status", "VARCHAR(32) DEFAULT 'pending' COMMENT '履约状态'");
|
|
|
+ensureColumn("product_orders", "fulfill_confirmed_at", "DATETIME COMMENT '供应商确认时间'");
|
|
|
+ensureColumn("product_orders", "received_at", "DATETIME COMMENT '用户确认收货时间'");
|
|
|
+
|
|
|
+// 3. 新建表 product_pickup_locations / product_pickup_time_slots
|
|
|
+// product_service_persons / product_service_person_slots / product_online_slots
|
|
|
+// (CREATE TABLE IF NOT EXISTS, 见上方DDL)
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 八、实体与 DTO 清单
|
|
|
+
|
|
|
+| 新增实体 | 表 | 用途 |
|
|
|
+|---------|-----|------|
|
|
|
+| `ProductPickupLocation` | `product_pickup_locations` | 自取地点 |
|
|
|
+| `ProductPickupTimeSlot` | `product_pickup_time_slots` | 自取时间段 |
|
|
|
+| `ProductServicePerson` | `product_service_persons` | 线上服务者 |
|
|
|
+| `ProductServicePersonSlot` | `product_service_person_slots` | 服务者时间段 |
|
|
|
+| `ProductOnlineSlot` | `product_online_slots` | 单人线上时间段 |
|
|
|
+
|
|
|
+| 实体扩展 | 新增字段 |
|
|
|
+|---------|---------|
|
|
|
+| `ProductOrder` | `pickupLocationId, pickupLocationSnapshot, timeSlotId, timeSlotSnapshot, servicePersonId, servicePersonSnapshot, fulfillStatus, fulfillConfirmedAt, receivedAt` |
|
|
|
+| `CreateProductOrderDTO` | `pickupLocationId, timeSlotId, servicePersonId` |
|
|
|
+| `ProductDetailVO` | `pickupLocations[], servicePersons[], onlineSlots[]` |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 九、Controller 清单
|
|
|
+
|
|
|
+| Controller | 路径前缀 | 职责 |
|
|
|
+|-----------|---------|------|
|
|
|
+| `ProductPickupLocationController` | `/api/product/pickup-location` | 自取地点CRUD |
|
|
|
+| `ProductServicePersonController` | `/api/product/service-person` | 线上服务者CRUD |
|
|
|
+| `ProductOnlineSlotController` | `/api/product/online-slot` | 单人线上时间段CRUD |
|
|
|
+| `ProductOrderController`(扩展) | `/api/product/order` | 新增 `vendor-confirm` 端点 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 十、Service 清单
|
|
|
+
|
|
|
+| Service | 职责 |
|
|
|
+|---------|------|
|
|
|
+| `ProductPickupLocationService` | 自取地点+时间段管理 |
|
|
|
+| `ProductServicePersonService` | 线上服务者+时间段管理 |
|
|
|
+| `ProductOnlineSlotService` | 单人线上时间段管理 |
|
|
|
+| `ProductOrderService`(扩展) | 下单时校验时间段可用性、生成快照、扣减 bookedCount、履约状态流转 |
|
|
|
+| `ProductFulfillService` | 履约确认(供应商confirm)、用户收货确认、评价触发 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 十一、下单校验逻辑(ProductOrderService.create 扩展)
|
|
|
+
|
|
|
+```
|
|
|
+1. 查商品 → 校验 status=on_shelf
|
|
|
+2. 根据 deliveryMethod 分支校验:
|
|
|
+ - 1(快递): 校验 consigneeId 存在且属于 buyer
|
|
|
+ - 2(自提): 校验 pickupLocationId 属于该商品且 enabled; 校验 timeSlotId 属于该地点且 bookedCount < maxCapacity
|
|
|
+ - 3(两者皆可): 根据 dto.deliveryMethod 再走分支1或2
|
|
|
+ - 4(线上-可选人): 校验 servicePersonId 属于该商品; 校验 timeSlotId 属于该人且有余量; 价格取 person.price
|
|
|
+ - 5(线上-单人): 校验 timeSlotId 属于该商品 onlineSlots 且有余量
|
|
|
+3. 生成快照 JSON 存入订单
|
|
|
+4. 扣减时间槽 bookedCount (加事务)
|
|
|
+5. 计算金额(线上-可选人用 person.price; 其余用 product.price * quantity)
|
|
|
+6. 保存订单
|
|
|
+```
|
|
|
+
|
|
|
+## 十二、履约流程
|
|
|
+
|
|
|
+| 角色 | 动作 | API | 状态变化 |
|
|
|
+|------|------|-----|---------|
|
|
|
+| 用户 | 下单+支付 | `/order/create` + `/order/pay` | `status=paid, fulfill_status=pending` |
|
|
|
+| 供应商 | 确认发出(自取到场/线上完成) | `/order/vendor-confirm` | `fulfill_status=confirmed_by_vendor` |
|
|
|
+| 用户 | 确认收货 | `/order/confirm` | `fulfill_status=received_by_user, status=completed` |
|
|
|
+| 用户 | 评价 | `/review/submit` | 触发评价写入 |
|
|
|
+
|
|
|
+> 供应商确认后用户才能确认收货;用户确认收货后才能评价。前端按 `fulfillStatus` 控制按钮显隐。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 十三、schema.sql 同步
|
|
|
+
|
|
|
+所有新建表 DDL 同步追加到 `schema.sql` 末尾的商品相关区域(`products` 表之后),保持 schema.sql 为完整快照。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 十四、实现顺序
|
|
|
+
|
|
|
+1. ✅ 数据库迁移 + schema.sql 同步
|
|
|
+2. ✅ 5个新实体 + Mapper
|
|
|
+3. ✅ 3个新 Controller (CRUD)
|
|
|
+4. ✅ 3个新 Service
|
|
|
+5. ✅ ProductOrder 实体字段 + CreateProductOrderDTO 字段
|
|
|
+6. ✅ ProductOrderService.create 扩展(分支校验+快照+扣量)
|
|
|
+7. ✅ ProductOrderController 新增 `/vendor-confirm` 端点
|
|
|
+8. ✅ ProductOrderService.confirmReceive 扩展(更新 fulfill_status)
|
|
|
+9. ✅ 评价端点(复用现有 or 新增 ProductReview)
|
|
|
+10. ✅ 前端商品详情/下单页/订单详情页适配
|