package cn.iocoder.yudao.module.mes.service.wm.itemreceipt; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptLineDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemreceipt.MesWmItemReceiptMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.wm.MesWmItemReceiptStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService; import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService; import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService; import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeLineService; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO; 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.bpm.api.task.BpmProcessInstanceApi; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import jakarta.annotation.Resource; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * MES 采购入库单 Service 实现类 */ @Service @Validated @Slf4j public class MesWmItemReceiptServiceImpl implements MesWmItemReceiptService { @Resource private MesWmItemReceiptMapper itemReceiptMapper; @Resource private MesWmItemReceiptLineService itemReceiptLineService; @Resource private MesWmItemReceiptDetailService itemReceiptDetailService; @Resource @Lazy private MesWmArrivalNoticeService arrivalNoticeService; @Resource private MesMdVendorService vendorService; @Resource @Lazy private MesQcIqcService iqcService; @Resource private MesWmTransactionService wmTransactionService; @Resource private MesWmArrivalNoticeLineService arrivalNoticeLineService; @Resource private BpmProcessInstanceApi processInstanceApi; @Resource private ErpPurchaseOrderApi purchaseOrderApi; @Override public Long createItemReceipt(MesWmItemReceiptSaveReqVO createReqVO) { // 校验数据 validateItemReceiptSaveData(createReqVO); // 插入 MesWmItemReceiptDO receipt = BeanUtils.toBean(createReqVO, MesWmItemReceiptDO.class); receipt.setStatus(MesWmItemReceiptStatusEnum.PREPARE.getStatus()); itemReceiptMapper.insert(receipt); return receipt.getId(); } @Override public void updateItemReceipt(MesWmItemReceiptSaveReqVO updateReqVO) { // 校验存在 + 草稿状态 validateItemReceiptExistsAndDraft(updateReqVO.getId()); // 校验数据 validateItemReceiptSaveData(updateReqVO); // 更新 MesWmItemReceiptDO updateObj = BeanUtils.toBean(updateReqVO, MesWmItemReceiptDO.class); itemReceiptMapper.updateById(updateObj); } private void validateItemReceiptSaveData(MesWmItemReceiptSaveReqVO reqVO) { // 校验编码唯一 validateCodeUnique(reqVO.getId(), reqVO.getCode()); // 校验供应商存在 vendorService.validateVendorExistsAndEnable(reqVO.getVendorId()); // 校验到货通知单存在 if (reqVO.getNoticeId() != null) { MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(reqVO.getNoticeId()); if (ObjUtil.notEqual(notice.getVendorId(), reqVO.getVendorId())) { throw exception(WM_ARRIVAL_NOTICE_VENDOR_MISMATCH); } } // 校验来料检验单存在 if (reqVO.getIqcId() != null) { iqcService.validateIqcExists(reqVO.getIqcId()); } } @Override @Transactional(rollbackFor = Exception.class) public void deleteItemReceipt(Long id) { // 校验存在 + 草稿状态 validateItemReceiptExistsAndDraft(id); // 级联删除明细和行 itemReceiptDetailService.deleteItemReceiptDetailByReceiptId(id); itemReceiptLineService.deleteItemReceiptLineByReceiptId(id); // 删除 itemReceiptMapper.deleteById(id); } @Override public MesWmItemReceiptDO getItemReceipt(Long id) { return itemReceiptMapper.selectById(id); } @Override public PageResult getItemReceiptPage(MesWmItemReceiptPageReqVO pageReqVO) { return itemReceiptMapper.selectPage(pageReqVO); } @Override @Transactional(rollbackFor = Exception.class) public void submitItemReceipt(Long id) { // 校验存在 + 草稿状态 validateItemReceiptExistsAndDraft(id); // 校验至少有一条行 List lines = itemReceiptLineService.getItemReceiptLineListByReceiptId(id); if (CollUtil.isEmpty(lines)) { throw exception(WM_ITEM_RECEIPT_NO_LINE); } // 提交(草稿 → 待上架) itemReceiptMapper.updateById(new MesWmItemReceiptDO() .setId(id).setStatus(MesWmItemReceiptStatusEnum.APPROVING.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void stockItemReceipt(Long id) { // 校验存在 MesWmItemReceiptDO receipt = validateItemReceiptExists(id); if (ObjUtil.notEqual(MesWmItemReceiptStatusEnum.APPROVING.getStatus(), receipt.getStatus())) { throw exception(WM_ITEM_RECEIPT_STATUS_ERROR); } // 校验每行明细数量之和是否等于行入库数量 List lines = itemReceiptLineService.getItemReceiptLineListByReceiptId(id); if (CollUtil.isEmpty(lines)) { throw exception(WM_ITEM_RECEIPT_NO_LINE); } for (MesWmItemReceiptLineDO line : lines) { List details = itemReceiptDetailService.getItemReceiptDetailListByLineId(line.getId()); BigDecimal totalDetailQty = CollectionUtils.getSumValue(details, MesWmItemReceiptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO); if (line.getReceivedQuantity() == null || totalDetailQty.compareTo(line.getReceivedQuantity()) != 0) { throw exception(WM_ITEM_RECEIPT_DETAIL_QUANTITY_MISMATCH); } } // 执行上架(待上架 → 待入库) itemReceiptMapper.updateById(new MesWmItemReceiptDO() .setId(id).setStatus(MesWmItemReceiptStatusEnum.APPROVED.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void finishItemReceipt(Long id) { // 1. 校验存在 MesWmItemReceiptDO receipt = validateItemReceiptExists(id); if (ObjUtil.notEqual(MesWmItemReceiptStatusEnum.APPROVED.getStatus(), receipt.getStatus())) { throw exception(WM_ITEM_RECEIPT_STATUS_ERROR); } // 2. 遍历所有明细,创建库存事务(增加库存 + 记录流水) createTransactionList(receipt); // 3. 更新入库单状态 itemReceiptMapper.updateById(new MesWmItemReceiptDO() .setId(id).setStatus(MesWmItemReceiptStatusEnum.FINISHED.getStatus())); // 4. 更新关联的到货通知单状态 if (receipt.getNoticeId() != null) { arrivalNoticeService.finishArrivalNotice(receipt.getNoticeId()); } // 5. 回写 ERP 采购订单的入库数量 updateErpPurchaseOrderInCount(receipt); } /** * 回写 ERP 采购订单的入库数量 */ private void updateErpPurchaseOrderInCount(MesWmItemReceiptDO receipt) { // 获取入库单行 List lines = itemReceiptLineService.getItemReceiptLineListByReceiptId(receipt.getId()); if (CollUtil.isEmpty(lines)) { return; } // 用于存储采购订单 ID,避免重复更新 Long purchaseOrderId = null; // 遍历行,按 purchaseOrderItemId 更新入库数量 for (MesWmItemReceiptLineDO line : lines) { if (line.getPurchaseOrderItemId() != null && line.getReceivedQuantity() != null) { purchaseOrderApi.updateOrderItemInCount(line.getPurchaseOrderItemId(), line.getReceivedQuantity()); // 获取采购订单 ID(从第一个有效的明细项获取) if (purchaseOrderId == null) { cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO itemDTO = purchaseOrderApi.getPurchaseOrderItem(line.getPurchaseOrderItemId()).getCheckedData(); if (itemDTO != null && itemDTO.getOrderId() != null) { purchaseOrderId = itemDTO.getOrderId(); } } } } // 更新采购订单的入库状态 if (purchaseOrderId != null) { purchaseOrderApi.updateOrderInStatus(purchaseOrderId); } } private void createTransactionList(MesWmItemReceiptDO receipt) { List details = itemReceiptDetailService.getItemReceiptDetailListByReceiptId(receipt.getId()); wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO() .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId()) .setQuantity(detail.getQuantity()) // 入库数量为正数 .setBatchId(detail.getBatchId()) .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId()) .setVendorId(receipt.getVendorId()).setReceiptTime(receipt.getReceiptDate()) .setBizType(MesBizTypeConstants.WM_ITEM_RECEIPT_IN).setBizId(receipt.getId()) .setBizCode(receipt.getCode()).setBizLineId(detail.getLineId()))); } @Override @Transactional(rollbackFor = Exception.class) public void cancelItemReceipt(Long id) { // 校验存在 MesWmItemReceiptDO receipt = validateItemReceiptExists(id); // 已完成和已取消不允许取消 if (ObjectUtils.equalsAny(receipt.getStatus(), MesWmItemReceiptStatusEnum.FINISHED.getStatus(), MesWmItemReceiptStatusEnum.CANCELED.getStatus())) { throw exception(WM_ITEM_RECEIPT_CANCEL_NOT_ALLOWED); } // 取消 itemReceiptMapper.updateById(new MesWmItemReceiptDO() .setId(id).setStatus(MesWmItemReceiptStatusEnum.CANCELED.getStatus())); } @Override public MesWmItemReceiptDO validateItemReceiptEditable(Long id) { MesWmItemReceiptDO receipt = validateItemReceiptExists(id); if (ObjUtil.notEqual(receipt.getStatus(), MesWmItemReceiptStatusEnum.PREPARE.getStatus()) && ObjUtil.notEqual(receipt.getStatus(), MesWmItemReceiptStatusEnum.APPROVING.getStatus())) { throw exception(WM_ITEM_RECEIPT_STATUS_NOT_PREPARE); } return receipt; } private MesWmItemReceiptDO validateItemReceiptExists(Long id) { MesWmItemReceiptDO receipt = itemReceiptMapper.selectById(id); if (receipt == null) { throw exception(WM_ITEM_RECEIPT_NOT_EXISTS); } return receipt; } /** * 校验采购入库单存在且为草稿状态 */ private MesWmItemReceiptDO validateItemReceiptExistsAndDraft(Long id) { MesWmItemReceiptDO receipt = validateItemReceiptExists(id); if (ObjUtil.notEqual(MesWmItemReceiptStatusEnum.PREPARE.getStatus(), receipt.getStatus())) { throw exception(WM_ITEM_RECEIPT_STATUS_NOT_PREPARE); } return receipt; } private void validateCodeUnique(Long id, String code) { MesWmItemReceiptDO receipt = itemReceiptMapper.selectByCode(code); if (receipt == null) { return; } if (ObjUtil.notEqual(id, receipt.getId())) { throw exception(WM_ITEM_RECEIPT_CODE_DUPLICATE); } } @Override public Long getItemReceiptCountByVendorId(Long vendorId) { return itemReceiptMapper.selectCountByVendorId(vendorId); } @Override public List getItemReceiptListByVendorId(Long vendorId) { return itemReceiptMapper.selectListByVendorId(vendorId); } @Override public List getItemReceiptList(Collection ids) { return itemReceiptMapper.selectByIds(ids); } @Override @Transactional(rollbackFor = Exception.class) public Long generateItemReceiptFromArrivalNotice(Long arrivalNoticeId) { // 1. 校验到货通知单存在且状态为待入库 MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(arrivalNoticeId); // 2. 获取到货通知单行 List lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(arrivalNoticeId); if (CollUtil.isEmpty(lines)) { throw exception(WM_ARRIVAL_NOTICE_NO_LINE); } // 3. 生成入库单编码 String code = generateItemReceiptCode(); // 4. 创建入库单主表 MesWmItemReceiptDO receipt = new MesWmItemReceiptDO(); receipt.setCode(code); receipt.setName(notice.getName() + "入库单"); receipt.setNoticeId(arrivalNoticeId); receipt.setPurchaseOrderCode(notice.getPurchaseOrderCode()); receipt.setVendorId(notice.getVendorId()); receipt.setStatus(MesWmItemReceiptStatusEnum.PREPARE.getStatus()); itemReceiptMapper.insert(receipt); // 5. 遍历行创建入库单行 for (MesWmArrivalNoticeLineDO noticeLine : lines) { MesWmItemReceiptLineDO receiptLine = new MesWmItemReceiptLineDO(); receiptLine.setReceiptId(receipt.getId()); receiptLine.setArrivalNoticeLineId(noticeLine.getId()); receiptLine.setPurchaseOrderItemId(noticeLine.getPurchaseOrderItemId()); receiptLine.setItemId(noticeLine.getItemId()); // 使用合格数量(如果有 IQC 则是合格数量,否则是到货数量) receiptLine.setReceivedQuantity(noticeLine.getQualifiedQuantity() != null ? noticeLine.getQualifiedQuantity() : noticeLine.getArrivalQuantity()); itemReceiptLineService.createItemReceiptLineFromArrivalNoticeLine(receiptLine); } return receipt.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void submitItemReceiptApproval(Long id, String processDefinitionKey) { // 1. 校验存在 + 草稿状态 validateItemReceiptExistsAndDraft(id); // 2. 创建 BPM 流程实例 Long userId = getCurrentUserId(); String processInstanceId = processInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() .setProcessDefinitionKey(processDefinitionKey) .setBusinessKey(String.valueOf(id))); // 3. 更新入库单状态为审批中 itemReceiptMapper.updateById(new MesWmItemReceiptDO() .setId(id) .setStatus(MesWmItemReceiptStatusEnum.APPROVING.getStatus()) .setProcessInstanceId(processInstanceId)); } @Override @Transactional(rollbackFor = Exception.class) public void updateItemReceiptAuditStatus(Long id, Integer bpmResult) { // 1. 校验存在 MesWmItemReceiptDO receipt = validateItemReceiptExists(id); if (!MesWmItemReceiptStatusEnum.APPROVING.getStatus().equals(receipt.getStatus())) { log.warn("[updateItemReceiptAuditStatus] 入库单({}) 不处于审批中状态", id); return; } // 2. 根据审批结果更新状态 Integer newStatus = convertBpmResultToReceiptStatus(bpmResult); if (newStatus != null) { itemReceiptMapper.updateById(new MesWmItemReceiptDO() .setId(id).setStatus(newStatus)); } } /** * 转换 BPM 审批结果为入库单状态 */ private Integer convertBpmResultToReceiptStatus(Integer bpmResult) { if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) { return MesWmItemReceiptStatusEnum.APPROVED.getStatus(); } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) { return MesWmItemReceiptStatusEnum.PREPARE.getStatus(); } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) { return MesWmItemReceiptStatusEnum.CANCELED.getStatus(); } return null; } /** * 获取当前用户 ID */ private Long getCurrentUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.getPrincipal() instanceof Long) { return (Long) authentication.getPrincipal(); } return 1L; // 默认用户 } /** * 生成入库单编码 */ private String generateItemReceiptCode() { // TODO: 使用编码规则服务生成编码 return "IR" + System.currentTimeMillis(); } }