第10章 中央库存系统

10.1 中央库存系统概述

中央库存系统是新零售SaaS平台的核心组成部分,它负责统一管理企业所有渠道的库存信息,实现库存的实时同步、精准控制和智能调配。在多渠道零售环境下,中央库存系统确保了库存数据的一致性和准确性,为企业提供了全局的库存视图和决策支持。

10.1.1 中央库存系统的重要性

在传统的零售模式中,不同渠道往往维护独立的库存系统,这导致了以下问题:

  1. 库存数据不一致:各渠道库存数据无法实时同步,容易出现超卖或缺货情况
  2. 库存可见性差:无法获得全局库存视图,影响采购和销售决策
  3. 库存周转率低:无法实现库存在不同渠道间的灵活调配
  4. 客户体验差:因库存信息不准确导致的订单取消和延迟交付

中央库存系统通过统一的库存管理,解决了这些问题:

/**
 * 中央库存系统核心价值
 */
public class CentralInventoryValue {
    
    /**
     * 库存统一管理的核心价值
     */
    public enum CoreValue {
        REAL_TIME_SYNC("实时同步", "所有渠道库存实时同步更新"),
        GLOBAL_VISIBILITY("全局可见", "提供企业级库存全局视图"),
        INTELLIGENT_ALLOCATION("智能调配", "基于算法的库存智能分配"),
        ACCURATE_CONTROL("精准控制", "精确的库存数量和状态管理"),
        UNIFIED_RULES("统一规则", "标准化的库存管理规则和流程");
        
        private final String name;
        private final String description;
        
        CoreValue(String name, String description) {
            this.name = name;
            this.description = description;
        }
        
        public String getName() { return name; }
        public String getDescription() { return description; }
    }
    
    /**
     * 业务收益指标
     */
    public static class BusinessBenefits {
        private double inventoryTurnoverImprovement;    // 库存周转率提升
        private double stockoutReduction;              // 缺货率降低
        private double oversellReduction;               // 超卖率降低
        private double customerSatisfactionIncrease;   // 客户满意度提升
        private double operationalEfficiencyGain;      // 运营效率提升
        
        public BusinessBenefits() {
            // 基于行业数据的预期收益
            this.inventoryTurnoverImprovement = 0.25;  // 25%提升
            this.stockoutReduction = 0.40;             // 40%降低
            this.oversellReduction = 0.90;             // 90%降低
            this.customerSatisfactionIncrease = 0.15;  // 15%提升
            this.operationalEfficiencyGain = 0.30;    // 30%提升
        }
        
        public Map<String, String> getBenefitsSummary() {
            Map<String, String> benefits = new HashMap<>();
            benefits.put("库存周转率", String.format("提升%.0f%%", inventoryTurnoverImprovement * 100));
            benefits.put("缺货率", String.format("降低%.0f%%", stockoutReduction * 100));
            benefits.put("超卖率", String.format("降低%.0f%%", oversellReduction * 100));
            benefits.put("客户满意度", String.format("提升%.0f%%", customerSatisfactionIncrease * 100));
            benefits.put("运营效率", String.format("提升%.0f%%", operationalEfficiencyGain * 100));
            return benefits;
        }
    }
}

10.1.2 系统架构设计原则

中央库存系统的设计遵循以下核心原则:

/**
 * 中央库存系统设计原则
 */
public class InventorySystemPrinciples {
    
    /**
     * 设计原则枚举
     */
    public enum DesignPrinciple {
        SINGLE_SOURCE_OF_TRUTH("单一数据源", "库存数据的唯一权威来源"),
        REAL_TIME_CONSISTENCY("实时一致性", "确保所有渠道库存数据实时一致"),
        HIGH_AVAILABILITY("高可用性", "系统7x24小时稳定运行"),
        SCALABILITY("可扩展性", "支持业务规模的快速增长"),
        FAULT_TOLERANCE("容错性", "具备故障自动恢复能力"),
        PERFORMANCE_OPTIMIZATION("性能优化", "高并发下的快速响应"),
        SECURITY_COMPLIANCE("安全合规", "数据安全和合规性保障");
        
        private final String name;
        private final String description;
        
        DesignPrinciple(String name, String description) {
            this.name = name;
            this.description = description;
        }
        
        public String getName() { return name; }
        public String getDescription() { return description; }
    }
    
    /**
     * 架构质量属性
     */
    public static class QualityAttributes {
        private Map<String, QualityRequirement> requirements;
        
        public QualityAttributes() {
            this.requirements = new HashMap<>();
            initializeRequirements();
        }
        
        private void initializeRequirements() {
            requirements.put("可用性", new QualityRequirement(
                "99.9%", "系统年度可用性不低于99.9%", "关键业务连续性保障"
            ));
            
            requirements.put("响应时间", new QualityRequirement(
                "<100ms", "库存查询响应时间小于100毫秒", "用户体验优化"
            ));
            
            requirements.put("并发处理", new QualityRequirement(
                "10000 TPS", "支持每秒10000次事务处理", "高并发场景支持"
            ));
            
            requirements.put("数据一致性", new QualityRequirement(
                "强一致性", "库存数据强一致性保证", "业务准确性要求"
            ));
            
            requirements.put("扩展性", new QualityRequirement(
                "水平扩展", "支持水平扩展到1000+节点", "业务增长支撑"
            ));
        }
        
        public QualityRequirement getRequirement(String attribute) {
            return requirements.get(attribute);
        }
        
        public Map<String, QualityRequirement> getAllRequirements() {
            return new HashMap<>(requirements);
        }
    }
    
    public static class QualityRequirement {
        private String target;          // 目标值
        private String description;     // 描述
        private String rationale;       // 理由
        
        public QualityRequirement(String target, String description, String rationale) {
            this.target = target;
            this.description = description;
            this.rationale = rationale;
        }
        
        // getters
        public String getTarget() { return target; }
        public String getDescription() { return description; }
        public String getRationale() { return rationale; }
    }
}

10.2 库存数据模型设计

库存数据模型是中央库存系统的基础,它定义了库存信息的结构、关系和约束。一个良好的数据模型能够支持复杂的业务场景,同时保证数据的完整性和一致性。

10.2.1 核心库存实体

/**
 * 库存核心实体定义
 */
@Entity
@Table(name = "inventory_item")
public class InventoryItem extends BaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "sku_id", nullable = false)
    private String skuId;                    // SKU标识
    
    @Column(name = "warehouse_id", nullable = false)
    private String warehouseId;              // 仓库标识
    
    @Column(name = "location_id")
    private String locationId;               // 库位标识
    
    @Column(name = "available_quantity", nullable = false)
    private Integer availableQuantity;       // 可用库存
    
    @Column(name = "reserved_quantity", nullable = false)
    private Integer reservedQuantity;        // 预留库存
    
    @Column(name = "allocated_quantity", nullable = false)
    private Integer allocatedQuantity;       // 已分配库存
    
    @Column(name = "in_transit_quantity", nullable = false)
    private Integer inTransitQuantity;       // 在途库存
    
    @Column(name = "damaged_quantity", nullable = false)
    private Integer damagedQuantity;         // 损坏库存
    
    @Column(name = "total_quantity", nullable = false)
    private Integer totalQuantity;           // 总库存
    
    @Column(name = "safety_stock")
    private Integer safetyStock;             // 安全库存
    
    @Column(name = "reorder_point")
    private Integer reorderPoint;            // 补货点
    
    @Column(name = "max_stock")
    private Integer maxStock;                // 最大库存
    
    @Column(name = "unit_cost")
    private BigDecimal unitCost;             // 单位成本
    
    @Column(name = "last_updated")
    private LocalDateTime lastUpdated;       // 最后更新时间
    
    @Column(name = "version", nullable = false)
    @Version
    private Long version;                    // 乐观锁版本号
    
    @Enumerated(EnumType.STRING)
    @Column(name = "status")
    private InventoryStatus status;          // 库存状态
    
    @Column(name = "batch_number")
    private String batchNumber;              // 批次号
    
    @Column(name = "expiry_date")
    private LocalDate expiryDate;            // 过期日期
    
    @Column(name = "supplier_id")
    private String supplierId;               // 供应商ID
    
    // 构造函数
    public InventoryItem() {}
    
    public InventoryItem(String skuId, String warehouseId) {
        this.skuId = skuId;
        this.warehouseId = warehouseId;
        this.availableQuantity = 0;
        this.reservedQuantity = 0;
        this.allocatedQuantity = 0;
        this.inTransitQuantity = 0;
        this.damagedQuantity = 0;
        this.totalQuantity = 0;
        this.status = InventoryStatus.ACTIVE;
        this.lastUpdated = LocalDateTime.now();
    }
    
    /**
     * 计算可销售库存
     */
    public Integer getAvailableForSale() {
        return availableQuantity - reservedQuantity;
    }
    
    /**
     * 检查是否需要补货
     */
    public boolean needsReorder() {
        return reorderPoint != null && availableQuantity <= reorderPoint;
    }
    
    /**
     * 检查是否库存充足
     */
    public boolean hasSufficientStock(Integer requiredQuantity) {
        return getAvailableForSale() >= requiredQuantity;
    }
    
    /**
     * 更新总库存
     */
    public void updateTotalQuantity() {
        this.totalQuantity = availableQuantity + reservedQuantity + 
                           allocatedQuantity + inTransitQuantity + damagedQuantity;
        this.lastUpdated = LocalDateTime.now();
    }
    
    /**
     * 预留库存
     */
    public boolean reserveStock(Integer quantity) {
        if (hasSufficientStock(quantity)) {
            this.availableQuantity -= quantity;
            this.reservedQuantity += quantity;
            updateTotalQuantity();
            return true;
        }
        return false;
    }
    
    /**
     * 释放预留库存
     */
    public void releaseReservedStock(Integer quantity) {
        int releaseQuantity = Math.min(quantity, this.reservedQuantity);
        this.reservedQuantity -= releaseQuantity;
        this.availableQuantity += releaseQuantity;
        updateTotalQuantity();
    }
    
    /**
     * 分配库存
     */
    public boolean allocateStock(Integer quantity) {
        if (this.reservedQuantity >= quantity) {
            this.reservedQuantity -= quantity;
            this.allocatedQuantity += quantity;
            updateTotalQuantity();
            return true;
        }
        return false;
    }
    
    /**
     * 出库
     */
    public boolean shipStock(Integer quantity) {
        if (this.allocatedQuantity >= quantity) {
            this.allocatedQuantity -= quantity;
            updateTotalQuantity();
            return true;
        }
        return false;
    }
    
    /**
     * 入库
     */
    public void receiveStock(Integer quantity) {
        this.availableQuantity += quantity;
        updateTotalQuantity();
    }
    
    // getters and setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getSkuId() { return skuId; }
    public void setSkuId(String skuId) { this.skuId = skuId; }
    
    public String getWarehouseId() { return warehouseId; }
    public void setWarehouseId(String warehouseId) { this.warehouseId = warehouseId; }
    
    public String getLocationId() { return locationId; }
    public void setLocationId(String locationId) { this.locationId = locationId; }
    
    public Integer getAvailableQuantity() { return availableQuantity; }
    public void setAvailableQuantity(Integer availableQuantity) { 
        this.availableQuantity = availableQuantity;
        updateTotalQuantity();
    }
    
    public Integer getReservedQuantity() { return reservedQuantity; }
    public void setReservedQuantity(Integer reservedQuantity) { 
        this.reservedQuantity = reservedQuantity;
        updateTotalQuantity();
    }
    
    public Integer getAllocatedQuantity() { return allocatedQuantity; }
    public void setAllocatedQuantity(Integer allocatedQuantity) { 
        this.allocatedQuantity = allocatedQuantity;
        updateTotalQuantity();
    }
    
    public Integer getInTransitQuantity() { return inTransitQuantity; }
    public void setInTransitQuantity(Integer inTransitQuantity) { 
        this.inTransitQuantity = inTransitQuantity;
        updateTotalQuantity();
    }
    
    public Integer getDamagedQuantity() { return damagedQuantity; }
    public void setDamagedQuantity(Integer damagedQuantity) { 
        this.damagedQuantity = damagedQuantity;
        updateTotalQuantity();
    }
    
    public Integer getTotalQuantity() { return totalQuantity; }
    
    public Integer getSafetyStock() { return safetyStock; }
    public void setSafetyStock(Integer safetyStock) { this.safetyStock = safetyStock; }
    
    public Integer getReorderPoint() { return reorderPoint; }
    public void setReorderPoint(Integer reorderPoint) { this.reorderPoint = reorderPoint; }
    
    public Integer getMaxStock() { return maxStock; }
    public void setMaxStock(Integer maxStock) { this.maxStock = maxStock; }
    
    public BigDecimal getUnitCost() { return unitCost; }
    public void setUnitCost(BigDecimal unitCost) { this.unitCost = unitCost; }
    
    public LocalDateTime getLastUpdated() { return lastUpdated; }
    public void setLastUpdated(LocalDateTime lastUpdated) { this.lastUpdated = lastUpdated; }
    
    public Long getVersion() { return version; }
    public void setVersion(Long version) { this.version = version; }
    
    public InventoryStatus getStatus() { return status; }
    public void setStatus(InventoryStatus status) { this.status = status; }
    
    public String getBatchNumber() { return batchNumber; }
    public void setBatchNumber(String batchNumber) { this.batchNumber = batchNumber; }
    
    public LocalDate getExpiryDate() { return expiryDate; }
    public void setExpiryDate(LocalDate expiryDate) { this.expiryDate = expiryDate; }
    
    public String getSupplierId() { return supplierId; }
    public void setSupplierId(String supplierId) { this.supplierId = supplierId; }
}

/**
 * 库存状态枚举
 */
public enum InventoryStatus {
    ACTIVE("正常"),
    INACTIVE("停用"),
    QUARANTINE("隔离"),
    EXPIRED("过期"),
    DAMAGED("损坏"),
    RESERVED("预留"),
    ALLOCATED("已分配"),
    IN_TRANSIT("在途");
    
    private final String description;
    
    InventoryStatus(String description) {
        this.description = description;
    }
    
    public String getDescription() {
         return description;
     }
 }

10.7 API接口设计

中央库存系统需要提供完整的REST API接口,支持各种库存操作:

/**
 * 库存管理API控制器
 */
@RestController
@RequestMapping("/api/v1/inventory")
@Validated
public class InventoryController {
    
    private final InventoryService inventoryService;
    private final InventoryAnalyticsService analyticsService;
    private final IntelligentReplenishmentService replenishmentService;
    private final InventoryAlertService alertService;
    
    public InventoryController(InventoryService inventoryService,
                             InventoryAnalyticsService analyticsService,
                             IntelligentReplenishmentService replenishmentService,
                             InventoryAlertService alertService) {
        this.inventoryService = inventoryService;
        this.analyticsService = analyticsService;
        this.replenishmentService = replenishmentService;
        this.alertService = alertService;
    }
    
    /**
     * 查询库存信息
     */
    @GetMapping("/items")
    public ResponseEntity<PageResult<InventoryItemDTO>> getInventoryItems(
            @RequestParam(required = false) String warehouseId,
            @RequestParam(required = false) String skuId,
            @RequestParam(required = false) InventoryStatus status,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        
        InventoryQueryRequest request = new InventoryQueryRequest(
            warehouseId, skuId, status, page, size
        );
        
        PageResult<InventoryItem> result = inventoryService.queryInventory(request);
        PageResult<InventoryItemDTO> dtoResult = result.map(this::convertToDTO);
        
        return ResponseEntity.ok(dtoResult);
    }
    
    /**
     * 获取单个库存项详情
     */
    @GetMapping("/items/{warehouseId}/{skuId}")
    public ResponseEntity<InventoryItemDTO> getInventoryItem(
            @PathVariable String warehouseId,
            @PathVariable String skuId) {
        
        Optional<InventoryItem> inventory = inventoryService.getInventory(skuId, warehouseId);
        
        return inventory
                .map(item -> ResponseEntity.ok(convertToDTO(item)))
                .orElse(ResponseEntity.notFound().build());
    }
    
    /**
     * 检查库存可用性
     */
    @GetMapping("/availability")
    public ResponseEntity<InventoryAvailabilityDTO> checkAvailability(
            @RequestParam String skuId,
            @RequestParam String warehouseId,
            @RequestParam int quantity) {
        
        boolean available = inventoryService.checkAvailability(skuId, warehouseId, quantity);
        InventoryItem inventory = inventoryService.getInventory(skuId, warehouseId)
                .orElseThrow(() -> new InventoryNotFoundException("库存不存在"));
        
        InventoryAvailabilityDTO dto = new InventoryAvailabilityDTO(
            skuId,
            warehouseId,
            quantity,
            available,
            inventory.getAvailableQuantity(),
            inventory.getReservedQuantity()
        );
        
        return ResponseEntity.ok(dto);
    }
    
