# 补货系统文档

## 系统概览

补货系统负责管理智能售货机的商品补货流程，包含两套子体系：

- **补货员体系**：独立的补货员账号管理、微信登录认证、设备绑定，以及小程序端执行补货操作
- **补货单体系**：运营后台创建补货计划单，管理补货单的完整生命周期

系统已从 V1（`t_stocker` 上货员）迁移到 V2（`t_replenisher` 补货员），V1 表已废弃。

---

## 一、补货员管理

### 1.1 数据模型

#### t_replenisher（补货员表）

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键，雪花算法生成 |
| name | VARCHAR | 姓名 |
| phone | VARCHAR | 手机号 |
| employee_id | VARCHAR | 工号 |
| wechat_openid | VARCHAR | 微信 OpenID（绑定后写入） |
| avatar | VARCHAR | 头像 URL |
| status | INT | 状态：1=启用，0=禁用 |
| total_tasks | INT | 累计任务数 |
| last_task_time | DATETIME | 最后任务时间 |
| create_time | DATETIME | 创建时间 |
| update_time | DATETIME | 更新时间 |

#### t_replenisher_device（设备绑定表）

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| replenisher_id | BIGINT | 补货员 ID |
| device_id | VARCHAR | 设备 SN 号 |
| source | VARCHAR | 绑定来源：MANUAL（手动）/ QR（扫码）/ SHOP_INHERIT（门店继承） |
| create_time | DATETIME | 绑定时间 |

### 1.2 API 接口

所有接口路由前缀为 `/replenishers`，需要管理员权限。

#### 补货员 CRUD

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/replenishers/list` | `replenisher:read` | 分页查询补货员列表，支持 keyword/status 筛选 |
| GET | `/replenishers/{id}` | `replenisher:read` | 获取补货员详情（含绑定设备数） |
| GET | `/replenishers/search` | `replenisher:read` | 按姓名/手机/工号模糊搜索 |
| POST | `/replenishers` | `replenisher:create` | 创建补货员（name 必填） |
| PUT | `/replenishers/{id}` | `replenisher:update` | 更新补货员信息 |
| PUT | `/replenishers/{id}/status` | `replenisher:update` | 启用/禁用补货员 |
| DELETE | `/replenishers/{id}` | `replenisher:delete` | 删除补货员（级联解绑所有设备） |

#### 设备绑定

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/replenishers/{id}/devices` | `replenisher:read` | 获取补货员绑定的设备 ID 列表 |
| POST | `/replenishers/{id}/devices` | `replenisher:update` | 绑定设备到补货员（INSERT IGNORE，幂等） |
| DELETE | `/replenishers/{id}/devices/{deviceId}` | `replenisher:update` | 解绑单个设备 |
| POST | `/replenishers/batch-bind` | `replenisher:update` | 批量绑定多个补货员到多个设备 |

#### 微信绑定

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| POST | `/replenishers/{id}/binding-code` | `replenisher:update` | 生成 24 位绑定码（Redis 存储，24小时有效） |

### 1.3 认证流程

补货员的绑定和登录全部在**运营端小程序（haha-admin-mp）**中完成。

#### 创建与绑定（首次使用）

```
管理员在 Web 后台创建补货员 → 生成 24 位绑定码（Redis 存储，24h 有效）
→ 将绑定码告知补货员
→ 补货员打开"哈哈运营平台"小程序 → 登录页点击"已有绑定码？点击绑定微信"
→ 输入绑定码 → 微信授权 → POST /replenisher/login/bind
→ 后端校验绑定码、获取 openid、写入 wechat_openid、激活账号
→ 自动登录 → 进入补货首页
```

#### 静默登录（已绑定后）

```
补货员打开运营端小程序 → 点击"补货员微信一键登录"
→ wx.login() 获取 code → POST /replenisher/login/wechat
→ 后端通过 code 换 openid → 查找已绑定的补货员
→ 生成 SaToken → 返回 token + 用户信息 → 进入补货首页
```

### 1.4 关联管理入口

补货员还可以通过以下入口绑定到门店/设备：

| 来源 | 接口 | 说明 |
|------|------|------|
| 门店管理 | `POST /shops/{id}/replenishers` | 将补货员绑定到门店下所有设备，source=SHOP_INHERIT |
| 门店管理 | `GET /shops/{id}/replenishers` | 查询门店关联的补货员列表 |
| 设备管理 | `POST /devices/{deviceId}/replenishers` | 直接绑定补货员到设备，source=MANUAL |
| 设备管理 | `GET /devices/{deviceId}/replenishers` | 查询设备绑定的补货员列表 |
| 设备管理 | `DELETE /devices/{deviceId}/replenishers/{replenisherId}` | 解绑设备的补货员 |

