# 发货管理 — 技术方案

> **依据：** 《发货管理功能需求.md》v1.3  
> **关联：** 平台《订单管理技术方案》v1.0.2、《订单管理功能需求.md》v1.0.1、《关联需求分析.md》v1.6；商家《全部订单技术方案》v1.0.2、《配送设置技术方案》v1.4、《商家端店铺上下文接口说明》  
> **范围：** 商家端 **履约工作台**（待发货 / 已发货 / 已完成）列表、角标、详情、整单发货、物流更新、送达登记；**不** 删除、**不** 关闭、**不** 创建订单。  
> **原则：** **无新建表**；与平台 / 全部订单 **同源** `biz_order` 三表；履约 **共用** `IOrderService`；`shop_id = X-Shop-Id` 强制隔离；**整单发货**（O5 / FM4）；发货时 **快照** 配送设置至订单（**DS6**：弹窗不预填）。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 权限 / 响应 | MyBatis；`@PreAuthorize`；`AjaxResult` / `TableDataInfo` |
| 店铺上下文 | `SellerShopContextInterceptor` + 请求头 **`X-Shop-Id`** |
| 脱敏 | 列表收货手机 `MemberMobileUtils.mask` |
| 日志 | 发货、物流、送达 `@Log` |
| 字典 | 与平台共用 `biz_order_status`、`biz_delivery_type`、`biz_order_trace_type` |

### 1.1 与全部订单 / 平台边界

| 维度 | 全部订单 | **发货管理（本方案）** | 平台 · 订单管理 |
|------|----------|------------------------|-----------------|
| 定位 | 全生命周期 | **履约工作台** | 全平台代运营 |
| 路径 | `/agri/seller/order` | **`/agri/seller/ship`** | `/agri/order` |
| 页签 | 含待支付/已关闭/已删除 | **仅 1/2/3** | 全状态 |
| 写操作 | 发货/物流/送达/删除 | 发货/物流/送达；**无 delete** | 含 close |
| Service | `IOrderService` | **同一实现**（委托） | 同一实现 |
| 表 | 同源三表 | **同表** | 同表 |

> **定稿：** 本模块 **不复制** 履约逻辑；`SellerShipController` 为 **薄 Controller**（校验页签范围 + 角标），业务走已有 `OrderServiceImpl`。

### 1.2 协作链

```text
【C 端】下单/支付 → biz_order（shop_id=本店, status=1）
            ↓
【登录】GET /agri/seller/context → X-Shop-Id
            ↓
【本模块】GET  /agri/seller/ship/badges     → 页签计数
           GET  /agri/seller/ship/list       → status∈{1,2,3}
           GET  /agri/seller/ship/{orderId}  → 详情+物流轴
           POST /agri/seller/ship/{id}/ship       → 1→2 + trace(1)
           POST /agri/seller/ship/{id}/logistics  → trace(2)
           POST /agri/seller/ship/{id}/delivered  → trace(3)
            ↓
【C 端】confirmReceive → status 2→3 + refreshConsumption（O11）
【平台】/agri/order/** 可代运营同一单（状态同步，不可重复发货）
【全部订单】/agri/seller/order/** 可查看 0/4/5 及 delete
```

| 关联模块 | 协作 |
|----------|------|
| **全部订单** | 同源 API 能力；非履约单 **跳转** 全部订单页 |
| **平台 · 订单管理** | 同源表与状态机；O8 定时关单商家只读 |
| **C 端 · 我的订单** | 读 `biz_order_logistics_trace` 最新节点；确认收货写 trace(4) |
| **会员管理** | 只读 `member_nick_name`；**不调用** `refreshConsumption` |
| **商品管理** | 明细读快照；发货 **不** 再扣库存（O4） |
| **店铺管理** | `shop_id` 隔离；删店 O10 走 `IOrderFacade` |
| **配送设置** | 发货时 **只读** 快照来源；详情待发货 **可选参考** | **不** 参与 logistics/delivered 写库 |

### 1.4 与配送设置边界

| 维度 | 配送设置 | **发货管理（本方案）** |
|------|----------|------------------------|
| 表 | `biz_shop_delivery_setting` | **`biz_order` 三表** |
| 路径 | `/agri/seller/delivery` | `/agri/seller/ship` |
| 写 | 店铺发货人/地址（每店一份） | 订单履约字段 + **发货人/地址快照** + trace |
| 读协作 | C 端 `IShopDeliveryFacade.getShipCity` | 详情 **待发货** 可选 `shopDelivery` 参考；**已发货** 读订单快照 |
| 预填 | — | **`OrderShipDTO` 不含** 配送设置；**不预填** 弹窗 |