    /**
     * 预留库存
     */
    @PostMapping("/reserve")
    public ResponseEntity<InventoryReservationDTO> reserveInventory(
            @Valid @RequestBody InventoryReservationRequest request) {
        
        try {
            String reservationId = inventoryService.reserveInventory(
                request.getSkuId(),
                request.getWarehouseId(),
                request.getQuantity(),
                request.getReservationReason(),
                request.getExpiryTime()
            );
            
            InventoryReservationDTO dto = new InventoryReservationDTO(
                reservationId,
                request.getSkuId(),
                request.getWarehouseId(),
                request.getQuantity(),
                LocalDateTime.now(),
                request.getExpiryTime(),
                "RESERVED"
            );
            
            return ResponseEntity.ok(dto);
            
        } catch (InsufficientInventoryException e) {
            return ResponseEntity.badRequest()
                    .body(new InventoryReservationDTO(null, request.getSkuId(), 
                          request.getWarehouseId(), request.getQuantity(), 
                          LocalDateTime.now(), null, "FAILED: " + e.getMessage()));
        }
    }
    
    /**
     * 释放预留库存
     */
    @PostMapping("/release")
    public ResponseEntity<Void> releaseReservation(
            @Valid @RequestBody InventoryReleaseRequest request) {
        
        inventoryService.releaseReservation(
            request.getSkuId(),
            request.getWarehouseId(),
            request.getQuantity(),
            request.getReleaseReason()
        );
        
        return ResponseEntity.ok().build();
    }
    
    /**
     * 分配库存
     */
    @PostMapping("/allocate")
    public ResponseEntity<InventoryAllocationDTO> allocateInventory(
            @Valid @RequestBody InventoryAllocationRequest request) {
        
        try {
            String allocationId = inventoryService.allocateInventory(
                request.getSkuId(),
                request.getWarehouseId(),
                request.getQuantity(),
                request.getOrderId(),
                request.getAllocationReason()
            );
            
            InventoryAllocationDTO dto = new InventoryAllocationDTO(
                allocationId,
                request.getSkuId(),
                request.getWarehouseId(),
                request.getQuantity(),
                request.getOrderId(),
                LocalDateTime.now(),
                "ALLOCATED"
            );
            
            return ResponseEntity.ok(dto);
            
        } catch (InsufficientInventoryException e) {
            return ResponseEntity.badRequest()
                    .body(new InventoryAllocationDTO(null, request.getSkuId(),
                          request.getWarehouseId(), request.getQuantity(),
                          request.getOrderId(), LocalDateTime.now(),
                          "FAILED: " + e.getMessage()));
        }
    }
    
    /**
     * 库存出库
     */
    @PostMapping("/outbound")
    public ResponseEntity<InventoryTransactionDTO> outboundInventory(
            @Valid @RequestBody InventoryOutboundRequest request) {
        
        try {
            String transactionId = inventoryService.outbound(
                request.getSkuId(),
                request.getWarehouseId(),
                request.getQuantity(),
                request.getUnitCost(),
                request.getDocumentId(),
                request.getDocumentType(),
                request.getReason()
            );
            
            InventoryTransactionDTO dto = new InventoryTransactionDTO(
                transactionId,
                request.getSkuId(),
                request.getWarehouseId(),
                TransactionType.OUTBOUND,
                request.getQuantity(),
                request.getUnitCost(),
                LocalDateTime.now(),
                "COMPLETED"
            );
            
            return ResponseEntity.ok(dto);
            
        } catch (InsufficientInventoryException e) {
            return ResponseEntity.badRequest()
                    .body(new InventoryTransactionDTO(null, request.getSkuId(),
                          request.getWarehouseId(), TransactionType.OUTBOUND,
                          request.getQuantity(), request.getUnitCost(),
                          LocalDateTime.now(), "FAILED: " + e.getMessage()));
        }
    }
    
    /**
     * 库存入库
     */
    @PostMapping("/inbound")
    public ResponseEntity<InventoryTransactionDTO> inboundInventory(
            @Valid @RequestBody InventoryInboundRequest request) {
        
        String transactionId = inventoryService.inbound(
            request.getSkuId(),
            request.getWarehouseId(),
            request.getQuantity(),
            request.getUnitCost(),
            request.getDocumentId(),
            request.getDocumentType(),
            request.getBatchNumber(),
            request.getExpiryDate(),
            request.getSupplierId(),
            request.getReason()
        );
        
        InventoryTransactionDTO dto = new InventoryTransactionDTO(
            transactionId,
            request.getSkuId(),
            request.getWarehouseId(),
            TransactionType.INBOUND,
            request.getQuantity(),
            request.getUnitCost(),
            LocalDateTime.now(),
            "COMPLETED"
        );
        
        return ResponseEntity.ok(dto);
    }
    
    /**
     * 库存调整
     */
    @PostMapping("/adjust")
    public ResponseEntity<InventoryTransactionDTO> adjustInventory(
            @Valid @RequestBody InventoryAdjustmentRequest request) {
        
        String transactionId = inventoryService.adjustInventory(
            request.getSkuId(),
            request.getWarehouseId(),
            request.getQuantityChange(),
            request.getUnitCost(),
            request.getReason()
        );
        
        InventoryTransactionDTO dto = new InventoryTransactionDTO(
            transactionId,
            request.getSkuId(),
            request.getWarehouseId(),
            TransactionType.ADJUSTMENT,
            request.getQuantityChange(),
            request.getUnitCost(),
            LocalDateTime.now(),
            "COMPLETED"
        );
        
        return ResponseEntity.ok(dto);
    }
    
    /**
     * 库存转移
     */
    @PostMapping("/transfer")
    public ResponseEntity<InventoryTransferDTO> transferInventory(
            @Valid @RequestBody InventoryTransferRequest request) {
        
        try {
            String transferId = inventoryService.transferInventory(
                request.getSkuId(),
                request.getFromWarehouseId(),
                request.getToWarehouseId(),
                request.getQuantity(),
                request.getReason()
            );
            
            InventoryTransferDTO dto = new InventoryTransferDTO(
                transferId,
                request.getSkuId(),
                request.getFromWarehouseId(),
                request.getToWarehouseId(),
                request.getQuantity(),
                LocalDateTime.now(),
                "COMPLETED"
            );
            
            return ResponseEntity.ok(dto);
            
        } catch (InsufficientInventoryException e) {
            return ResponseEntity.badRequest()
                    .body(new InventoryTransferDTO(null, request.getSkuId(),
                          request.getFromWarehouseId(), request.getToWarehouseId(),
                          request.getQuantity(), LocalDateTime.now(),
                          "FAILED: " + e.getMessage()));
        }
    }
    
    /**
     * 获取库存事务历史
     */
    @GetMapping("/transactions")
    public ResponseEntity<PageResult<InventoryTransactionDTO>> getTransactionHistory(
            @RequestParam(required = false) String skuId,
            @RequestParam(required = false) String warehouseId,
            @RequestParam(required = false) TransactionType transactionType,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        
        PageResult<InventoryTransaction> result = inventoryService.getTransactionHistory(
            skuId, warehouseId, transactionType, startTime, endTime, page, size
        );
        
        PageResult<InventoryTransactionDTO> dtoResult = result.map(this::convertTransactionToDTO);
        
        return ResponseEntity.ok(dtoResult);
    }
    
    /**
     * 获取库存概览统计
     */
    @GetMapping("/stats/overview")
    public ResponseEntity<InventoryOverviewStatsDTO> getInventoryOverview(
            @RequestParam String warehouseId) {
        
        InventoryOverviewStats stats = analyticsService.getInventoryOverview(warehouseId);
        InventoryOverviewStatsDTO dto = convertOverviewStatsToDTO(stats);
        
        return ResponseEntity.ok(dto);
    }
    
    /**
     * 获取库存周转分析
     */
    @GetMapping("/analytics/turnover")
    public ResponseEntity<InventoryTurnoverAnalysisDTO> getTurnoverAnalysis(
            @RequestParam String warehouseId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
        
        InventoryTurnoverAnalysis analysis = analyticsService.getInventoryTurnoverAnalysis(
            warehouseId, startDate, endDate
        );
        
        InventoryTurnoverAnalysisDTO dto = convertTurnoverAnalysisToDTO(analysis);
        
        return ResponseEntity.ok(dto);
    }
    
    /**
     * 获取ABC分析
     */
    @GetMapping("/analytics/abc")
    public ResponseEntity<ABCAnalysisResultDTO> getABCAnalysis(
            @RequestParam String warehouseId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
        
        ABCAnalysisResult analysis = analyticsService.getABCAnalysis(warehouseId, startDate, endDate);
        ABCAnalysisResultDTO dto = convertABCAnalysisToDTO(analysis);
        
        return ResponseEntity.ok(dto);
    }
    
    /**
     * 获取库存异常分析
     */
    @GetMapping("/analytics/anomalies")
    public ResponseEntity<InventoryAnomalyAnalysisDTO> getAnomalyAnalysis(
            @RequestParam String warehouseId) {
        
        InventoryAnomalyAnalysis analysis = analyticsService.getInventoryAnomalyAnalysis(warehouseId);
        InventoryAnomalyAnalysisDTO dto = convertAnomalyAnalysisToDTO(analysis);
        
        return ResponseEntity.ok(dto);
    }
    
    /**
     * 获取补货建议
     */
    @GetMapping("/replenishment/recommendations")
    public ResponseEntity<ReplenishmentAnalysisResultDTO> getReplenishmentRecommendations(
            @RequestParam String warehouseId) {
        
        ReplenishmentAnalysisResult result = replenishmentService.analyzeWarehouseReplenishment(warehouseId);
        ReplenishmentAnalysisResultDTO dto = convertReplenishmentResultToDTO(result);
        
        return ResponseEntity.ok(dto);
    }
    
    /**
     * 获取库存预警
     */
    @GetMapping("/alerts")
    public ResponseEntity<List<InventoryAlertDTO>> getInventoryAlerts(
            @RequestParam String warehouseId,
            @RequestParam(required = false) AlertType alertType,
            @RequestParam(required = false) AlertLevel alertLevel) {
        
        List<InventoryAlert> alerts = alertService.checkInventoryAlerts(warehouseId, alertType, alertLevel);
        List<InventoryAlertDTO> dtoList = alerts.stream()
                .map(this::convertAlertToDTO)
                .collect(Collectors.toList());
        
        return ResponseEntity.ok(dtoList);
    }
    
    // DTO转换方法
    private InventoryItemDTO convertToDTO(InventoryItem item) {
        return new InventoryItemDTO(
            item.getSkuId(),
            item.getWarehouseId(),
            item.getLocationId(),
            item.getAvailableQuantity(),
            item.getReservedQuantity(),
            item.getAllocatedQuantity(),
            item.getInTransitQuantity(),
            item.getDamagedQuantity(),
            item.getTotalQuantity(),
            item.getSafetyStock(),
            item.getReorderPoint(),
            item.getMaxStock(),
            item.getUnitCost(),
            item.getLastUpdated(),
            item.getVersion(),
            item.getStatus(),
            item.getBatchNumber(),
            item.getExpiryDate(),
            item.getSupplierId()
        );
    }
    
    private InventoryTransactionDTO convertTransactionToDTO(InventoryTransaction transaction) {
        return new InventoryTransactionDTO(
            transaction.getTransactionId(),
            transaction.getSkuId(),
            transaction.getWarehouseId(),
            transaction.getTransactionType(),
            transaction.getQuantityChange(),
            transaction.getUnitCost(),
            transaction.getTransactionTime(),
            transaction.getTransactionStatus().name()
        );
    }
    
    private InventoryAlertDTO convertAlertToDTO(InventoryAlert alert) {
        return new InventoryAlertDTO(
            alert.getAlertId(),
            alert.getSkuId(),
            alert.getWarehouseId(),
            alert.getAlertType(),
            alert.getAlertLevel(),
            alert.getMessage(),
            alert.getCurrentQuantity(),
            alert.getThresholdQuantity(),
            alert.getCreatedTime(),
            alert.isResolved()
        );
    }
    
    // 其他DTO转换方法...
    private InventoryOverviewStatsDTO convertOverviewStatsToDTO(InventoryOverviewStats stats) {
        // 实现转换逻辑
        return new InventoryOverviewStatsDTO(/* 参数 */);
    }
    
    private InventoryTurnoverAnalysisDTO convertTurnoverAnalysisToDTO(InventoryTurnoverAnalysis analysis) {
        // 实现转换逻辑
        return new InventoryTurnoverAnalysisDTO(/* 参数 */);
    }
    
    private ABCAnalysisResultDTO convertABCAnalysisToDTO(ABCAnalysisResult analysis) {
        // 实现转换逻辑
        return new ABCAnalysisResultDTO(/* 参数 */);
    }
    
    private InventoryAnomalyAnalysisDTO convertAnomalyAnalysisToDTO(InventoryAnomalyAnalysis analysis) {
        // 实现转换逻辑
        return new InventoryAnomalyAnalysisDTO(/* 参数 */);
    }
    
    private ReplenishmentAnalysisResultDTO convertReplenishmentResultToDTO(ReplenishmentAnalysisResult result) {
        // 实现转换逻辑
        return new ReplenishmentAnalysisResultDTO(/* 参数 */);
    }
}

/**
 * 全局异常处理器
 */
@ControllerAdvice
public class InventoryExceptionHandler {
    
