gaoluyang
5 天以前 6fa82a1ad7c3ae061ef499f0d0806a16846e122b
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -136,10 +136,8 @@
            accountService.validateAccount(createReqVO.getAccountId());
        }
        // 1.4 生成订单号,并校验唯一性
        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
        if (purchaseOrderMapper.selectByNo(no) != null) {
            throw exception(PURCHASE_ORDER_NO_EXISTS);
        }
        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX,
                candidate -> purchaseOrderMapper.selectByNo(candidate) != null);
        // 2.1 插入订单(submit=false 时保存为草稿,等待业务确认后手动提交审批)
        ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
@@ -302,15 +300,21 @@
            itemVO.setCount(row.getCount());
            itemVO.setTaxPercent(row.getTaxPercent());
            itemVO.setRemark(row.getRemark());
            itemVO.setQcCheckFlag("是".equals(StrUtil.trim(row.getQcCheckFlag())));
            // 采购台账不再提供「是否质检」选择:导入的明细一律需要来料检验
            itemVO.setQcCheckFlag(true);
            items.add(itemVO);
        }
        // 3. 单号:填了就用(校验唯一),没填自动生成
        String no = StrUtil.isNotBlank(head.getNo()) ? head.getNo().trim()
                : noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
        if (purchaseOrderMapper.selectByNo(no) != null) {
            throw new IllegalArgumentException("采购单编号已存在:" + no);
        String no;
        if (StrUtil.isNotBlank(head.getNo())) {
            no = head.getNo().trim();
            if (purchaseOrderMapper.selectByNo(no) != null) {
                throw new IllegalArgumentException("采购单编号已存在:" + no);
            }
        } else {
            no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX,
                    candidate -> purchaseOrderMapper.selectByNo(candidate) != null);
        }
        // 4. 组装主表:勾选“是否审核” => 已审核(20),否则未审核(10)
@@ -450,6 +454,43 @@
        }
    }
    /**
     * 提交审批前校验采购台账数据是否已补齐
     *
     * <p>采购申请直接生成的台账只有物料与数量(零金额、无供应商),
     * 采购员需要在台账里补录供应商、单价、付款日期后才能提交审批。逐项校验,命中第一处即提示具体怎么改。</p>
     *
     * @param purchaseOrder 采购台账
     */
    private void validatePurchaseOrderReadyForSubmit(ErpPurchaseOrderDO purchaseOrder) {
        // 2.1 供应商必选
        if (purchaseOrder.getSupplierId() == null) {
            throw exception(PURCHASE_ORDER_SUBMIT_SUPPLIER_REQUIRED, purchaseOrder.getNo());
        }
        // 2.2 付款日期必选:采购申请自动生成的草稿订单没有付款日期
        if (purchaseOrder.getPaymentDate() == null) {
            throw exception(PURCHASE_ORDER_PAYMENT_DATE_REQUIRED);
        }
        // 2.3 明细单价必须补录(大于 0)
        List<ErpPurchaseOrderItemDO> items = purchaseOrderItemMapper.selectListByOrderId(purchaseOrder.getId());
        if (CollUtil.isEmpty(items)) {
            throw exception(PURCHASE_ORDER_SUBMIT_ITEM_REQUIRED, purchaseOrder.getNo());
        }
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(items, ErpPurchaseOrderItemDO::getProductId));
        for (ErpPurchaseOrderItemDO item : items) {
            BigDecimal productPrice = item.getProductPrice();
            if (productPrice != null && productPrice.compareTo(BigDecimal.ZERO) > 0) {
                continue;
            }
            MdmItemRespDTO itemDTO = itemMap.get(item.getProductId());
            String itemName = itemDTO != null && StrUtil.isNotBlank(itemDTO.getName())
                    ? itemDTO.getName() : "ID=" + item.getProductId();
            throw exception(PURCHASE_ORDER_SUBMIT_PRICE_REQUIRED, itemName,
                    productPrice == null ? "未填写" : productPrice.toPlainString());
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId) {
@@ -460,10 +501,9 @@
        if (!submitable) {
            throw exception(PURCHASE_ORDER_SUBMIT_FAIL_STATUS);
        }
        // 2. 付款日期必选:采购申请自动生成的草稿订单没有付款日期,提交前必须先编辑补齐
        if (purchaseOrder.getPaymentDate() == null) {
            throw exception(PURCHASE_ORDER_PAYMENT_DATE_REQUIRED);
        }
        // 2. 提交前校验台账数据已补齐:采购申请直接生成的草稿只有物料与数量,
        //    供应商、单价等需要采购员在台账中补录后才能提交审批
        validatePurchaseOrderReadyForSubmit(purchaseOrder);
        // 3. 创建 BPM 流程实例
        // 全局免审批:业务校验通过后直接执行审批通过逻辑,无需流程模型。
@@ -572,6 +612,8 @@
    private List<ErpPurchaseOrderItemDO> validatePurchaseOrderItems(List<ErpPurchaseOrderSaveReqVO.Item> list) {
        // 转化为 ErpPurchaseOrderItemDO 列表(产品验证已移除)
        List<ErpPurchaseOrderItemDO> items = convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> {
            // 采购台账不再提供「是否质检」选择:采购明细一律需要来料检验
            item.setQcCheckFlag(true);
            item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
            if (item.getTotalPrice() == null) {
                return;