```text
去发货 POST /ship/{id}/ship
    → OrderServiceImpl.shipOrder
    → IShopDeliveryService.getByShopId(shopId)   # 读配送设置（只读）
    → OrderSupport.applyShipperSnapshot          # 快照至 BizOrder
    → UPDATE biz_order + INSERT trace
    → 不 UPDATE biz_shop_delivery_setting
```

**前置：** 配送设置未配置完整 → `MSG_SHIP_DELIVERY_SETTING_REQUIRED`。

**详情（FM17 · 功能 §7.2）：**

```text
GET /ship/{orderId}
    → OrderDetailVO.shipperName / shipperMobile / shipRegionName / shipDetailAddress  # 已发货快照
    → + shopDelivery?: ShopDeliverySettingVO   # 待发货可选参考；已快照时 UI 优先展示订单字段
```

### 1.3 模块落位

**包路径：** `com.ruoyi.web.modules.order`（与全部订单 **同包**）

```text
baqing-shop/src/main/java/com/ruoyi/web/modules/order/
├── controller/
│   ├── SellerOrderController.java        # 全部订单 /agri/seller/order（已有）
│   └── SellerShipController.java         # 发货管理 /agri/seller/ship（待建 · 薄层）
├── service/
│   ├── IOrderService.java                # 已有 · 共用
│   └── impl/OrderServiceImpl.java        # 已有 · ship/logistics/delivered
├── dto/
│   ├── SellerOrderQuery.java             # 列表检索（复用）
│   ├── OrderShipDTO.java                 # 发货
│   ├── OrderLogisticsDTO.java            # 更新物流
│   └── OrderDeliveredDTO.java            # 登记送达
├── vo/
│   ├── OrderListRowVO.java               # 列表行（复用）
│   ├── OrderDetailVO.java                # 详情（复用；可扩展 shopDelivery）
│   └── SellerShipBadgeVO.java            # 页签角标（待建）
├── mapper/BizOrderMapper.java            # + countShopByStatus（角标）
└── support/OrderSupport.java             # 已有 · 状态/归属校验

sql/biz_order.sql                         # 权威 DDL（无新建表）

com.ruoyi.web.modules.delivery/         # 配送设置（并列 · 不耦合）
├── IShopDeliveryService.getByShopId     # 详情可选只读摘要
└── IShopDeliveryFacade.getShipCity      # C 端；本模块 ship 不使用
```

**前端（建议）：**

| 项 | 值 |
|----|-----|
| 页面 | `ruoyi-ui/src/views/agri/seller/ship/index.vue` |
| API | `ruoyi-ui/src/api/agri/seller/ship.js` |

---

## 2. 数据库设计

**无新建表。** 权威 DDL：[`sql/biz_order.sql`](../../../../sql/biz_order.sql)（与平台、全部订单一致）。

### 2.1 表与用法

| 表 | 本模块用法 |
|----|------------|
| `biz_order` | 列表/详情；发货写 `order_status`、`delivery_type`、物流/配送员字段、`ship_time`；**WHERE shop_id = #{shopId} AND order_status IN ('1','2','3')** |
| `biz_order_item` | 详情明细、列表首行摘要；**只读**快照 |
| `biz_order_logistics_trace` | 详情时间轴；发货/物流/送达 **追加** trace |

### 2.2 状态与页签（本模块可见）

| order_status | 含义 | 页签 tab | 写操作 |
|:------------:|------|----------|--------|
| 1 | 待发货 | `PENDING_SHIP` | ship |
| 2 | 已发货 | `SHIPPED` | logistics、delivered |
| 3 | 已完成 | `COMPLETED` | 只读 |

> **不展示：** `0` 待支付、`4` 已关闭、`5` 已删除 — 列表 SQL **固定排除**；前端可链至全部订单。

### 2.3 枚举（与平台 §2.2 一致）

**delivery_type：** `1` 物流（logistics_company + tracking_no）· `2` 商家配送（vehicle_no + courier_name + courier_mobile）

**trace_type：** `1` 已发货 · `2` 运输更新 · `3` 已送达 · `4` 确认收货（C 端，本模块不写）

### 2.4 本模块重点字段

