ITPub博客

首页 > 应用开发 > IT综合 > 基于Spring Boot的东方红食品企业采购与供应链管理系统设计与实现

基于Spring Boot的东方红食品企业采购与供应链管理系统设计与实现

IT综合 作者:bhluo 时间:2026-01-28 11:48:57 0 删除 编辑

**基于Spring Boot的东方红食品企业采购与供应链管理系统设计与实现**


食品企业的采购管理涉及原料质量、供应商协同、库存周转和成本控制等多维度挑战。东方红食品公司作为区域性食品生产企业,传统采购流程存在信息不透明、审批效率低、供应商管理粗放等问题。本文设计并实现了一个基于Spring Boot的食品企业采购管理系统,旨在构建一个集供应商管理、采购执行、库存对接和成本分析于一体的数字化采购平台。


### 一、 系统架构与业务场景


系统采用模块化设计,针对食品行业特点,重点解决原料追溯、批次管理和供应商评估等核心需求。


**技术架构**:

- 后端框架:Spring Boot 2.7 + Spring Security + Spring Data JPA

- 数据库:MySQL 8.0(业务数据) + Redis 7.0(缓存与会话)

- 消息中间件:RabbitMQ 3.11(异步通知)

- 文档处理:Apache POI(Excel导入导出)

- 权限控制:RBAC基于角色的访问控制

- 前端框架:Vue 3 + Element Plus


**核心业务模块**:

1. 供应商全生命周期管理

2. 采购需求与审批流程

3. 原料批次与质量追溯

4. 库存预警与自动补货

5. 采购成本分析与报表


### 二、 核心业务模块实现


#### 1. 供应商分级与评估体系

针对食品原料特殊性,建立供应商多维度评估模型,实现动态分级管理。


```java

// 供应商评估服务

@Service

@Transactional

@Slf4j

public class SupplierEvaluationService {

    

    @Autowired

    private SupplierRepository supplierRepo;

    

    @Autowired

    private PurchaseOrderRepository orderRepo;

    

    @Autowired

    private QualityInspectionRepository inspectionRepo;

    

    /**

     * 计算供应商综合评分(月度评估)

     * 权重:质量40% + 交货30% + 价格20% + 服务10%

     */

    public SupplierRatingDTO evaluateSupplier(Long supplierId, LocalDate month) {

        Supplier supplier = supplierRepo.findById(supplierId)

                .orElseThrow(() -> new BusinessException("供应商不存在"));

        

        // 质量评分(基于检验合格率)

        Double qualityScore = calculateQualityScore(supplierId, month);

        

        // 交货评分(准时率与数量准确率)

        DeliveryScore deliveryScore = calculateDeliveryScore(supplierId, month);

        

        // 价格评分(市场比价与价格稳定性)

        PriceScore priceScore = calculatePriceScore(supplierId, month);

        

        // 服务评分(响应速度与问题处理)

        ServiceScore serviceScore = calculateServiceScore(supplierId, month);

        

        // 综合评分计算

        Double compositeScore = qualityScore * 0.4 

                + deliveryScore.getOnTimeRate() * 0.3

                + priceScore.getPriceCompetitiveness() * 0.2

                + serviceScore.getResponseScore() * 0.1;

        

        // 确定等级:A(≥90)、B(80-89)、C(70-79)、D(<70)

        String newGrade = determineGrade(compositeScore);

        

        // 更新供应商等级记录

        SupplierRating rating = SupplierRating.builder()

                .supplierId(supplierId)

                .evaluationMonth(month)

                .qualityScore(qualityScore)

                .deliveryScore(deliveryScore.getOnTimeRate())

                .priceScore(priceScore.getPriceCompetitiveness())

                .serviceScore(serviceScore.getResponseScore())

                .compositeScore(compositeScore)

                .grade(newGrade)

                .evaluatedAt(LocalDateTime.now())

                .build();

        

        ratingRepository.save(rating);

        

        // 自动调整供应商状态

        updateSupplierStatus(supplierId, newGrade);

        

        log.info("供应商{}月度评估完成,综合评分:{},等级:{}", 

                supplierId, compositeScore, newGrade);

        

        return convertToDTO(rating);

    }

    

    /**

     * 质量评分计算(基于批次检验数据)

     */

    private Double calculateQualityScore(Long supplierId, LocalDate month) {

        List<QualityInspection> inspections = inspectionRepo

                .findBySupplierAndMonth(supplierId, month);

        

        if (inspections.isEmpty()) {

            return 80.0; // 默认分

        }

        

        long totalBatches = inspections.size();

        long qualifiedBatches = inspections.stream()

                .filter(insp -> "QUALIFIED".equals(insp.getResult()))

                .count();

        

        double qualificationRate = (double) qualifiedBatches / totalBatches * 100;

        

        // 严重质量问题扣分(如食品安全问题)

        long seriousIssues = inspections.stream()

                .filter(insp -> "SERIOUS_ISSUE".equals(insp.getIssueLevel()))

                .count();

        

        double baseScore = qualificationRate;

        double penalty = seriousIssues * 20; // 每个严重问题扣20分

        

        return Math.max(baseScore - penalty, 0);

    }

    

    /**

     * 根据评分自动调整供应商合作状态

     */

    private void updateSupplierStatus(Long supplierId, String grade) {

        Supplier supplier = supplierRepo.findById(supplierId).orElse(null);

        if (supplier == null) return;

        

        switch (grade) {

            case "A":

                supplier.setStatus(SupplierStatus.PREFERRED);

                supplier.setApprovalLimit(new BigDecimal("500000.00")); // 提高审批限额

                break;

            case "B":

                supplier.setStatus(SupplierStatus.ACTIVE);

                supplier.setApprovalLimit(new BigDecimal("200000.00"));

                break;

            case "C":

                supplier.setStatus(SupplierStatus.RESTRICTED);

                supplier.setApprovalLimit(new BigDecimal("50000.00"));

                supplier.setRequireExtraApproval(true); // 需要额外审批

                break;

            case "D":

                supplier.setStatus(SupplierStatus.SUSPENDED);

                supplier.setActive(false);

                // 触发供应商审查流程

                initiateSupplierReview(supplierId);

                break;

        }

        

        supplierRepo.save(supplier);

    }

}

```


