gaoluyang
5 天以前 d39e4f7d839167c16425086dfa2f8747bccfdff9
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -29,7 +29,9 @@
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.ErpPaymentMethodEnum;
import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
import cn.iocoder.yudao.module.erp.enums.ErpPurchaseTypeEnum;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
@@ -117,8 +119,16 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO) {
        return createPurchaseOrder(createReqVO, true);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO, boolean submit) {
        // 1.1 校验订单项的有效性
        List<ErpPurchaseOrderItemDO> purchaseOrderItems = validatePurchaseOrderItems(createReqVO.getItems());
        // 1.1.1 校验采购台账字段
        validateLedgerFields(createReqVO);
        // 1.2 校验供应商
        supplierService.validateSupplier(createReqVO.getSupplierId());
        // 1.3 校验结算账户
@@ -131,9 +141,9 @@
            throw exception(PURCHASE_ORDER_NO_EXISTS);
        }
        // 2.1 插入订单(采购订单创建后为未审核状态,需要提交审批)
        // 2.1 插入订单(submit=false 时保存为草稿,等待业务确认后手动提交审批)
        ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
                .setNo(no).setStatus(submit ? ErpAuditStatus.PROCESS.getStatus() : ErpAuditStatus.DRAFT.getStatus())
                .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus()));
        calculateTotalPrice(purchaseOrder, purchaseOrderItems);
        purchaseOrderMapper.insert(purchaseOrder);
@@ -145,7 +155,9 @@
                purchaseOrder.getId(), createReqVO.getBlobIds());
        // 3. 创建后自动提交审批到协同办公(审批在协同办公完成,不在本页面审批)
        submitPurchaseOrder(purchaseOrder.getId(), PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY, getLoginUserId());
        if (submit) {
            submitPurchaseOrder(purchaseOrder.getId(), PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY, getLoginUserId());
        }
        return purchaseOrder.getId();
    }
@@ -167,6 +179,8 @@
        }
        // 1.4 校验订单项的有效性
        List<ErpPurchaseOrderItemDO> purchaseOrderItems = validatePurchaseOrderItems(updateReqVO.getItems());
        // 1.5 校验采购台账字段
        validateLedgerFields(updateReqVO);
        // 2.1 更新订单
        ErpPurchaseOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseOrderDO.class);
@@ -276,6 +290,7 @@
            }
            ErpPurchaseOrderSaveReqVO.Item itemVO = new ErpPurchaseOrderSaveReqVO.Item();
            itemVO.setProductId(item.getId());
            itemVO.setProductCode(item.getCode());
            itemVO.setProductUnitId(item.getUnitMeasureId());
            itemVO.setProductPrice(row.getProductPrice() == null ? BigDecimal.ZERO : row.getProductPrice());
            itemVO.setCount(row.getCount());
@@ -297,6 +312,11 @@
        purchaseOrder.setNo(no);
        purchaseOrder.setSupplierId(supplierId);
        purchaseOrder.setOrderTime(parseImportOrderTime(head.getOrderTime()));
        purchaseOrder.setPurchaseType(parsePurchaseType(head.getPurchaseType()));
        purchaseOrder.setContractNo(StrUtil.trimToNull(head.getContractNo()));
        purchaseOrder.setProjectName(StrUtil.trimToNull(head.getProjectName()));
        purchaseOrder.setPaymentDate(parseImportDateTime(head.getPaymentDate(), "付款日期"));
        purchaseOrder.setPaymentMethod(parsePaymentMethod(head.getPaymentMethod()));
        purchaseOrder.setRemark(head.getRemark());
        purchaseOrder.setStatus(Boolean.TRUE.equals(directApprove)
                ? ErpAuditStatus.APPROVE.getStatus() : ErpAuditStatus.PROCESS.getStatus());
