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.*; /** * MES 装箱明细 Service 实现类 * * @author 超级管理员 */ @Service @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; @Resource @Lazy private MesWmPackageService packageService; @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) { // 校验数据 int boxCount = validateLineSaveData(createReqVO); // 插入 MesWmPackageLineDO line = BeanUtils.toBean(createReqVO, MesWmPackageLineDO.class); line.setBoxCount(boxCount); packageLineMapper.insert(line); return line.getId(); } @Override public void updatePackageLine(MesWmPackageLineSaveReqVO updateReqVO) { // 校验存在 MesWmPackageLineDO line = validatePackageLineExists(updateReqVO.getId()); // 校验数据 updateReqVO.setPackageId(line.getPackageId()); int boxCount = validateLineSaveData(updateReqVO); // 更新 MesWmPackageLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmPackageLineDO.class); updateObj.setBoxCount(boxCount); packageLineMapper.updateById(updateObj); } @Override public void deletePackageLine(Long id) { // 校验存在 MesWmPackageLineDO line = validatePackageLineExists(id); // 校验装箱单状态为草稿 packageService.validatePackageStatusDraft(line.getPackageId()); // 删除 packageLineMapper.deleteById(id); } @Override public MesWmPackageLineDO getPackageLine(Long id) { return packageLineMapper.selectById(id); } @Override public PageResult getPackageLinePage(MesWmPackageLinePageReqVO pageReqVO) { if (pageReqVO.getPackageId() == null) { return PageResult.empty(); } List packageIds = packageService.getPackageAndDescendantIds(pageReqVO.getPackageId()); return packageLineMapper.selectPage(pageReqVO, packageIds); } @Override public void deletePackageLineByPackageId(Long packageId) { packageLineMapper.deleteByPackageId(packageId); } // ========== 校验方法 ========== /** * 校验保存时的关联数据,并返回按 数量 / 每箱数量 向上取整计算的箱数 */ private int validateLineSaveData(MesWmPackageLineSaveReqVO reqVO) { // 校验装箱单状态为草稿 packageService.validatePackageStatusDraft(reqVO.getPackageId()); // 校验产品物料存在 mdmItemService.validateItemExistsAndEnable(reqVO.getItemId()); // 校验物料档案:必须属于成品分类,且已维护每箱数量 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 processIds = workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrder.getId()) .stream() .map(MesProWorkOrderProcessDO::getProcessId) .filter(Objects::nonNull) .distinct() .toList(); List 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) { MesWmPackageLineDO line = packageLineMapper.selectById(id); if (line == null) { throw exception(WM_PACKAGE_LINE_NOT_EXISTS); } return line; } }