zhang_nuo
2026-09-20 3d950b8475cc52084cb40ed7a9373900f0606167
feat(production): 新增生产订单自动领料功能与订单号生成方法

- 新增 ProductionOrderPickService#autoPick 方法及实现,支持
批量生产订单自动领料,含BOM需求读取、已领料去重判断、
领料执行与成功/跳过/失败明细汇总,涉及
ProductionOrderPickService 及实现类
- 新增 ProductionOrderService#generateNextOrderNo 接口方法,
支持按指定日期生成下一个生产订单号
- 优化 ApplicationTypeEnum、RecordTypeEnum 枚举类,使用
Lombok @Getter 注解替换手写 getType 方法,简化代码
已修改10个文件
200 ■■■■■ 文件已修改
pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderPickService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -375,6 +375,12 @@
            <artifactId>thumbnailator</artifactId>
            <version>${thumbnailator.version}</version>
        </dependency>
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi</artifactId>
            <version>3.8.9</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java
@@ -1,5 +1,8 @@
package com.ruoyi.basic.enums;
import lombok.Getter;
@Getter
public enum ApplicationTypeEnum {
    IMAGE("image"),
    FILE("file"),
@@ -10,7 +13,6 @@
    private final String type;
    ApplicationTypeEnum(String type) { this.type = type; }
    public String getType() { return type; }
    /**
     * 根据 type 值获取对应的枚举实例
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -1,5 +1,8 @@
package com.ruoyi.basic.enums;
import lombok.Getter;
@Getter
public enum RecordTypeEnum {
    SHIPPING_INFO("shipping_info"),
    INSPECTION_TASK("inspection_task"),
@@ -200,7 +203,6 @@
    private final String type;
    RecordTypeEnum(String type) { this.type = type; }
    public String getType() { return type; }
    /**
     * 根据 type 值获取对应的枚举实例
src/main/java/com/ruoyi/production/service/ProductionOrderPickService.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.production.bean.dto.ProductionOrderPickDto;
import com.ruoyi.production.bean.vo.AutoPickResultVo;
import com.ruoyi.production.bean.vo.ProductionOrderPickVo;
import com.ruoyi.production.pojo.ProductionOrderPick;
@@ -22,4 +23,6 @@
    Boolean updatePick(ProductionOrderPickDto dto);
    List<ProductionOrderPickVo> listPickedDetail(Long productionOrderId);
    AutoPickResultVo autoPick(List<Long> orderIds);
}
src/main/java/com/ruoyi/production/service/ProductionOrderService.java
@@ -10,6 +10,7 @@
import com.ruoyi.production.bean.vo.ProductionOrderWorkOrderDetailVo;
import com.ruoyi.production.pojo.ProductionOrder;
import java.time.LocalDate;
import java.util.List;
public interface ProductionOrderService extends IService<ProductionOrder> {
@@ -35,4 +36,12 @@
    ProductionOrderWorkOrderDetailVo getWorkOrderReportInspectDetail(ProductionOrderDto productionOrderDto);
    int updateOrder(ProductionOrderDto productionOrderDto);
    /**
     * 按指定日期生成下一个生产订单号
     *
     * @param orderDate 订单日期,为 null 时使用当天
     * @return 生产订单号 SC+日期+4位序号
     */
    String generateNextOrderNo(LocalDate orderDate);
}
src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
@@ -9,6 +9,7 @@
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.production.bean.dto.ProductionOrderPickDto;
import com.ruoyi.production.bean.vo.AutoPickResultVo;
import com.ruoyi.production.bean.vo.ProductionOrderPickVo;
import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
import com.ruoyi.production.mapper.ProductionOrderMapper;
@@ -16,6 +17,7 @@
import com.ruoyi.production.mapper.ProductionOrderPickRecordMapper;
import com.ruoyi.production.pojo.ProductionOrder;
import com.ruoyi.production.pojo.ProductionOrderPick;
import com.ruoyi.production.service.ProductionOrderService;
import com.ruoyi.production.pojo.ProductionOrderPickRecord;
import com.ruoyi.production.service.ProductionOrderPickService;
import com.ruoyi.stock.dto.StockInventoryDto;
@@ -51,6 +53,7 @@
    private static final String FEED_RETURN_IN_RECORD_TYPE = StockInQualifiedRecordTypeEnum.FEED_RETURN_IN.getCode();
    private final ProductionOrderMapper productionOrderMapper;
    private final ProductionOrderService productionOrderService;
    private final ProductionOperationTaskMapper productionOperationTaskMapper;
    private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper;
    private final StockInventoryMapper stockInventoryMapper;