@@ -316,8 +336,16 @@
     * 解析导入的采购时间,兼容多种常见格式;为空取当前时间
     */
    private LocalDateTime parseImportOrderTime(String text) {
        LocalDateTime value = parseImportDateTime(text, "采购时间");
        return value != null ? value : LocalDateTime.now();
    }
    /**
     * 解析导入的日期时间,兼容多种常见格式;为空返回 null
     */
    private LocalDateTime parseImportDateTime(String text, String fieldName) {
        if (StrUtil.isBlank(text)) {
            return LocalDateTime.now();
            return null;
        }
        String value = text.trim();
        for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm:ss"}) {
@@ -334,7 +362,33 @@
                // 继续尝试下一个格式
            }
        }
        throw new IllegalArgumentException("采购时间格式不正确:" + text + ",示例 2026-01-01 10:00:00");
        throw new IllegalArgumentException(fieldName + "格式不正确:" + text + ",示例 2026-01-01 10:00:00");
    }
    /**
     * 解析导入的采购类型名称;为空或无法识别返回 null
     */
    private Integer parsePurchaseType(String text) {
        if (StrUtil.isBlank(text)) {
            return null;
        }
        String value = text.trim();
        return Arrays.stream(ErpPurchaseTypeEnum.values())
                .filter(item -> item.getName().equals(value))
                .map(ErpPurchaseTypeEnum::getType).findFirst().orElse(null);
    }
    /**
     * 解析导入的付款方式名称;为空或无法识别返回 null
     */
    private Integer parsePaymentMethod(String text) {
        if (StrUtil.isBlank(text)) {
            return null;
        }
        String value = text.trim();
        return Arrays.stream(ErpPaymentMethodEnum.values())
                .filter(item -> item.getName().equals(value))
                .map(ErpPaymentMethodEnum::getType).findFirst().orElse(null);
    }
    /**
@@ -351,7 +405,8 @@
        purchaseOrder.setTotalCount(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getCount, BigDecimal::add));
        purchaseOrder.setTotalProductPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseOrder.setTotalTaxPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseOrder.setTotalPrice(purchaseOrder.getTotalProductPrice().add(purchaseOrder.getTotalTaxPrice()));
        // 单价为含税单价:价税合计 = 单价 * 数量,税额已包含在单价中,不再额外加税
        purchaseOrder.setTotalPrice(purchaseOrder.getTotalProductPrice());
        // 计算优惠价格
        if (purchaseOrder.getDiscountPercent() == null) {
            purchaseOrder.setDiscountPercent(BigDecimal.ZERO);
@@ -392,13 +447,29 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId) {
        // 1. 校验存在 + 未审核状态
        // 1. 校验存在 + 未审核状态(草稿和未审核均可提交)
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
        if (!ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus())) {
        boolean submitable = ErpAuditStatus.DRAFT.getStatus().equals(purchaseOrder.getStatus())
                || ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus());
        if (!submitable) {
            throw exception(PURCHASE_ORDER_SUBMIT_FAIL_STATUS);
        }
        // 2. 付款日期必选:采购申请自动生成的草稿订单没有付款日期,提交前必须先编辑补齐
        if (purchaseOrder.getPaymentDate() == null) {
            throw exception(PURCHASE_ORDER_PAYMENT_DATE_REQUIRED);
        }
        // 2. 创建 BPM 流程实例
        // 3. 创建 BPM 流程实例
        // 全局免审批:业务校验通过后直接执行审批通过逻辑,无需流程模型。
        if (processInstanceApi.isGlobalAutoApprove()) {
            purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
                    .setId(id)
                    .setStatus(ErpAuditStatus.APPROVING.getStatus())
                    .setProcessInstanceId(""));
            updatePurchaseOrderAuditStatus(id, cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.APPROVE.getStatus());
            return;
        }
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
@@ -494,15 +565,53 @@
    private List<ErpPurchaseOrderItemDO> validatePurchaseOrderItems(List<ErpPurchaseOrderSaveReqVO.Item> list) {
        // 转化为 ErpPurchaseOrderItemDO 列表(产品验证已移除)
        return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> {
        List<ErpPurchaseOrderItemDO> items = convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> {
            item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
            if (item.getTotalPrice() == null) {
                return;
            }
            if (item.getTaxPercent() != null) {
                item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
                // 单价为含税单价:税额从价税合计中反算,而不是在合计上再加税
                item.setTaxPrice(MoneyUtils.taxPriceIncluded(item.getTotalPrice(), item.getTaxPercent()));
            }
        }));
        // 采购台账:产品编号取 MDM 物料编码快照,保证列表/导出可直接展示
        fillProductCode(items);
        return items;
    }
    /**
     * 回填明细行的产品编号(MDM 物料编码)
     */
    private void fillProductCode(List<ErpPurchaseOrderItemDO> items) {
        if (CollUtil.isEmpty(items)) {
            return;
        }
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(items, ErpPurchaseOrderItemDO::getProductId));
        items.forEach(item -> {
            MdmItemRespDTO mdmItem = itemMap.get(item.getProductId());
            if (mdmItem == null) {
                return;
            }
            if (StrUtil.isBlank(mdmItem.getCode())) {
                throw exception(PURCHASE_ORDER_ITEM_PRODUCT_CODE_MISSING,
                        StrUtil.blankToDefault(mdmItem.getName(), "ID=" + mdmItem.getId()));
            }
            item.setProductCode(mdmItem.getCode());
        });
    }
    /**
     * 校验采购台账字段(采购类型、付款方式)
     */
    private void validateLedgerFields(ErpPurchaseOrderSaveReqVO reqVO) {
        if (!ErpPurchaseTypeEnum.isValid(reqVO.getPurchaseType())) {
            throw exception(PURCHASE_ORDER_PURCHASE_TYPE_INVALID, reqVO.getPurchaseType());
        }
        if (!ErpPaymentMethodEnum.isValid(reqVO.getPaymentMethod())) {
            throw exception(PURCHASE_ORDER_PAYMENT_METHOD_INVALID, reqVO.getPaymentMethod());
        }
    }
    private void updatePurchaseOrderItemList(Long id, List<ErpPurchaseOrderItemDO> newList) {