---

## 二、补货操作

### 2.1 权限控制

补货员只能操作自己绑定的设备。操作前系统会：

1. 从当前登录的 SaToken session 中获取补货员 ID
2. 查询补货员绑定的设备 ID 列表
3. 校验目标设备是否在绑定列表中，不在则拒绝访问

### 2.2 API 接口

所有接口路由前缀为 `/replenisher`，需要补货员角色（`REPLENISHER`）。

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/replenisher/my-info` | 获取当前补货员个人信息 |
| GET | `/replenisher/device/list` | 获取绑定设备列表（含各设备库存概览、在线状态） |
| GET | `/replenisher/device/inventory/{deviceId}` | 获取设备库存详情（含商品信息、层模板分组） |
| POST | `/replenisher/stock/replenish` | 执行补货操作，逐商品增加库存 |

### 2.3 补货操作流程

```
补货员选择设备 → GET /replenisher/device/inventory/{deviceId} → 查看当前库存
→ 输入各商品补货数量 → POST /replenisher/stock/replenish
→ 后端逐商品调用 deviceInventoryService.increaseStock()
→ 库存增加成功 → 返回执行结果（成功/失败明细）
```

补货请求体（`ReplenishDTO`）：

```json
{
  "deviceId": "SN20250101001",
  "items": [
    {
      "productId": 1001,
      "productCode": "P001",
      "productName": "可口可乐",
      "shelfNum": 1,
      "position": "left",
      "quantity": 10
    }
  ]
}
```

### 2.4 库存记录

补货操作后系统自动记录上货日志：

- 日志类型：`InventoryLog.TYPE_RESTOCK`（补货上货）
- 数据库表：`t_inventory_log` 记录 beforeStock → afterStock 变动
- 可通过 `POST /inventory/records/v2` 创建上货记录时关联补货员信息（`replenisherId`、`replenisherName`、`replenisherPhone`）

---

## 三、补货单管理

补货单用于运营人员制定补货计划，管理补货任务从创建到完成的全流程。

### 3.1 数据模型

#### t_replenishment_order（补货单表）

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| order_no | VARCHAR | 补货单号，规则：`RO` + yyyyMMdd + 4位序号 |
| device_id | VARCHAR | 目标设备 SN |
| shop_id | BIGINT | 目标门店 ID |
| status | INT | 状态：0 草稿 / 1 已提交 / 2 已同步ERP / 3 已完成 / 4 已取消 |
| erp_order_no | VARCHAR | ERP 采购单号（同步后回填） |
| erp_sync_time | DATETIME | ERP 同步时间 |
| supplier_name | VARCHAR | 供应商名称 |
| warehouse_name | VARCHAR | 发货仓库 |
| expected_arrival_time | DATETIME | 预计到货时间 |
| total_quantity | INT | 补货总数量 |
| total_amount | DECIMAL | 补货总金额 |
| remark | VARCHAR | 备注 |
| creator_id | BIGINT | 创建人 ID |
| creator_name | VARCHAR | 创建人姓名 |

#### t_replenishment_order_item（补货单明细表）

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| order_id | BIGINT | 关联补货单 ID |
| device_id | VARCHAR | 设备 SN（冗余） |
| product_id | BIGINT | 商品 ID |
| product_code | VARCHAR | 商品编码 |
| product_name | VARCHAR | 商品名称 |
| planned_quantity | INT | 计划补货数量（≥1） |
| actual_quantity | INT | 实际补货数量 |
| before_stock | INT | 补货前库存 |
| after_stock | INT | 补货后库存 |
| unit_price | DECIMAL | 单价 |
| total_price | DECIMAL | 小计金额 |
| shelf_num | INT | 货架层号 |
| position | VARCHAR | 货道位置（left/right） |

### 3.2 状态流转

```
草稿(0) ──提交──→ 已提交(1) ──同步ERP──→ 已同步ERP(2) ──完成──→ 已完成(3)
  │                  │                                    │
  └──取消──→ 已取消(4) ←──取消──┘                        │
       ↑                                                   │
       └───────────────────────────────────────────────────┘
                          (已完成也可以取消)
