电商库存管理系统设计与实现

2024/1/1

# 电商库存管理系统设计与实现

# 系统概述

库存管理系统是电商平台的核心组件,负责商品库存的实时监控、预警、调拨和优化。一个高效的库存管理系统需要处理高并发的库存查询和更新操作,确保数据一致性,防止超卖现象,同时提供准确的库存预警和分析功能。

# 系统架构设计

# 整体架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   订单系统      │    │   购物车系统    │    │   商品系统      │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         └───────────────────────┼───────────────────────┘
                                 │
         ┌─────────────────────────────────────────────────┐
         │              库存管理系统                        │
         │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
         │  │  库存查询   │  │  库存扣减   │  │  库存预警   │ │
         │  └─────────────┘  └─────────────┘  └─────────────┘ │
         │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
         │  │  库存调拨   │  │  库存盘点   │  │  库存分析   │ │
         │  └─────────────┘  └─────────────┘  └─────────────┘ │
         └─────────────────────────────────────────────────┘
                                 │
         ┌─────────────────────────────────────────────────┐
         │              数据存储层                          │
         │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
         │  │  Redis缓存   │  │  MySQL数据库 │  │  消息队列   │ │
         │  └─────────────┘  └─────────────┘  └─────────────┘ │
         └─────────────────────────────────────────────────┘

# 核心模块

  1. 库存查询模块:提供实时库存查询服务
  2. 库存扣减模块:处理订单库存扣减操作
  3. 库存预警模块:监控库存水位,及时预警
  4. 库存调拨模块:处理仓库间库存调拨
  5. 库存盘点模块:定期盘点,确保数据准确性
  6. 库存分析模块:提供库存分析和报表功能

# 核心实体设计

# 1. 库存实体

@Entity
@Table(name = "inventory")
public class Inventory {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private Long productId;
    
    @Column(nullable = false)
    private Long warehouseId;
    
    @Column(nullable = false)
    private Integer totalStock; // 总库存
    
    @Column(nullable = false)
    private Integer availableStock; // 可用库存
    
    @Column(nullable = false)
    private Integer reservedStock; // 预留库存
    
    @Column(nullable = false)
    private Integer frozenStock; // 冻结库存
    
    @Column(nullable = false)
    private Integer safetyStock; // 安全库存
    
    @Column(nullable = false)
    private Integer reorderPoint; // 补货点
    
    @Column(nullable = false)
    private Integer maxStock; // 最大库存
    
    @Version
    private Long version; // 乐观锁版本号
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    private LocalDateTime updatedAt;
    
    // 计算可销售库存
    public Integer getSellableStock() {
        return availableStock - reservedStock;
    }
    
    // 检查是否需要补货
    public Boolean needReorder() {
        return availableStock <= reorderPoint;
    }
    
    // 检查是否库存不足
    public Boolean isLowStock() {
        return availableStock <= safetyStock;
    }
}

# 2. 库存变更记录

@Entity
@Table(name = "inventory_transactions")
public class InventoryTransaction {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private Long productId;
    
    @Column(nullable = false)
    private Long warehouseId;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private TransactionType type;
    
    @Column(nullable = false)
    private Integer quantity;
    
    @Column(nullable = false)
    private Integer beforeStock;
    
    @Column(nullable = false)
    private Integer afterStock;
    
    @Column(length = 100)
    private String referenceId; // 关联业务ID(订单号、调拨单号等)
    
    @Column(length = 50)
    private String referenceType; // 关联业务类型
    
    @Column(length = 500)
    private String remark;
    
    @Column(nullable = false)
    private Long operatorId;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
}

@Getter
public enum TransactionType {
    IN_PURCHASE("采购入库"),
    IN_RETURN("退货入库"),
    IN_TRANSFER("调拨入库"),
    IN_ADJUSTMENT("盘点调整入库"),
    OUT_SALE("销售出库"),
    OUT_TRANSFER("调拨出库"),
    OUT_DAMAGE("损耗出库"),
    OUT_ADJUSTMENT("盘点调整出库"),
    RESERVE("预留"),
    RELEASE("释放"),
    FREEZE("冻结"),
    UNFREEZE("解冻");
    