#### 2. 食品原料采购与批次追溯

建立原料批次与采购订单的关联关系,实现从供应商到成品的一体化追溯。


```java

// 原料批次追溯服务

@Service

@Transactional

public class MaterialTraceService {

    

    @Autowired

    private BatchRecordRepository batchRepo;

    

    @Autowired

    private PurchaseOrderItemRepository orderItemRepo;

<"x3.h4k7.org.cn"><"b7.h4k7.org.cn"><"j3.h4k7.org.cn">

    

    /**

     * 创建原料批次记录(采购入库时调用)

     */

    public BatchRecord createBatchRecord(CreateBatchRequest request) {

        // 验证采购订单项

        PurchaseOrderItem orderItem = orderItemRepo.findById(request.getOrderItemId())

                .orElseThrow(() -> new BusinessException("采购订单项不存在"));

        

        // 生成批次号:供应商编码+日期+序号

        String batchNumber = generateBatchNumber(

                orderItem.getSupplier().getCode(),

                request.getProductionDate(),

                request.getSequence()

        );

        

        // 创建批次记录

        BatchRecord batch = BatchRecord.builder()

                .batchNumber(batchNumber)

                .materialId(orderItem.getMaterial().getId())

                .supplierId(orderItem.getSupplier().getId())

                .purchaseOrderId(orderItem.getOrder().getId())

                .quantity(request.getQuantity())

                .unit(request.getUnit())

                .productionDate(request.getProductionDate())

                .expiryDate(request.getExpiryDate())

                .storageCondition(request.getStorageCondition())

                .qualityInspectionId(request.getInspectionId())

                .warehouseLocation(request.getWarehouseLocation())

                .qrCode(generateQRCode(batchNumber))

                .build();

        

        batchRepo.save(batch);

        

        // 更新库存批次关联

        inventoryService.linkBatchToInventory(

                batch.getId(), 

                request.getWarehouseId(), 

                request.getLocationCode()

        );

        

        // 发送批次创建通知

        notificationService.sendBatchCreatedNotification(batch);

        

        log.info("原料批次创建成功:{},数量:{} {}", 

                batchNumber, request.getQuantity(), request.getUnit());

        

        return batch;

    }

    

    /**

     * 完整追溯查询(正向:原料→成品;反向:成品→原料)

     */

    public TraceResultVO queryTrace(String traceCode, TraceDirection direction) {

        if (direction == TraceDirection.FORWARD) {

            // 正向追溯:原料批次到使用该原料的所有成品

            return forwardTrace(traceCode);

        } else {

            // 反向追溯:成品批次到所有原料批次

            return backwardTrace(traceCode);

        }

    }

    

    /**

     * 正向追溯逻辑

     */

    private TraceResultVO forwardTrace(String batchNumber) {

        BatchRecord sourceBatch = batchRepo.findByBatchNumber(batchNumber)

                .orElseThrow(() -> new BusinessException("批次记录不存在"));

        

        // 查找使用该原料的生产批次

        List<ProductionBatch> productionBatches = productionBatchRepo

                .findByMaterialBatch(batchNumber);

        

        // 构建追溯树

        TraceResultVO result = new TraceResultVO();

        result.setSourceBatch(convertToBatchVO(sourceBatch));

        

        List<ProductionBatchVO> batchVOs = productionBatches.stream()

                .map(batch -> {

                    ProductionBatchVO vo = convertToProductionBatchVO(batch);

                    

                    // 查找该生产批次对应的成品批次

                    List<FinishedProductBatch> finishedBatches = 

                            finishedProductRepo.findByProductionBatch(batch.getBatchNumber());

                    vo.setFinishedProducts(convertToFinishedProductVOs(finishedBatches));

                    

                    return vo;

                })

                .collect(Collectors.toList());

        

        result.setProductionBatches(batchVOs);

        result.setTraceTime(LocalDateTime.now());

        

        return result;

    }

    

    /**

     * 批次二维码生成

     */

    private String generateQRCode(String batchNumber) {

        try {

            QRCodeWriter qrCodeWriter = new QRCodeWriter();

            String traceUrl = "https://trace.dfhfood.com/batch/" + batchNumber;

            

            BitMatrix bitMatrix = qrCodeWriter.encode(

                    traceUrl, 

                    BarcodeFormat.QR_CODE, 

                    200, 200

            );

            

            // 转换为Base64图片

            ByteArrayOutputStream os = new ByteArrayOutputStream();

            MatrixToImageWriter.writeToStream(bitMatrix, "PNG", os);

            

            return "data:image/png;base64," + 

                    Base64.getEncoder().encodeToString(os.toByteArray());

        } catch (Exception e) {

            log.error("生成批次二维码失败:{}", batchNumber, e);

            return "";

        }

    }

}

```