```

状态说明：

| 状态码 | 状态名 | 标签色 | 说明 |
|--------|--------|--------|------|
| 0 | 草稿 | info | 可编辑、可删除、可提交、可取消 |
| 1 | 已提交 | warning | 可同步ERP、可取消 |
| 2 | 已同步ERP | primary | 已推送至采购系统，可完成、可取消 |
| 3 | 已完成 | success | 终态（也可取消） |
| 4 | 已取消 | danger | 终态 |

### 3.3 API 接口

路由前缀为 `/replenishment-orders`，需要相应权限。

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/replenishment-orders/list` | `replenishment:order:read` | 分页查询，支持 orderNo/deviceId/shopId/status/时间范围/创建人 筛选 |
| GET | `/replenishment-orders/{id}` | `replenishment:order:read` | 获取补货单详情 |
| GET | `/replenishment-orders/{id}/items` | `replenishment:order:read` | 获取补货单商品明细列表 |
| POST | `/replenishment-orders` | `replenishment:order:create` | 创建补货单（含商品明细） |
| PUT | `/replenishment-orders` | `replenishment:order:update` | 更新补货单（仅草稿状态可更新） |
| DELETE | `/replenishment-orders/{id}` | `replenishment:order:delete` | 删除补货单（仅草稿状态可删除） |
| POST | `/replenishment-orders/{id}/submit` | `replenishment:order:submit` | 提交补货单（草稿 → 已提交） |
| POST | `/replenishment-orders/{id}/sync-erp` | `replenishment:order:sync` | 同步到 ERP 系统（已提交 → 已同步ERP） |
| POST | `/replenishment-orders/{id}/complete` | `replenishment:order:complete` | 完成补货单（已同步ERP → 已完成） |
| POST | `/replenishment-orders/{id}/cancel` | `replenishment:order:update` | 取消补货单 |

### 3.4 操作约束

- **创建**：需传入 deviceId（必填）、商品明细列表（plannedQuantity ≥ 1），系统自动生成单号
- **更新**：仅草稿状态可更新，更新时重新计算 totalQuantity 和 totalAmount
- **删除**：仅草稿状态可删除，删除时级联删除明细
- **提交**：草稿 → 已提交，此后不可再编辑
- **同步 ERP**：目前为桩代码，预留企得宝 SDK 集成接口
- **完成**：已同步ERP → 已完成
- **取消**：草稿、已提交、已同步ERP、已完成 均可取消（取消是终态）

---

## 四、前端页面

### 4.1 Web 管理后台（haha-admin-web）

| 页面 | 路由 | 说明 |
|------|------|------|
| 补货员列表 | `/operation/replenisher` | 补货员 CRUD、设备绑定/解绑、绑定码生成 |
| 补货单列表 | 侧边栏「库存管理 → 补货单列表」 | 补货单查询、新建、状态操作 |
| 补货单详情 | 侧边栏「库存管理 → 补货单详情」 | 补货单完整信息 + 商品明细 |
| 设备管理 | 设备列表页内嵌弹窗 | 为指定设备绑定/解绑补货员 |
| 门店管理 | 门店列表页内嵌弹窗 | 为门店批量绑定补货员到其下所有设备 |
| 库存记录 | 库存管理 → 上货记录 | 上货记录中可关联补货员信息（V2 补货员体系） |

### 4.2 运营端小程序（haha-admin-mp）

| 页面 | 路径 | 说明 |
|------|------|------|
| 补货首页 | `pages/replenish/index` | 补货员信息卡片、绑定设备列表、待补货统计、**扫码开柜**按钮 |
| 补货操作 | `pages/replenish/operation` | 设备商品清单、补货数量输入、提交补货 |
| 绑定微信 | `pages/replenish/bind` | 通过小程序码/绑定码参数进入，完成微信授权绑定 |
| 补货员管理 | `pages/replenisher/list` | 管理员查看补货员列表 |
| 编辑补货员 | `pages/replenisher/form` | 管理员新增/编辑补货员 |
| 设备绑定 | `pages/replenisher/bind-device` | 管理员为补货员绑定设备（门店设备树） |
| 配置补货员 | `pages/device/replenisher-config` | 从设备端配置其绑定的补货员 |

### 4.3 扫码开柜

补货首页提供"扫码开柜"按钮，补货员可扫描设备机身二维码直接进入补货操作：

1. 点击「扫码开柜」→ 调用 `uni.scanCode` 扫描设备二维码
2. 机身二维码格式：`https://dev-haha.kuaiyuman.cn/{deviceId}`
3. 解析提取 deviceId → 校验是否在绑定设备列表中
4. 跳转 `/pages/replenish/operation?deviceId=xxx` 执行补货

---

## 五、数据库变更记录

| 文件 | 说明 |
|------|------|
| `docs/database/migrate_replenisher.sql` | V1 `t_stocker` → V2 `t_replenisher` 数据迁移 |
| `docs/database/migrate_replenishment_to_inventory.sql` | 补货单菜单注册到库存管理模块下 |
| `haha-admin/src/main/resources/sql/replenishment_order.sql` | `t_replenishment_order` 和 `t_replenishment_order_item` 建表 DDL |