    private final String description;
    
    TransactionType(String description) {
        this.description = description;
    }
}

# 3. 仓库实体

@Entity
@Table(name = "warehouses")
public class Warehouse {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 100)
    private String name;
    
    @Column(nullable = false, length = 50)
    private String code;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private WarehouseType type;
    
    @Column(length = 500)
    private String address;
    
    @Column(length = 20)
    private String contactPhone;
    
    @Column(length = 100)
    private String contactPerson;
    
    @Column(nullable = false)
    private Boolean active = true;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    private LocalDateTime updatedAt;
}

@Getter
public enum WarehouseType {
    MAIN("主仓"),
    BRANCH("分仓"),
    VIRTUAL("虚拟仓"),
    SUPPLIER("供应商仓");
    
    private final String description;
    
    WarehouseType(String description) {
        this.description = description;
    }
}

# 库存服务实现

# 1. 核心库存服务

@Service
public class InventoryService {
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    @Autowired
    private InventoryTransactionRepository transactionRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    private static final String INVENTORY_LOCK_PREFIX = "inventory:lock:";
    private static final String INVENTORY_CACHE_PREFIX = "inventory:";
    
    /**
     * 查询商品库存
     */
    public InventoryDTO getInventory(Long productId, Long warehouseId) {
        // 先从缓存获取
        String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + warehouseId;
        InventoryDTO cached = (InventoryDTO) redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            return cached;
        }
        
        // 从数据库获取
        Inventory inventory = inventoryRepository.findByProductIdAndWarehouseId(productId, warehouseId);
        if (inventory == null) {
            return null;
        }
        
        InventoryDTO dto = convertToDTO(inventory);
        
        // 缓存结果
        redisTemplate.opsForValue().set(cacheKey, dto, 300, TimeUnit.SECONDS);
        
