库存管理模块.md 16 KB

库存管理模块说明

本文档描述系统商品库存管理的完整架构,涵盖库存数据模型、服务层设计、库存变动流程、与订单/支付/退款模块的集成,以及幂等性保护机制。 帮助开发人员理解库存数据的生命周期和关键设计决策。


一、模块概述

1.1 库存管理范围

本系统的库存管理完全在本地进行,haha 平台不提供库存管理 API。两者的分工:

数据 管理方 说明
商品信息(名称、条码、价格、图片) haha 平台 → 本地同步 DataSyncService.syncProducts() 定时拉取
设备列表 haha 平台 → 本地同步 DataSyncService.syncDevices()
订单支付状态 本地 → haha 平台推送 notifyHahaOrderStatusChanged()
库存数量 纯本地管理 haha 不存库存,不提供库存接口

1.2 库存维度

库存按 设备 + 商品 维度管理,即同一商品在不同设备上有独立的库存记录(t_device_inventory 表)。

1.3 为什么库存不放在 haha 平台?

haha 平台的职责是 AI 识别(开关门事件 + 商品识别结果)和设备管理。其 SDK 提供的 GoodsApiOrderApi 均无库存查询/更新端点,商品 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 表保证只扣一次:

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 字段