| | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum; |
| | | import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi; |
| | | import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi; |
| | | import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO; |
| | | import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | |
| | | |
| | | @Resource |
| | | private MesWmArrivalNoticeLineService arrivalNoticeLineService; |
| | | |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService vendorService; |
| | | private ErpSupplierApi supplierApi; |
| | | |
| | | @Resource |
| | | private ErpPurchaseOrderApi purchaseOrderApi; |
| | | |
| | | @Resource |
| | | private MesMdItemService mdItemService; |
| | | private MesMdmItemService mdmItemService; |
| | | |
| | | @Resource |
| | | @Lazy |
| | | private MesQcIqcService iqcService; |
| | | |
| | | @Override |
| | | public Long createArrivalNotice(MesWmArrivalNoticeSaveReqVO createReqVO) { |
| | |
| | | private void validateArrivalNoticeSaveData(MesWmArrivalNoticeSaveReqVO reqVO) { |
| | | // 校验编码唯一 |
| | | validateCodeUnique(reqVO.getId(), reqVO.getCode()); |
| | | // 校验供应商存在且启用 |
| | | vendorService.validateVendorExistsAndEnable(reqVO.getVendorId()); |
| | | // 校验供应商存在(ERP 采购供应商) |
| | | supplierApi.validateSupplier(reqVO.getVendorId()); |
| | | // 校验采购订单存在 |
| | | if (reqVO.getPurchaseOrderId() != null) { |
| | | ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId()).getCheckedData(); |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void submitArrivalNotice(Long id) { |
| | | // 1.1 校验存在 + 草稿状态 |
| | | validateArrivalNoticeExistsAndDraft(id); |
| | | MesWmArrivalNoticeDO notice = validateArrivalNoticeExistsAndDraft(id); |
| | | // 1.2 检查是否有行项目 |
| | | List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id); |
| | | if (CollUtil.isEmpty(lines)) { |
| | |
| | | boolean needCheck = CollectionUtils.anyMatch(lines, |
| | | line -> Boolean.TRUE.equals(line.getIqcCheckFlag())); |
| | | if (needCheck) { |
| | | // 需要检验,进入待质检 |
| | | // 2.1 需要检验,进入待质检 |
| | | arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO() |
| | | .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus())); |
| | | // 2.2 为需要质检的行自动创建 IQC 检验单 |
| | | for (MesWmArrivalNoticeLineDO line : lines) { |
| | | if (Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null) { |
| | | try { |
| | | Long iqcId = iqcService.createIqcFromArrivalNotice( |
| | | id, line, notice.getCode(), notice.getVendorId()); |
| | | // 更新行上的 iqcId |
| | | arrivalNoticeLineService.updateArrivalNoticeLineIqcId(line.getId(), iqcId); |
| | | log.info("[submitArrivalNotice][到货通知单({}) 行({}) 自动创建 IQC 检验单({})]", |
| | | id, line.getId(), iqcId); |
| | | } catch (Exception e) { |
| | | log.warn("[submitArrivalNotice][到货通知单({}) 行({}) 创建 IQC 失败: {}]", |
| | | id, line.getId(), e.getMessage()); |
| | | // 继续处理其他行,不中断流程 |
| | | } |
| | | } |
| | | } |
| | | return; |
| | | } |
| | | // 不需要检验,直接进入待入库 |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, BigDecimal qualifiedQuantity) { |
| | | public void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity) { |
| | | // 1.1 校验到货通知单存在 |
| | | MesWmArrivalNoticeDO notice = validateArrivalNoticeExists(id); |
| | | // 1.2 校验状态为待质检 |
| | |
| | | throw exception(WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_QC); |
| | | } |
| | | |
| | | // 2. 更新到货通知单行:绑定 iqcId + 合格数量 |
| | | arrivalNoticeLineService.updateArrivalNoticeLineWhenIqcFinish(lineId, iqcId, qualifiedQuantity); |
| | | // 2. 更新到货通知单行:绑定 iqcId + 质检结果 + 合格数量 |
| | | arrivalNoticeLineService.updateArrivalNoticeLineWhenIqcFinish(lineId, iqcId, checkResult, qualifiedQuantity); |
| | | |
| | | // 3.1 判断是否所有需检行都已完成 |
| | | // 3.1 判断是否所有需检行都已完成质检 |
| | | List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id); |
| | | boolean hasUnchecked = CollectionUtils.anyMatch(lines, |
| | | line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null); |
| | | line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcCheckResult() == null); |
| | | if (hasUnchecked) { |
| | | log.info("[updateArrivalNoticeWhenIqcFinish][到货通知单({}) 还有未完成检验的行,暂不更新状态]", id); |
| | | return; |
| | | } |
| | | // 3.2 所有行检验完成,更新主表状态 PENDING_QC → PENDING_RECEIPT |
| | | |
| | | // 3.2 判断是否所有需检行都可以入库(合格或特采) |
| | | boolean hasNonInbound = CollectionUtils.anyMatch(lines, |
| | | line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) |
| | | && !MesQcCheckResultEnum.canInbound(line.getIqcCheckResult())); |
| | | if (hasNonInbound) { |
| | | log.info("[updateArrivalNoticeWhenIqcFinish][到货通知单({}) 存在不可入库的行(退货/报废),状态保持待质检]", id); |
| | | return; |
| | | } |
| | | |
| | | // 3.3 所有行检验完成且可入库,更新主表状态 PENDING_QC → PENDING_RECEIPT |
| | | arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO() |
| | | .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus())); |
| | | } |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long generateArrivalNoticeFromPurchaseOrder(Long purchaseOrderId) { |
| | | // 1. 获取采购订单 |
| | | // 1. 校验是否已生成过到货通知单(防止重复生成) |
| | | MesWmArrivalNoticeDO existNotice = arrivalNoticeMapper.selectByPurchaseOrderId(purchaseOrderId); |
| | | if (existNotice != null) { |
| | | throw exception(WM_ARRIVAL_NOTICE_ALREADY_EXISTS); |
| | | } |
| | | |
| | | // 2. 获取采购订单 |
| | | ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(purchaseOrderId).getCheckedData(); |
| | | if (purchaseOrder == null) { |
| | | throw exception(WM_PURCHASE_ORDER_NOT_EXISTS); |
| | | } |
| | | |
| | | // 2. 获取采购订单明细 |
| | | // 3. 获取采购订单明细 |
| | | List<ErpPurchaseOrderItemRespDTO> items = purchaseOrderApi.getPurchaseOrderItemList(purchaseOrderId).getCheckedData(); |
| | | if (CollUtil.isEmpty(items)) { |
| | | throw exception(WM_PURCHASE_ORDER_NO_ITEM); |
| | | } |
| | | |
| | | // 3. 生成到货通知单编码(使用编码规则服务) |
| | | // 4. 生成到货通知单编码 |
| | | String code = generateArrivalNoticeCode(); |
| | | |
| | | // 4. 创建到货通知单主表 |
| | | // 5. 创建到货通知单主表,直接设为待入库状态 |
| | | MesWmArrivalNoticeDO notice = new MesWmArrivalNoticeDO(); |
| | | notice.setCode(code); |
| | | notice.setName(purchaseOrder.getNo() + "到货通知"); |
| | | notice.setPurchaseOrderId(purchaseOrderId); |
| | | notice.setPurchaseOrderCode(purchaseOrder.getNo()); |
| | | notice.setVendorId(purchaseOrder.getSupplierId()); |
| | | notice.setStatus(MesWmArrivalNoticeStatusEnum.PREPARE.getStatus()); |
| | | notice.setArrivalDate(java.time.LocalDateTime.now()); // 到货时间为当前时间 |
| | | notice.setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus()); |
| | | arrivalNoticeMapper.insert(notice); |
| | | |
| | | // 5. 遍历明细创建到货通知单行 |
| | | // 6. 遍历明细创建到货通知单行 |
| | | boolean hasNeedQc = false; |
| | | for (ErpPurchaseOrderItemRespDTO item : items) { |
| | | MesWmArrivalNoticeLineDO line = new MesWmArrivalNoticeLineDO(); |
| | | line.setNoticeId(notice.getId()); |
| | | line.setPurchaseOrderItemId(item.getId()); |
| | | // 注意:这里需要根据 ERP 产品 ID 映射到 MES 物料 ID |
| | | // 实际项目中需要通过映射服务处理,这里暂时留空让用户手动选择 |
| | | line.setArrivalQuantity(item.getCount() != null ? item.getCount().subtract(item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO) : BigDecimal.ZERO); |
| | | line.setIqcCheckFlag(false); // 默认不需要检验,用户可修改 |
| | | line.setItemId(item.getProductId()); // 从采购订单明细带入物料编号 |
| | | BigDecimal arrivalQuantity = item.getCount() != null ? item.getCount().subtract(item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO) : BigDecimal.ZERO; |
| | | line.setArrivalQuantity(arrivalQuantity); |
| | | // 从采购订单明细带入质检标识 |
| | | boolean needQc = Boolean.TRUE.equals(item.getQcCheckFlag()); |
| | | line.setIqcCheckFlag(needQc); |
| | | // 如果不需要质检,合格数量等于到货数量 |
| | | if (!needQc) { |
| | | line.setQualifiedQuantity(arrivalQuantity); |
| | | } else { |
| | | hasNeedQc = true; |
| | | } |
| | | arrivalNoticeLineService.createArrivalNoticeLineFromPurchaseOrderItem(line); |
| | | } |
| | | |
| | | // 7. 如果有需要质检的行,更新状态为待质检并自动创建 IQC 单 |
| | | if (hasNeedQc) { |
| | | arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO() |
| | | .setId(notice.getId()).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus())); |
| | | // 为需要质检的行自动创建 IQC 检验单 |
| | | List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(notice.getId()); |
| | | for (MesWmArrivalNoticeLineDO line : lines) { |
| | | if (Boolean.TRUE.equals(line.getIqcCheckFlag())) { |
| | | Long iqcId = iqcService.createIqcFromArrivalNotice( |
| | | notice.getId(), line, notice.getCode(), notice.getVendorId()); |
| | | arrivalNoticeLineService.updateArrivalNoticeLineIqcId(line.getId(), iqcId); |
| | | log.info("[generateArrivalNoticeFromPurchaseOrder][到货通知单({}) 行({}) 自动创建 IQC 检验单({})]", |
| | | notice.getId(), line.getId(), iqcId); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return notice.getId(); |
| | | } |
| | | |