#### 3. 智能采购需求预测

基于历史消耗、季节因素和销售预测,实现原料需求的智能预测。


```java

// 采购需求预测服务

@Service

@Slf4j

public class PurchaseForecastService {

    

    @Autowired

    private MaterialConsumptionRepository consumptionRepo;

    

    @Autowired

    private SalesForecastRepository salesForecastRepo;

    

    @Autowired

    private InventoryRepository inventoryRepo;

    

    /**

     * 生成采购需求预测(月度)

     */

    public List<PurchaseForecastDTO> generateMonthlyForecast(LocalDate forecastMonth) {

<"n7.h4k7.org.cn"><"w9.h4k7.org.cn"><"g2.h4k7.org.cn">

        // 获取所有活跃原料

        List<Material> activeMaterials = materialRepo.findActiveMaterials();

        

        List<PurchaseForecastDTO> forecasts = new ArrayList<>();

        

        for (Material material : activeMaterials) {

            // 1. 基于历史消耗的趋势预测

            Double trendForecast = calculateTrendForecast(material.getId(), forecastMonth);

            

            // 2. 基于销售预测的关联预测

            Double salesBasedForecast = calculateSalesBasedForecast(material.getId(), forecastMonth);

            

            // 3. 考虑季节性因素

            Double seasonalFactor = getSeasonalFactor(material.getId(), forecastMonth.getMonthValue());

            

            // 4. 加权综合预测

            Double finalForecast = trendForecast * 0.6 

                    + salesBasedForecast * 0.3 

                    + (trendForecast * seasonalFactor) * 0.1;

            

            // 5. 考虑安全库存和当前库存

            Integer currentStock = inventoryRepo.getCurrentStock(material.getId());

            Integer safetyStock = material.getSafetyStock();

            Integer reorderPoint = material.getReorderPoint();

            

            // 计算建议采购量

            Integer suggestedQuantity = calculateSuggestedQuantity(

                    finalForecast.intValue(),

                    currentStock,

                    safetyStock,

                    material.getLeadTimeDays()

            );

            

            if (suggestedQuantity > 0) {

                PurchaseForecastDTO forecast = PurchaseForecastDTO.builder()

                        .materialId(material.getId())

                        .materialName(material.getName())

                        .materialCode(material.getCode())

                        .forecastMonth(forecastMonth)

                        .trendForecast(trendForecast)

                        .salesBasedForecast(salesBasedForecast)

                        .seasonalFactor(seasonalFactor)

                        .finalForecast(finalForecast)

                        .currentStock(currentStock)

                        .safetyStock(safetyStock)

                        .suggestedQuantity(suggestedQuantity)

                        .urgencyLevel(determineUrgency(currentStock, reorderPoint, suggestedQuantity))

                        .generatedAt(LocalDateTime.now())

                        .build();

                

                forecasts.add(forecast);

            }

        }

        

        // 按紧急程度排序

        forecasts.sort(Comparator.comparing(PurchaseForecastDTO::getUrgencyLevel).reversed());

        

        // 保存预测结果

        saveForecastResults(forecasts, forecastMonth);

        

        return forecasts;

    }

    

    /**

     * 趋势预测(移动平均法)

     */

    private Double calculateTrendForecast(Long materialId, LocalDate targetMonth) {

        // 获取过去12个月的消耗数据

        List<MonthlyConsumption> past12Months = consumptionRepo

                .findLast12Months(materialId, targetMonth.minusMonths(1));

        

        if (past12Months.size() < 6) {

            // 数据不足,返回最近月份的平均值

            return past12Months.stream()

                    .mapToDouble(MonthlyConsumption::getQuantity)

                    .average()

                    .orElse(0.0);

        }

        

        // 计算加权移动平均(最近月份权重更高)

        double totalWeight = 0;

        double weightedSum = 0;

        

        for (int i = 0; i < past12Months.size(); i++) {

            double weight = (i + 1) * 0.5; // 线性权重

            weightedSum += past12Months.get(i).getQuantity() * weight;

            totalWeight += weight;

        }

        

        return weightedSum / totalWeight;

    }

    

    /**

     * 确定采购紧急程度

     */

    private String determineUrgency(Integer currentStock, Integer reorderPoint, Integer suggestedQuantity) {

        double stockCoverageDays = currentStock / (suggestedQuantity / 30.0);

        

        if (currentStock <= reorderPoint * 0.5) {

            return "URGENT"; // 紧急:库存低于订货点50%

        } else if (stockCoverageDays < 15) {

            return "HIGH";   // 高:库存覆盖不足15天

        } else if (stockCoverageDays < 30) {

            return "MEDIUM"; // 中:库存覆盖不足30天

        } else {

            return "LOW";    // 低:库存充足

        }

    }

}

```