| 字段 | 列表 | 详情 | 写 | 说明 |
|------|:----:|:----:|:--:|------|
| shop_id | — | — | — | 隔离键 = X-Shop-Id |
| order_status | ✓ | ✓ | 发货→2 | 列表仅 1/2/3 |
| delivery_type | 发货后 | ✓ | 发货 | 字典 biz_delivery_type |
| logistics_company, tracking_no | — | ✓ | 发货(1) | |
| vehicle_no, courier_*, ship_remark | — | ✓ | 发货(2) | |
| member_nick_name, consignee_* | ✓ | ✓ | — | 手机脱敏 |
| pay_amount, create_time | ✓ | ✓ | — | |
| ship_time, finish_time | — | ✓ | — | finish 由 C 端确认收货写入 |

### 2.5 列表 SQL 过滤

| 前端 tab | Query `orderStatus` | SQL |
|----------|---------------------|-----|
| 待发货 | `1` | `shop_id=? AND order_status='1' AND del_flag='0'` |
| 已发货 | `2` | `shop_id=? AND order_status='2'` |
| 已完成 | `3` | `shop_id=? AND order_status='3'` |

**固定排除：** `order_status NOT IN ('0','4','5')`（即使误传 orderStatus 也须在 Controller 校验）。

**高级检索（与功能需求 §6.3）：** orderNo、goodsName（EXISTS item）、consigneeName/Mobile/Address、beginTime/endTime、deliveryType — 与页签 **AND** 组合。

**排序：** 默认 `create_time DESC`；待发货页签前端可传 `orderBy=create_time ASC`（优先早单，可选）。

### 2.6 角标 SQL（新增 Mapper 方法）

```sql
SELECT COUNT(1) FROM biz_order
WHERE shop_id = #{shopId} AND order_status = #{orderStatus} AND del_flag = '0'
```

| VO 字段 | order_status |
|---------|:------------:|
| pendingShipCount | 1 |
| shippedCount | 2 |
| completedCount | 3 |

### 2.7 索引

| 索引 | 用途 |
|------|------|
| `idx_shop_status (shop_id, order_status)` | 页签 + 角标 |
| `idx_create_time` | 排序/时间区间 |
| `uk_order_no` | 订单编号检索 |

---

## 3. 接口设计

**基路径：** `/agri/seller/ship`  
**上下文：** 请求头 **`X-Shop-Id`**（必填）  
**权限前缀：** `agri:seller:ship:*`（与全部订单 **分菜单**；可与 order:ship **共用按钮**）

### 3.1 接口一览

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/badges` | `agri:seller:ship:list` | 页签角标 |
| GET | `/list` | `agri:seller:ship:list` | 分页列表（仅 1/2/3） |
| GET | `/{orderId}` | `agri:seller:ship:query` | 详情 |
| POST | `/{orderId}/ship` | `agri:seller:ship:ship` | 整单发货 |
| POST | `/{orderId}/logistics` | `agri:seller:ship:ship` | 更新物流 |
| POST | `/{orderId}/delivered` | `agri:seller:ship:ship` | 登记送达 |

> **不提供：** `DELETE`（删除见 `/agri/seller/order/{id}`）；`PUT /close`（平台专责）。

### 3.2 角标 `GET /badges`

**data（SellerShipBadgeVO）：**

| 字段 | 说明 |
|------|------|
| pendingShipCount | status=1 |
| shippedCount | status=2 |
| completedCount | status=3 |

### 3.3 列表 `GET /list`

**Query（SellerOrderQuery，复用）：**

| 参数 | 必填 | 说明 |
|------|:----:|------|
| orderStatus | ✓ | **`1` / `2` / `3`**；Controller 拒绝其他值 |
| pageNum, pageSize | 否 | 分页 |
| orderNo | 否 | 模糊 |
| goodsName | 否 | 关联 item 模糊 |
| consigneeName, consigneeMobile, consigneeAddress | 否 | 模糊 |
| deliveryType | 否 | `1` / `2` |
| beginTime, endTime | 否 | create_time 区间 |

> **不含** `shipStatus`（功能需求：页签已表达履约阶段）。

**rows：** 复用 `OrderListRowVO`（与全部订单 §3.2 相同字段）。

### 3.4 详情 `GET /{orderId}`

**前置：** `shop_id` 匹配；`order_status IN ('1','2','3')`，否则「订单不存在或无权访问」。

**data：** 复用 `OrderDetailVO`（base / items / consignee / **shipper 快照** / logistics / traceList / payInfo）。

**扩展字段（v1.3 · 发货快照）：**

| 字段 | 说明 |
|------|------|
| shipperName | 发货人快照 |
| shipperMobile | 发货人联系方式快照 |
| shipRegionName | 发货地区名称快照（省/市/区） |
| shipDetailAddress | 发货详细地址快照 |

**可选扩展（待发货参考 · 对齐功能 §7.2）：**

| 字段 | 类型 | 说明 |
|------|------|------|
| shopDelivery | `ShopDeliverySettingVO` \| null | 待发货 **只读参考**；已发货以订单快照为准 |

> `OrderShipDTO` / `shipOrder` **Body 仍不含** 配送设置字段；快照由 **后端发货时读取**。

### 3.5 发货 `POST /{orderId}/ship`

**前置：** `shop_id` 匹配；`order_status='1'`。

**Body（OrderShipDTO · 对齐功能需求 §8.2 / 草稿 §4.2）：**

```json
{
  "shipTime": "2026-05-28 14:00:00",
  "deliveryType": "1",
  "logisticsCompany": "顺丰",
  "trackingNo": "SF123456",
  "shipRemark": ""
}
```

| deliveryType | 必填 |
|:------------:|------|
| `1` | logisticsCompany + trackingNo |
| `2` | vehicleNo + courierName + courierMobile |

| Body 字段 | 说明 |
|-----------|------|
| shipTime | 发货时间 |
| deliveryType | `1` 物流 / `2` 商家配送 |
| shipRemark | 选填 |

> **UI 展示：** 本单 **发货商品及数量** 只读清单（草稿 §4.2）；后端 **整单一次发完**（O5），**无** 拆单/部分发货 API。

**事务（委托 `OrderServiceImpl.shipOrder`）：**

```text
校验 shop_id + status=1
→ getByShopId + applyShipperSnapshot（配送设置须完整）
→ UPDATE biz_order SET order_status='2', delivery_type, 物流字段, ship_time,
                       shipper_name, shipper_mobile, ship_region_code, ship_region_name, ship_detail_address
