|
|
@@ -0,0 +1,362 @@
|
|
|
+# 库存管理模块说明
|
|
|
+
|
|
|
+> 本文档描述系统商品库存管理的完整架构,涵盖库存数据模型、服务层设计、库存变动流程、与订单/支付/退款模块的集成,以及幂等性保护机制。
|
|
|
+> 帮助开发人员理解库存数据的生命周期和关键设计决策。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、模块概述
|
|
|
+
|
|
|
+### 1.1 库存管理范围
|
|
|
+
|
|
|
+本系统的库存管理**完全在本地进行**,haha 平台不提供库存管理 API。两者的分工:
|
|
|
+
|
|
|
+| 数据 | 管理方 | 说明 |
|
|
|
+|------|--------|------|
|
|
|
+| 商品信息(名称、条码、价格、图片) | haha 平台 → 本地同步 | `DataSyncService.syncProducts()` 定时拉取 |
|
|
|
+| 设备列表 | haha 平台 → 本地同步 | `DataSyncService.syncDevices()` |
|
|
|
+| 订单支付状态 | 本地 → haha 平台推送 | `notifyHahaOrderStatusChanged()` |
|
|
|
+| **库存数量** | **纯本地管理** | haha 不存库存,不提供库存接口 |
|
|
|
+
|
|
|
+### 1.2 库存维度
|
|
|
+
|
|
|
+库存按 **设备 + 商品** 维度管理,即同一商品在不同设备上有独立的库存记录(`t_device_inventory` 表)。
|
|
|
+
|
|
|
+### 1.3 为什么库存不放在 haha 平台?
|
|
|
+
|
|
|
+haha 平台的职责是 AI 识别(开关门事件 + 商品识别结果)和设备管理。其 SDK 提供的 `GoodsApi` 和 `OrderApi` 均无库存查询/更新端点,商品 API 仅管理产品目录和上架状态。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二、数据库设计
|
|
|
+
|
|
|
+### 2.1 核心表
|
|
|
+
|
|
|
+#### `t_device_inventory` — 设备库存表
|
|
|
+
|
|
|
+每台设备上每个商品一条记录,使用乐观锁保证并发安全。
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT | 主键,雪花算法 |
|
|
|
+| `device_id` | VARCHAR(64) | 设备 SN |
|
|
|
+| `product_id` | BIGINT | 商品 ID(关联 `t_product`) |
|
|
|
+| `product_code` | VARCHAR(100) | haha 平台商品编码 |
|
|
|
+| `product_name` | VARCHAR(200) | 商品名称快照 |
|
|
|
+| `stock` | INT | **当前库存数量** |
|
|
|
+| `shelf_num` | INT | 货架层位置 |
|
|
|
+| `position` | VARCHAR(50) | 通道位置(left/right) |
|
|
|
+| `warning_threshold` | INT | 低库存预警阈值(默认 5) |
|
|
|
+| `last_restock_time` | DATETIME | 最近补货时间 |
|
|
|
+| `version` | INT | **乐观锁版本号**(MyBatis-Plus @Version) |
|
|
|
+| `create_time` | DATETIME | 创建时间 |
|
|
|
+| `update_time` | DATETIME | 更新时间 |
|
|
|
+
|
|
|
+唯一约束:`(device_id, product_id)`
|
|
|
+
|
|
|
+#### `t_inventory_log` — 库存变动日志表
|
|
|
+
|
|
|
+记录每一次库存变动的审计跟踪,用于溯源和统计。
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT | 主键 |
|
|
|
+| `device_id` | VARCHAR(64) | 设备 ID |
|
|
|
+| `product_id` | BIGINT | 商品 ID |
|
|
|
+| `product_code` | VARCHAR(100) | 商品编码 |
|
|
|
+| `product_name` | VARCHAR(200) | 商品名称 |
|
|
|
+| `change_type` | INT | 变动类型(见下表) |
|
|
|
+| `change_quantity` | INT | 变动数量(正数增加,负数减少) |
|
|
|
+| `before_stock` | INT | 变动前库存 |
|
|
|
+| `after_stock` | INT | 变动后库存 |
|
|
|
+| `order_id` | VARCHAR(64) | 关联订单号(销售时) |
|
|
|
+| `activity_id` | VARCHAR(64) | 关联活动号 |
|
|
|
+| `operator_id` | BIGINT | 操作人 ID |
|
|
|
+| `operator_name` | VARCHAR(50) | 操作人名称 |
|
|
|
+| `remark` | VARCHAR(500) | 备注 |
|
|
|
+| `create_time` | DATETIME | 创建时间 |
|
|
|
+
|
|
|
+变动类型常量:
|
|
|
+
|
|
|
+| 常量 | 值 | 说明 |
|
|
|
+|------|---|------|
|
|
|
+| `TYPE_RESTOCK` | 1 | 上货增加 |
|
|
|
+| `TYPE_SALE` | 2 | 销售减少 |
|
|
|
+| `TYPE_ADJUST_ADD` | 3 | 调整增加 |
|
|
|
+| `TYPE_ADJUST_SUB` | 4 | 调整减少 |
|
|
|
+| `TYPE_INVENTORY` | 5 | 盘点调整 |
|
|
|
+
|
|
|
+#### `t_stock_record` — 上货记录表(补货任务)
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT | 主键 |
|
|
|
+| `device_id` | VARCHAR(64) | 设备 ID |
|
|
|
+| `type` | INT | 类型:1=首次上货,2=补货 |
|
|
|
+| `status` | INT | 状态:1=进行中,2=已完成,3=已取消 |
|
|
|
+| `operator_id` | BIGINT | 操作人 ID |
|
|
|
+| `activity_id` | VARCHAR(64) | 活动号 |
|
|
|
+| `create_time` | DATETIME | 创建时间 |
|
|
|
+
|
|
|
+#### `t_stock_record_item` — 上货记录商品明细
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT | 主键 |
|
|
|
+| `stock_record_id` | BIGINT | 上货记录 ID |
|
|
|
+| `product_id` | BIGINT | 商品 ID |
|
|
|
+| `product_code` | VARCHAR(100) | 商品编码 |
|
|
|
+| `product_name` | VARCHAR(200) | 商品名称 |
|
|
|
+| `quantity` | INT | 上货数量 |
|
|
|
+| `before_stock` | INT | 上货前库存 |
|
|
|
+| `after_stock` | INT | 上货后库存 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 三、服务层设计
|
|
|
+
|
|
|
+### 3.1 服务职责
|
|
|
+
|
|
|
+| 服务 | 职责 |
|
|
|
+|------|------|
|
|
|
+| `DeviceInventoryService` | 库存核心 CRUD:增、减、调整、查询、统计 |
|
|
|
+| `InventoryLogService` | 记录并查询库存变动日志,提供幂等性检查 |
|
|
|
+| `StockRecordService` | 管理上货任务(创建、完成、取消) |
|
|
|
+| `OrderInventoryService` | **桥梁服务**:连接订单/支付/退款与库存操作 |
|
|
|
+
|
|
|
+### 3.2 核心方法
|
|
|
+
|
|
|
+#### DeviceInventoryService
|
|
|
+
|
|
|
+```
|
|
|
+increaseStock() → 补货入库,首次自动创建库存记录
|
|
|
+decreaseStock() → 销售出库,库存不足抛 BusinessException
|
|
|
+adjustStock() → 直接设置库存值,用于盘点/退款恢复
|
|
|
+getByDeviceAndProduct() → 查询单条库存记录
|
|
|
+getStatistics() → 聚合统计(总记录数/总库存/低库存/零库存)
|
|
|
+```
|
|
|
+
|
|
|
+#### OrderInventoryService(桥梁服务)
|
|
|
+
|
|
|
+```
|
|
|
+decreaseStockOnPaid(Order) → 扣减库存(幂等,识别时和支付时均可调用,先到达者生效)
|
|
|
+restoreStockOnRefund(Order, List) → 退款成功后恢复库存(支持部分退款,受 restoreStock 标志控制)
|
|
|
+checkStockAvailable(deviceId, items) → 下单前校验库存是否充足
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、库存变动流程
|
|
|
+
|
|
|
+### 4.1 补货入库流程
|
|
|
+
|
|
|
+```
|
|
|
+管理员/补货员 → POST /inventory/increase 或 POST /replenisher/stock/replenish
|
|
|
+ → DeviceInventoryService.increaseStock()
|
|
|
+ → 查询现有库存记录
|
|
|
+ → 不存在则新建,存在则累加
|
|
|
+ → InventoryLogService.logChange(TYPE_RESTOCK)
|
|
|
+```
|
|
|
+
|
|
|
+**特性**:首次为设备添加商品时自动创建库存记录;后续补货直接累加数量。
|
|
|
+
|
|
|
+### 4.2 销售出库流程(AI 识别后立即扣减)
|
|
|
+
|
|
|
+**核心原则**:用户关门那一刻,商品已物理离开柜子——库存应立即扣减,不等支付。支付是独立的财务环节,可能延迟甚至失败。
|
|
|
+
|
|
|
+**主流程**(识别时触发):
|
|
|
+
|
|
|
+```
|
|
|
+用户关门 → AI 识别完成 → handleOrcResult
|
|
|
+ → handleConsume (OUT 类型,正常取出)
|
|
|
+ → updateOrderFromRecognition(设置 items)
|
|
|
+ → decreaseStockOnPaid(order) ← 立即扣减
|
|
|
+ → 幂等性检查:查询 t_inventory_log 是否已有 TYPE_SALE 记录
|
|
|
+ → 解析订单 items JSON,遍历商品
|
|
|
+ → DeviceInventoryService.decreaseStock()
|
|
|
+ → 扣减并记录 InventoryLog(TYPE_SALE)
|
|
|
+ → IN 类型(异物放入)跳过,不扣库存
|
|
|
+```
|
|
|
+
|
|
|
+**兜底流程**(支付时触发,幂等性保证不会重复扣减):
|
|
|
+
|
|
|
+```
|
|
|
+用户支付成功
|
|
|
+ → 支付回调 / 支付分回调
|
|
|
+ → decreaseStockOnPaid(order) ← 若识别时已扣,幂等检查跳过
|
|
|
+```
|
|
|
+
|
|
|
+**触发点汇总**:
|
|
|
+
|
|
|
+| 阶段 | 触发路径 | 服务 | 方法 |
|
|
|
+|------|---------|------|------|
|
|
|
+| **识别时**(主) | AI 识别为 OUT 类型 | `HahaCallbackServiceImpl` | `handleConsume()` |
|
|
|
+| 支付时(兜底) | 微信支付 V3 回调 | `PaymentServiceImpl` | `handleCallback()` |
|
|
|
+| 支付时(兜底) | 支付分完结扣款成功 | `PayScoreServiceImpl` | `completeServiceOrder()` |
|
|
|
+| 支付时(兜底) | 支付分 USER_PAID 回调 | `PayScoreServiceImpl` | `handleCallback()` |
|
|
|
+| 支付时(兜底) | 支付分状态同步为 DONE | `PayScoreServiceImpl` | `syncPayScoreStatus()` |
|
|
|
+| 支付时(兜底) | 支付分同步支付信息 | `PayScoreServiceImpl` | `syncOrderPaid()` |
|
|
|
+| 支付时(兜底) | 订单回调中支付分扣费成功 | `HahaCallbackServiceImpl` | `processPayScorePayment()` |
|
|
|
+| 支付时(兜底) | 直接完成订单(无支付分) | `OrderServiceImpl` | `completeOrderWithPayScore()` |
|
|
|
+
|
|
|
+### 4.3 退款恢复库存流程(区分场景)
|
|
|
+
|
|
|
+**核心原则**:库存代表柜内物理商品数量。只有商品**没被拿走**时才恢复库存(如 AI 识别多扣);商品已被取走但退款(如质量问题)不应恢复。
|
|
|
+
|
|
|
+通过 `t_refund.restore_stock` 字段控制(默认 1=恢复):
|
|
|
+
|
|
|
+| 场景 | 举例 | `restoreStock` | 行为 |
|
|
|
+|------|------|----------------|------|
|
|
|
+| **识别多扣** | 拿了 1 瓶水,AI 识别成 2 瓶 | 1(默认) | 退钱 + **恢复库存** |
|
|
|
+| **订单错误** | 重复扣款、金额计算错误 | 1(默认) | 退钱 + **恢复库存**(用户实际没取走多余) |
|
|
|
+| **质量问题** | 拿了商品,发现有质量问题要退款 | 0 | 只退钱,**不恢复库存**(商品已取走) |
|
|
|
+| **用户不满** | 拿了商品,不满意要退款 | 0 | 只退钱,**不恢复库存**(商品已取走) |
|
|
|
+
|
|
|
+```
|
|
|
+管理员审核通过 / 直接退款成功
|
|
|
+ → RefundServiceImpl.approveRefund() 或 markRefunded()
|
|
|
+ → restoreStockForRefund(refund)
|
|
|
+ → 检查 refund.restoreStock:
|
|
|
+ → 0(不恢复)→ 跳过,仅记录日志
|
|
|
+ → 1 或 null(恢复)→ 继续
|
|
|
+ → 获取订单 + 退款商品明细(RefundItem 列表)
|
|
|
+ → OrderInventoryService.restoreStockOnRefund(order, refundItems)
|
|
|
+ → 逐商品 adjustStock(current + quantity)
|
|
|
+ → 记录 InventoryLog(TYPE_ADJUST_ADD)
|
|
|
+```
|
|
|
+
|
|
|
+**支持部分退款**:按 RefundItem 明细逐商品恢复,只恢复退款的那部分数量。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、幂等性保护
|
|
|
+
|
|
|
+### 5.1 库存扣减幂等性
|
|
|
+
|
|
|
+`decreaseStockOnPaid()` 在识别时和支付时都会被调用,通过查询 `t_inventory_log` 表保证只扣一次:
|
|
|
+
|
|
|
+```java
|
|
|
+long deducted = inventoryLogService.countByOrderIdAndType(orderNo, TYPE_SALE);
|
|
|
+if (deducted > 0) {
|
|
|
+ log.info("订单库存已扣减,跳过重复扣减: orderNo={}", orderNo);
|
|
|
+ return;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**保护场景**:
|
|
|
+- 识别时已扣减(主路径),支付回调到来时幂等跳过(兜底)
|
|
|
+- 微信支付回调可能因网络重试多次推送
|
|
|
+- 支付分回调与主动查询可能并发触发
|
|
|
+
|
|
|
+**注意**:扣减失败不阻断主流程(外部 try-catch 包裹),确保库存问题不影响用户支付体验。
|
|
|
+
|
|
|
+### 5.2 库存恢复幂等性
|
|
|
+
|
|
|
+退款恢复依赖 `restoreStock` 标志控制是否执行。如果执行恢复,按 `RefundItem` 精确的商品明细操作,每次调用只恢复本次退款的商品数量。
|
|
|
+
|
|
|
+**当前策略**:退款为管理员审核操作,重复调用概率极低。如需严格幂等,可在 `InventoryLog.remark` 中记录 `refundId` 实现精准去重。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 六、API 端点
|
|
|
+
|
|
|
+### 6.1 管理后台(InventoryController)
|
|
|
+
|
|
|
+| 端点 | 方法 | 用途 | 权限 |
|
|
|
+|------|------|------|------|
|
|
|
+| `/inventory/device-stats` | GET | 按设备分组的库存统计 | `inventory:view` |
|
|
|
+| `/inventory/list` | GET | 库存列表(分页,可筛选) | `inventory:view` |
|
|
|
+| `/inventory/device/{deviceId}` | GET | 单台设备库存详情 | `inventory:view` |
|
|
|
+| `/inventory/low-stock` | GET | 低库存商品列表 | `inventory:view` |
|
|
|
+| `/inventory/statistics` | GET | 汇总统计 | `inventory:view` |
|
|
|
+| `/inventory/increase` | POST | 增加库存(上货) | `inventory:update` |
|
|
|
+| `/inventory/adjust` | POST | 调整库存 | `inventory:update` |
|
|
|
+| `/inventory/logs` | GET | 库存变动日志 | `inventory:view` |
|
|
|
+| `/inventory/logs/statistics` | GET | 库存变动统计 | `inventory:view` |
|
|
|
+| `/inventory/records` | GET | 上货记录列表 | `inventory:view` |
|
|
|
+| `/inventory/records/{id}` | GET | 上货记录详情 | `inventory:view` |
|
|
|
+| `/inventory/records` | POST | 创建上货记录(V1) | `inventory:update` |
|
|
|
+| `/inventory/records/v2` | POST | 创建上货记录(V2) | `inventory:update` |
|
|
|
+| `/inventory/records/{id}/complete` | PUT | 完成上货记录 | `inventory:update` |
|
|
|
+| `/inventory/records/{id}/cancel` | PUT | 取消上货记录 | `inventory:update` |
|
|
|
+| `/inventory/records/stocker-statistics` | GET | 补货员统计 | `inventory:view` |
|
|
|
+
|
|
|
+### 6.2 补货员端(ReplenisherOperationController)
|
|
|
+
|
|
|
+| 端点 | 方法 | 用途 |
|
|
|
+|------|------|------|
|
|
|
+| `/replenisher/my-info` | GET | 当前补货员信息 |
|
|
|
+| `/replenisher/device/list` | GET | 补货员管辖的设备列表(含库存概览) |
|
|
|
+| `/replenisher/device/inventory/{deviceId}` | GET | 单台设备库存详情 |
|
|
|
+| `/replenisher/stock/replenish` | POST | 执行补货操作 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、低库存预警
|
|
|
+
|
|
|
+系统支持低库存预警机制:
|
|
|
+
|
|
|
+- 每个库存记录的 `warning_threshold` 字段(默认值 5)
|
|
|
+- 当 `stock <= warning_threshold` 时,该商品出现在低库存列表中
|
|
|
+- 管理后台 `/inventory/low-stock` 和统计面板可查看低库存情况
|
|
|
+- 设备库存统计页按设备汇总:正常 / 低库存 / 缺货 三种状态
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 八、关键设计决策
|
|
|
+
|
|
|
+| 决策 | 原因 |
|
|
|
+|------|------|
|
|
|
+| **识别时立即扣减** | 关门时商品已物理离开柜子,库存应即时反映;不等支付(可能延迟/失败) |
|
|
|
+| 支付时兜底扣减 | 若识别时未扣(如异常跳过),支付回调仍会触发;幂等性保证不重复 |
|
|
|
+| 库存扣减不阻断主流程 | 库存问题不应影响用户支付/取货体验;异常仅记录日志 |
|
|
|
+| **退款恢复区分场景** | `restore_stock` 标志区分"识别多扣退库存"和"质量问题不退库存",避免恢复不该恢复的商品 |
|
|
|
+| 乐观锁而非悲观锁 | `t_device_inventory` 有 `@Version` 字段,MyBatis-Plus 自动处理并发冲突 |
|
|
|
+| 幂等性通过日志表实现 | 不在订单表增加 `stock_deducted` 字段,避免侵入;日志表本身就是审计需求 |
|
|
|
+| 退款恢复用 `adjustStock` | 相比 `increaseStock`,不需要补货所需的 shelf_num/position 等字段 |
|
|
|
+| 库存纯本地管理 | haha 平台不提供库存 API,且本地管理可灵活控制预警阈值、统计维度 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 九、相关文件索引
|
|
|
+
|
|
|
+### 实体
|
|
|
+- `haha-entity/.../entity/DeviceInventory.java`
|
|
|
+- `haha-entity/.../entity/InventoryLog.java`
|
|
|
+- `haha-entity/.../entity/StockRecord.java`
|
|
|
+- `haha-entity/.../entity/StockRecordItem.java`
|
|
|
+- `haha-entity/.../entity/Refund.java` — 含 `restoreStock` 标志
|
|
|
+- `haha-entity/.../entity/RefundItem.java`
|
|
|
+
|
|
|
+### 服务
|
|
|
+- `haha-service/.../service/DeviceInventoryService.java`
|
|
|
+- `haha-service/.../service/impl/DeviceInventoryServiceImpl.java`
|
|
|
+- `haha-service/.../service/InventoryLogService.java`
|
|
|
+- `haha-service/.../service/impl/InventoryLogServiceImpl.java`
|
|
|
+- `haha-service/.../service/OrderInventoryService.java`
|
|
|
+- `haha-service/.../service/impl/OrderInventoryServiceImpl.java`
|
|
|
+- `haha-service/.../service/StockRecordService.java`
|
|
|
+- `haha-service/.../service/impl/StockRecordServiceImpl.java`
|
|
|
+- `haha-service/.../service/RefundService.java`
|
|
|
+- `haha-service/.../service/impl/RefundServiceImpl.java`
|
|
|
+
|
|
|
+### 控制器
|
|
|
+- `haha-admin/.../controller/InventoryController.java`
|
|
|
+- `haha-admin/.../controller/ReplenisherOperationController.java`
|
|
|
+
|
|
|
+### SQL
|
|
|
+- `haha-admin/.../sql/inventory.sql`
|
|
|
+- `haha-admin/.../sql/inventory_test_data.sql`
|
|
|
+- `haha-admin/.../db/migration/V5__create_refund_tables.sql`
|
|
|
+- `haha-admin/.../db/migration/V6__add_refund_restore_stock.sql`
|
|
|
+
|
|
|
+### 集成点
|
|
|
+- `haha-service/.../service/impl/HahaCallbackServiceImpl.java` — **主路径**:AI 识别后立即扣减库存 + 支付分扣费兜底
|
|
|
+- `haha-service/.../service/payment/impl/PaymentServiceImpl.java` — 微信支付回调兜底扣减
|
|
|
+- `haha-service/.../service/payment/payscore/impl/PayScoreServiceImpl.java` — 支付分 4 个路径兜底扣减
|
|
|
+- `haha-service/.../service/impl/OrderServiceImpl.java` — 直接完成订单兜底扣减
|
|
|
+- `haha-service/.../service/impl/RefundServiceImpl.java` — 退款恢复库存(受 restoreStock 标志控制)
|
|
|
+
|
|
|
+### DTO / VO
|
|
|
+- `haha-entity/.../dto/RefundDTO.java` — 管理员退款请求,含 `restoreStock` 字段
|
|
|
+- `haha-common/.../vo/RefundApplicationVO.java` — 退款详情响应,含 `restoreStock` 字段
|