### 三、 系统特色与优化


1. **食品行业适配性**:

   - 原料保质期预警:自动预警近效期原料

   - 批次隔离管理:支持同一原料多批次独立管理

   - 供应商资质管理:食品生产许可证、检验报告等电子化管理


2. **流程自动化**:

   - 采购订单自动生成:根据预测和库存自动创建

   - 审批流程电子化:支持多级审批和移动端审批

   - 到货提醒:供应商发货后自动通知仓库


3. **数据安全保障**:

   - 采购价格数据权限控制:不同角色查看权限不同

   - 操作日志完整记录:所有关键操作可追溯

   - 数据定期备份:业务数据每日增量备份


4. **集成扩展能力**:

   - 与ERP系统对接:采购数据同步到企业ERP

   - 与财务系统集成:采购应付账款自动生成

   - 供应商门户:供应商自助查询订单和付款状态


### 四、 应用成效与价值


系统在东方红食品公司实施后,取得了显著的业务改进:


1. **采购效率提升**:采购订单处理时间从平均3天缩短至4小时,审批流程效率提升70%

2. **成本控制优化**:通过供应商比价和集中采购,原料采购成本平均降低8.5%

3. **库存周转改善**:库存周转率从5.2提升至7.8,库存资金占用减少25%

4. **质量管理强化**:原料不合格率从2.3%下降至0.8%,质量问题追溯时间从小时级缩短至分钟级

5. **供应商协同增强**:供应商准时交货率从85%提升至96%


系统特别在以下方面体现价值:

- **风险防控**:通过对供应商的动态评估,及时识别和淘汰不合格供应商

- **合规管理**:完整记录采购过程,满足食品行业监管要求

- **决策支持**:提供多维度的采购分析报表,支持管理层决策


未来扩展方向包括:

- 引入物联网技术,实现仓库温湿度监控与原料状态实时感知

- 开发供应商协同平台,实现订单状态实时共享

- 集成物流跟踪系统,实现运输过程可视化

- 应用机器学习算法,优化采购预测准确率


本系统通过Spring Boot框架的稳健性和扩展性,结合食品行业特殊需求,构建了一个专业化、智能化的采购管理平台。系统不仅实现了采购流程的数字化,更通过数据分析和智能预测,为食品企业的供应链优化提供了有力支持,帮助企业在保证食品安全和质量的前提下,实现采购成本的优化和运营效率的提升。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3109217/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2015-11-01

  • 博文量
    895
  • 访问量
    934144