yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.mes.service.pro.feedback; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -33,6 +32,7 @@ import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService; import cn.iocoder.yudao.module.mes.service.wm.productissue.MesWmProductIssueService; import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService; import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService; import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; @@ -92,6 +92,8 @@ private MesProTaskService taskService; @Resource private MesWmItemConsumeService itemConsumeService; @Resource private MesWmProductIssueService productIssueService; @Resource private MesWmProductProduceService productProduceService; @Resource @@ -274,9 +276,9 @@ // 3. 物料消耗:倒冲工序按 BOM 自动扣料;非倒冲工序跳过(物料走手工领料/投料) boolean backflushFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getBackflushFlag()); if (backflushFlag) { // 3.1 校验倒冲工序必须配置 BOM 消耗,避免静默不扣料 List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId( feedback.getWorkOrderId()); // 3.1 校验当前倒冲工序必须配置投料 BOM,避免静默不扣料 List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderIdAndProcessId( feedback.getWorkOrderId(), feedback.getProcessId()); if (CollUtil.isEmpty(boms)) { throw exception(PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED); } @@ -284,6 +286,8 @@ MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback); if (itemConsume != null) { itemConsumeService.finishItemConsume(itemConsume.getId()); // 消耗单负责实际扣库;领料单仅作为倒冲业务凭证,避免重复扣减库存 productIssueService.generateBackflushProductIssue(feedback, itemConsume); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomService.java
@@ -63,6 +63,15 @@ List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderId(Long workOrderId); /** * 根据工单编号与工序编号获得 BOM 列表(倒冲仅处理当前报工工序的投料) * * @param workOrderId 工单编号 * @param processId 工序编号 * @return BOM 列表 */ List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderIdAndProcessId(Long workOrderId, Long processId); /** * 根据工单编号删除 BOM * * @param workOrderId 工单编号 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomServiceImpl.java
@@ -116,6 +116,11 @@ } @Override public List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderIdAndProcessId(Long workOrderId, Long processId) { return workOrderBomMapper.selectListByWorkOrderIdAndProcessId(workOrderId, processId); } @Override public void deleteWorkOrderBomByWorkOrderId(Long workOrderId) { workOrderBomMapper.deleteByWorkOrderId(workOrderId); } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java
@@ -78,9 +78,9 @@ if (feedback.getWorkOrderId() == null) { throw exception(PRO_FEEDBACK_ROUTE_PROCESS_INVALID); } // 1.2 查询工单 BOM 物料列表(倒冲按工单投料,非工艺路线) List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId( feedback.getWorkOrderId()); // 1.2 只查询当前倒冲工序的投料 BOM,避免重复扣减其他工序的物料 List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderIdAndProcessId( feedback.getWorkOrderId(), feedback.getProcessId()); if (CollUtil.isEmpty(boms)) { return null; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueService.java
@@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssueImportRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssuePageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssueSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO; import jakarta.validation.Valid; @@ -24,6 +26,17 @@ Long createProductIssue(@Valid MesWmProductIssueSaveReqVO createReqVO); /** * 根据倒冲消耗记录生成已完成的生产领料单。 * * <p>该方法只生成领料业务凭证,不再创建库存事务;实际扣库由物料消耗单完成。</p> * * @param feedback 报工记录 * @param consume 物料消耗记录 * @return 领料单编号 */ Long generateBackflushProductIssue(MesProFeedbackDO feedback, MesWmItemConsumeDO consume); /** * 修改领料出库单 * * @param updateReqVO 修改信息 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
@@ -12,12 +12,18 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssuePageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssueSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskIssueDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeLineDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueLineDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskIssueMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueDetailMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueLineMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductIssueStatusEnum; @@ -29,6 +35,8 @@ import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskIssueService; import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeDetailService; import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeLineService; import cn.iocoder.yudao.module.mes.service.wm.stockreserve.MesWmStockReserveService; import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; @@ -63,11 +71,19 @@ @Resource private MesWmProductIssueMapper issueMapper; @Resource private MesWmProductIssueLineMapper issueLineMapper; @Resource private MesWmProductIssueDetailMapper issueDetailMapper; @Resource private MesWmProductIssueLineService issueLineService; @Resource private MesWmProductIssueDetailService issueDetailService; @Resource private MesWmItemConsumeLineService itemConsumeLineService; @Resource private MesWmItemConsumeDetailService itemConsumeDetailService; @Resource private MesMdWorkstationService workstationService; @Resource @@ -118,6 +134,70 @@ @Override @Transactional(rollbackFor = Exception.class) public Long generateBackflushProductIssue(MesProFeedbackDO feedback, MesWmItemConsumeDO consume) { String code = "AUTO-BC-" + consume.getId(); MesWmProductIssueDO existing = issueMapper.selectByCode(code); if (existing != null) { return existing.getId(); } LocalDateTime now = LocalDateTime.now(); MesWmProductIssueDO issue = MesWmProductIssueDO.builder() .code(code) .name("倒冲自动领料-" + feedback.getCode()) .taskId(feedback.getTaskId()) .workOrderId(feedback.getWorkOrderId()) .workstationId(feedback.getWorkstationId()) .issueType(MesWmProductIssueTypeEnum.NORMAL.getType()) .issueDate(now) .requiredTime(now) .status(MesWmProductIssueStatusEnum.FINISHED.getStatus()) .remark("工序倒冲自动生成,物料消耗单ID:" + consume.getId()) .build(); issueMapper.insert(issue); List<MesWmItemConsumeLineDO> consumeLines = itemConsumeLineService .getItemConsumeLineListByConsumeId(consume.getId()); List<MesWmItemConsumeDetailDO> consumeDetails = itemConsumeDetailService .getItemConsumeDetailListByConsumeId(consume.getId()); Map<Long, List<MesWmItemConsumeDetailDO>> detailsByLineId = CollectionUtils.convertMultiMap( consumeDetails, MesWmItemConsumeDetailDO::getLineId); for (MesWmItemConsumeLineDO consumeLine : consumeLines) { MesWmProductIssueLineDO issueLine = MesWmProductIssueLineDO.builder() .issueId(issue.getId()) .taskId(feedback.getTaskId()) .itemId(consumeLine.getItemId()) .quantity(consumeLine.getQuantity()) .remark("工序倒冲自动生成") .build(); issueLineMapper.insert(issueLine); for (MesWmItemConsumeDetailDO consumeDetail : detailsByLineId .getOrDefault(consumeLine.getId(), List.of())) { MesMdItemDO item = mdmItemService.getItem(consumeDetail.getItemId()); Long mdmItemId = item != null && item.getMdmItemId() != null ? item.getMdmItemId() : consumeDetail.getItemId(); issueDetailMapper.insert(MesWmProductIssueDetailDO.builder() .issueId(issue.getId()) .lineId(issueLine.getId()) .materialStockId(consumeDetail.getMaterialStockId()) .itemId(mdmItemId) .quantity(consumeDetail.getQuantity()) .batchId(consumeDetail.getBatchId()) .batchCode(consumeDetail.getBatchCode()) .warehouseId(consumeDetail.getWarehouseId()) .locationId(consumeDetail.getLocationId()) .areaId(consumeDetail.getAreaId()) .remark("工序倒冲自动生成") .build()); } } return issue.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateProductIssue(MesWmProductIssueSaveReqVO updateReqVO) { // 1.1 校验存在 + 准备中状态 validateProductIssueExistsAndPrepare(updateReqVO.getId()); yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; 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.batch.MesWmBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; @@ -21,7 +22,9 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService; import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; import cn.iocoder.yudao.module.mes.service.wm.productreceipt.MesWmProductReceiptService; import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; @@ -57,6 +60,10 @@ @Resource private MesProWorkOrderService workOrderService; @Resource private MesProWorkOrderProcessService workOrderProcessService; @Resource private MesWmProductReceiptService productReceiptService; @Resource private MesWmBatchService batchService; @Resource private MesWmTransactionService wmTransactionService; @@ -71,7 +78,7 @@ @Transactional(rollbackFor = Exception.class) public void finishProductProduce(Long id) { // 1.1 校验存在 + 草稿状态 validateProductProduceExistsAndPrepare(id); MesWmProductProduceDO produce = validateProductProduceExistsAndPrepare(id); // 1.2 校验至少有一条行 List<MesWmProductProduceLineDO> lines = productProduceLineService.getProductProduceLineListByProduceId(id); if (CollUtil.isEmpty(lines)) { @@ -84,6 +91,13 @@ // 3. 更新入库单状态 productProduceMapper.updateById(new MesWmProductProduceDO() .setId(id).setStatus(MesWmProductProduceStatusEnum.FINISHED.getStatus())); // 4. 只有末道工序的合格产出才生成产品入库单,避免中间工序重复生成成品入库 MesProWorkOrderProcessDO lastProcess = workOrderProcessService .getLastWorkOrderProcessByWorkOrderId(produce.getWorkOrderId()); if (lastProcess != null && ObjUtil.equal(lastProcess.getProcessId(), produce.getProcessId())) { productReceiptService.generateProductReceipt(produce, lines); } } private void createTransactionList(Long produceId, List<MesWmProductProduceLineDO> lines) { yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptService.java
@@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptImportRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptDO; import jakarta.validation.Valid; @@ -24,6 +26,16 @@ Long createProductReceipt(@Valid MesWmProductReceiptSaveReqVO createReqVO); /** * 根据末道工序的产品产出生成产品入库单草稿及合格品行。 * * @param produce 产品产出单 * @param produceLines 产品产出行 * @return 产品入库单编号;没有合格品时返回 {@code null} */ Long generateProductReceipt(MesWmProductProduceDO produce, List<MesWmProductProduceLineDO> produceLines); /** * 修改产品收货单 * * @param updateReqVO 修改信息 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java
@@ -11,7 +11,11 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptImportRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptLineDO; @@ -19,10 +23,14 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptLineMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductReceiptStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; @@ -34,6 +42,7 @@ import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -51,6 +60,8 @@ @Resource private MesWmProductReceiptMapper productReceiptMapper; @Resource private MesWmProductReceiptLineMapper productReceiptLineMapper; @Resource private MesWmProductReceiptLineService productReceiptLineService; @@ -60,6 +71,10 @@ private MesWmTransactionService wmTransactionService; @Resource private MesProWorkOrderService workOrderService; @Resource private MesMdItemService mdItemService; @Resource private MesWmMaterialStockService materialStockService; @Resource private MesWmWarehouseService warehouseService; @Resource @@ -83,6 +98,68 @@ } @Override @Transactional(rollbackFor = Exception.class) public Long generateProductReceipt(MesWmProductProduceDO produce, List<MesWmProductProduceLineDO> produceLines) { List<MesWmProductProduceLineDO> qualifiedLines = produceLines.stream() .filter(line -> ObjUtil.equal(line.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus())) .filter(line -> line.getQuantity() != null && line.getQuantity().compareTo(BigDecimal.ZERO) > 0) .toList(); if (CollUtil.isEmpty(qualifiedLines)) { return null; } String code = "AUTO-PR-" + produce.getId(); MesWmProductReceiptDO existing = productReceiptMapper.selectByCode(code); if (existing != null) { return existing.getId(); } MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(produce.getWorkOrderId()); MesMdItemDO firstItem = mdItemService.getItem(qualifiedLines.get(0).getItemId()); Long firstMdmItemId = firstItem != null && firstItem.getMdmItemId() != null ? firstItem.getMdmItemId() : qualifiedLines.get(0).getItemId(); String workOrderCode = workOrder != null ? workOrder.getCode() : String.valueOf(produce.getWorkOrderId()); MesWmProductReceiptDO receipt = MesWmProductReceiptDO.builder() .code(code) .name("生产完成自动入库-" + workOrderCode) .workOrderId(produce.getWorkOrderId()) .itemId(firstMdmItemId) .receiptDate(LocalDateTime.now()) .status(MesWmProductReceiptStatusEnum.PREPARE.getStatus()) .remark("末道工序产出自动生成,产出单ID:" + produce.getId()) .build(); productReceiptMapper.insert(receipt); MesWmWarehouseDO virtualWarehouse = warehouseService.getWarehouseByCode( MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE); MesWmWarehouseLocationDO virtualLocation = locationService.getWarehouseLocationByCode( MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION); MesWmWarehouseAreaDO virtualArea = areaService.getWarehouseAreaByCode( MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA); for (MesWmProductProduceLineDO produceLine : qualifiedLines) { MesMdItemDO item = mdItemService.getItem(produceLine.getItemId()); Long mdmItemId = item != null && item.getMdmItemId() != null ? item.getMdmItemId() : produceLine.getItemId(); MesWmMaterialStockDO stock = materialStockService.getMaterialStockByCompositeKey( mdmItemId, virtualWarehouse.getId(), virtualLocation.getId(), virtualArea.getId(), produceLine.getBatchId(), null); productReceiptLineMapper.insert(MesWmProductReceiptLineDO.builder() .receiptId(receipt.getId()) .itemId(mdmItemId) .materialStockId(stock != null ? stock.getId() : null) .quantity(produceLine.getQuantity()) .batchId(produceLine.getBatchId()) .batchCode(produceLine.getBatchCode()) .remark("末道工序合格品自动生成") .build()); } return receipt.getId(); } @Override public void updateProductReceipt(MesWmProductReceiptSaveReqVO updateReqVO) { // 1.1 校验存在 + 草稿状态 validateProductReceiptExistsAndDraft(updateReqVO.getId());