---

## 六、技术要点

### 6.1 库存操作

补货操作调用 `DeviceInventoryService.increaseStock()` 增加库存，该方法：

- 若库存记录不存在则创建（stock = quantity）
- 若已存在则累加（stock = beforeStock + quantity）
- 无上限限制
- 写入 `t_inventory_log` 记录变动
- 使用乐观锁（version 字段）防止并发冲突

### 6.2 绑定码机制

- 管理员在 Web 后台补货员列表点击「绑定码」按钮调用 `POST /replenishers/{id}/binding-code`
- 后端生成 24 位随机字符串（UUID 截取）存入 Redis，Key 格式为 `replenisher:bind:code:{code}`，TTL 24 小时
- 将绑定码告知补货员，补货员在运营端小程序登录页输入绑定码完成微信绑定
- 绑定码一次性使用，验证后立即从 Redis 删除

### 6.3 补货单号规则

- 格式：`RO` + `yyyyMMdd` + 4位自增序号（如 `RO202507060001`）
- 通过查询当天最大单号 + 1 生成
- 数据库 `order_no` 字段有 UNIQUE 索引保证唯一性
- 日订单量超过 9999 会报错

### 6.4 ERP 集成状态

`syncToErp` 接口目前为桩代码，日志记录"模拟同步ERP成功"。企得宝 SDK 已在 `haha-sdk` 模块中预留，后续接入时需实现真实推送逻辑。

---

## 七、相关文件索引

### 后端核心文件

```
haha-entity/src/main/java/com/haha/entity/
├── Replenisher.java                    # 补货员实体
├── ReplenisherDevice.java              # 设备绑定实体
├── ReplenishmentOrder.java             # 补货单实体
├── ReplenishmentOrderItem.java         # 补货单明细实体
└── dto/
    ├── ReplenishDTO.java               # 补货操作请求 DTO
    ├── ReplenisherCreateDTO.java       # 创建补货员 DTO
    ├── ReplenisherUpdateDTO.java       # 更新补货员 DTO
    ├── ReplenisherQueryDTO.java        # 补货员查询 DTO
    ├── ReplenisherBindDTO.java         # 微信绑定 DTO
    ├── BindReplenisherDTO.java         # 门店绑定补货员 DTO
    ├── ReplenishmentOrderCreateDTO.java    # 创建补货单 DTO
    ├── ReplenishmentOrderUpdateDTO.java    # 更新补货单 DTO
    └── ReplenishmentOrderQueryDTO.java     # 补货单查询 DTO

haha-admin/src/main/java/com/haha/admin/controller/
├── ReplenisherController.java          # 补货员管理接口
├── ReplenisherLoginController.java     # 补货员认证接口
├── ReplenisherOperationController.java # 补货员操作接口
└── ReplenishmentOrderController.java   # 补货单管理接口

haha-service/src/main/java/com/haha/service/
├── ReplenisherService.java             # 补货员服务接口
├── ReplenishmentOrderService.java      # 补货单服务接口
└── impl/
    ├── ReplenisherServiceImpl.java     # 补货员服务实现
    └── ReplenishmentOrderServiceImpl.java  # 补货单服务实现

haha-mapper/src/main/java/com/haha/mapper/
├── ReplenisherMapper.java
├── ReplenisherDeviceMapper.java
├── ReplenishmentOrderMapper.java
└── ReplenishmentOrderItemMapper.java

haha-common/src/main/java/com/haha/common/enums/
└── ReplenishmentOrderStatusEnum.java   # 补货单状态枚举
```

### 前端核心文件

```
haha-admin-web/src/
├── views/replenisher/                  # 补货员管理页面
│   ├── index.vue
│   └── utils/
│       ├── hook.tsx
│       └── types.ts
├── views/replenishment-order/          # 补货单管理页面
│   ├── index.vue
│   ├── detail.vue
│   └── components/
│       └── CreateDialog.vue
└── api/
    ├── replenisher.ts
    └── replenishmentOrder.ts

haha-admin-mp/src/
├── pages/replenish/                    # 补货员小程序页面
│   ├── index.vue
│   ├── operation.vue
│   └── bind.vue
├── pages/replenisher/                  # 补货员管理页面
│   ├── list.vue
│   ├── form.vue
│   └── bind-device.vue
├── pages/device/
│   └── replenisher-config.vue
└── api/
    ├── replenish.ts
    └── replenisher.ts

haha-mp/src/
└── pages/replenish/
    └── bind.vue                       # 用户端绑定页面
```