→ INSERT trace(trace_type='1', content=摘要)
（不 deductStock；不 UPDATE biz_shop_delivery_setting）
```

**存量库升级：** [`sql/biz_order_ship_snapshot.sql`](../../../../sql/biz_order_ship_snapshot.sql)

### 3.6 更新物流 `POST /{orderId}/logistics`

**Body（OrderLogisticsDTO）：**

```json
{ "traceTime": "2026-05-28 18:00:00", "content": "到达中转站" }
```

**前置：** `order_status='2'` → 追加 `trace_type='2'`；**不改** order_status。

### 3.7 登记送达 `POST /{orderId}/delivered`

**Body（OrderDeliveredDTO）：**

```json
{ "traceTime": "2026-05-29 10:00:00", "content": "已送达" }
```

**前置：** `order_status='2'` → 追加 `trace_type='3'`；**不改** order_status（O7）。

### 3.8 业务错误 msg

| 场景 | msg |
|------|-----|
| 未选店铺 | 请先选择当前店铺 |
| 越权/不存在 | 订单不存在或无权访问 |
| orderStatus 非法 | 页签参数无效 |
| 非待发货发货 | 当前状态不可发货 |
| 非已发货更新物流 | 当前状态不可更新物流 |
| 非已发货登记送达 | 当前状态不可登记送达 |
| 平台已抢先发货 | 当前状态不可发货（并发） |

---

## 4. Service 分层

```text
SellerShipController
    → IOrderService.selectSellerList(shopId, query)      # list
    → IOrderService.selectSellerDetail(shopId, orderId)   # detail（加 status 校验）
    → [可选] IShopDeliveryService.getByShopId(shopId)      # 详情 shopDelivery 只读
    → IOrderService.shipOrder / appendLogistics / registerDelivered
    → BizOrderMapper.countShopByStatus(shopId, status)    # badges（新增）

OrderServiceImpl（已有 · 与 SellerOrderController 共用）
    → OrderSupport.assertOrderBelongsToShop
    → OrderSupport.validateShipDto / validateTraceDto / validateDeliveredDto
    → BizOrderMapper.updateForShip / BizOrderLogisticsTraceMapper.insertTrace
