yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageLineServiceImpl.java
@@ -1,22 +1,38 @@
package cn.iocoder.yudao.module.mes.service.wm.packages;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.packages.vo.line.MesWmPackageLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.packages.vo.line.MesWmPackageLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.packages.MesWmPackageLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.packages.MesWmPackageLineMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PACKAGE_LINE_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
 * MES 装箱明细 Service 实现类
@@ -27,6 +43,9 @@
@Validated
public class MesWmPackageLineServiceImpl implements MesWmPackageLineService {
    private static final String ASSEMBLY_PROCESS_CODE = "PRO-ASSEMBLY";
    private static final String ASSEMBLY_PROCESS_NAME = "组装";
    @Resource
    private MesWmPackageLineMapper packageLineMapper;
@@ -36,15 +55,26 @@
    @Resource
    private MesMdmItemService mdmItemService;
    @Resource
    private MesMdItemService mdItemService;
    @Resource
    private MesMdItemTypeService itemTypeService;
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
    private MesProWorkOrderProcessService workOrderProcessService;
    @Resource
    private MesProProcessService processService;
    @Resource
    private MesProFeedbackMapper feedbackMapper;
    @Override
    public Long createPackageLine(MesWmPackageLineSaveReqVO createReqVO) {
        // 校验数据
        validateLineSaveData(createReqVO);
        int boxCount = validateLineSaveData(createReqVO);
        // 插入
        MesWmPackageLineDO line = BeanUtils.toBean(createReqVO, MesWmPackageLineDO.class);
        line.setBoxCount(boxCount);
        packageLineMapper.insert(line);
        return line.getId();
    }
@@ -55,10 +85,11 @@
        MesWmPackageLineDO line = validatePackageLineExists(updateReqVO.getId());
        // 校验数据
        updateReqVO.setPackageId(line.getPackageId());
        validateLineSaveData(updateReqVO);
        int boxCount = validateLineSaveData(updateReqVO);
        // 更新
        MesWmPackageLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmPackageLineDO.class);
        updateObj.setBoxCount(boxCount);
        packageLineMapper.updateById(updateObj);
    }
@@ -95,15 +126,87 @@
    // ========== 校验方法 ==========
    /**
     * 校验保存时的关联数据
     * 校验保存时的关联数据,并返回按 数量 / 每箱数量 向上取整计算的箱数
     */
    private void validateLineSaveData(MesWmPackageLineSaveReqVO reqVO) {
    private int validateLineSaveData(MesWmPackageLineSaveReqVO reqVO) {
        // 校验装箱单状态为草稿
        packageService.validatePackageStatusDraft(reqVO.getPackageId());
        // 校验产品物料存在
        mdmItemService.validateItemExistsAndEnable(reqVO.getItemId());
        // 校验工单已确认
        workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
        // 校验物料档案:必须属于成品分类,且已维护每箱数量
        MesMdItemDO item = mdItemService.getItem(reqVO.getItemId());
        if (item == null) {
            throw exception(WM_PACKAGE_LINE_BOX_QUANTITY_NOT_CONFIGURED, "ID=" + reqVO.getItemId());
        }
        String itemName = StrUtil.isNotBlank(item.getName()) ? item.getName() : item.getCode();
        MesMdItemTypeDO itemType = item.getItemTypeId() != null
                ? itemTypeService.getItemTypeMap(Collections.singleton(item.getItemTypeId()))
                        .get(item.getItemTypeId())
                : null;
        if (itemType == null || !Boolean.TRUE.equals(itemType.getFinishedFlag())) {
            throw exception(WM_PACKAGE_LINE_ITEM_NOT_FINISHED, itemName);
        }
        if (item.getBoxQuantity() == null || item.getBoxQuantity().compareTo(BigDecimal.ZERO) <= 0) {
            throw exception(WM_PACKAGE_LINE_BOX_QUANTITY_NOT_CONFIGURED, itemName);
        }
        // 校验工单已确认,且装箱物料必须为工单的产品
        MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
        if (ObjUtil.notEqual(reqVO.getItemId(), workOrder.getProductId())) {
            MesMdItemDO product = workOrder.getProductId() != null
                    ? mdItemService.getItem(workOrder.getProductId()) : null;
            String productName = product != null
                    ? (StrUtil.isNotBlank(product.getName()) ? product.getName() : product.getCode())
                    : "ID=" + workOrder.getProductId();
            throw exception(WM_PACKAGE_LINE_WORK_ORDER_PRODUCT_MISMATCH,
                    itemName, workOrder.getCode(), productName);
        }
        // 校验同一工单累计装箱数量不超过工单数量(草稿与已完成的装箱单都计入)
        BigDecimal plannedQuantity = packageLineMapper.selectListByWorkOrderId(workOrder.getId()).stream()
                .filter(line -> ObjUtil.notEqual(line.getId(), reqVO.getId()))
                .map(MesWmPackageLineDO::getQuantity)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalQuantity = plannedQuantity.add(reqVO.getQuantity());
        if (workOrder.getQuantity() != null && totalQuantity.compareTo(workOrder.getQuantity()) > 0) {
            throw exception(WM_PACKAGE_LINE_WORK_ORDER_QUANTITY_EXCEED,
                    workOrder.getCode(), workOrder.getQuantity(), plannedQuantity, reqVO.getQuantity());
        }
        // 校验组装工序已完成报工:组装合格数量必须覆盖累计装箱数量
        validateAssemblyCompleted(workOrder, plannedQuantity, reqVO.getQuantity(), totalQuantity);
        // 箱数 = 数量 / 每箱数量,向上取整
        return reqVO.getQuantity().divide(item.getBoxQuantity(), 0, RoundingMode.CEILING).intValue();
    }
    /**
     * 校验工单的【组装】工序已完成报工,且合格数量不少于累计装箱数量
     */
    private void validateAssemblyCompleted(MesProWorkOrderDO workOrder, BigDecimal packedQuantity,
                                           BigDecimal currentQuantity, BigDecimal totalQuantity) {
        List<Long> processIds = workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrder.getId())
                .stream()
                .map(MesProWorkOrderProcessDO::getProcessId)
                .filter(Objects::nonNull)
                .distinct()
                .toList();
        List<Long> assemblyProcessIds = processService.getProcessMap(processIds).values().stream()
                .filter(process -> ASSEMBLY_PROCESS_CODE.equalsIgnoreCase(process.getCode())
                        || ASSEMBLY_PROCESS_NAME.equals(process.getName()))
                .map(MesProProcessDO::getId)
                .toList();
        if (assemblyProcessIds.isEmpty()) {
            throw exception(WM_PACKAGE_LINE_ASSEMBLY_PROCESS_NOT_FOUND, workOrder.getCode());
        }
        BigDecimal assembledQuantity = feedbackMapper.selectFinishedQualifiedQuantitySum(
                workOrder.getId(), assemblyProcessIds);
        if (assembledQuantity.compareTo(totalQuantity) < 0) {
            throw exception(WM_PACKAGE_LINE_ASSEMBLY_NOT_COMPLETED,
                    workOrder.getCode(), assembledQuantity, totalQuantity, packedQuantity, currentQuantity);
        }
    }
    private MesWmPackageLineDO validatePackageLineExists(Long id) {