        return dto;
    }
    
    /**
     * 批量查询库存
     */
    public Map<Long, InventoryDTO> batchGetInventory(List<Long> productIds, Long warehouseId) {
        Map<Long, InventoryDTO> result = new HashMap<>();
        List<Long> uncachedIds = new ArrayList<>();
        
        // 先从缓存获取
        for (Long productId : productIds) {
            String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + warehouseId;
            InventoryDTO cached = (InventoryDTO) redisTemplate.opsForValue().get(cacheKey);
            if (cached != null) {
                result.put(productId, cached);
            } else {
                uncachedIds.add(productId);
            }
        }
        
        // 从数据库批量获取未缓存的
        if (!uncachedIds.isEmpty()) {
            List<Inventory> inventories = inventoryRepository.findByProductIdInAndWarehouseId(
                uncachedIds, warehouseId);
            
            for (Inventory inventory : inventories) {
                InventoryDTO dto = convertToDTO(inventory);
                result.put(inventory.getProductId(), dto);
                
                // 缓存结果
                String cacheKey = INVENTORY_CACHE_PREFIX + inventory.getProductId() + ":" + warehouseId;
                redisTemplate.opsForValue().set(cacheKey, dto, 300, TimeUnit.SECONDS);
            }
        }
        
        return result;
    }
    
    /**
     * 扣减库存
     */
    @Transactional
    public Boolean deductStock(Long productId, Long warehouseId, Integer quantity, 
                              String referenceId, String referenceType) {
        String lockKey = INVENTORY_LOCK_PREFIX + productId + ":" + warehouseId;
        
        // 分布式锁
        Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
            lockKey, "1", 10, TimeUnit.SECONDS);
        
        if (!lockAcquired) {
            throw new BusinessException("库存操作繁忙,请稍后重试");
        }
        
        try {
            Inventory inventory = inventoryRepository.findByProductIdAndWarehouseIdForUpdate(
                productId, warehouseId);
            
            if (inventory == null) {
                throw new BusinessException("库存记录不存在");
            }
            
            if (inventory.getAvailableStock() < quantity) {
                throw new BusinessException("库存不足");
            }
            
            // 记录变更前库存
            Integer beforeStock = inventory.getAvailableStock();
            
            // 扣减库存
            inventory.setAvailableStock(inventory.getAvailableStock() - quantity);
            inventory.setTotalStock(inventory.getTotalStock() - quantity);
            
            inventoryRepository.save(inventory);
            
            // 记录库存变更
            recordTransaction(inventory, TransactionType.OUT_SALE, quantity, 
                beforeStock, inventory.getAvailableStock(), referenceId, referenceType);
            
            // 清除缓存
            clearInventoryCache(productId, warehouseId);
            
            // 检查库存预警
            checkStockAlert(inventory);
            
            return true;
            
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
    /**
     * 预留库存
     */
    @Transactional
    public Boolean reserveStock(Long productId, Long warehouseId, Integer quantity, 
                               String referenceId, String referenceType) {
        String lockKey = INVENTORY_LOCK_PREFIX + productId + ":" + warehouseId;
        
        Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
            lockKey, "1", 10, TimeUnit.SECONDS);
        
        if (!lockAcquired) {
            throw new BusinessException("库存操作繁忙,请稍后重试");
        }
        
        try {
            Inventory inventory = inventoryRepository.findByProductIdAndWarehouseIdForUpdate(
                productId, warehouseId);
            
            if (inventory == null) {
                throw new BusinessException("库存记录不存在");
            }
            
            if (inventory.getSellableStock() < quantity) {
                return false; // 库存不足
            }
            
            // 记录变更前库存
            Integer beforeReserved = inventory.getReservedStock();
            
            // 预留库存
            inventory.setReservedStock(inventory.getReservedStock() + quantity);
            
            inventoryRepository.save(inventory);
            
            // 记录库存变更
            recordTransaction(inventory, TransactionType.RESERVE, quantity, 
                beforeReserved, inventory.getReservedStock(), referenceId, referenceType);
            
            // 清除缓存
            clearInventoryCache(productId, warehouseId);
            
            return true;
            
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
    /**
     * 释放预留库存
     */
    @Transactional
    public void releaseReservedStock(Long productId, Long warehouseId, Integer quantity, 
                                    String referenceId, String referenceType) {
        String lockKey = INVENTORY_LOCK_PREFIX + productId + ":" + warehouseId;
        
        Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
            lockKey, "1", 10, TimeUnit.SECONDS);
        
        if (!lockAcquired) {
            throw new BusinessException("库存操作繁忙,请稍后重试");
        }
        
        try {
            Inventory inventory = inventoryRepository.findByProductIdAndWarehouseIdForUpdate(
                productId, warehouseId);
            
            if (inventory == null) {
                return;
            }
            
            // 记录变更前库存
            Integer beforeReserved = inventory.getReservedStock();
            
            // 释放预留库存
            inventory.setReservedStock(Math.max(0, inventory.getReservedStock() - quantity));
            
            inventoryRepository.save(inventory);
            
            // 记录库存变更
            recordTransaction(inventory, TransactionType.RELEASE, quantity, 
                beforeReserved, inventory.getReservedStock(), referenceId, referenceType);
            
            // 清除缓存
            clearInventoryCache(productId, warehouseId);
            
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
    /**
     * 入库操作
     */
    @Transactional
    public void inboundStock(Long productId, Long warehouseId, Integer quantity, 
                            TransactionType type, String referenceId, String referenceType) {
        String lockKey = INVENTORY_LOCK_PREFIX + productId + ":" + warehouseId;
        
        Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
            lockKey, "1", 10, TimeUnit.SECONDS);
        
        if (!lockAcquired) {
            throw new BusinessException("库存操作繁忙,请稍后重试");
        }
        
        try {
            Inventory inventory = inventoryRepository.findByProductIdAndWarehouseIdForUpdate(
                productId, warehouseId);
            
            if (inventory == null) {
                // 创建新的库存记录
                inventory = new Inventory();
                inventory.setProductId(productId);
                inventory.setWarehouseId(warehouseId);
                inventory.setTotalStock(0);
                inventory.setAvailableStock(0);
                inventory.setReservedStock(0);
                inventory.setFrozenStock(0);
                inventory.setSafetyStock(10); // 默认安全库存
                inventory.setReorderPoint(20); // 默认补货点
                inventory.setMaxStock(1000); // 默认最大库存
            }
            
            // 记录变更前库存
            Integer beforeStock = inventory.getAvailableStock();
            
            // 入库
            inventory.setAvailableStock(inventory.getAvailableStock() + quantity);
            inventory.setTotalStock(inventory.getTotalStock() + quantity);
            
            inventoryRepository.save(inventory);
            
            // 记录库存变更
            recordTransaction(inventory, type, quantity, 
                beforeStock, inventory.getAvailableStock(), referenceId, referenceType);
            
            // 清除缓存
            clearInventoryCache(productId, warehouseId);
            
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
    /**
     * 记录库存变更
     */
    private void recordTransaction(Inventory inventory, TransactionType type, Integer quantity,
                                  Integer beforeStock, Integer afterStock, 
                                  String referenceId, String referenceType) {
        InventoryTransaction transaction = new InventoryTransaction();
        transaction.setProductId(inventory.getProductId());
        transaction.setWarehouseId(inventory.getWarehouseId());
        transaction.setType(type);
        transaction.setQuantity(quantity);
        transaction.setBeforeStock(beforeStock);
        transaction.setAfterStock(afterStock);
        transaction.setReferenceId(referenceId);
        transaction.setReferenceType(referenceType);
        transaction.setOperatorId(getCurrentUserId());
        
        transactionRepository.save(transaction);
    }
    
    /**
     * 清除库存缓存
     */
    private void clearInventoryCache(Long productId, Long warehouseId) {
        String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + warehouseId;
        redisTemplate.delete(cacheKey);
    }
    
    /**
     * 检查库存预警
     */
    private void checkStockAlert(Inventory inventory) {
        if (inventory.needReorder()) {
            // 发送补货提醒
            StockAlertEvent event = new StockAlertEvent();
            event.setProductId(inventory.getProductId());
            event.setWarehouseId(inventory.getWarehouseId());
            event.setCurrentStock(inventory.getAvailableStock());
            event.setReorderPoint(inventory.getReorderPoint());
            event.setAlertType(StockAlertType.REORDER);
            
            rabbitTemplate.convertAndSend("stock.alert.exchange", "stock.alert.reorder", event);
        }
        
        if (inventory.isLowStock()) {
            // 发送低库存预警
            StockAlertEvent event = new StockAlertEvent();
            event.setProductId(inventory.getProductId());
            event.setWarehouseId(inventory.getWarehouseId());
            event.setCurrentStock(inventory.getAvailableStock());
            event.setSafetyStock(inventory.getSafetyStock());
            event.setAlertType(StockAlertType.LOW_STOCK);
            
            rabbitTemplate.convertAndSend("stock.alert.exchange", "stock.alert.low", event);
        }
    }
}

# 2. 库存预警服务

@Service
public class StockAlertService {
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    @Autowired
    private NotificationService notificationService;
    
    @Autowired
    private PurchaseService purchaseService;
    
    /**
     * 扫描库存预警
     */
    @Scheduled(cron = "0 */10 * * * ?") // 每10分钟执行一次
    public void scanStockAlerts() {
        // 查找需要补货的商品
        List<Inventory> reorderItems = inventoryRepository.findReorderItems();
        for (Inventory inventory : reorderItems) {
            handleReorderAlert(inventory);
        }
        
        // 查找低库存商品
        List<Inventory> lowStockItems = inventoryRepository.findLowStockItems();
        for (Inventory inventory : lowStockItems) {
            handleLowStockAlert(inventory);
        }
        
        // 查找零库存商品
        List<Inventory> zeroStockItems = inventoryRepository.findZeroStockItems();
        for (Inventory inventory : zeroStockItems) {
            handleZeroStockAlert(inventory);
        }
    }
    
    /**
     * 处理补货预警
     */
    private void handleReorderAlert(Inventory inventory) {
        // 检查是否已经有未完成的采购订单
        boolean hasPendingPurchase = purchaseService.hasPendingPurchaseOrder(
            inventory.getProductId(), inventory.getWarehouseId());
        
        if (!hasPendingPurchase) {
            // 自动创建采购建议
            PurchaseSuggestion suggestion = new PurchaseSuggestion();
            suggestion.setProductId(inventory.getProductId());
            suggestion.setWarehouseId(inventory.getWarehouseId());
            suggestion.setCurrentStock(inventory.getAvailableStock());
            suggestion.setSuggestedQuantity(inventory.getMaxStock() - inventory.getAvailableStock());
            suggestion.setReason("库存低于补货点");
            
            purchaseService.createPurchaseSuggestion(suggestion);
            
            // 发送通知
            notificationService.sendReorderNotification(inventory);
        }
    }
    
    /**
     * 处理低库存预警
     */
    private void handleLowStockAlert(Inventory inventory) {
        notificationService.sendLowStockNotification(inventory);
    }
    
    /**
     * 处理零库存预警
     */
    private void handleZeroStockAlert(Inventory inventory) {
        notificationService.sendZeroStockNotification(inventory);
        
        // 自动下架商品
        productService.setProductOffline(inventory.getProductId(), "库存为零");
    }
}

# 3. 库存调拨服务

@Service
public class InventoryTransferService {
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private TransferOrderRepository transferOrderRepository;
    
    /**
     * 创建调拨单
     */
    @Transactional
    public TransferOrder createTransferOrder(CreateTransferOrderRequest request) {
        // 验证调出仓库库存
        InventoryDTO fromInventory = inventoryService.getInventory(
            request.getProductId(), request.getFromWarehouseId());
        
        if (fromInventory == null || fromInventory.getSellableStock() < request.getQuantity()) {
            throw new BusinessException("调出仓库库存不足");
        }
        
        // 创建调拨单
        TransferOrder transferOrder = new TransferOrder();
        transferOrder.setTransferNo(generateTransferNo());
        transferOrder.setProductId(request.getProductId());
        transferOrder.setFromWarehouseId(request.getFromWarehouseId());
        transferOrder.setToWarehouseId(request.getToWarehouseId());
        transferOrder.setQuantity(request.getQuantity());
        transferOrder.setStatus(TransferStatus.PENDING);
        transferOrder.setReason(request.getReason());
        transferOrder.setCreatorId(getCurrentUserId());
        
        transferOrderRepository.save(transferOrder);
        
        return transferOrder;
    }
    
    /**
     * 执行调拨
     */
    @Transactional
    public void executeTransfer(Long transferOrderId) {
        TransferOrder transferOrder = transferOrderRepository.findById(transferOrderId)
            .orElseThrow(() -> new BusinessException("调拨单不存在"));
        
        if (transferOrder.getStatus() != TransferStatus.PENDING) {
            throw new BusinessException("调拨单状态不正确");
        }
        
        try {
            // 调出库存
            inventoryService.deductStock(
                transferOrder.getProductId(),
                transferOrder.getFromWarehouseId(),
                transferOrder.getQuantity(),
                transferOrder.getTransferNo(),
                "TRANSFER_OUT"
            );
            
            // 调入库存
            inventoryService.inboundStock(
                transferOrder.getProductId(),
                transferOrder.getToWarehouseId(),
                transferOrder.getQuantity(),
                TransactionType.IN_TRANSFER,
                transferOrder.getTransferNo(),
                "TRANSFER_IN"
            );
            
            // 更新调拨单状态
            transferOrder.setStatus(TransferStatus.COMPLETED);
            transferOrder.setCompletedAt(LocalDateTime.now());
            transferOrderRepository.save(transferOrder);
            
        } catch (Exception e) {
            // 调拨失败
            transferOrder.setStatus(TransferStatus.FAILED);
            transferOrder.setFailReason(e.getMessage());
            transferOrderRepository.save(transferOrder);
            
            throw e;
        }
    }
}

# 库存盘点系统

# 1. 盘点服务

@Service
public class InventoryCountService {
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    @Autowired
    private CountTaskRepository countTaskRepository;
    
    @Autowired
    private CountDetailRepository countDetailRepository;
    
    /**
     * 创建盘点任务
     */
    @Transactional
    public CountTask createCountTask(CreateCountTaskRequest request) {
        CountTask countTask = new CountTask();
        countTask.setTaskNo(generateTaskNo());
        countTask.setWarehouseId(request.getWarehouseId());
        countTask.setCountType(request.getCountType());
        countTask.setStatus(CountStatus.PENDING);
        countTask.setScheduledDate(request.getScheduledDate());
        countTask.setRemark(request.getRemark());
        countTask.setCreatorId(getCurrentUserId());
        
        countTaskRepository.save(countTask);
        
        // 生成盘点明细
        generateCountDetails(countTask, request);
        
        return countTask;
    }
    
    /**
     * 生成盘点明细
     */
    private void generateCountDetails(CountTask countTask, CreateCountTaskRequest request) {
        List<Inventory> inventories;
        
        if (request.getCountType() == CountType.FULL) {
            // 全盘
            inventories = inventoryRepository.findByWarehouseId(request.getWarehouseId());
        } else if (request.getCountType() == CountType.PARTIAL) {
            // 部分盘点
            inventories = inventoryRepository.findByProductIdInAndWarehouseId(
                request.getProductIds(), request.getWarehouseId());
        } else {
            // 循环盘点
            inventories = inventoryRepository.findCyclicCountItems(
                request.getWarehouseId(), request.getScheduledDate());
        }
        
        List<CountDetail> details = new ArrayList<>();
        for (Inventory inventory : inventories) {
            CountDetail detail = new CountDetail();
            detail.setCountTaskId(countTask.getId());
            detail.setProductId(inventory.getProductId());
            detail.setSystemStock(inventory.getAvailableStock());
            detail.setStatus(CountDetailStatus.PENDING);
            details.add(detail);
        }
        
        countDetailRepository.saveAll(details);
    }
    
    /**
     * 录入盘点结果
     */
    @Transactional
    public void inputCountResult(Long countDetailId, Integer actualStock, String remark) {
        CountDetail detail = countDetailRepository.findById(countDetailId)
            .orElseThrow(() -> new BusinessException("盘点明细不存在"));
        
        detail.setActualStock(actualStock);
        detail.setDifferenceStock(actualStock - detail.getSystemStock());
        detail.setRemark(remark);
        detail.setStatus(CountDetailStatus.COUNTED);
        detail.setCountedAt(LocalDateTime.now());
        detail.setCounterId(getCurrentUserId());
        
        countDetailRepository.save(detail);
        
        // 检查任务是否完成
        checkTaskCompletion(detail.getCountTaskId());
    }
    
    /**
     * 检查盘点任务完成情况
     */
    private void checkTaskCompletion(Long countTaskId) {
        long pendingCount = countDetailRepository.countByCountTaskIdAndStatus(
            countTaskId, CountDetailStatus.PENDING);
        
        if (pendingCount == 0) {
            CountTask countTask = countTaskRepository.findById(countTaskId).orElse(null);
            if (countTask != null) {
                countTask.setStatus(CountStatus.COMPLETED);
                countTask.setCompletedAt(LocalDateTime.now());
                countTaskRepository.save(countTask);
            }
        }
    }
    
    /**
     * 审核盘点结果
     */
    @Transactional
    public void auditCountResult(Long countTaskId, Boolean approved, String auditRemark) {
        CountTask countTask = countTaskRepository.findById(countTaskId)
            .orElseThrow(() -> new BusinessException("盘点任务不存在"));
        
        if (countTask.getStatus() != CountStatus.COMPLETED) {
            throw new BusinessException("盘点任务未完成");
        }
        
        if (approved) {
            // 审核通过,调整库存
            adjustInventoryByCountResult(countTaskId);
            countTask.setStatus(CountStatus.APPROVED);
        } else {
            // 审核不通过
            countTask.setStatus(CountStatus.REJECTED);
        }
        
        countTask.setAuditRemark(auditRemark);
        countTask.setAuditedAt(LocalDateTime.now());
        countTask.setAuditorId(getCurrentUserId());
        
        countTaskRepository.save(countTask);
    }
    
    /**
     * 根据盘点结果调整库存
     */
    private void adjustInventoryByCountResult(Long countTaskId) {
        List<CountDetail> details = countDetailRepository.findByCountTaskIdAndDifferenceStockNot(
            countTaskId, 0);
        
        for (CountDetail detail : details) {
            if (detail.getDifferenceStock() > 0) {
                // 盘盈,入库
                inventoryService.inboundStock(
                    detail.getProductId(),
                    getWarehouseIdByCountTask(countTaskId),
                    detail.getDifferenceStock(),
                    TransactionType.IN_ADJUSTMENT,
                    "COUNT_" + countTaskId,
                    "INVENTORY_COUNT"
                );
            } else {
                // 盘亏,出库
                inventoryService.deductStock(
                    detail.getProductId(),
                    getWarehouseIdByCountTask(countTaskId),
                    Math.abs(detail.getDifferenceStock()),
                    "COUNT_" + countTaskId,
                    "INVENTORY_COUNT"
                );
            }
        }
    }
}

# 库存分析与报表

# 1. 库存分析服务

@Service
public class InventoryAnalysisService {
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    @Autowired
    private InventoryTransactionRepository transactionRepository;
    
    /**
     * 库存周转率分析
     */
    public InventoryTurnoverDTO analyzeInventoryTurnover(Long productId, Long warehouseId, 
                                                        LocalDate startDate, LocalDate endDate) {
        // 计算期间销售出库量
        Integer salesQuantity = transactionRepository.sumQuantityByProductAndWarehouseAndTypeAndDateRange(
            productId, warehouseId, TransactionType.OUT_SALE, startDate, endDate);
        
        // 计算平均库存
        BigDecimal avgInventory = calculateAverageInventory(productId, warehouseId, startDate, endDate);
        
        // 计算周转率
        BigDecimal turnoverRate = BigDecimal.ZERO;
        if (avgInventory.compareTo(BigDecimal.ZERO) > 0) {
            turnoverRate = BigDecimal.valueOf(salesQuantity).divide(avgInventory, 2, RoundingMode.HALF_UP);
        }
        
        // 计算周转天数
        BigDecimal turnoverDays = BigDecimal.ZERO;
        if (turnoverRate.compareTo(BigDecimal.ZERO) > 0) {
            long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
            turnoverDays = BigDecimal.valueOf(daysBetween).divide(turnoverRate, 2, RoundingMode.HALF_UP);
        }
        
        InventoryTurnoverDTO dto = new InventoryTurnoverDTO();
        dto.setProductId(productId);
        dto.setWarehouseId(warehouseId);
        dto.setSalesQuantity(salesQuantity);
        dto.setAverageInventory(avgInventory);
        dto.setTurnoverRate(turnoverRate);
        dto.setTurnoverDays(turnoverDays);
        dto.setStartDate(startDate);
        dto.setEndDate(endDate);
        
        return dto;
    }
    
    /**
     * 计算平均库存
     */
    private BigDecimal calculateAverageInventory(Long productId, Long warehouseId, 
                                               LocalDate startDate, LocalDate endDate) {
        List<DailyInventory> dailyInventories = inventoryRepository.findDailyInventory(
            productId, warehouseId, startDate, endDate);
        
        if (dailyInventories.isEmpty()) {
            return BigDecimal.ZERO;
        }
        
        BigDecimal totalInventory = dailyInventories.stream()
            .map(DailyInventory::getInventoryQuantity)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        return totalInventory.divide(BigDecimal.valueOf(dailyInventories.size()), 2, RoundingMode.HALF_UP);
    }
    
    /**
     * 库存ABC分析
     */
    public List<InventoryABCDTO> analyzeInventoryABC(Long warehouseId, LocalDate startDate, LocalDate endDate) {
        // 获取商品销售数据
        List<ProductSalesData> salesData = transactionRepository.getProductSalesData(
            warehouseId, startDate, endDate);
        
        // 按销售额排序
        salesData.sort((a, b) -> b.getSalesAmount().compareTo(a.getSalesAmount()));
        
        // 计算累计销售额
        BigDecimal totalSales = salesData.stream()
            .map(ProductSalesData::getSalesAmount)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        List<InventoryABCDTO> result = new ArrayList<>();
        BigDecimal cumulativeSales = BigDecimal.ZERO;
        
        for (ProductSalesData data : salesData) {
            cumulativeSales = cumulativeSales.add(data.getSalesAmount());
            BigDecimal cumulativePercentage = cumulativeSales.divide(totalSales, 4, RoundingMode.HALF_UP)
                .multiply(BigDecimal.valueOf(100));
            
            InventoryABCDTO dto = new InventoryABCDTO();
            dto.setProductId(data.getProductId());
            dto.setSalesAmount(data.getSalesAmount());
            dto.setSalesQuantity(data.getSalesQuantity());
            dto.setCumulativePercentage(cumulativePercentage);
            
            // 分类
            if (cumulativePercentage.compareTo(BigDecimal.valueOf(80)) <= 0) {
                dto.setCategory("A");
            } else if (cumulativePercentage.compareTo(BigDecimal.valueOf(95)) <= 0) {
                dto.setCategory("B");
            } else {
                dto.setCategory("C");
            }
            
            result.add(dto);
        }
        
        return result;
    }
    
    /**
     * 滞销商品分析
     */
    public List<SlowMovingInventoryDTO> analyzeSlowMovingInventory(Long warehouseId, Integer days) {
        LocalDate cutoffDate = LocalDate.now().minusDays(days);
        
        List<Inventory> slowMovingItems = inventoryRepository.findSlowMovingInventory(
            warehouseId, cutoffDate);
        
        return slowMovingItems.stream()
            .map(this::convertToSlowMovingDTO)
            .collect(Collectors.toList());
    }
}

# 性能优化策略

# 1. 缓存优化

@Component
public class InventoryCacheManager {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String HOT_INVENTORY_KEY = "hot:inventory";
    private static final String INVENTORY_CACHE_PREFIX = "inventory:";
    
    /**
     * 预热热门商品库存缓存
     */
    @PostConstruct
    public void preloadHotInventory() {
        // 获取热门商品列表
        List<Long> hotProductIds = getHotProductIds();
        
        for (Long productId : hotProductIds) {
            List<Inventory> inventories = inventoryRepository.findByProductId(productId);
            for (Inventory inventory : inventories) {
                String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + inventory.getWarehouseId();
                InventoryDTO dto = convertToDTO(inventory);
                redisTemplate.opsForValue().set(cacheKey, dto, 600, TimeUnit.SECONDS);
            }
        }
    }
    
    /**
     * 批量刷新库存缓存
     */
    @Async
    public void batchRefreshInventoryCache(List<Long> productIds) {
        for (Long productId : productIds) {
            List<Inventory> inventories = inventoryRepository.findByProductId(productId);
            for (Inventory inventory : inventories) {
                String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + inventory.getWarehouseId();
                InventoryDTO dto = convertToDTO(inventory);
                redisTemplate.opsForValue().set(cacheKey, dto, 300, TimeUnit.SECONDS);
            }
        }
    }
}

# 2. 数据库优化

-- 库存表索引优化
CREATE INDEX idx_inventory_product_warehouse ON inventory(product_id, warehouse_id);
CREATE INDEX idx_inventory_warehouse_stock ON inventory(warehouse_id, available_stock);
CREATE INDEX idx_inventory_reorder ON inventory(warehouse_id, available_stock, reorder_point);
CREATE INDEX idx_inventory_updated_at ON inventory(updated_at);

-- 库存变更记录表索引
CREATE INDEX idx_transaction_product_warehouse_date ON inventory_transactions(product_id, warehouse_id, created_at);
CREATE INDEX idx_transaction_reference ON inventory_transactions(reference_id, reference_type);
CREATE INDEX idx_transaction_type_date ON inventory_transactions(type, created_at);

-- 分区表设计(按月分区)
CREATE TABLE inventory_transactions_202401 PARTITION OF inventory_transactions
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

# 总结

库存管理系统的关键设计要点:

  1. 数据一致性:使用分布式锁、乐观锁、事务保证库存数据准确性
  2. 高并发处理:Redis缓存、批量操作、异步处理提升性能
  3. 库存预警:实时监控、自动预警、智能补货建议
  4. 精细化管理:多仓库支持、库存调拨、盘点管理
  5. 数据分析:周转率分析、ABC分析、滞销分析
  6. 扩展性:事件驱动、微服务架构、水平扩展

通过以上设计,可以构建一个高效、准确、智能的库存管理系统,为电商业务提供强有力的库存保障。