```

**SellerShipController 列表校验示例：**

```java
if (!Arrays.asList("1", "2", "3").contains(query.getOrderStatus())) {
    throw new ServiceException("页签参数无效");
}
return getDataTable(orderService.selectSellerList(requireShopId(), query));
```

---

## 5. 菜单与权限

| 菜单 | 路由 | 组件 | 权限前缀 |
|------|------|------|----------|
| 发货管理 | `/agri/seller/ship` | `agri/seller/ship/index` | `agri:seller:ship` |

| 权限 | 说明 |
|------|------|
| `agri:seller:ship:list` | 列表、角标 |
| `agri:seller:ship:query` | 详情 |
| `agri:seller:ship:ship` | 发货、更新物流、登记送达 |

> 可与 `agri:seller:order:ship` **合并授权**（同一履约能力）；菜单独立便于员工作业入口分离。

---

## 6. 与关联方案对照

| 能力 | 全部订单 | 发货管理 | 平台 |
|------|----------|----------|------|
| 列表 | `/order/list` 全页签 | `/ship/list` **仅 1/2/3** | `/agri/order/list` |
| 角标 | 可选 | **`/ship/badges`** | — |
| 发货/物流/送达 | 同 Body | **委托同一 Service** | 同规则 |
| 删除 | ✓ | **无** | ✓ |
| 关闭 | 无 | 无 | ✓ |
| O8 关单 | 只读 | **不出现** | Job |
| O11 | 不参与 | 不参与 | C 端 confirmReceive |
| DS6 / FM16 | 读 delivery **快照** 至 order | **shipOrder 读 delivery** | — |
| 店铺发货地 C 端 | `getShipCity` | — | — |

---

## 7. 测试要点

| 类型 | 要点 |
|------|------|
| 单测 | `SellerShipController` 委托；orderStatus 白名单；badges 计数 |
| 接口 | `SellerShipControllerTest`：X-Shop-Id、status 白名单；**ship 缺配送设置拒绝**；快照字段断言 |
| 复用 | 履约核心用例 **与** `OrderServiceImplTest` / `SellerOrderControllerTest` **共用** |

---

## 8. 实现状态

| 项 | 状态 |
|----|------|
| DDL `sql/biz_order.sql` | **已有** |
| `IOrderService` / `OrderServiceImpl` | **已实现** |
| `SellerOrderController` | **已实现**（全部订单） |
| `SellerShipController` | **待建**（薄层 + badges） |
| `BizOrderMapper.countShopByStatus` | **待建** |
| `SellerShipBadgeVO` | **待建** |
| 菜单 SQL | **待配置** |
| 前端 `ship/index.vue` | **待实现** |

---

## 9. 需求规则映射

| 规则 | 实现 |
|------|------|
| FM1 | SQL/Service 带 `shop_id`；列表 status∈{1,2,3} |
| FM2～FM6 | 共用 `OrderServiceImpl` 状态机 |
| FM3 | 页签 orderStatus=1/2/3 |
| FM4 | `shipOrder` 整单；O5 |
| FM5 | `appendLogistics` 可多次 |
| FM6 | `registerDelivered` 不改 status |
| FM7 | **无** delete/close 接口 |
| FM9 | 列表 VO 脱敏 |
| FM10 | X-Shop-Id + 前端切换刷新 |
| FM11 | `SellerOrderQuery` 检索字段 |
| FM12 | `traceList` 倒序 |
| FM13 | `OrderShipDTO` 分支校验 |
| FM14 | 单表多 Controller |
| FM15 | `@PreAuthorize` |
| FM16 | `shipOrder` **读** 配送设置 **快照** 至 `biz_order`；**不 UPDATE** `biz_shop_delivery_setting` |
| FM17 | 详情 **已发货** 展示 `shipper*` 订单快照；**待发货** `shopDelivery` 只读参考；`consignee_*` 仍来自订单 |
| FM18 | 配送设置未配置完整 → **不可发货** |

---

## 10. 非本期

| 项 | 说明 |
|----|------|
| 分批发货/拆单 | 草稿未要求；平台 O5 整单 |
| 商家 close / delete | 全部订单 / 平台 |
| 代确认收货 | C 端 |
| Excel 批量发货 | 平台 §14 |
| 新建表 / 副本库 | 不做 |
| 去发货预填配送设置 | DS6：不预填；**快照** 已实现 |
| 详情编辑配送设置 | `/agri/seller/delivery` |

---

## 11. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.3** | `biz_order` 发货人/地址快照；`OrderSupport.applyShipperSnapshot`；升级脚本 `biz_order_ship_snapshot.sql` |
| **v1.2** | 对齐功能需求 v1.2 / 草稿 §4.2；§3.5 补充字段表与整单说明 |
| **v1.1** | 配送设置边界；详情 `shopDelivery` 只读 |
| **v1.0** | 首版 |

---

*文档版本：v1.3 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 代码落位：`com.ruoyi.web.modules.order` · DDL：[`sql/biz_order.sql`](../../../../sql/biz_order.sql)、[`sql/biz_order_ship_snapshot.sql`](../../../../sql/biz_order_ship_snapshot.sql) · 关联《发货管理功能需求.md》v1.3 · 配送设置见《配送设置技术方案.md》v1.4 · 平台侧见《订单管理技术方案.md》v1.0.3 · 商家侧见《全部订单技术方案.md》v1.0.2*
