gaoluyang
昨天 8c65f898ed6e5b0c7a94c792c4562f6ec831dccd
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
@@ -62,6 +62,7 @@
     * 附件业务类型:MES 供应商退货单
     */
    public static final String RETURN_VENDOR_RECORD_TYPE = "mes_wm_return_vendor";
    private static final String RETURN_VENDOR_APPROVAL_KEY = "purchase_return_vendor_approve";
    @Resource
    private MesWmReturnVendorMapper returnVendorMapper;
@@ -185,26 +186,25 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitReturnVendor(Long id) {
        // 校验存在 + 草稿状态
        validateReturnVendorExistsAndPrepare(id);
        // 校验至少有一条行
        List<MesWmReturnVendorLineDO> lines = returnVendorLineService.getReturnVendorLineListByReturnId(id);
        if (CollUtil.isEmpty(lines)) {
            throw exception(WM_RETURN_VENDOR_NO_LINE);
        }
        // 提交(草稿 -> 待拣货)
        returnVendorMapper.updateById(new MesWmReturnVendorDO()
                .setId(id).setStatus(MesWmReturnVendorStatusEnum.APPROVING.getStatus()));
        submitReturnVendorApproval(id, RETURN_VENDOR_APPROVAL_KEY);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitReturnVendorApproval(Long id, String processDefinitionKey) {
        if (!RETURN_VENDOR_APPROVAL_KEY.equals(processDefinitionKey)) {
            throw exception(WM_RETURN_VENDOR_APPROVAL_PROCESS_INVALID, RETURN_VENDOR_APPROVAL_KEY);
        }
        // 1. 校验存在 + 草稿状态 + 至少一条行
        MesWmReturnVendorDO returnVendor = validateReturnVendorExistsAndPrepare(id);
        if (CollUtil.isEmpty(returnVendorLineService.getReturnVendorLineListByReturnId(id))) {
            throw exception(WM_RETURN_VENDOR_NO_LINE);
        }
        validateReturnVendorOrderQuantity(returnVendor);
        // 拣货可在审批通过后进行;如已提前录入明细,则提交前必须数量守恒。
        if (CollUtil.isNotEmpty(returnVendorDetailService.getReturnVendorDetailListByReturnId(id))
                && !checkReturnVendorQuantity(id)) {
            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
        }
        // 1.1. 质量部发起退货必须上传附件(质检报告、不合格照片等),否则审批人无法判断退货依据
        if (CollUtil.isEmpty(storageAttachmentApi.listAttachments(RETURN_VENDOR_RECORD_TYPE, id))) {
@@ -284,6 +284,11 @@
        if (ObjUtil.notEqual(MesWmReturnVendorStatusEnum.APPROVING.getStatus(), returnVendor.getStatus())) {
            throw exception(WM_RETURN_VENDOR_STATUS_INVALID);
        }
        validateReturnVendorApproval(returnVendor);
        if (!checkReturnVendorQuantity(id)) {
            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
        }
        validateReturnVendorOrderQuantity(returnVendor);
        // 执行拣货(待拣货 -> 待执行退货)
        returnVendorMapper.updateById(new MesWmReturnVendorDO()
                .setId(id).setStatus(MesWmReturnVendorStatusEnum.APPROVED.getStatus()));
@@ -297,11 +302,16 @@
        if (ObjUtil.notEqual(MesWmReturnVendorStatusEnum.APPROVED.getStatus(), returnVendor.getStatus())) {
            throw exception(WM_RETURN_VENDOR_STATUS_INVALID);
        }
        validateReturnVendorApproval(returnVendor);
        // 1.2 校验明细不为空
        List<MesWmReturnVendorDetailDO> details = returnVendorDetailService.getReturnVendorDetailListByReturnId(id);
        if (CollUtil.isEmpty(details)) {
            throw exception(WM_RETURN_VENDOR_NO_DETAIL);
        }
        if (!checkReturnVendorQuantity(id)) {
            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
        }
        validateReturnVendorOrderQuantity(returnVendor);
        // 1.3 校验处理方式(退款 / 换货)
        if (!MesWmReturnVendorHandleTypeEnum.isValid(returnVendor.getHandleType())) {
@@ -326,6 +336,13 @@
        }
    }
    private void validateReturnVendorApproval(MesWmReturnVendorDO returnVendor) {
        if (!RETURN_VENDOR_APPROVAL_KEY.equals(returnVendor.getProcessDefinitionKey())
                || returnVendor.getProcessInstanceId() == null) {
            throw exception(WM_RETURN_VENDOR_APPROVAL_REQUIRED, returnVendor.getCode());
        }
    }
    /**
     * 把退货数量增量回写到关联采购订单的明细行,并同步订单级退货数量
     */
@@ -343,15 +360,58 @@
            return;
        }
        Map<Long, Long> purchaseOrderItemIdMap = new HashMap<>();
        Set<Long> ambiguousProductIds = new HashSet<>();
        orderItems.stream()
                .filter(item -> item.getProductId() != null)
                .forEach(item -> purchaseOrderItemIdMap.putIfAbsent(item.getProductId(), item.getId()));
                .forEach(item -> {
                    if (purchaseOrderItemIdMap.putIfAbsent(item.getProductId(), item.getId()) != null) {
                        ambiguousProductIds.add(item.getProductId());
                    }
                });
        for (MesWmReturnVendorLineDO line : lines) {
            if (ambiguousProductIds.contains(line.getItemId())) {
                throw exception(WM_RETURN_VENDOR_PURCHASE_ITEM_AMBIGUOUS, line.getItemId());
            }
            Long purchaseOrderItemId = purchaseOrderItemIdMap.get(line.getItemId());
            if (purchaseOrderItemId == null || line.getQuantity() == null) {
                continue;
            }
            purchaseOrderApi.updateOrderItemReturnCount(purchaseOrderItemId, line.getQuantity());
            if (!Boolean.TRUE.equals(purchaseOrderApi.updateOrderItemReturnCount(purchaseOrderItemId, line.getQuantity()).getCheckedData())) {
                throw exception(WM_RETURN_VENDOR_ITEM_NOT_IN_PURCHASE_ORDER);
            }
        }
    }
    private void validateReturnVendorOrderQuantity(MesWmReturnVendorDO returnVendor) {
        if (returnVendor.getPurchaseOrderId() == null) {
            return;
        }
        List<ErpPurchaseOrderItemRespDTO> items = purchaseOrderApi
                .getPurchaseOrderItemList(returnVendor.getPurchaseOrderId()).getCheckedData();
        Map<Long, ErpPurchaseOrderItemRespDTO> byProduct = new HashMap<>();
        Set<Long> ambiguousProductIds = new HashSet<>();
        for (ErpPurchaseOrderItemRespDTO item : items) {
            if (item.getProductId() != null && byProduct.putIfAbsent(item.getProductId(), item) != null) {
                ambiguousProductIds.add(item.getProductId());
            }
        }
        Map<Long, BigDecimal> requested = new HashMap<>();
        for (MesWmReturnVendorLineDO line : returnVendorLineService.getReturnVendorLineListByReturnId(returnVendor.getId())) {
            requested.merge(line.getItemId(), line.getQuantity(), BigDecimal::add);
        }
        for (Map.Entry<Long, BigDecimal> entry : requested.entrySet()) {
            if (ambiguousProductIds.contains(entry.getKey())) {
                throw exception(WM_RETURN_VENDOR_PURCHASE_ITEM_AMBIGUOUS, entry.getKey());
            }
            ErpPurchaseOrderItemRespDTO item = byProduct.get(entry.getKey());
            if (item == null) {
                throw exception(WM_RETURN_VENDOR_ITEM_NOT_IN_PURCHASE_ORDER);
            }
            BigDecimal available = (item.getInCount() == null ? BigDecimal.ZERO : item.getInCount())
                    .subtract(item.getReturnCount() == null ? BigDecimal.ZERO : item.getReturnCount());
            if (entry.getValue().compareTo(available) > 0) {
                throw exception(WM_RETURN_VENDOR_QUANTITY_EXCEED, entry.getKey(), entry.getValue(), available);
            }
        }
    }
@@ -506,6 +566,15 @@
            throw exception(WM_RETURN_VENDOR_CANCEL_NOT_ALLOWED);
        }
        // 审批中先撤销 BPM 实例,撤销失败时保留单据原状态和待办。
        if (MesWmReturnVendorStatusEnum.BPM_APPROVING.getStatus().equals(returnVendor.getStatus())) {
            if (StrUtil.isBlank(returnVendor.getProcessInstanceId())) {
                throw exception(WM_RETURN_VENDOR_APPROVAL_REQUIRED, returnVendor.getCode());
            }
            processInstanceApi.cancelProcessInstanceByStartUser(getCurrentUserId(),
                    returnVendor.getProcessInstanceId(), "供应商退货单取消");
        }
        // 取消
        returnVendorMapper.updateById(new MesWmReturnVendorDO()
                .setId(id).setStatus(MesWmReturnVendorStatusEnum.CANCELED.getStatus()));