@@ -194,6 +197,147 @@
        return detailList;
    }
    @Override
    public AutoPickResultVo autoPick(List<Long> orderIds) {
        // 自动领料总流程:
        // 1) 遍历目标订单,读取BOM需求清单;
        // 2) 按 订单ID+规格ID+工序ID 去重判断是否已领料;
        // 3) 对未领料且有需求的物料,逐笔执行 savePick 领料;
        // 4) 汇总成功/跳过/失败明细并返回。
        AutoPickResultVo result = new AutoPickResultVo();
        if (orderIds == null || orderIds.isEmpty()) {
            return result;
        }
        result.setOrderTotal(orderIds.size());
        for (Long orderId : orderIds) {
            ProductionOrder order = productionOrderMapper.selectById(orderId);
            if (order == null) {
                result.setOrderFail(result.getOrderFail() + 1);
                AutoPickResultVo.ItemDetail failItem = new AutoPickResultVo.ItemDetail();
                failItem.setProductionOrderId(orderId);
                failItem.setReason("生产订单不存在");
                result.getFailList().add(failItem);
                continue;
            }
            // 获取BOM需求清单
            List<ProductionOrderPickVo> demandList = productionOrderService.pick(orderId);
            if (demandList == null || demandList.isEmpty()) {
                result.setOrderFail(result.getOrderFail() + 1);
                AutoPickResultVo.ItemDetail failItem = new AutoPickResultVo.ItemDetail();
                failItem.setProductionOrderId(orderId);
                failItem.setProductionOrderNo(order.getNpsNo());
                failItem.setReason("无BOM物料需求");
                result.getFailList().add(failItem);
                continue;
            }
            // 查询已领料记录(按 工序ID#规格ID 去重)
            Set<String> pickedKeys = list(Wrappers.<ProductionOrderPick>lambdaQuery()
                    .eq(ProductionOrderPick::getProductionOrderId, orderId)
                    .eq(ProductionOrderPick::getReturned, false)).stream()
                    .map(p -> (p.getTechnologyOperationId() == null ? "null" : p.getTechnologyOperationId())
                            + "#" + p.getProductModelId())
                    .collect(Collectors.toSet());
            boolean orderHasSuccess = false;
            boolean orderHasFail = false;
            for (ProductionOrderPickVo demand : demandList) {
                result.setMaterialTotal(result.getMaterialTotal() + 1);
                String key = (demand.getTechnologyOperationId() == null ? "null" : demand.getTechnologyOperationId())
                        + "#" + demand.getProductModelId();
                // 已领料的跳过
                if (pickedKeys.contains(key)) {
                    result.setMaterialSkip(result.getMaterialSkip() + 1);
                    AutoPickResultVo.ItemDetail skipItem = new AutoPickResultVo.ItemDetail();
                    skipItem.setProductionOrderId(orderId);
                    skipItem.setProductionOrderNo(order.getNpsNo());
                    skipItem.setProductModelId(demand.getProductModelId());
                    skipItem.setTechnologyOperationId(demand.getTechnologyOperationId());
                    skipItem.setOperationName(demand.getOperationName());
                    skipItem.setDemandedQuantity(demand.getDemandedQuantity());
                    skipItem.setReason("已领料");
                    result.getSkipList().add(skipItem);
                    continue;
                }
                BigDecimal demandQty = demand.getDemandedQuantity();
                if (demandQty == null || demandQty.compareTo(BigDecimal.ZERO) <= 0) {
                    result.setMaterialSkip(result.getMaterialSkip() + 1);
                    AutoPickResultVo.ItemDetail skipItem = new AutoPickResultVo.ItemDetail();
                    skipItem.setProductionOrderId(orderId);
                    skipItem.setProductionOrderNo(order.getNpsNo());
                    skipItem.setProductModelId(demand.getProductModelId());
                    skipItem.setTechnologyOperationId(demand.getTechnologyOperationId());
                    skipItem.setOperationName(demand.getOperationName());
                    skipItem.setDemandedQuantity(demandQty);
                    skipItem.setReason("需求数量为0或空");
                    result.getSkipList().add(skipItem);
                    continue;
                }
                // 执行领料
                try {
                    ProductionOrderPickDto dto = new ProductionOrderPickDto();
                    dto.setProductionOrderId(orderId);
                    dto.setProductModelId(demand.getProductModelId());
                    dto.setPickQuantity(demandQty);
                    dto.setPickType((byte) 1); // 1=正常领料
                    dto.setBatchNoList(demand.getBatchNoList());
                    dto.setTechnologyOperationId(demand.getTechnologyOperationId());
                    dto.setOperationName(demand.getOperationName());
                    dto.setDemandedQuantity(demand.getDemandedQuantity());
                    dto.setBom(demand.getBom());
                    dto.setRemark("自动领料");
                    savePick(dto);
                    result.setMaterialSuccess(result.getMaterialSuccess() + 1);
                    orderHasSuccess = true;
                    AutoPickResultVo.ItemDetail successItem = new AutoPickResultVo.ItemDetail();
                    successItem.setProductionOrderId(orderId);
                    successItem.setProductionOrderNo(order.getNpsNo());
                    successItem.setProductModelId(demand.getProductModelId());
                    successItem.setTechnologyOperationId(demand.getTechnologyOperationId());
                    successItem.setOperationName(demand.getOperationName());
                    successItem.setDemandedQuantity(demandQty);
                    successItem.setPickQuantity(demandQty);
                    result.getSuccessList().add(successItem);
                } catch (Exception e) {
                    result.setMaterialFail(result.getMaterialFail() + 1);
                    orderHasFail = true;
                    AutoPickResultVo.ItemDetail failItem = new AutoPickResultVo.ItemDetail();
                    failItem.setProductionOrderId(orderId);
                    failItem.setProductionOrderNo(order.getNpsNo());
                    failItem.setProductModelId(demand.getProductModelId());
                    failItem.setTechnologyOperationId(demand.getTechnologyOperationId());
                    failItem.setOperationName(demand.getOperationName());
                    failItem.setDemandedQuantity(demandQty);
                    failItem.setReason(e.getMessage());
                    result.getFailList().add(failItem);
                }
            }
            if (orderHasSuccess) {
                result.setOrderSuccess(result.getOrderSuccess() + 1);
            }
            if (orderHasFail) {
                result.setOrderFail(result.getOrderFail() + 1);
            }
            // 如果既没有成功也没有失败(全跳过),订单不算失败也不算成功
            if (!orderHasSuccess && !orderHasFail) {
                result.setOrderSuccess(result.getOrderSuccess() + 1);
            }
        }
        return result;
    }
    private void processDeletePickIds(ProductionOrderPickDto rootDto,
                                      Map<Long, ProductionOrderPick> existingPickMap,
                                      Long productionOrderId) {
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -441,8 +441,20 @@
    }
    private String generateNextOrderNo() {
        // 生成下一个生产订单号
        String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        // 生成下一个生产订单号(默认按当天日期)
        return generateNextOrderNo(LocalDate.now());
    }
    /**
     * 按指定日期生成下一个生产订单号
     *
     * @param orderDate 订单日期(用于生成日期前缀),为 null 时使用当天
     */
    public String generateNextOrderNo(LocalDate orderDate) {
        if (orderDate == null) {
            orderDate = LocalDate.now();
        }
        String datePrefix = orderDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String prefix = "SC" + datePrefix;
        ProductionOrder latestOrder = this.getOne(Wrappers.<ProductionOrder>lambdaQuery()
                .likeRight(ProductionOrder::getNpsNo, prefix)
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
@@ -151,6 +151,10 @@
        productionOrder.setProductModelId(firstPlan.getProductModelId());
        productionOrder.setQuantity(assignedQuantity);
        productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime());
        // 生产订单号按主生产计划的需求日期生成
        ProductionPlan primaryPlan = this.getById(firstPlan.getId());
        LocalDate orderNoDate = primaryPlan != null ? primaryPlan.getRequiredDate() : null;
        productionOrder.setNpsNo(productionOrderService.generateNextOrderNo(orderNoDate));
        if (!productionOrderService.saveProductionOrder(productionOrder)) {
            throw new ServiceException("下发失败,生产订单保存失败");
        }
src/main/resources/application-dev.yml
@@ -73,7 +73,7 @@
    druid:
      # 主库数据源
      master:
        url: jdbc:mysql://localhost:3306/product-inventory-management?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        url: jdbc:mysql://localhost:3306/product-inventory-management-rfsy?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      # 从库数据源
src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml
@@ -89,14 +89,10 @@
    <select id="existsByStockInRecordId" resultType="java.lang.Boolean">
         SELECT COUNT(*) > 0
        FROM account_payment_application
        WHERE status != 2
        <if test="stockInRecordIds != null and stockInRecordIds.size() > 0">
            AND (
            <foreach collection="stockInRecordIds" item="id" separator=" OR ">
        WHERE status != 2 AND
        <foreach collection="stockInRecordIds" item="id" open="(" separator=" OR " close=")">
                FIND_IN_SET(#{id}, stock_in_record_ids)
            </foreach>
            )
        </if>
    </select>
</mapper>