    @ExceptionHandler(InventoryNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleInventoryNotFound(InventoryNotFoundException e) {
        ErrorResponse error = new ErrorResponse(
            "INVENTORY_NOT_FOUND",
            e.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    @ExceptionHandler(InsufficientInventoryException.class)
    public ResponseEntity<ErrorResponse> handleInsufficientInventory(InsufficientInventoryException e) {
        ErrorResponse error = new ErrorResponse(
            "INSUFFICIENT_INVENTORY",
            e.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
    
    @ExceptionHandler(InventoryLockException.class)
    public ResponseEntity<ErrorResponse> handleInventoryLock(InventoryLockException e) {
        ErrorResponse error = new ErrorResponse(
            "INVENTORY_LOCK_FAILED",
            e.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
    }
    
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ErrorResponse> handleValidation(ValidationException e) {
        ErrorResponse error = new ErrorResponse(
            "VALIDATION_ERROR",
            e.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneral(Exception e) {
        ErrorResponse error = new ErrorResponse(
            "INTERNAL_ERROR",
            "系统内部错误,请稍后重试",
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

/**
 * 错误响应
 */
public class ErrorResponse {
    private final String code;
    private final String message;
    private final LocalDateTime timestamp;
    
    public ErrorResponse(String code, String message, LocalDateTime timestamp) {
        this.code = code;
        this.message = message;
        this.timestamp = timestamp;
    }
    
    // getters
    public String getCode() { return code; }
    public String getMessage() { return message; }
    public LocalDateTime getTimestamp() { return timestamp; }
}

10.8 性能优化策略

中央库存系统作为高并发系统,需要采用多种性能优化策略:

10.8.1 缓存策略

/**
 * 库存缓存服务
 */
@Service
public class InventoryCacheService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final InventoryRepository inventoryRepository;
    
    private static final String INVENTORY_CACHE_PREFIX = "inventory:";
    private static final String AVAILABILITY_CACHE_PREFIX = "availability:";
    private static final int CACHE_TTL_SECONDS = 300; // 5分钟
    
    public InventoryCacheService(RedisTemplate<String, Object> redisTemplate,
                               InventoryRepository inventoryRepository) {
        this.redisTemplate = redisTemplate;
        this.inventoryRepository = inventoryRepository;
    }
    
    /**
     * 获取缓存的库存信息
     */
    public Optional<InventoryItem> getCachedInventory(String skuId, String warehouseId) {
        String key = INVENTORY_CACHE_PREFIX + skuId + ":" + warehouseId;
        InventoryItem cached = (InventoryItem) redisTemplate.opsForValue().get(key);
        return Optional.ofNullable(cached);
    }
    
    /**
     * 缓存库存信息
     */
    public void cacheInventory(InventoryItem inventory) {
        String key = INVENTORY_CACHE_PREFIX + inventory.getSkuId() + ":" + inventory.getWarehouseId();
        redisTemplate.opsForValue().set(key, inventory, CACHE_TTL_SECONDS, TimeUnit.SECONDS);
    }
    
    /**
     * 删除库存缓存
     */
    public void evictInventoryCache(String skuId, String warehouseId) {
        String key = INVENTORY_CACHE_PREFIX + skuId + ":" + warehouseId;
        redisTemplate.delete(key);
    }
    
    /**
     * 批量预热库存缓存
     */
    @Async
    public void warmUpInventoryCache(String warehouseId) {
        List<InventoryItem> inventories = inventoryRepository.findByWarehouseId(warehouseId);
        
        for (InventoryItem inventory : inventories) {
            cacheInventory(inventory);
        }
    }
    
    /**
     * 缓存库存可用性检查结果
     */
    public void cacheAvailabilityCheck(String skuId, String warehouseId, int quantity, boolean available) {
        String key = AVAILABILITY_CACHE_PREFIX + skuId + ":" + warehouseId + ":" + quantity;
        redisTemplate.opsForValue().set(key, available, 60, TimeUnit.SECONDS); // 1分钟缓存
    }
    
    /**
     * 获取缓存的可用性检查结果
     */
    public Optional<Boolean> getCachedAvailabilityCheck(String skuId, String warehouseId, int quantity) {
        String key = AVAILABILITY_CACHE_PREFIX + skuId + ":" + warehouseId + ":" + quantity;
        Boolean cached = (Boolean) redisTemplate.opsForValue().get(key);
        return Optional.ofNullable(cached);
    }
}

10.8.2 数据库优化

-- 库存表索引优化
CREATE INDEX idx_inventory_warehouse_sku ON inventory_items(warehouse_id, sku_id);
CREATE INDEX idx_inventory_status ON inventory_items(status);
CREATE INDEX idx_inventory_updated ON inventory_items(last_updated);
CREATE INDEX idx_inventory_safety_stock ON inventory_items(available_quantity, safety_stock);

-- 事务表索引优化
CREATE INDEX idx_transaction_sku_warehouse ON inventory_transactions(sku_id, warehouse_id);
CREATE INDEX idx_transaction_time ON inventory_transactions(transaction_time);
CREATE INDEX idx_transaction_type ON inventory_transactions(transaction_type);
CREATE INDEX idx_transaction_document ON inventory_transactions(document_id, document_type);

-- 分区策略(按时间分区)
CREATE TABLE inventory_transactions_2024_01 PARTITION OF inventory_transactions
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

CREATE TABLE inventory_transactions_2024_02 PARTITION OF inventory_transactions
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');

-- 库存汇总视图
CREATE MATERIALIZED VIEW inventory_summary AS
SELECT 
    warehouse_id,
    COUNT(*) as total_skus,
    SUM(total_quantity) as total_quantity,
    SUM(available_quantity) as available_quantity,
    SUM(reserved_quantity) as reserved_quantity,
    SUM(unit_cost * total_quantity) as total_value,
    COUNT(CASE WHEN available_quantity <= COALESCE(safety_stock, 10) THEN 1 END) as low_stock_count,
    COUNT(CASE WHEN available_quantity <= 0 THEN 1 END) as out_of_stock_count
FROM inventory_items
WHERE status = 'ACTIVE'
GROUP BY warehouse_id;

-- 定期刷新汇总视图
CREATE OR REPLACE FUNCTION refresh_inventory_summary()
RETURNS void AS $$
BEGIN
    REFRESH MATERIALIZED VIEW inventory_summary;
END;
$$ LANGUAGE plpgsql;

-- 创建定时任务刷新视图
SELECT cron.schedule('refresh-inventory-summary', '*/5 * * * *', 'SELECT refresh_inventory_summary();');

10.8.3 读写分离

/**
 * 读写分离配置
 */
@Configuration
public class DatabaseConfig {
    
    @Bean
    @Primary
    public DataSource routingDataSource() {
        RoutingDataSource routingDataSource = new RoutingDataSource();
        
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("write", writeDataSource());
        dataSourceMap.put("read", readDataSource());
        
        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(writeDataSource());
        
        return routingDataSource;
    }
    
    @Bean
    public DataSource writeDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://write-db:5432/inventory");
        config.setUsername("inventory_user");
        config.setPassword("password");
        config.setMaximumPoolSize(20);
        return new HikariDataSource(config);
    }
    
    @Bean
    public DataSource readDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://read-db:5432/inventory");
        config.setUsername("inventory_user");
        config.setPassword("password");
        config.setMaximumPoolSize(30);
        config.setReadOnly(true);
        return new HikariDataSource(config);
    }
}

/**
 * 动态数据源路由
 */
public class RoutingDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDataSourceType();
    }
}

/**
 * 数据源上下文
 */
public class DatabaseContextHolder {
    
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }
    
    public static String getDataSourceType() {
        return contextHolder.get();
    }
    
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

/**
 * 读写分离注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
}

/**
 * 读写分离切面
 */
@Aspect
@Component
public class ReadOnlyAspect {
    
    @Before("@annotation(readOnly)")
    public void setReadDataSourceType(ReadOnly readOnly) {
        DatabaseContextHolder.setDataSourceType("read");
    }
    
    @Before("@within(readOnly) && !@annotation(org.springframework.transaction.annotation.Transactional)")
    public void setReadDataSourceTypeForClass(ReadOnly readOnly) {
        DatabaseContextHolder.setDataSourceType("read");
    }
    
    @After("@annotation(readOnly) || @within(readOnly)")
    public void resetDataSourceType() {
        DatabaseContextHolder.clearDataSourceType();
    }
}

10.9 本章总结

本章详细介绍了SaaS平台中央库存系统的设计与实现,涵盖了以下核心内容:

10.9.1 系统架构设计

中央库存系统采用了分层架构设计,包括:

  • 数据层:基于关系型数据库的库存数据模型,支持多仓库、多SKU的复杂库存管理
  • 服务层:核心业务逻辑实现,包括库存查询、预留、分配、出入库等操作
  • 缓存层:基于Redis的分布式缓存,提升系统性能和响应速度
  • API层:RESTful API接口,支持各种库存操作和查询需求

10.9.2 核心功能实现

系统实现了完整的库存管理功能:

  1. 基础库存管理

    • 多维度库存数据模型(可用、预留、分配、在途、损坏等)
    • 完整的库存事务记录和审计跟踪
    • 灵活的库存状态管理和生命周期控制
  2. 库存操作功能

    • 库存查询和可用性检查
    • 库存预留和释放机制
    • 库存分配和出入库操作
    • 库存调整和转移功能
  3. 数据一致性保证

    • 基于Redis的分布式锁机制
    • 事件驱动的库存同步
    • 库存数据一致性检查和修复
  4. 智能预警和补货

    • 多层次库存预警系统
    • 基于历史数据的智能补货策略
    • 经济订货量(EOQ)和安全库存计算
  5. 统计分析功能

    • 库存概览和实时统计
    • 库存周转率分析
    • ABC分析和异常检测

10.9.3 技术特点

系统在技术实现上具有以下特点:

  1. 高并发处理

    • 分布式锁保证并发安全
    • 缓存策略提升访问性能
    • 读写分离优化数据库负载
  2. 数据一致性

    • 事务管理确保操作原子性
    • 事件驱动保证数据同步
    • 定期一致性检查和修复
  3. 可扩展性

    • 微服务架构支持水平扩展
    • 数据库分区支持大数据量
    • 插件化设计支持功能扩展
  4. 监控和运维

    • 完整的日志记录和审计
    • 性能监控和预警机制
    • 自动化运维和故障恢复

10.9.4 实践建议

在实际项目中实施中央库存系统时,建议:

  1. 分阶段实施

    • 先实现基础库存管理功能
    • 逐步增加智能预警和分析功能
    • 最后优化性能和扩展功能
  2. 数据质量管理

    • 建立完善的数据校验机制
    • 定期进行数据一致性检查
    • 制定数据清理和修复流程
  3. 性能优化

    • 合理设计缓存策略
    • 优化数据库查询和索引
    • 监控系统性能指标
  4. 业务集成

    • 与订单系统紧密集成
    • 支持多渠道库存共享
    • 提供灵活的API接口

10.9.5 发展趋势

中央库存系统的发展趋势包括:

  1. 智能化升级

    • AI驱动的需求预测
    • 机器学习优化补货策略
    • 智能库存配置建议
  2. 实时化处理

    • 流式数据处理
    • 实时库存同步
    • 即时预警和响应
  3. 云原生架构

    • 容器化部署
    • 服务网格管理
    • 弹性伸缩能力

中央库存系统作为SaaS平台的核心组件,其设计和实现质量直接影响整个平台的稳定性和性能。通过本章的学习,读者应该能够理解和掌握构建高可用、高性能库存管理系统的关键技术和最佳实践。

10.2.2 库存事务记录

为了确保库存数据的可追溯性和审计能力,系统需要记录所有的库存变更事务:

/**
 * 库存事务记录
 */
@Entity
@Table(name = "inventory_transaction")
public class InventoryTransaction extends BaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "transaction_id", unique = true, nullable = false)
    private String transactionId;            // 事务ID
    
    @Column(name = "sku_id", nullable = false)
    private String skuId;                    // SKU标识
    
    @Column(name = "warehouse_id", nullable = false)
    private String warehouseId;              // 仓库标识
    
    @Column(name = "location_id")
    private String locationId;               // 库位标识
    
    @Enumerated(EnumType.STRING)
    @Column(name = "transaction_type", nullable = false)
    private TransactionType transactionType; // 事务类型
    
    @Column(name = "quantity_change", nullable = false)
    private Integer quantityChange;          // 数量变更
    
    @Column(name = "quantity_before")
    private Integer quantityBefore;          // 变更前数量
    
    @Column(name = "quantity_after")
    private Integer quantityAfter;           // 变更后数量
    
    @Column(name = "unit_cost")
    private BigDecimal unitCost;             // 单位成本
    
    @Column(name = "total_cost")
    private BigDecimal totalCost;            // 总成本
    
    @Column(name = "reference_id")
    private String referenceId;              // 关联单据ID
    
    @Column(name = "reference_type")
    private String referenceType;            // 关联单据类型
    
    @Column(name = "batch_number")
    private String batchNumber;              // 批次号
    
    @Column(name = "supplier_id")
    private String supplierId;               // 供应商ID
    
    @Column(name = "customer_id")
    private String customerId;               // 客户ID
    
    @Column(name = "reason")
    private String reason;                   // 变更原因
    
    @Column(name = "notes")
    private String notes;                    // 备注
    
    @Column(name = "transaction_time", nullable = false)
    private LocalDateTime transactionTime;   // 事务时间
    
    @Column(name = "processed_by")
    private String processedBy;              // 处理人
    
    @Enumerated(EnumType.STRING)
    @Column(name = "status")
    private TransactionStatus status;        // 事务状态
    
    // 构造函数
    public InventoryTransaction() {
        this.transactionId = generateTransactionId();
        this.transactionTime = LocalDateTime.now();
        this.status = TransactionStatus.PENDING;
    }
    
    public InventoryTransaction(String skuId, String warehouseId, 
                              TransactionType transactionType, Integer quantityChange) {
        this();
        this.skuId = skuId;
        this.warehouseId = warehouseId;
        this.transactionType = transactionType;
        this.quantityChange = quantityChange;
    }
    
    /**
     * 生成事务ID
     */
    private String generateTransactionId() {
        return "TXN" + System.currentTimeMillis() + 
               String.format("%04d", new Random().nextInt(10000));
    }
    
    /**
     * 计算总成本
     */
    public void calculateTotalCost() {
        if (unitCost != null && quantityChange != null) {
            this.totalCost = unitCost.multiply(new BigDecimal(Math.abs(quantityChange)));
        }
    }
    
    /**
     * 标记事务完成
     */
    public void markCompleted() {
        this.status = TransactionStatus.COMPLETED;
    }
    
    /**
     * 标记事务失败
     */
    public void markFailed(String reason) {
        this.status = TransactionStatus.FAILED;
        this.reason = reason;
    }
    
    // getters and setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getTransactionId() { return transactionId; }
    public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
    
    public String getSkuId() { return skuId; }
    public void setSkuId(String skuId) { this.skuId = skuId; }
    
    public String getWarehouseId() { return warehouseId; }
    public void setWarehouseId(String warehouseId) { this.warehouseId = warehouseId; }
    
    public String getLocationId() { return locationId; }
    public void setLocationId(String locationId) { this.locationId = locationId; }
    
    public TransactionType getTransactionType() { return transactionType; }
    public void setTransactionType(TransactionType transactionType) { this.transactionType = transactionType; }
    
    public Integer getQuantityChange() { return quantityChange; }
    public void setQuantityChange(Integer quantityChange) { 
        this.quantityChange = quantityChange;
        calculateTotalCost();
    }
    
    public Integer getQuantityBefore() { return quantityBefore; }
    public void setQuantityBefore(Integer quantityBefore) { this.quantityBefore = quantityBefore; }
    
    public Integer getQuantityAfter() { return quantityAfter; }
    public void setQuantityAfter(Integer quantityAfter) { this.quantityAfter = quantityAfter; }
    
    public BigDecimal getUnitCost() { return unitCost; }
    public void setUnitCost(BigDecimal unitCost) { 
        this.unitCost = unitCost;
        calculateTotalCost();
    }
    
    public BigDecimal getTotalCost() { return totalCost; }
    public void setTotalCost(BigDecimal totalCost) { this.totalCost = totalCost; }
    
    public String getReferenceId() { return referenceId; }
    public void setReferenceId(String referenceId) { this.referenceId = referenceId; }
    
    public String getReferenceType() { return referenceType; }
    public void setReferenceType(String referenceType) { this.referenceType = referenceType; }
    
    public String getBatchNumber() { return batchNumber; }
    public void setBatchNumber(String batchNumber) { this.batchNumber = batchNumber; }
    
    public String getSupplierId() { return supplierId; }
    public void setSupplierId(String supplierId) { this.supplierId = supplierId; }
    
    public String getCustomerId() { return customerId; }
    public void setCustomerId(String customerId) { this.customerId = customerId; }
    
    public String getReason() { return reason; }
    public void setReason(String reason) { this.reason = reason; }
    
    public String getNotes() { return notes; }
    public void setNotes(String notes) { this.notes = notes; }
    
    public LocalDateTime getTransactionTime() { return transactionTime; }
    public void setTransactionTime(LocalDateTime transactionTime) { this.transactionTime = transactionTime; }
    
    public String getProcessedBy() { return processedBy; }
    public void setProcessedBy(String processedBy) { this.processedBy = processedBy; }
    
    public TransactionStatus getStatus() { return status; }
    public void setStatus(TransactionStatus status) { this.status = status; }
}

/**
 * 事务类型枚举
 */
public enum TransactionType {
    INBOUND("入库", 1),
    OUTBOUND("出库", -1),
    TRANSFER_IN("调拨入库", 1),
    TRANSFER_OUT("调拨出库", -1),
    ADJUSTMENT_INCREASE("盘盈", 1),
    ADJUSTMENT_DECREASE("盘亏", -1),
    RESERVE("预留", 0),
    RELEASE_RESERVE("释放预留", 0),
    ALLOCATE("分配", 0),
    DAMAGE("损坏", -1),
    RETURN("退货", 1),
    CONSUME("消耗", -1);
    
    private final String description;
    private final int quantityMultiplier;  // 数量乘数,用于计算库存变化方向
    
    TransactionType(String description, int quantityMultiplier) {
        this.description = description;
        this.quantityMultiplier = quantityMultiplier;
    }
    
    public String getDescription() {
        return description;
    }
    
    public int getQuantityMultiplier() {
        return quantityMultiplier;
    }
    
    /**
     * 是否为入库类型
     */
    public boolean isInbound() {
        return quantityMultiplier > 0;
    }
    
    /**
     * 是否为出库类型
     */
    public boolean isOutbound() {
        return quantityMultiplier < 0;
    }
    
    /**
     * 是否为状态变更类型
     */
    public boolean isStatusChange() {
        return quantityMultiplier == 0;
    }
}

/**
 * 事务状态枚举
 */
public enum TransactionStatus {
    PENDING("待处理"),
    PROCESSING("处理中"),
    COMPLETED("已完成"),
    FAILED("失败"),
    CANCELLED("已取消"),
    REVERSED("已冲销");
    
    private final String description;
    
    TransactionStatus(String description) {
        this.description = description;
    }
    
    public String getDescription() {
        return description;
    }
}

10.3 库存服务核心功能

中央库存系统的核心功能包括库存查询、库存更新、库存预留、库存分配等。这些功能需要保证高性能、高并发和数据一致性。

10.3.1 库存服务接口设计

/**
 * 库存服务接口
 */
public interface InventoryService {
    
    /**
     * 查询库存信息
     */
    InventoryItem getInventory(String skuId, String warehouseId);
    
    /**
     * 批量查询库存信息
     */
    List<InventoryItem> getInventories(List<String> skuIds, String warehouseId);
    
    /**
     * 查询SKU在所有仓库的库存
     */
    List<InventoryItem> getInventoriesBySku(String skuId);
    
    /**
     * 查询仓库所有库存
     */
    List<InventoryItem> getInventoriesByWarehouse(String warehouseId);
    
    /**
     * 检查库存可用性
     */
    boolean checkAvailability(String skuId, String warehouseId, Integer quantity);
    
    /**
     * 批量检查库存可用性
     */
    Map<String, Boolean> checkAvailabilities(Map<String, Integer> skuQuantities, String warehouseId);
    
    /**
     * 预留库存
     */
    ReservationResult reserveInventory(InventoryReservationRequest request);
    
    /**
     * 批量预留库存
     */
    List<ReservationResult> reserveInventories(List<InventoryReservationRequest> requests);
    
    /**
     * 释放预留库存
     */
    boolean releaseReservation(String reservationId);
    
    /**
     * 分配库存
     */
    AllocationResult allocateInventory(InventoryAllocationRequest request);
    
    /**
     * 批量分配库存
     */
    List<AllocationResult> allocateInventories(List<InventoryAllocationRequest> requests);
    
    /**
     * 出库
     */
    ShipmentResult shipInventory(InventoryShipmentRequest request);
    
    /**
     * 入库
     */
    ReceiptResult receiveInventory(InventoryReceiptRequest request);
    
    /**
     * 库存调整
     */
    AdjustmentResult adjustInventory(InventoryAdjustmentRequest request);
    
    /**
     * 库存转移
     */
    TransferResult transferInventory(InventoryTransferRequest request);
    
    /**
     * 获取库存事务历史
     */
    List<InventoryTransaction> getTransactionHistory(String skuId, String warehouseId, 
                                                   LocalDateTime startTime, LocalDateTime endTime);
    
    /**
     * 获取低库存预警
     */
    List<InventoryAlert> getLowStockAlerts(String warehouseId);
    
    /**
     * 获取库存统计信息
     */
    InventoryStatistics getInventoryStatistics(String warehouseId, LocalDate date);
}

10.3.2 库存服务实现

/**
 * 库存服务实现
 */
@Service
@Transactional
public class InventoryServiceImpl implements InventoryService {
    
    private final InventoryRepository inventoryRepository;
    private final InventoryTransactionRepository transactionRepository;
    private final InventoryLockService lockService;
    private final InventoryEventPublisher eventPublisher;
    private final InventoryValidator validator;
    private final RedisTemplate<String, Object> redisTemplate;
    
    public InventoryServiceImpl(InventoryRepository inventoryRepository,
                              InventoryTransactionRepository transactionRepository,
                              InventoryLockService lockService,
                              InventoryEventPublisher eventPublisher,
                              InventoryValidator validator,
                              RedisTemplate<String, Object> redisTemplate) {
        this.inventoryRepository = inventoryRepository;
        this.transactionRepository = transactionRepository;
        this.lockService = lockService;
        this.eventPublisher = eventPublisher;
        this.validator = validator;
        this.redisTemplate = redisTemplate;
    }
    
    @Override
    @Cacheable(value = "inventory", key = "#skuId + ':' + #warehouseId")
    public InventoryItem getInventory(String skuId, String warehouseId) {
        return inventoryRepository.findBySkuIdAndWarehouseId(skuId, warehouseId)
                .orElse(null);
    }
    
    @Override
    public List<InventoryItem> getInventories(List<String> skuIds, String warehouseId) {
        return inventoryRepository.findBySkuIdInAndWarehouseId(skuIds, warehouseId);
    }
    
    @Override
    public List<InventoryItem> getInventoriesBySku(String skuId) {
        return inventoryRepository.findBySkuId(skuId);
    }
    
    @Override
    public List<InventoryItem> getInventoriesByWarehouse(String warehouseId) {
        return inventoryRepository.findByWarehouseId(warehouseId);
    }
    
    @Override
    public boolean checkAvailability(String skuId, String warehouseId, Integer quantity) {
        InventoryItem inventory = getInventory(skuId, warehouseId);
        return inventory != null && inventory.hasSufficientStock(quantity);
    }
    
    @Override
    public Map<String, Boolean> checkAvailabilities(Map<String, Integer> skuQuantities, String warehouseId) {
        Map<String, Boolean> results = new HashMap<>();
        
        List<String> skuIds = new ArrayList<>(skuQuantities.keySet());
        List<InventoryItem> inventories = getInventories(skuIds, warehouseId);
        
        Map<String, InventoryItem> inventoryMap = inventories.stream()
                .collect(Collectors.toMap(InventoryItem::getSkuId, Function.identity()));
        
        for (Map.Entry<String, Integer> entry : skuQuantities.entrySet()) {
            String skuId = entry.getKey();
            Integer quantity = entry.getValue();
            
            InventoryItem inventory = inventoryMap.get(skuId);
            boolean available = inventory != null && inventory.hasSufficientStock(quantity);
            results.put(skuId, available);
        }
        
        return results;
    }
    
    @Override
    public ReservationResult reserveInventory(InventoryReservationRequest request) {
        String lockKey = generateLockKey(request.getSkuId(), request.getWarehouseId());
        
        return lockService.executeWithLock(lockKey, () -> {
            // 验证请求
            validator.validateReservationRequest(request);
            
            // 获取库存
            InventoryItem inventory = getInventory(request.getSkuId(), request.getWarehouseId());
            if (inventory == null) {
                return ReservationResult.failure("库存不存在");
            }
            
            // 检查库存可用性
            if (!inventory.hasSufficientStock(request.getQuantity())) {
                return ReservationResult.failure("库存不足");
            }
            
            // 预留库存
            boolean success = inventory.reserveStock(request.getQuantity());
            if (!success) {
                return ReservationResult.failure("预留失败");
            }
            
            // 保存库存变更
            inventoryRepository.save(inventory);
            
            // 记录事务
            InventoryTransaction transaction = createTransaction(
                request.getSkuId(), request.getWarehouseId(),
                TransactionType.RESERVE, request.getQuantity(),
                request.getReferenceId(), request.getReferenceType()
            );
            transactionRepository.save(transaction);
            
            // 清除缓存
            evictInventoryCache(request.getSkuId(), request.getWarehouseId());
            
            // 发布事件
            eventPublisher.publishInventoryReserved(inventory, request.getQuantity());
            
            return ReservationResult.success(transaction.getTransactionId());
        });
    }
    
    @Override
    public List<ReservationResult> reserveInventories(List<InventoryReservationRequest> requests) {
        List<ReservationResult> results = new ArrayList<>();
        
        for (InventoryReservationRequest request : requests) {
            try {
                ReservationResult result = reserveInventory(request);
                results.add(result);
                
                // 如果预留失败,回滚之前的预留
                if (!result.isSuccess()) {
                    rollbackReservations(results);
                    break;
                }
            } catch (Exception e) {
                results.add(ReservationResult.failure("预留异常: " + e.getMessage()));
                rollbackReservations(results);
                break;
            }
        }
        
        return results;
    }
    
    @Override
    public boolean releaseReservation(String reservationId) {
        InventoryTransaction transaction = transactionRepository.findByTransactionId(reservationId)
                .orElse(null);
        
        if (transaction == null || transaction.getTransactionType() != TransactionType.RESERVE) {
            return false;
        }
        
        String lockKey = generateLockKey(transaction.getSkuId(), transaction.getWarehouseId());
        
        return lockService.executeWithLock(lockKey, () -> {
            InventoryItem inventory = getInventory(transaction.getSkuId(), transaction.getWarehouseId());
            if (inventory == null) {
                return false;
            }
            
            // 释放预留库存
            inventory.releaseReservedStock(transaction.getQuantityChange());
            inventoryRepository.save(inventory);
            
            // 记录释放事务
            InventoryTransaction releaseTransaction = createTransaction(
                transaction.getSkuId(), transaction.getWarehouseId(),
                TransactionType.RELEASE_RESERVE, transaction.getQuantityChange(),
                transaction.getReferenceId(), transaction.getReferenceType()
            );
            transactionRepository.save(releaseTransaction);
            
            // 清除缓存
            evictInventoryCache(transaction.getSkuId(), transaction.getWarehouseId());
            
            // 发布事件
            eventPublisher.publishReservationReleased(inventory, transaction.getQuantityChange());
            
            return true;
        });
    }
    
    @Override
    public AllocationResult allocateInventory(InventoryAllocationRequest request) {
        String lockKey = generateLockKey(request.getSkuId(), request.getWarehouseId());
        
        return lockService.executeWithLock(lockKey, () -> {
            // 验证请求
            validator.validateAllocationRequest(request);
            
            // 获取库存
            InventoryItem inventory = getInventory(request.getSkuId(), request.getWarehouseId());
            if (inventory == null) {
                return AllocationResult.failure("库存不存在");
            }
            
            // 分配库存
            boolean success = inventory.allocateStock(request.getQuantity());
            if (!success) {
                return AllocationResult.failure("分配失败,预留库存不足");
            }
            
            // 保存库存变更
            inventoryRepository.save(inventory);
            
            // 记录事务
            InventoryTransaction transaction = createTransaction(
                request.getSkuId(), request.getWarehouseId(),
                TransactionType.ALLOCATE, request.getQuantity(),
                request.getReferenceId(), request.getReferenceType()
            );
            transactionRepository.save(transaction);
            
            // 清除缓存
            evictInventoryCache(request.getSkuId(), request.getWarehouseId());
            
            // 发布事件
            eventPublisher.publishInventoryAllocated(inventory, request.getQuantity());
            
            return AllocationResult.success(transaction.getTransactionId());
        });
    }
    
    @Override
    public List<AllocationResult> allocateInventories(List<InventoryAllocationRequest> requests) {
        List<AllocationResult> results = new ArrayList<>();
        
        for (InventoryAllocationRequest request : requests) {
            try {
                AllocationResult result = allocateInventory(request);
                results.add(result);
                
                // 如果分配失败,回滚之前的分配
                if (!result.isSuccess()) {
                    rollbackAllocations(results);
                    break;
                }
            } catch (Exception e) {
                results.add(AllocationResult.failure("分配异常: " + e.getMessage()));
                rollbackAllocations(results);
                break;
            }
        }
        
        return results;
    }
    
    @Override
    public ShipmentResult shipInventory(InventoryShipmentRequest request) {
        String lockKey = generateLockKey(request.getSkuId(), request.getWarehouseId());
        
        return lockService.executeWithLock(lockKey, () -> {
            // 验证请求
            validator.validateShipmentRequest(request);
            
            // 获取库存
            InventoryItem inventory = getInventory(request.getSkuId(), request.getWarehouseId());
            if (inventory == null) {
                return ShipmentResult.failure("库存不存在");
            }
            
            // 出库
            boolean success = inventory.shipStock(request.getQuantity());
            if (!success) {
                return ShipmentResult.failure("出库失败,已分配库存不足");
            }
            
            // 保存库存变更
            inventoryRepository.save(inventory);
            
            // 记录事务
            InventoryTransaction transaction = createTransaction(
                request.getSkuId(), request.getWarehouseId(),
                TransactionType.OUTBOUND, request.getQuantity(),
                request.getReferenceId(), request.getReferenceType()
            );
            transaction.setCustomerId(request.getCustomerId());
            transactionRepository.save(transaction);
            
            // 清除缓存
            evictInventoryCache(request.getSkuId(), request.getWarehouseId());
            
            // 发布事件
            eventPublisher.publishInventoryShipped(inventory, request.getQuantity());
            
            return ShipmentResult.success(transaction.getTransactionId());
        });
    }
    
    @Override
    public ReceiptResult receiveInventory(InventoryReceiptRequest request) {
        String lockKey = generateLockKey(request.getSkuId(), request.getWarehouseId());
        
        return lockService.executeWithLock(lockKey, () -> {
            // 验证请求
            validator.validateReceiptRequest(request);
            
            // 获取或创建库存
            InventoryItem inventory = getInventory(request.getSkuId(), request.getWarehouseId());
            if (inventory == null) {
                inventory = new InventoryItem(request.getSkuId(), request.getWarehouseId());
                inventory.setLocationId(request.getLocationId());
                inventory.setBatchNumber(request.getBatchNumber());
                inventory.setSupplierId(request.getSupplierId());
                inventory.setUnitCost(request.getUnitCost());
            }
            
            // 入库
            inventory.receiveStock(request.getQuantity());
            
            // 保存库存变更
            inventoryRepository.save(inventory);
            
            // 记录事务
            InventoryTransaction transaction = createTransaction(
                request.getSkuId(), request.getWarehouseId(),
                TransactionType.INBOUND, request.getQuantity(),
                request.getReferenceId(), request.getReferenceType()
            );
            transaction.setSupplierId(request.getSupplierId());
            transaction.setBatchNumber(request.getBatchNumber());
            transaction.setUnitCost(request.getUnitCost());
            transactionRepository.save(transaction);
            
            // 清除缓存
            evictInventoryCache(request.getSkuId(), request.getWarehouseId());
            
            // 发布事件
            eventPublisher.publishInventoryReceived(inventory, request.getQuantity());
            
            return ReceiptResult.success(transaction.getTransactionId());
        });
    }
    
    @Override
    public AdjustmentResult adjustInventory(InventoryAdjustmentRequest request) {
        String lockKey = generateLockKey(request.getSkuId(), request.getWarehouseId());
        
        return lockService.executeWithLock(lockKey, () -> {
            // 验证请求
            validator.validateAdjustmentRequest(request);
            
            // 获取库存
            InventoryItem inventory = getInventory(request.getSkuId(), request.getWarehouseId());
            if (inventory == null) {
                return AdjustmentResult.failure("库存不存在");
            }
            
            // 记录调整前数量
            Integer quantityBefore = inventory.getAvailableQuantity();
            
            // 调整库存
            inventory.setAvailableQuantity(request.getNewQuantity());
            
            // 保存库存变更
            inventoryRepository.save(inventory);
            
            // 计算调整数量
            Integer adjustmentQuantity = request.getNewQuantity() - quantityBefore;
            TransactionType transactionType = adjustmentQuantity > 0 ? 
                TransactionType.ADJUSTMENT_INCREASE : TransactionType.ADJUSTMENT_DECREASE;
            
            // 记录事务
            InventoryTransaction transaction = createTransaction(
                request.getSkuId(), request.getWarehouseId(),
                transactionType, Math.abs(adjustmentQuantity),
                request.getReferenceId(), "ADJUSTMENT"
            );
            transaction.setQuantityBefore(quantityBefore);
            transaction.setQuantityAfter(request.getNewQuantity());
            transaction.setReason(request.getReason());
            transactionRepository.save(transaction);
            
            // 清除缓存
            evictInventoryCache(request.getSkuId(), request.getWarehouseId());
            
            // 发布事件
            eventPublisher.publishInventoryAdjusted(inventory, adjustmentQuantity);
            
            return AdjustmentResult.success(transaction.getTransactionId());
        });
    }
    
    @Override
    public TransferResult transferInventory(InventoryTransferRequest request) {
        // 使用分布式锁确保转移操作的原子性
        String lockKey1 = generateLockKey(request.getSkuId(), request.getFromWarehouseId());
        String lockKey2 = generateLockKey(request.getSkuId(), request.getToWarehouseId());
        
        // 按字典序排序锁键,避免死锁
        List<String> lockKeys = Arrays.asList(lockKey1, lockKey2);
        Collections.sort(lockKeys);
        
        return lockService.executeWithLocks(lockKeys, () -> {
            // 验证请求
            validator.validateTransferRequest(request);
            
            // 获取源仓库库存
            InventoryItem fromInventory = getInventory(request.getSkuId(), request.getFromWarehouseId());
            if (fromInventory == null || !fromInventory.hasSufficientStock(request.getQuantity())) {
                return TransferResult.failure("源仓库库存不足");
            }
            
            // 获取或创建目标仓库库存
            InventoryItem toInventory = getInventory(request.getSkuId(), request.getToWarehouseId());
            if (toInventory == null) {
                toInventory = new InventoryItem(request.getSkuId(), request.getToWarehouseId());
            }
            
            // 执行转移
            fromInventory.setAvailableQuantity(fromInventory.getAvailableQuantity() - request.getQuantity());
            toInventory.receiveStock(request.getQuantity());
            
            // 保存库存变更
            inventoryRepository.save(fromInventory);
            inventoryRepository.save(toInventory);
            
            // 记录转出事务
            InventoryTransaction outTransaction = createTransaction(
                request.getSkuId(), request.getFromWarehouseId(),
                TransactionType.TRANSFER_OUT, request.getQuantity(),
                request.getReferenceId(), "TRANSFER"
            );
            transactionRepository.save(outTransaction);
            
            // 记录转入事务
            InventoryTransaction inTransaction = createTransaction(
                request.getSkuId(), request.getToWarehouseId(),
                TransactionType.TRANSFER_IN, request.getQuantity(),
                request.getReferenceId(), "TRANSFER"
            );
            transactionRepository.save(inTransaction);
            
            // 清除缓存
            evictInventoryCache(request.getSkuId(), request.getFromWarehouseId());
            evictInventoryCache(request.getSkuId(), request.getToWarehouseId());
            
            // 发布事件
            eventPublisher.publishInventoryTransferred(fromInventory, toInventory, request.getQuantity());
            
            return TransferResult.success(outTransaction.getTransactionId(), inTransaction.getTransactionId());
        });
    }
    
    @Override
    public List<InventoryTransaction> getTransactionHistory(String skuId, String warehouseId, 
                                                          LocalDateTime startTime, LocalDateTime endTime) {
        return transactionRepository.findBySkuIdAndWarehouseIdAndTransactionTimeBetween(
            skuId, warehouseId, startTime, endTime
        );
    }
    
    @Override
    public List<InventoryAlert> getLowStockAlerts(String warehouseId) {
        List<InventoryItem> lowStockItems = inventoryRepository.findLowStockItems(warehouseId);
        
        return lowStockItems.stream()
                .map(this::createLowStockAlert)
                .collect(Collectors.toList());
    }
    
    @Override
    public InventoryStatistics getInventoryStatistics(String warehouseId, LocalDate date) {
        return inventoryRepository.calculateStatistics(warehouseId, date);
    }
    
    // 私有辅助方法
    
    private String generateLockKey(String skuId, String warehouseId) {
        return "inventory:lock:" + skuId + ":" + warehouseId;
    }
    
    private InventoryTransaction createTransaction(String skuId, String warehouseId,
                                                 TransactionType type, Integer quantity,
                                                 String referenceId, String referenceType) {
        InventoryTransaction transaction = new InventoryTransaction(skuId, warehouseId, type, quantity);
        transaction.setReferenceId(referenceId);
        transaction.setReferenceType(referenceType);
        transaction.setProcessedBy(getCurrentUserId());
        return transaction;
    }
    
    private void evictInventoryCache(String skuId, String warehouseId) {
        String cacheKey = skuId + ":" + warehouseId;
        redisTemplate.delete("inventory::" + cacheKey);
    }
    
    private void rollbackReservations(List<ReservationResult> results) {
        for (ReservationResult result : results) {
            if (result.isSuccess()) {
                releaseReservation(result.getReservationId());
            }
        }
    }
    
    private void rollbackAllocations(List<AllocationResult> results) {
        // 实现分配回滚逻辑
        // 这里需要根据具体业务需求实现
    }
    
    private InventoryAlert createLowStockAlert(InventoryItem inventory) {
        return new InventoryAlert(
            inventory.getSkuId(),
            inventory.getWarehouseId(),
            AlertType.LOW_STOCK,
            "库存不足,当前库存: " + inventory.getAvailableQuantity() + 
            ", 安全库存: " + inventory.getSafetyStock()
        );
    }
    
    private String getCurrentUserId() {
        // 从安全上下文获取当前用户ID
        return "system"; // 简化实现
    }
}

10.4 库存同步与一致性保证

在分布式环境下,确保库存数据的一致性是一个重大挑战。系统需要处理并发访问、网络分区、系统故障等各种情况,同时保证数据的准确性和业务的连续性。

10.4.1 分布式锁实现

/**
 * 基于Redis的分布式锁服务
 */
@Service
public class InventoryLockService {
    
    private final RedisTemplate<String, String> redisTemplate;
    private final RedisScript<Boolean> lockScript;
    private final RedisScript<Boolean> unlockScript;
    
    private static final String LOCK_PREFIX = "inventory:lock:";
    private static final int DEFAULT_LOCK_TIMEOUT = 30; // 30秒
    private static final int DEFAULT_RETRY_TIMES = 3;
    private static final int RETRY_INTERVAL = 100; // 100毫秒
    
    public InventoryLockService(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
        
        // 加锁脚本
        this.lockScript = new DefaultRedisScript<>(
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('expire', KEYS[1], ARGV[2]) " +
            "else " +
            "    return redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) " +
            "end",
            Boolean.class
        );
        
        // 解锁脚本
        this.unlockScript = new DefaultRedisScript<>(
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end",
            Boolean.class
        );
    }
    
    /**
     * 获取锁并执行业务逻辑
     */
    public <T> T executeWithLock(String lockKey, Supplier<T> supplier) {
        return executeWithLock(lockKey, DEFAULT_LOCK_TIMEOUT, supplier);
    }
    
    /**
     * 获取锁并执行业务逻辑(指定超时时间)
     */
    public <T> T executeWithLock(String lockKey, int timeoutSeconds, Supplier<T> supplier) {
        String lockValue = generateLockValue();
        String fullLockKey = LOCK_PREFIX + lockKey;
        
        boolean acquired = false;
        try {
            // 尝试获取锁
            acquired = acquireLock(fullLockKey, lockValue, timeoutSeconds);
            if (!acquired) {
                throw new InventoryLockException("获取锁失败: " + lockKey);
            }
            
            // 执行业务逻辑
            return supplier.get();
            
        } finally {
            if (acquired) {
                releaseLock(fullLockKey, lockValue);
            }
        }
    }
    
    /**
     * 获取多个锁并执行业务逻辑
     */
    public <T> T executeWithLocks(List<String> lockKeys, Supplier<T> supplier) {
        List<String> fullLockKeys = lockKeys.stream()
                .map(key -> LOCK_PREFIX + key)
                .collect(Collectors.toList());
        
        Map<String, String> lockValues = new HashMap<>();
        List<String> acquiredLocks = new ArrayList<>();
        
        try {
            // 按顺序获取所有锁
            for (String fullLockKey : fullLockKeys) {
                String lockValue = generateLockValue();
                boolean acquired = acquireLock(fullLockKey, lockValue, DEFAULT_LOCK_TIMEOUT);
                
                if (!acquired) {
                    throw new InventoryLockException("获取锁失败: " + fullLockKey);
                }
                
                lockValues.put(fullLockKey, lockValue);
                acquiredLocks.add(fullLockKey);
            }
            
            // 执行业务逻辑
            return supplier.get();
            
        } finally {
            // 释放所有已获取的锁
            for (String lockKey : acquiredLocks) {
                releaseLock(lockKey, lockValues.get(lockKey));
            }
        }
    }
    
    /**
     * 获取锁
     */
    private boolean acquireLock(String lockKey, String lockValue, int timeoutSeconds) {
        for (int i = 0; i < DEFAULT_RETRY_TIMES; i++) {
            Boolean result = redisTemplate.execute(lockScript, 
                Collections.singletonList(lockKey), 
                lockValue, 
                String.valueOf(timeoutSeconds)
            );
            
            if (Boolean.TRUE.equals(result)) {
                return true;
            }
            
            // 等待后重试
            try {
                Thread.sleep(RETRY_INTERVAL);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        
        return false;
    }
    
    /**
     * 释放锁
     */
    private void releaseLock(String lockKey, String lockValue) {
        try {
            redisTemplate.execute(unlockScript, 
                Collections.singletonList(lockKey), 
                lockValue
            );
        } catch (Exception e) {
            // 记录日志但不抛出异常
            log.warn("释放锁失败: {}", lockKey, e);
        }
    }
    
    /**
     * 生成锁值
     */
    private String generateLockValue() {
        return Thread.currentThread().getId() + ":" + System.currentTimeMillis();
    }
}

/**
 * 库存锁异常
 */
public class InventoryLockException extends RuntimeException {
    public InventoryLockException(String message) {
        super(message);
    }
    
    public InventoryLockException(String message, Throwable cause) {
        super(message, cause);
    }
}

10.4.2 事件驱动的库存同步

为了确保库存数据在各个系统间的一致性,采用事件驱动架构进行库存同步:

/**
 * 库存事件发布器
 */
@Component
public class InventoryEventPublisher {
    
    private final ApplicationEventPublisher eventPublisher;
    private final MessageProducer messageProducer;
    
    public InventoryEventPublisher(ApplicationEventPublisher eventPublisher,
                                 MessageProducer messageProducer) {
        this.eventPublisher = eventPublisher;
        this.messageProducer = messageProducer;
    }
    
    /**
     * 发布库存预留事件
     */
    public void publishInventoryReserved(InventoryItem inventory, Integer quantity) {
        InventoryReservedEvent event = new InventoryReservedEvent(
            inventory.getSkuId(),
            inventory.getWarehouseId(),
            quantity,
            inventory.getAvailableQuantity()
        );
        
        // 本地事件
        eventPublisher.publishEvent(event);
        
        // 分布式事件
        messageProducer.send("inventory.reserved", event);
    }
    
    /**
     * 发布预留释放事件
     */
    public void publishReservationReleased(InventoryItem inventory, Integer quantity) {
        ReservationReleasedEvent event = new ReservationReleasedEvent(
            inventory.getSkuId(),
            inventory.getWarehouseId(),
            quantity,
            inventory.getAvailableQuantity()
        );
        
        eventPublisher.publishEvent(event);
        messageProducer.send("inventory.reservation.released", event);
    }
    
    /**
     * 发布库存分配事件
     */
    public void publishInventoryAllocated(InventoryItem inventory, Integer quantity) {
        InventoryAllocatedEvent event = new InventoryAllocatedEvent(
            inventory.getSkuId(),
            inventory.getWarehouseId(),
            quantity,
            inventory.getAllocatedQuantity()
        );
        
        eventPublisher.publishEvent(event);
        messageProducer.send("inventory.allocated", event);
    }
    
    /**
     * 发布库存出库事件
     */
    public void publishInventoryShipped(InventoryItem inventory, Integer quantity) {
        InventoryShippedEvent event = new InventoryShippedEvent(
            inventory.getSkuId(),
            inventory.getWarehouseId(),
            quantity,
            inventory.getAvailableQuantity()
        );
        
        eventPublisher.publishEvent(event);
        messageProducer.send("inventory.shipped", event);
    }
    
    /**
     * 发布库存入库事件
     */
    public void publishInventoryReceived(InventoryItem inventory, Integer quantity) {
        InventoryReceivedEvent event = new InventoryReceivedEvent(
            inventory.getSkuId(),
            inventory.getWarehouseId(),
            quantity,
            inventory.getAvailableQuantity()
        );
        
        eventPublisher.publishEvent(event);
        messageProducer.send("inventory.received", event);
    }
    
    /**
     * 发布库存调整事件
     */
    public void publishInventoryAdjusted(InventoryItem inventory, Integer adjustmentQuantity) {
        InventoryAdjustedEvent event = new InventoryAdjustedEvent(
            inventory.getSkuId(),
            inventory.getWarehouseId(),
            adjustmentQuantity,
            inventory.getAvailableQuantity()
        );
        
        eventPublisher.publishEvent(event);
        messageProducer.send("inventory.adjusted", event);
    }
    
    /**
     * 发布库存转移事件
     */
    public void publishInventoryTransferred(InventoryItem fromInventory, 
                                          InventoryItem toInventory, 
                                          Integer quantity) {
        InventoryTransferredEvent event = new InventoryTransferredEvent(
            fromInventory.getSkuId(),
            fromInventory.getWarehouseId(),
            toInventory.getWarehouseId(),
            quantity
        );
        
        eventPublisher.publishEvent(event);
        messageProducer.send("inventory.transferred", event);
    }
}

/**
 * 库存事件基类
 */
public abstract class InventoryEvent {
    private final String skuId;
    private final String warehouseId;
    private final Integer quantity;
    private final LocalDateTime timestamp;
    
    public InventoryEvent(String skuId, String warehouseId, Integer quantity) {
        this.skuId = skuId;
        this.warehouseId = warehouseId;
        this.quantity = quantity;
        this.timestamp = LocalDateTime.now();
    }
    
    // getters
    public String getSkuId() { return skuId; }
    public String getWarehouseId() { return warehouseId; }
    public Integer getQuantity() { return quantity; }
    public LocalDateTime getTimestamp() { return timestamp; }
}

/**
 * 库存预留事件
 */
public class InventoryReservedEvent extends InventoryEvent {
    private final Integer availableQuantity;
    
    public InventoryReservedEvent(String skuId, String warehouseId, 
                                Integer quantity, Integer availableQuantity) {
        super(skuId, warehouseId, quantity);
        this.availableQuantity = availableQuantity;
    }
    
    public Integer getAvailableQuantity() { return availableQuantity; }
}

/**
 * 预留释放事件
 */
public class ReservationReleasedEvent extends InventoryEvent {
    private final Integer availableQuantity;
    
    public ReservationReleasedEvent(String skuId, String warehouseId, 
                                  Integer quantity, Integer availableQuantity) {
        super(skuId, warehouseId, quantity);
        this.availableQuantity = availableQuantity;
    }
    
    public Integer getAvailableQuantity() { return availableQuantity; }
}

/**
 * 库存分配事件
 */
public class InventoryAllocatedEvent extends InventoryEvent {
    private final Integer allocatedQuantity;
    
    public InventoryAllocatedEvent(String skuId, String warehouseId, 
                                 Integer quantity, Integer allocatedQuantity) {
        super(skuId, warehouseId, quantity);
        this.allocatedQuantity = allocatedQuantity;
    }
    
    public Integer getAllocatedQuantity() { return allocatedQuantity; }
}

/**
 * 库存出库事件
 */
public class InventoryShippedEvent extends InventoryEvent {
    private final Integer remainingQuantity;
    
    public InventoryShippedEvent(String skuId, String warehouseId, 
                               Integer quantity, Integer remainingQuantity) {
        super(skuId, warehouseId, quantity);
        this.remainingQuantity = remainingQuantity;
    }
    
    public Integer getRemainingQuantity() { return remainingQuantity; }
}

/**
 * 库存入库事件
 */
public class InventoryReceivedEvent extends InventoryEvent {
    private final Integer totalQuantity;
    
    public InventoryReceivedEvent(String skuId, String warehouseId, 
                                Integer quantity, Integer totalQuantity) {
        super(skuId, warehouseId, quantity);
        this.totalQuantity = totalQuantity;
    }
    
    public Integer getTotalQuantity() { return totalQuantity; }
}

/**
 * 库存调整事件
 */
public class InventoryAdjustedEvent extends InventoryEvent {
    private final Integer currentQuantity;
    
    public InventoryAdjustedEvent(String skuId, String warehouseId, 
                                Integer adjustmentQuantity, Integer currentQuantity) {
        super(skuId, warehouseId, adjustmentQuantity);
        this.currentQuantity = currentQuantity;
    }
    
    public Integer getCurrentQuantity() { return currentQuantity; }
}

/**
 * 库存转移事件
 */
public class InventoryTransferredEvent extends InventoryEvent {
    private final String toWarehouseId;
    
    public InventoryTransferredEvent(String skuId, String fromWarehouseId, 
                                   String toWarehouseId, Integer quantity) {
        super(skuId, fromWarehouseId, quantity);
        this.toWarehouseId = toWarehouseId;
    }
    
    public String getToWarehouseId() { return toWarehouseId; }
}

10.4.3 库存数据一致性检查

定期进行库存数据一致性检查,确保数据的准确性:

/**
 * 库存一致性检查服务
 */
@Service
public class InventoryConsistencyService {
    
    private final InventoryRepository inventoryRepository;
    private final InventoryTransactionRepository transactionRepository;
    private final InventoryAuditService auditService;
    
    public InventoryConsistencyService(InventoryRepository inventoryRepository,
                                     InventoryTransactionRepository transactionRepository,
                                     InventoryAuditService auditService) {
        this.inventoryRepository = inventoryRepository;
        this.transactionRepository = transactionRepository;
        this.auditService = auditService;
    }
    
    /**
     * 执行库存一致性检查
     */
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    public void performConsistencyCheck() {
        log.info("开始执行库存一致性检查");
        
        List<String> warehouses = inventoryRepository.findAllWarehouseIds();
        
        for (String warehouseId : warehouses) {
            checkWarehouseConsistency(warehouseId);
        }
        
        log.info("库存一致性检查完成");
    }
    
    /**
     * 检查单个仓库的一致性
     */
    public ConsistencyCheckResult checkWarehouseConsistency(String warehouseId) {
        List<InventoryItem> inventories = inventoryRepository.findByWarehouseId(warehouseId);
        List<ConsistencyIssue> issues = new ArrayList<>();
        
        for (InventoryItem inventory : inventories) {
            ConsistencyIssue issue = checkInventoryConsistency(inventory);
            if (issue != null) {
                issues.add(issue);
            }
        }
        
        ConsistencyCheckResult result = new ConsistencyCheckResult(warehouseId, issues);
        
        if (!issues.isEmpty()) {
            auditService.recordConsistencyIssues(result);
            notifyConsistencyIssues(result);
        }
        
        return result;
    }
    
    /**
     * 检查单个库存项的一致性
     */
    private ConsistencyIssue checkInventoryConsistency(InventoryItem inventory) {
        // 检查总库存计算是否正确
        Integer calculatedTotal = inventory.getAvailableQuantity() + 
                                inventory.getReservedQuantity() + 
                                inventory.getAllocatedQuantity() + 
                                inventory.getInTransitQuantity() + 
                                inventory.getDamagedQuantity();
        
        if (!calculatedTotal.equals(inventory.getTotalQuantity())) {
            return new ConsistencyIssue(
                inventory.getSkuId(),
                inventory.getWarehouseId(),
                ConsistencyIssueType.TOTAL_QUANTITY_MISMATCH,
                "总库存计算不匹配",
                Map.of(
                    "expected", calculatedTotal,
                    "actual", inventory.getTotalQuantity()
                )
            );
        }
        
        // 检查负库存
        if (inventory.getAvailableQuantity() < 0) {
            return new ConsistencyIssue(
                inventory.getSkuId(),
                inventory.getWarehouseId(),
                ConsistencyIssueType.NEGATIVE_INVENTORY,
                "可用库存为负数",
                Map.of("availableQuantity", inventory.getAvailableQuantity())
            );
        }
        
        // 检查事务记录一致性
        LocalDateTime checkTime = LocalDateTime.now().minusDays(1);
        List<InventoryTransaction> recentTransactions = 
            transactionRepository.findBySkuIdAndWarehouseIdAndTransactionTimeAfter(
                inventory.getSkuId(), inventory.getWarehouseId(), checkTime
            );
        
        Integer transactionSum = recentTransactions.stream()
                .mapToInt(t -> t.getQuantityChange() * t.getTransactionType().getQuantityMultiplier())
                .sum();
        
        // 这里可以添加更复杂的事务一致性检查逻辑
        
        return null; // 没有发现问题
    }
    
    /**
     * 修复库存一致性问题
     */
    public void fixConsistencyIssue(ConsistencyIssue issue) {
        switch (issue.getType()) {
            case TOTAL_QUANTITY_MISMATCH:
                fixTotalQuantityMismatch(issue);
                break;
            case NEGATIVE_INVENTORY:
                fixNegativeInventory(issue);
                break;
            default:
                log.warn("未知的一致性问题类型: {}", issue.getType());
        }
    }
    
    private void fixTotalQuantityMismatch(ConsistencyIssue issue) {
        InventoryItem inventory = inventoryRepository
            .findBySkuIdAndWarehouseId(issue.getSkuId(), issue.getWarehouseId())
            .orElse(null);
        
        if (inventory != null) {
            inventory.updateTotalQuantity();
            inventoryRepository.save(inventory);
            
            auditService.recordConsistencyFix(issue, "修复总库存计算");
        }
    }
    
    private void fixNegativeInventory(ConsistencyIssue issue) {
        // 负库存需要人工处理,这里只记录
        auditService.recordConsistencyIssue(issue, "负库存需要人工处理");
    }
    
    private void notifyConsistencyIssues(ConsistencyCheckResult result) {
        // 发送通知给相关人员
        // 这里可以集成邮件、短信、钉钉等通知方式
    }
}

/**
 * 一致性检查结果
 */
public class ConsistencyCheckResult {
    private final String warehouseId;
    private final List<ConsistencyIssue> issues;
    private final LocalDateTime checkTime;
    
    public ConsistencyCheckResult(String warehouseId, List<ConsistencyIssue> issues) {
        this.warehouseId = warehouseId;
        this.issues = issues;
        this.checkTime = LocalDateTime.now();
    }
    
    public boolean hasIssues() {
        return !issues.isEmpty();
    }
    
    // getters
    public String getWarehouseId() { return warehouseId; }
    public List<ConsistencyIssue> getIssues() { return issues; }
    public LocalDateTime getCheckTime() { return checkTime; }
}

/**
 * 一致性问题
 */
public class ConsistencyIssue {
    private final String skuId;
    private final String warehouseId;
    private final ConsistencyIssueType type;
    private final String description;
    private final Map<String, Object> details;
    private final LocalDateTime discoveredTime;
    
    public ConsistencyIssue(String skuId, String warehouseId, 
                          ConsistencyIssueType type, String description,
                          Map<String, Object> details) {
        this.skuId = skuId;
        this.warehouseId = warehouseId;
        this.type = type;
        this.description = description;
        this.details = details;
        this.discoveredTime = LocalDateTime.now();
    }
    
    // getters
    public String getSkuId() { return skuId; }
    public String getWarehouseId() { return warehouseId; }
    public ConsistencyIssueType getType() { return type; }
    public String getDescription() { return description; }
    public Map<String, Object> getDetails() { return details; }
    public LocalDateTime getDiscoveredTime() { return discoveredTime; }
}

/**
 * 一致性问题类型
 */
public enum ConsistencyIssueType {
    TOTAL_QUANTITY_MISMATCH("总库存计算不匹配"),
    NEGATIVE_INVENTORY("负库存"),
    TRANSACTION_MISMATCH("事务记录不匹配"),
    RESERVED_QUANTITY_OVERFLOW("预留库存溢出"),
    ALLOCATED_QUANTITY_OVERFLOW("分配库存溢出");
    
    private final String description;
    
    ConsistencyIssueType(String description) {
        this.description = description;
    }
    
    public String getDescription() {
        return description;
    }
}

10.5 库存预警与补货策略

智能的库存预警和补货策略是中央库存系统的重要功能,它能够帮助企业优化库存水平,减少缺货风险,提高资金周转率。

10.5.1 库存预警系统

/**
 * 库存预警服务
 */
@Service
public class InventoryAlertService {
    
    private final InventoryRepository inventoryRepository;
    private final AlertConfigRepository alertConfigRepository;
    private final NotificationService notificationService;
    private final InventoryForecastService forecastService;
    
    public InventoryAlertService(InventoryRepository inventoryRepository,
                               AlertConfigRepository alertConfigRepository,
                               NotificationService notificationService,
                               InventoryForecastService forecastService) {
        this.inventoryRepository = inventoryRepository;
        this.alertConfigRepository = alertConfigRepository;
        this.notificationService = notificationService;
        this.forecastService = forecastService;
    }
    
    /**
     * 执行库存预警检查
     */
    @Scheduled(fixedRate = 300000) // 每5分钟执行一次
    public void performAlertCheck() {
        List<String> warehouses = inventoryRepository.findAllWarehouseIds();
        
        for (String warehouseId : warehouses) {
            checkWarehouseAlerts(warehouseId);
        }
    }
    
    /**
     * 检查仓库预警
     */
    public void checkWarehouseAlerts(String warehouseId) {
        List<InventoryItem> inventories = inventoryRepository.findByWarehouseId(warehouseId);
        List<AlertConfig> alertConfigs = alertConfigRepository.findByWarehouseId(warehouseId);
        
        Map<String, AlertConfig> configMap = alertConfigs.stream()
                .collect(Collectors.toMap(AlertConfig::getSkuId, Function.identity()));
        
        for (InventoryItem inventory : inventories) {
            AlertConfig config = configMap.get(inventory.getSkuId());
            if (config == null) {
                config = getDefaultAlertConfig();
            }
            
            checkInventoryAlerts(inventory, config);
        }
    }
    
    /**
     * 检查单个库存项的预警
     */
    private void checkInventoryAlerts(InventoryItem inventory, AlertConfig config) {
        List<InventoryAlert> alerts = new ArrayList<>();
        
        // 低库存预警
        if (isLowStock(inventory, config)) {
            alerts.add(createAlert(inventory, AlertType.LOW_STOCK, 
                "库存低于安全库存线", AlertLevel.WARNING));
        }
        
        // 缺货预警
        if (isOutOfStock(inventory)) {
            alerts.add(createAlert(inventory, AlertType.OUT_OF_STOCK, 
                "库存为零", AlertLevel.CRITICAL));
        }
        
        // 过期预警
        if (isNearExpiry(inventory, config)) {
            alerts.add(createAlert(inventory, AlertType.NEAR_EXPIRY, 
                "商品即将过期", AlertLevel.WARNING));
        }
        
        // 滞销预警
        if (isSlowMoving(inventory, config)) {
            alerts.add(createAlert(inventory, AlertType.SLOW_MOVING, 
                "商品滞销", AlertLevel.INFO));
        }
        
        // 超储预警
        if (isOverStock(inventory, config)) {
            alerts.add(createAlert(inventory, AlertType.OVER_STOCK, 
                "库存过多", AlertLevel.WARNING));
        }
        
        // 发送预警通知
        for (InventoryAlert alert : alerts) {
            sendAlert(alert);
        }
    }
    
    /**
     * 检查是否低库存
     */
    private boolean isLowStock(InventoryItem inventory, AlertConfig config) {
        Integer safetyStock = inventory.getSafetyStock();
        if (safetyStock == null) {
            safetyStock = config.getDefaultSafetyStock();
        }
        
        return inventory.getAvailableQuantity() <= safetyStock;
    }
    
    /**
     * 检查是否缺货
     */
    private boolean isOutOfStock(InventoryItem inventory) {
        return inventory.getAvailableQuantity() <= 0;
    }
    
    /**
     * 检查是否即将过期
     */
    private boolean isNearExpiry(InventoryItem inventory, AlertConfig config) {
        if (inventory.getExpiryDate() == null) {
            return false;
        }
        
        LocalDate alertDate = inventory.getExpiryDate().minusDays(config.getExpiryAlertDays());
        return LocalDate.now().isAfter(alertDate);
    }
    
    /**
     * 检查是否滞销
     */
    private boolean isSlowMoving(InventoryItem inventory, AlertConfig config) {
        // 获取最近销售数据
        LocalDateTime since = LocalDateTime.now().minusDays(config.getSlowMovingDays());
        Integer soldQuantity = inventoryRepository.getSoldQuantitySince(
            inventory.getSkuId(), inventory.getWarehouseId(), since
        );
        
        return soldQuantity < config.getMinSalesThreshold();
    }
    
    /**
     * 检查是否超储
     */
    private boolean isOverStock(InventoryItem inventory, AlertConfig config) {
        Integer maxStock = inventory.getMaxStock();
        if (maxStock == null) {
            maxStock = config.getDefaultMaxStock();
        }
        
        return inventory.getTotalQuantity() > maxStock;
    }
    
    /**
     * 创建预警
     */
    private InventoryAlert createAlert(InventoryItem inventory, AlertType type, 
                                     String message, AlertLevel level) {
        return new InventoryAlert(
            inventory.getSkuId(),
            inventory.getWarehouseId(),
            type,
            level,
            message,
            inventory.getAvailableQuantity(),
            inventory.getTotalQuantity()
        );
    }
    
    /**
     * 发送预警通知
     */
    private void sendAlert(InventoryAlert alert) {
        // 检查是否已经发送过相同预警
        if (isDuplicateAlert(alert)) {
            return;
        }
        
        // 根据预警级别选择通知方式
        switch (alert.getLevel()) {
            case CRITICAL:
                notificationService.sendUrgentNotification(alert);
                break;
            case WARNING:
                notificationService.sendWarningNotification(alert);
                break;
            case INFO:
                notificationService.sendInfoNotification(alert);
                break;
        }
        
        // 记录预警历史
        recordAlertHistory(alert);
    }
    
    private boolean isDuplicateAlert(InventoryAlert alert) {
        // 检查最近是否发送过相同类型的预警
        return false; // 简化实现
    }
    
    private void recordAlertHistory(InventoryAlert alert) {
        // 记录预警历史
    }
    
    private AlertConfig getDefaultAlertConfig() {
        return new AlertConfig(
            null, // skuId
            null, // warehouseId
            10,   // defaultSafetyStock
            1000, // defaultMaxStock
            7,    // expiryAlertDays
            30,   // slowMovingDays
            5     // minSalesThreshold
        );
    }
}

/**
 * 库存预警
 */
public class InventoryAlert {
    private final String skuId;
    private final String warehouseId;
    private final AlertType type;
    private final AlertLevel level;
    private final String message;
    private final Integer currentQuantity;
    private final Integer totalQuantity;
    private final LocalDateTime alertTime;
    
    public InventoryAlert(String skuId, String warehouseId, AlertType type, 
                        AlertLevel level, String message, 
                        Integer currentQuantity, Integer totalQuantity) {
        this.skuId = skuId;
        this.warehouseId = warehouseId;
        this.type = type;
        this.level = level;
        this.message = message;
        this.currentQuantity = currentQuantity;
        this.totalQuantity = totalQuantity;
        this.alertTime = LocalDateTime.now();
    }
    
    // getters
    public String getSkuId() { return skuId; }
    public String getWarehouseId() { return warehouseId; }
    public AlertType getType() { return type; }
    public AlertLevel getLevel() { return level; }
    public String getMessage() { return message; }
    public Integer getCurrentQuantity() { return currentQuantity; }
    public Integer getTotalQuantity() { return totalQuantity; }
    public LocalDateTime getAlertTime() { return alertTime; }
}

/**
 * 预警类型
 */
public enum AlertType {
    LOW_STOCK("低库存"),
    OUT_OF_STOCK("缺货"),
    NEAR_EXPIRY("即将过期"),
    SLOW_MOVING("滞销"),
    OVER_STOCK("超储"),
    DAMAGED("损坏");
    
    private final String description;
    
    AlertType(String description) {
        this.description = description;
    }
    
    public String getDescription() {
        return description;
    }
}

/**
 * 预警级别
 */
public enum AlertLevel {
    INFO("信息", 1),
    WARNING("警告", 2),
    CRITICAL("严重", 3);
    
    private final String description;
    private final int priority;
    
    AlertLevel(String description, int priority) {
        this.description = description;
        this.priority = priority;
    }
    
    public String getDescription() {
        return description;
    }
    
    public int getPriority() {
        return priority;
    }
}

/**
 * 预警配置
 */
@Entity
@Table(name = "alert_config")
public class AlertConfig extends BaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "sku_id")
    private String skuId;                    // SKU ID(为空表示默认配置)
    
    @Column(name = "warehouse_id")
    private String warehouseId;              // 仓库ID(为空表示全局配置)
    
    @Column(name = "default_safety_stock")
    private Integer defaultSafetyStock;      // 默认安全库存
    
    @Column(name = "default_max_stock")
    private Integer defaultMaxStock;         // 默认最大库存
    
    @Column(name = "expiry_alert_days")
    private Integer expiryAlertDays;         // 过期预警天数
    
    @Column(name = "slow_moving_days")
    private Integer slowMovingDays;          // 滞销判断天数
    
    @Column(name = "min_sales_threshold")
    private Integer minSalesThreshold;       // 最小销售阈值
    
    @Column(name = "enabled")
    private Boolean enabled;                 // 是否启用
    
    // 构造函数
    public AlertConfig() {
        this.enabled = true;
    }
    
    public AlertConfig(String skuId, String warehouseId, 
                      Integer defaultSafetyStock, Integer defaultMaxStock,
                      Integer expiryAlertDays, Integer slowMovingDays,
                      Integer minSalesThreshold) {
        this();
        this.skuId = skuId;
        this.warehouseId = warehouseId;
        this.defaultSafetyStock = defaultSafetyStock;
        this.defaultMaxStock = defaultMaxStock;
        this.expiryAlertDays = expiryAlertDays;
        this.slowMovingDays = slowMovingDays;
        this.minSalesThreshold = minSalesThreshold;
    }
    
    // getters and setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getSkuId() { return skuId; }
    public void setSkuId(String skuId) { this.skuId = skuId; }
    
    public String getWarehouseId() { return warehouseId; }
    public void setWarehouseId(String warehouseId) { this.warehouseId = warehouseId; }
    
    public Integer getDefaultSafetyStock() { return defaultSafetyStock; }
    public void setDefaultSafetyStock(Integer defaultSafetyStock) { this.defaultSafetyStock = defaultSafetyStock; }
    
    public Integer getDefaultMaxStock() { return defaultMaxStock; }
    public void setDefaultMaxStock(Integer defaultMaxStock) { this.defaultMaxStock = defaultMaxStock; }
    
    public Integer getExpiryAlertDays() { return expiryAlertDays; }
    public void setExpiryAlertDays(Integer expiryAlertDays) { this.expiryAlertDays = expiryAlertDays; }
    
    public Integer getSlowMovingDays() { return slowMovingDays; }
    public void setSlowMovingDays(Integer slowMovingDays) { this.slowMovingDays = slowMovingDays; }
    
    public Integer getMinSalesThreshold() { return minSalesThreshold; }
    public void setMinSalesThreshold(Integer minSalesThreshold) { this.minSalesThreshold = minSalesThreshold; }
    
    public Boolean getEnabled() { return enabled; }
     public void setEnabled(Boolean enabled) { this.enabled = enabled; }
 }

10.5.2 智能补货策略

基于历史销售数据和预测算法的智能补货系统:

/**
 * 智能补货服务
 */
@Service
public class IntelligentReplenishmentService {
    
    private final InventoryRepository inventoryRepository;
    private final SalesDataRepository salesDataRepository;
    private final SupplierRepository supplierRepository;
    private final PurchaseOrderService purchaseOrderService;
    private final InventoryForecastService forecastService;
    
    public IntelligentReplenishmentService(InventoryRepository inventoryRepository,
                                         SalesDataRepository salesDataRepository,
                                         SupplierRepository supplierRepository,
                                         PurchaseOrderService purchaseOrderService,
                                         InventoryForecastService forecastService) {
        this.inventoryRepository = inventoryRepository;
        this.salesDataRepository = salesDataRepository;
        this.supplierRepository = supplierRepository;
        this.purchaseOrderService = purchaseOrderService;
        this.forecastService = forecastService;
    }
    
    /**
     * 执行智能补货分析
     */
    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
    public void performReplenishmentAnalysis() {
        List<String> warehouses = inventoryRepository.findAllWarehouseIds();
        
        for (String warehouseId : warehouses) {
            analyzeWarehouseReplenishment(warehouseId);
        }
    }
    
    /**
     * 分析仓库补货需求
     */
    public ReplenishmentAnalysisResult analyzeWarehouseReplenishment(String warehouseId) {
        List<InventoryItem> inventories = inventoryRepository.findByWarehouseId(warehouseId);
        List<ReplenishmentRecommendation> recommendations = new ArrayList<>();
        
        for (InventoryItem inventory : inventories) {
            ReplenishmentRecommendation recommendation = analyzeItemReplenishment(inventory);
            if (recommendation != null) {
                recommendations.add(recommendation);
            }
        }
        
        return new ReplenishmentAnalysisResult(warehouseId, recommendations);
    }
    
    /**
     * 分析单个商品的补货需求
     */
    private ReplenishmentRecommendation analyzeItemReplenishment(InventoryItem inventory) {
        // 获取历史销售数据
        LocalDateTime since = LocalDateTime.now().minusDays(90);
        List<SalesData> salesHistory = salesDataRepository.findBySkuIdAndWarehouseIdAndDateAfter(
            inventory.getSkuId(), inventory.getWarehouseId(), since
        );
        
        if (salesHistory.isEmpty()) {
            return null; // 没有销售历史,不进行补货分析
        }
        
        // 计算销售统计
        SalesStatistics stats = calculateSalesStatistics(salesHistory);
        
        // 预测未来需求
        DemandForecast forecast = forecastService.forecastDemand(
            inventory.getSkuId(), inventory.getWarehouseId(), 30
        );
        
        // 计算补货建议
        ReplenishmentCalculation calculation = calculateReplenishment(
            inventory, stats, forecast
        );
        
        if (calculation.getRecommendedQuantity() > 0) {
            return new ReplenishmentRecommendation(
                inventory.getSkuId(),
                inventory.getWarehouseId(),
                calculation.getRecommendedQuantity(),
                calculation.getUrgencyLevel(),
                calculation.getReason(),
                calculation.getExpectedDeliveryDate(),
                calculation.getEstimatedCost()
            );
        }
        
        return null;
    }
    
    /**
     * 计算销售统计
     */
    private SalesStatistics calculateSalesStatistics(List<SalesData> salesHistory) {
        double totalSales = salesHistory.stream()
                .mapToDouble(SalesData::getQuantity)
                .sum();
        
        double averageDailySales = totalSales / salesHistory.size();
        
        double variance = salesHistory.stream()
                .mapToDouble(s -> Math.pow(s.getQuantity() - averageDailySales, 2))
                .average()
                .orElse(0.0);
        
        double standardDeviation = Math.sqrt(variance);
        
        // 计算趋势
        double trend = calculateTrend(salesHistory);
        
        return new SalesStatistics(
            totalSales,
            averageDailySales,
            standardDeviation,
            trend
        );
    }
    
    /**
     * 计算销售趋势
     */
    private double calculateTrend(List<SalesData> salesHistory) {
        if (salesHistory.size() < 2) {
            return 0.0;
        }
        
        // 简单线性回归计算趋势
        int n = salesHistory.size();
        double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
        
        for (int i = 0; i < n; i++) {
            double x = i + 1; // 时间序列
            double y = salesHistory.get(i).getQuantity();
            
            sumX += x;
            sumY += y;
            sumXY += x * y;
            sumX2 += x * x;
        }
        
        // 计算斜率(趋势)
        double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
        return slope;
    }
    
    /**
     * 计算补货建议
     */
    private ReplenishmentCalculation calculateReplenishment(InventoryItem inventory,
                                                          SalesStatistics stats,
                                                          DemandForecast forecast) {
        // 计算安全库存
        double safetyStock = calculateSafetyStock(stats, inventory.getLeadTimeDays());
        
        // 计算再订货点
        double reorderPoint = (stats.getAverageDailySales() * inventory.getLeadTimeDays()) + safetyStock;
        
        // 计算经济订货量(EOQ)
        double eoq = calculateEOQ(stats.getAverageDailySales() * 365, inventory.getOrderingCost(), inventory.getHoldingCost());
        
        // 当前可用库存
        int currentStock = inventory.getAvailableQuantity();
        
        // 判断是否需要补货
        if (currentStock <= reorderPoint) {
            // 计算建议订货量
            double recommendedQuantity = Math.max(eoq, reorderPoint - currentStock + (stats.getAverageDailySales() * 7));
            
            // 考虑预测趋势调整
            if (stats.getTrend() > 0) {
                recommendedQuantity *= (1 + stats.getTrend() * 0.1); // 上升趋势增加10%
            } else if (stats.getTrend() < 0) {
                recommendedQuantity *= (1 + stats.getTrend() * 0.05); // 下降趋势减少5%
            }
            
            // 确定紧急程度
            UrgencyLevel urgency = determineUrgency(currentStock, reorderPoint, stats.getAverageDailySales());
            
            // 计算预期交货日期
            LocalDate expectedDeliveryDate = LocalDate.now().plusDays(inventory.getLeadTimeDays());
            
            // 估算成本
            BigDecimal estimatedCost = inventory.getUnitCost().multiply(BigDecimal.valueOf(recommendedQuantity));
            
            String reason = String.format(
                "当前库存(%d)低于再订货点(%.0f),建议补货。平均日销量:%.1f,安全库存:%.0f",
                currentStock, reorderPoint, stats.getAverageDailySales(), safetyStock
            );
            
            return new ReplenishmentCalculation(
                (int) Math.ceil(recommendedQuantity),
                urgency,
                reason,
                expectedDeliveryDate,
                estimatedCost
            );
        }
        
        return new ReplenishmentCalculation(0, UrgencyLevel.NONE, "库存充足,无需补货", null, BigDecimal.ZERO);
    }
    
    /**
     * 计算安全库存
     */
    private double calculateSafetyStock(SalesStatistics stats, int leadTimeDays) {
        // 安全库存 = Z值 * 标准差 * sqrt(提前期)
        double zValue = 1.65; // 95%服务水平
        return zValue * stats.getStandardDeviation() * Math.sqrt(leadTimeDays);
    }
    
    /**
     * 计算经济订货量(EOQ)
     */
    private double calculateEOQ(double annualDemand, BigDecimal orderingCost, BigDecimal holdingCost) {
        if (holdingCost.compareTo(BigDecimal.ZERO) == 0) {
            return annualDemand / 12; // 默认月订货量
        }
        
        double eoq = Math.sqrt((2 * annualDemand * orderingCost.doubleValue()) / holdingCost.doubleValue());
        return eoq;
    }
    
    /**
     * 确定补货紧急程度
     */
    private UrgencyLevel determineUrgency(int currentStock, double reorderPoint, double averageDailySales) {
        double daysOfStock = currentStock / averageDailySales;
        
        if (daysOfStock <= 3) {
            return UrgencyLevel.CRITICAL;
        } else if (daysOfStock <= 7) {
            return UrgencyLevel.HIGH;
        } else if (currentStock <= reorderPoint * 1.2) {
            return UrgencyLevel.MEDIUM;
        } else {
            return UrgencyLevel.LOW;
        }
    }
    
    /**
     * 自动创建采购订单
     */
    public void autoCreatePurchaseOrders(String warehouseId) {
        ReplenishmentAnalysisResult result = analyzeWarehouseReplenishment(warehouseId);
        
        // 按供应商分组
        Map<String, List<ReplenishmentRecommendation>> supplierGroups = result.getRecommendations().stream()
                .filter(r -> r.getUrgencyLevel().getPriority() >= UrgencyLevel.MEDIUM.getPriority())
                .collect(Collectors.groupingBy(this::getPreferredSupplier));
        
        // 为每个供应商创建采购订单
        for (Map.Entry<String, List<ReplenishmentRecommendation>> entry : supplierGroups.entrySet()) {
            String supplierId = entry.getKey();
            List<ReplenishmentRecommendation> items = entry.getValue();
            
            purchaseOrderService.createPurchaseOrder(supplierId, warehouseId, items);
        }
    }
    
    private String getPreferredSupplier(ReplenishmentRecommendation recommendation) {
        // 获取商品的首选供应商
        return supplierRepository.findPreferredSupplierBySkuId(recommendation.getSkuId())
                .map(Supplier::getId)
                .orElse("DEFAULT_SUPPLIER");
    }
}

/**
 * 销售统计
 */
public class SalesStatistics {
    private final double totalSales;
    private final double averageDailySales;
    private final double standardDeviation;
    private final double trend;
    
    public SalesStatistics(double totalSales, double averageDailySales, 
                          double standardDeviation, double trend) {
        this.totalSales = totalSales;
        this.averageDailySales = averageDailySales;
        this.standardDeviation = standardDeviation;
        this.trend = trend;
    }
    
    // getters
    public double getTotalSales() { return totalSales; }
    public double getAverageDailySales() { return averageDailySales; }
    public double getStandardDeviation() { return standardDeviation; }
    public double getTrend() { return trend; }
}

/**
 * 补货计算结果
 */
public class ReplenishmentCalculation {
    private final int recommendedQuantity;
    private final UrgencyLevel urgencyLevel;
    private final String reason;
    private final LocalDate expectedDeliveryDate;
    private final BigDecimal estimatedCost;
    
    public ReplenishmentCalculation(int recommendedQuantity, UrgencyLevel urgencyLevel,
                                  String reason, LocalDate expectedDeliveryDate,
                                  BigDecimal estimatedCost) {
        this.recommendedQuantity = recommendedQuantity;
        this.urgencyLevel = urgencyLevel;
        this.reason = reason;
        this.expectedDeliveryDate = expectedDeliveryDate;
        this.estimatedCost = estimatedCost;
    }
    
    // getters
    public int getRecommendedQuantity() { return recommendedQuantity; }
    public UrgencyLevel getUrgencyLevel() { return urgencyLevel; }
    public String getReason() { return reason; }
    public LocalDate getExpectedDeliveryDate() { return expectedDeliveryDate; }
    public BigDecimal getEstimatedCost() { return estimatedCost; }
}

/**
 * 补货建议
 */
public class ReplenishmentRecommendation {
    private final String skuId;
    private final String warehouseId;
    private final int recommendedQuantity;
    private final UrgencyLevel urgencyLevel;
    private final String reason;
    private final LocalDate expectedDeliveryDate;
    private final BigDecimal estimatedCost;
    private final LocalDateTime createdTime;
    
    public ReplenishmentRecommendation(String skuId, String warehouseId,
                                     int recommendedQuantity, UrgencyLevel urgencyLevel,
                                     String reason, LocalDate expectedDeliveryDate,
                                     BigDecimal estimatedCost) {
        this.skuId = skuId;
        this.warehouseId = warehouseId;
        this.recommendedQuantity = recommendedQuantity;
        this.urgencyLevel = urgencyLevel;
        this.reason = reason;
        this.expectedDeliveryDate = expectedDeliveryDate;
        this.estimatedCost = estimatedCost;
        this.createdTime = LocalDateTime.now();
    }
    
    // getters
    public String getSkuId() { return skuId; }
    public String getWarehouseId() { return warehouseId; }
    public int getRecommendedQuantity() { return recommendedQuantity; }
    public UrgencyLevel getUrgencyLevel() { return urgencyLevel; }
    public String getReason() { return reason; }
    public LocalDate getExpectedDeliveryDate() { return expectedDeliveryDate; }
    public BigDecimal getEstimatedCost() { return estimatedCost; }
    public LocalDateTime getCreatedTime() { return createdTime; }
}

/**
 * 补货分析结果
 */
public class ReplenishmentAnalysisResult {
    private final String warehouseId;
    private final List<ReplenishmentRecommendation> recommendations;
    private final LocalDateTime analysisTime;
    
    public ReplenishmentAnalysisResult(String warehouseId, List<ReplenishmentRecommendation> recommendations) {
        this.warehouseId = warehouseId;
        this.recommendations = recommendations;
        this.analysisTime = LocalDateTime.now();
    }
    
    public int getCriticalCount() {
        return (int) recommendations.stream()
                .filter(r -> r.getUrgencyLevel() == UrgencyLevel.CRITICAL)
                .count();
    }
    
    public BigDecimal getTotalEstimatedCost() {
        return recommendations.stream()
                .map(ReplenishmentRecommendation::getEstimatedCost)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    
    // getters
    public String getWarehouseId() { return warehouseId; }
    public List<ReplenishmentRecommendation> getRecommendations() { return recommendations; }
    public LocalDateTime getAnalysisTime() { return analysisTime; }
}

/**
 * 紧急程度
 */
public enum UrgencyLevel {
    NONE("无需补货", 0),
    LOW("低", 1),
    MEDIUM("中", 2),
    HIGH("高", 3),
    CRITICAL("紧急", 4);
    
    private final String description;
    private final int priority;
    
    UrgencyLevel(String description, int priority) {
        this.description = description;
        this.priority = priority;
    }
    
    public String getDescription() {
        return description;
    }
    
    public int getPriority() {
        return priority;
    }
}

10.6 库存统计与分析

为了支持业务决策,中央库存系统需要提供全面的统计分析功能:

/**
 * 库存统计分析服务
 */
@Service
public class InventoryAnalyticsService {
    
    private final InventoryRepository inventoryRepository;
    private final InventoryTransactionRepository transactionRepository;
    private final SalesDataRepository salesDataRepository;
    
    public InventoryAnalyticsService(InventoryRepository inventoryRepository,
                                   InventoryTransactionRepository transactionRepository,
                                   SalesDataRepository salesDataRepository) {
        this.inventoryRepository = inventoryRepository;
        this.transactionRepository = transactionRepository;
        this.salesDataRepository = salesDataRepository;
    }
    
    /**
     * 获取库存概览统计
     */
    public InventoryOverviewStats getInventoryOverview(String warehouseId) {
        List<InventoryItem> inventories = inventoryRepository.findByWarehouseId(warehouseId);
        
        int totalSkus = inventories.size();
        long totalQuantity = inventories.stream()
                .mapToLong(InventoryItem::getTotalQuantity)
                .sum();
        
        long availableQuantity = inventories.stream()
                .mapToLong(InventoryItem::getAvailableQuantity)
                .sum();
        
        long reservedQuantity = inventories.stream()
                .mapToLong(InventoryItem::getReservedQuantity)
                .sum();
        
        BigDecimal totalValue = inventories.stream()
                .map(inv -> inv.getUnitCost().multiply(BigDecimal.valueOf(inv.getTotalQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        // 低库存商品数量
        long lowStockCount = inventories.stream()
                .filter(inv -> inv.getAvailableQuantity() <= (inv.getSafetyStock() != null ? inv.getSafetyStock() : 10))
                .count();
        
        // 缺货商品数量
        long outOfStockCount = inventories.stream()
                .filter(inv -> inv.getAvailableQuantity() <= 0)
                .count();
        
        return new InventoryOverviewStats(
            warehouseId,
            totalSkus,
            totalQuantity,
            availableQuantity,
            reservedQuantity,
            totalValue,
            lowStockCount,
            outOfStockCount
        );
    }
    
    /**
     * 获取库存周转分析
     */
    public InventoryTurnoverAnalysis getInventoryTurnoverAnalysis(String warehouseId, 
                                                                LocalDate startDate, 
                                                                LocalDate endDate) {
        List<InventoryItem> inventories = inventoryRepository.findByWarehouseId(warehouseId);
        List<InventoryTurnoverItem> turnoverItems = new ArrayList<>();
        
        for (InventoryItem inventory : inventories) {
            // 获取期间销售数据
            List<SalesData> salesData = salesDataRepository.findBySkuIdAndWarehouseIdAndDateBetween(
                inventory.getSkuId(), warehouseId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)
            );
            
            double totalSold = salesData.stream()
                    .mapToDouble(SalesData::getQuantity)
                    .sum();
            
            // 计算平均库存
            double averageInventory = calculateAverageInventory(inventory, startDate, endDate);
            
            // 计算周转率
            double turnoverRate = averageInventory > 0 ? totalSold / averageInventory : 0;
            
            // 计算周转天数
            long periodDays = ChronoUnit.DAYS.between(startDate, endDate);
            double turnoverDays = turnoverRate > 0 ? periodDays / turnoverRate : periodDays;
            
            turnoverItems.add(new InventoryTurnoverItem(
                inventory.getSkuId(),
                totalSold,
                averageInventory,
                turnoverRate,
                turnoverDays
            ));
        }
        
        return new InventoryTurnoverAnalysis(warehouseId, startDate, endDate, turnoverItems);
    }
    
    /**
     * 计算平均库存
     */
    private double calculateAverageInventory(InventoryItem inventory, LocalDate startDate, LocalDate endDate) {
        // 获取期间内的库存变动记录
        List<InventoryTransaction> transactions = transactionRepository
                .findBySkuIdAndWarehouseIdAndTransactionTimeBetween(
                    inventory.getSkuId(), 
                    inventory.getWarehouseId(),
                    startDate.atStartOfDay(),
                    endDate.atTime(23, 59, 59)
                );
        
        if (transactions.isEmpty()) {
            return inventory.getTotalQuantity();
        }
        
        // 简化计算:期初库存 + 期末库存 / 2
        int currentQuantity = inventory.getTotalQuantity();
        int totalChange = transactions.stream()
                .mapToInt(t -> t.getQuantityChange() * t.getTransactionType().getQuantityMultiplier())
                .sum();
        
        int startQuantity = currentQuantity - totalChange;
        
        return (startQuantity + currentQuantity) / 2.0;
    }
    
    /**
     * 获取ABC分析
     */
    public ABCAnalysisResult getABCAnalysis(String warehouseId, LocalDate startDate, LocalDate endDate) {
        List<InventoryItem> inventories = inventoryRepository.findByWarehouseId(warehouseId);
        List<ABCAnalysisItem> analysisItems = new ArrayList<>();
        
        for (InventoryItem inventory : inventories) {
            // 获取销售数据
            List<SalesData> salesData = salesDataRepository.findBySkuIdAndWarehouseIdAndDateBetween(
                inventory.getSkuId(), warehouseId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)
            );
            
            double totalSalesQuantity = salesData.stream()
                    .mapToDouble(SalesData::getQuantity)
                    .sum();
            
            BigDecimal totalSalesValue = salesData.stream()
                    .map(s -> BigDecimal.valueOf(s.getQuantity()).multiply(s.getUnitPrice()))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            
            analysisItems.add(new ABCAnalysisItem(
                inventory.getSkuId(),
                totalSalesQuantity,
                totalSalesValue,
                inventory.getTotalQuantity(),
                inventory.getUnitCost().multiply(BigDecimal.valueOf(inventory.getTotalQuantity()))
            ));
        }
        
        // 按销售金额排序
        analysisItems.sort((a, b) -> b.getSalesValue().compareTo(a.getSalesValue()));
        
        // 计算累计百分比并分类
        BigDecimal totalSalesValue = analysisItems.stream()
                .map(ABCAnalysisItem::getSalesValue)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        BigDecimal cumulativeValue = BigDecimal.ZERO;
        for (ABCAnalysisItem item : analysisItems) {
            cumulativeValue = cumulativeValue.add(item.getSalesValue());
            double cumulativePercentage = cumulativeValue.divide(totalSalesValue, 4, RoundingMode.HALF_UP)
                    .multiply(BigDecimal.valueOf(100)).doubleValue();
            
            if (cumulativePercentage <= 80) {
                item.setCategory(ABCCategory.A);
            } else if (cumulativePercentage <= 95) {
                item.setCategory(ABCCategory.B);
            } else {
                item.setCategory(ABCCategory.C);
            }
        }
        
        return new ABCAnalysisResult(warehouseId, startDate, endDate, analysisItems);
    }
    
    /**
     * 获取库存异常分析
     */
    public InventoryAnomalyAnalysis getInventoryAnomalyAnalysis(String warehouseId) {
        List<InventoryItem> inventories = inventoryRepository.findByWarehouseId(warehouseId);
        List<InventoryAnomaly> anomalies = new ArrayList<>();
        
        LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
        
        for (InventoryItem inventory : inventories) {
            // 检查长期无销售
            List<SalesData> recentSales = salesDataRepository.findBySkuIdAndWarehouseIdAndDateAfter(
                inventory.getSkuId(), warehouseId, thirtyDaysAgo
            );
            
            if (recentSales.isEmpty() && inventory.getTotalQuantity() > 0) {
                anomalies.add(new InventoryAnomaly(
                    inventory.getSkuId(),
                    AnomalyType.NO_SALES_30_DAYS,
                    "30天内无销售记录",
                    inventory.getTotalQuantity(),
                    inventory.getUnitCost().multiply(BigDecimal.valueOf(inventory.getTotalQuantity()))
                ));
            }
            
            // 检查库存过多
            if (inventory.getMaxStock() != null && inventory.getTotalQuantity() > inventory.getMaxStock()) {
                anomalies.add(new InventoryAnomaly(
                    inventory.getSkuId(),
                    AnomalyType.OVER_STOCK,
                    String.format("库存(%d)超过最大库存(%d)", inventory.getTotalQuantity(), inventory.getMaxStock()),
                    inventory.getTotalQuantity() - inventory.getMaxStock(),
                    inventory.getUnitCost().multiply(BigDecimal.valueOf(inventory.getTotalQuantity() - inventory.getMaxStock()))
                ));
            }
            
            // 检查即将过期
            if (inventory.getExpiryDate() != null) {
                long daysToExpiry = ChronoUnit.DAYS.between(LocalDate.now(), inventory.getExpiryDate());
                if (daysToExpiry <= 7 && daysToExpiry > 0) {
                    anomalies.add(new InventoryAnomaly(
                        inventory.getSkuId(),
                        AnomalyType.NEAR_EXPIRY,
                        String.format("商品将在%d天后过期", daysToExpiry),
                        inventory.getTotalQuantity(),
                        inventory.getUnitCost().multiply(BigDecimal.valueOf(inventory.getTotalQuantity()))
                    ));
                }
            }
        }
        
        return new InventoryAnomalyAnalysis(warehouseId, anomalies);
    }
}

/**
 * 库存概览统计
 */
public class InventoryOverviewStats {
    private final String warehouseId;
    private final int totalSkus;
    private final long totalQuantity;
    private final long availableQuantity;
    private final long reservedQuantity;
    private final BigDecimal totalValue;
    private final long lowStockCount;
    private final long outOfStockCount;
    private final LocalDateTime generatedTime;
    
    public InventoryOverviewStats(String warehouseId, int totalSkus, long totalQuantity,
                                long availableQuantity, long reservedQuantity, BigDecimal totalValue,
                                long lowStockCount, long outOfStockCount) {
        this.warehouseId = warehouseId;
        this.totalSkus = totalSkus;
        this.totalQuantity = totalQuantity;
        this.availableQuantity = availableQuantity;
        this.reservedQuantity = reservedQuantity;
        this.totalValue = totalValue;
        this.lowStockCount = lowStockCount;
        this.outOfStockCount = outOfStockCount;
        this.generatedTime = LocalDateTime.now();
    }
    
    // getters
    public String getWarehouseId() { return warehouseId; }
    public int getTotalSkus() { return totalSkus; }
    public long getTotalQuantity() { return totalQuantity; }
    public long getAvailableQuantity() { return availableQuantity; }
    public long getReservedQuantity() { return reservedQuantity; }
    public BigDecimal getTotalValue() { return totalValue; }
    public long getLowStockCount() { return lowStockCount; }
    public long getOutOfStockCount() { return outOfStockCount; }
    public LocalDateTime getGeneratedTime() { return generatedTime; }
}

/**
 * 库存周转分析
 */
public class InventoryTurnoverAnalysis {
    private final String warehouseId;
    private final LocalDate startDate;
    private final LocalDate endDate;
    private final List<InventoryTurnoverItem> items;
    
    public InventoryTurnoverAnalysis(String warehouseId, LocalDate startDate, 
                                   LocalDate endDate, List<InventoryTurnoverItem> items) {
        this.warehouseId = warehouseId;
        this.startDate = startDate;
        this.endDate = endDate;
        this.items = items;
    }
    
    public double getAverageTurnoverRate() {
        return items.stream()
                .mapToDouble(InventoryTurnoverItem::getTurnoverRate)
                .average()
                .orElse(0.0);
    }
    
    public double getAverageTurnoverDays() {
        return items.stream()
                .mapToDouble(InventoryTurnoverItem::getTurnoverDays)
                .average()
                .orElse(0.0);
    }
    
    // getters
    public String getWarehouseId() { return warehouseId; }
    public LocalDate getStartDate() { return startDate; }
    public LocalDate getEndDate() { return endDate; }
    public List<InventoryTurnoverItem> getItems() { return items; }
}

/**
 * 库存周转项目
 */
public class InventoryTurnoverItem {
    private final String skuId;
    private final double totalSold;
    private final double averageInventory;
    private final double turnoverRate;
    private final double turnoverDays;
    
    public InventoryTurnoverItem(String skuId, double totalSold, double averageInventory,
                               double turnoverRate, double turnoverDays) {
        this.skuId = skuId;
        this.totalSold = totalSold;
        this.averageInventory = averageInventory;
        this.turnoverRate = turnoverRate;
        this.turnoverDays = turnoverDays;
    }
    
    // getters
    public String getSkuId() { return skuId; }
    public double getTotalSold() { return totalSold; }
    public double getAverageInventory() { return averageInventory; }
    public double getTurnoverRate() { return turnoverRate; }
    public double getTurnoverDays() { return turnoverDays; }
}

/**
 * ABC分析结果
 */
public class ABCAnalysisResult {
    private final String warehouseId;
    private final LocalDate startDate;
    private final LocalDate endDate;
    private final List<ABCAnalysisItem> items;
    
    public ABCAnalysisResult(String warehouseId, LocalDate startDate, 
                           LocalDate endDate, List<ABCAnalysisItem> items) {
        this.warehouseId = warehouseId;
        this.startDate = startDate;
        this.endDate = endDate;
        this.items = items;
    }
    
    public Map<ABCCategory, Long> getCategoryCount() {
        return items.stream()
                .collect(Collectors.groupingBy(
                    ABCAnalysisItem::getCategory,
                    Collectors.counting()
                ));
    }
    
    public Map<ABCCategory, BigDecimal> getCategoryValue() {
        return items.stream()
                .collect(Collectors.groupingBy(
                    ABCAnalysisItem::getCategory,
                    Collectors.reducing(
                        BigDecimal.ZERO,
                        ABCAnalysisItem::getSalesValue,
                        BigDecimal::add
                    )
                ));
    }
    
    // getters
    public String getWarehouseId() { return warehouseId; }
    public LocalDate getStartDate() { return startDate; }
    public LocalDate getEndDate() { return endDate; }
    public List<ABCAnalysisItem> getItems() { return items; }
}

/**
 * ABC分析项目
 */
public class ABCAnalysisItem {
    private final String skuId;
    private final double salesQuantity;
    private final BigDecimal salesValue;
    private final int inventoryQuantity;
    private final BigDecimal inventoryValue;
    private ABCCategory category;
    
    public ABCAnalysisItem(String skuId, double salesQuantity, BigDecimal salesValue,
                         int inventoryQuantity, BigDecimal inventoryValue) {
        this.skuId = skuId;
        this.salesQuantity = salesQuantity;
        this.salesValue = salesValue;
        this.inventoryQuantity = inventoryQuantity;
        this.inventoryValue = inventoryValue;
    }
    
    // getters and setters
    public String getSkuId() { return skuId; }
    public double getSalesQuantity() { return salesQuantity; }
    public BigDecimal getSalesValue() { return salesValue; }
    public int getInventoryQuantity() { return inventoryQuantity; }
    public BigDecimal getInventoryValue() { return inventoryValue; }
    public ABCCategory getCategory() { return category; }
    public void setCategory(ABCCategory category) { this.category = category; }
}

/**
 * ABC分类
 */
public enum ABCCategory {
    A("A类商品", "高价值商品,占销售额80%"),
    B("B类商品", "中等价值商品,占销售额15%"),
    C("C类商品", "低价值商品,占销售额5%");
    
    private final String name;
    private final String description;
    
    ABCCategory(String name, String description) {
        this.name = name;
        this.description = description;
    }
    
    public String getName() { return name; }
    public String getDescription() { return description; }
}

/**
 * 库存异常分析
 */
public class InventoryAnomalyAnalysis {
    private final String warehouseId;
    private final List<InventoryAnomaly> anomalies;
    private final LocalDateTime analysisTime;
    
    public InventoryAnomalyAnalysis(String warehouseId, List<InventoryAnomaly> anomalies) {
        this.warehouseId = warehouseId;
        this.anomalies = anomalies;
        this.analysisTime = LocalDateTime.now();
    }
    
    public Map<AnomalyType, Long> getAnomalyCount() {
        return anomalies.stream()
                .collect(Collectors.groupingBy(
                    InventoryAnomaly::getType,
                    Collectors.counting()
                ));
    }
    
    public BigDecimal getTotalAnomalyValue() {
        return anomalies.stream()
                .map(InventoryAnomaly::getValue)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    
    // getters
    public String getWarehouseId() { return warehouseId; }
    public List<InventoryAnomaly> getAnomalies() { return anomalies; }
    public LocalDateTime getAnalysisTime() { return analysisTime; }
}

/**
 * 库存异常
 */
public class InventoryAnomaly {
    private final String skuId;
    private final AnomalyType type;
    private final String description;
    private final int quantity;
    private final BigDecimal value;
    
    public InventoryAnomaly(String skuId, AnomalyType type, String description,
                          int quantity, BigDecimal value) {
        this.skuId = skuId;
        this.type = type;
        this.description = description;
        this.quantity = quantity;
        this.value = value;
    }
    
    // getters
    public String getSkuId() { return skuId; }
    public AnomalyType getType() { return type; }
    public String getDescription() { return description; }
    public int getQuantity() { return quantity; }
    public BigDecimal getValue() { return value; }
}

/**
 * 异常类型
 */
public enum AnomalyType {
    NO_SALES_30_DAYS("30天无销售"),
    OVER_STOCK("库存过多"),
    NEAR_EXPIRY("即将过期"),
    DAMAGED_STOCK("损坏库存"),
    NEGATIVE_STOCK("负库存");
    
    private final String description;
    
    AnomalyType(String description) {
        this.description = description;
    }
    
    public String getDescription() {
        return description;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芝麻开门-新起点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值