| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | |
| | | import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportDuplicateVO; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportExcelVO; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportRespVO; |
| | | import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; |
| | | import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO; |
| | | import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO; |
| | | import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO; |
| | | import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInvoiceOrderMapper; |
| | | import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper; |
| | | import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderMapper; |
| | | import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestItemMapper; |
| | |
| | | import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; |
| | | import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum; |
| | | 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; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO; |
| | | import cn.iocoder.yudao.module.mes.api.arrivalnotice.MesArrivalNoticeApi; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import jakarta.annotation.Resource; |
| | |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| | |
| | | private ErpPurchaseOrderMapper purchaseOrderMapper; |
| | | @Resource |
| | | private ErpPurchaseOrderItemMapper purchaseOrderItemMapper; |
| | | @Resource |
| | | private ErpPurchaseInvoiceOrderMapper invoiceOrderMapper; |
| | | |
| | | @Resource |
| | | private ErpNoRedisDAO noRedisDAO; |
| | |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | |
| | | @Resource |
| | | private SrmSupplierApi srmSupplierApi; |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO) { |
| | |
| | | if (createReqVO.getAccountId() != null) { |
| | | accountService.validateAccount(createReqVO.getAccountId()); |
| | | } |
| | | // 1.4 生成订单号,并校验唯一性 |
| | | String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX); |
| | | // 1.4 确定订单号(前端填写则用自定义单号,留空则自动生成),并校验唯一性 |
| | | String customNo = StrUtil.trim(createReqVO.getNo()); |
| | | String no = StrUtil.isNotBlank(customNo) ? customNo |
| | | : noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX); |
| | | if (purchaseOrderMapper.selectByNo(no) != null) { |
| | | throw exception(PURCHASE_ORDER_NO_EXISTS); |
| | | } |
| | |
| | | updatePurchaseOrderItemList(updateReqVO.getId(), purchaseOrderItems); |
| | | // 2.3 更新附件 |
| | | storageAttachmentApi.updateAttachments("file", "erp_purchase_order", updateReqVO.getId(), updateReqVO.getBlobIds()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public ErpPurchaseOrderImportRespVO importPurchaseOrderList(List<ErpPurchaseOrderImportExcelVO> importList, |
| | | Boolean directApprove, Boolean overwrite) { |
| | | ErpPurchaseOrderImportRespVO respVO = ErpPurchaseOrderImportRespVO.builder() |
| | | .createList(new ArrayList<>()) |
| | | .updateList(new ArrayList<>()) |
| | | .failureList(new LinkedHashMap<>()) |
| | | .duplicateList(new ArrayList<>()) |
| | | .build(); |
| | | if (CollUtil.isEmpty(importList)) { |
| | | return respVO; |
| | | } |
| | | |
| | | // 1. 预热“供应商名称/编码 -> 供应商编号”映射(ERP 供应商主数据在 SRM 模块) |
| | | Map<String, Long> supplierIdMap = new HashMap<>(); |
| | | CommonResult<List<SrmSupplierRespDTO>> supplierResult = srmSupplierApi.getSupplierSimpleList(); |
| | | if (supplierResult != null && CollUtil.isNotEmpty(supplierResult.getData())) { |
| | | supplierResult.getData().forEach(supplier -> { |
| | | if (StrUtil.isNotBlank(supplier.getName())) { |
| | | supplierIdMap.put(supplier.getName().trim(), supplier.getId()); |
| | | } |
| | | if (StrUtil.isNotBlank(supplier.getCode())) { |
| | | supplierIdMap.putIfAbsent(supplier.getCode().trim(), supplier.getId()); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // 2. 预热系统内有效采购订单:按单号、按“供应商 + 明细”指纹建索引,用于识别重复导入 |
| | | List<ErpPurchaseOrderDO> existingOrders = purchaseOrderMapper.selectList(); |
| | | Map<Long, List<ErpPurchaseOrderItemDO>> existingItemsMap = new HashMap<>(); |
| | | if (CollUtil.isNotEmpty(existingOrders)) { |
| | | purchaseOrderItemMapper.selectListByOrderIds( |
| | | existingOrders.stream().map(ErpPurchaseOrderDO::getId).toList()) |
| | | .forEach(item -> existingItemsMap.computeIfAbsent(item.getOrderId(), k -> new ArrayList<>()).add(item)); |
| | | } |
| | | Map<String, ErpPurchaseOrderDO> existingNoMap = new HashMap<>(); |
| | | Map<String, List<ErpPurchaseOrderDO>> existingFingerprintMap = new HashMap<>(); |
| | | for (ErpPurchaseOrderDO order : existingOrders) { |
| | | if (StrUtil.isNotBlank(order.getNo())) { |
| | | existingNoMap.put(order.getNo(), order); |
| | | } |
| | | String fingerprint = buildOrderFingerprint(order.getSupplierId(), existingItemsMap.get(order.getId())); |
| | | if (fingerprint != null) { |
| | | existingFingerprintMap.computeIfAbsent(fingerprint, k -> new ArrayList<>()).add(order); |
| | | } |
| | | } |
| | | |
| | | // 3. 按“采购单编号”分组:同编号多行 = 一张订单的多条明细;编号为空则每行独立成单 |
| | | Map<String, List<ErpPurchaseOrderImportExcelVO>> orderGroups = new LinkedHashMap<>(); |
| | | for (int i = 0; i < importList.size(); i++) { |
| | | ErpPurchaseOrderImportExcelVO row = importList.get(i); |
| | | if (row == null || (StrUtil.isBlank(row.getSupplierName()) && StrUtil.isBlank(row.getProductCode()) |
| | | && StrUtil.isBlank(row.getNo()))) { |
| | | continue; // 跳过空行 |
| | | } |
| | | String key = StrUtil.isNotBlank(row.getNo()) ? row.getNo().trim() : "__row_" + (i + 2); |
| | | orderGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(row); |
| | | } |
| | | |
| | | // 4. 逐个订单导入(单张订单失败不影响其它) |
| | | for (Map.Entry<String, List<ErpPurchaseOrderImportExcelVO>> entry : orderGroups.entrySet()) { |
| | | List<ErpPurchaseOrderImportExcelVO> rows = entry.getValue(); |
| | | String label = StrUtil.isNotBlank(rows.get(0).getNo()) ? rows.get(0).getNo().trim() |
| | | : "第 " + entry.getKey().substring("__row_".length()) + " 行"; |
| | | try { |
| | | ImportOrderData importData = prepareImportOrder(rows, supplierIdMap); |
| | | |
| | | // 4.1 先按单号找;只有“单号为空、由系统生成编号”的行才按“供应商 + 完整明细”指纹找。 |
| | | // 文件里写了明确单号的,视为用户明确要新增该单号,指纹相同也不拦截,避免正常的重复采购被误判。 |
| | | ErpPurchaseOrderDO existing = importData.order.getNo() == null ? null |
| | | : existingNoMap.get(importData.order.getNo()); |
| | | String duplicateReason = existing != null ? "采购单编号已存在" : null; |
| | | boolean matchedByFingerprint = false; |
| | | List<ErpPurchaseOrderDO> fingerprintCandidates = null; |
| | | if (existing == null && importData.generatedNo && importData.fingerprint != null) { |
| | | fingerprintCandidates = existingFingerprintMap.get(importData.fingerprint); |
| | | if (CollUtil.isNotEmpty(fingerprintCandidates)) { |
| | | existing = fingerprintCandidates.get(0); |
| | | matchedByFingerprint = true; |
| | | duplicateReason = "供应商、明细、数量、单价、金额与已有订单一致"; |
| | | } |
| | | } |
| | | |
| | | // 4.2 发现重复:默认不导入,返回给前端弹框提示;勾选覆盖后才更新已有订单 |
| | | if (existing != null) { |
| | | if (!Boolean.TRUE.equals(overwrite)) { |
| | | respVO.getDuplicateList().add(buildImportDuplicate(label, existing, duplicateReason)); |
| | | continue; |
| | | } |
| | | if (matchedByFingerprint && fingerprintCandidates.size() > 1) { |
| | | String nos = fingerprintCandidates.stream().map(ErpPurchaseOrderDO::getNo) |
| | | .collect(Collectors.joining("、")); |
| | | throw new IllegalArgumentException("找到多张相同订单(" + nos |
| | | + "),无法自动覆盖,请先保留一张后再导入"); |
| | | } |
| | | updatePurchaseOrderByImport(existing, importData, directApprove); |
| | | respVO.getUpdateList().add(existing.getNo()); |
| | | continue; |
| | | } |
| | | |
| | | // 4.3 没有重复,正常新增 |
| | | createPurchaseOrderByImport(importData, directApprove); |
| | | respVO.getCreateList().add(importData.order.getNo()); |
| | | } catch (Exception ex) { |
| | | respVO.getFailureList().put(label, resolveImportErrorMessage(ex)); |
| | | } |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | /** |
| | | * 校验并组装一张导入订单(不落库)。 |
| | | */ |
| | | private ImportOrderData prepareImportOrder(List<ErpPurchaseOrderImportExcelVO> rows, |
| | | Map<String, Long> supplierIdMap) { |
| | | // 1. 供应商(取该组首行) |
| | | ErpPurchaseOrderImportExcelVO head = rows.get(0); |
| | | if (StrUtil.isBlank(head.getSupplierName())) { |
| | | throw new IllegalArgumentException("供应商名称不能为空"); |
| | | } |
| | | Long supplierId = supplierIdMap.get(head.getSupplierName().trim()); |
| | | if (supplierId == null) { |
| | | throw new IllegalArgumentException("供应商不存在:" + head.getSupplierName()); |
| | | } |
| | | supplierService.validateSupplier(supplierId); |
| | | |
| | | // 2. 明细 |
| | | List<ErpPurchaseOrderSaveReqVO.Item> items = new ArrayList<>(); |
| | | for (ErpPurchaseOrderImportExcelVO row : rows) { |
| | | if (StrUtil.isBlank(row.getProductCode())) { |
| | | throw new IllegalArgumentException("产品编码不能为空"); |
| | | } |
| | | CommonResult<MdmItemRespDTO> itemResult = mdmItemApi.getItemByCode(row.getProductCode().trim()); |
| | | MdmItemRespDTO item = itemResult == null ? null : itemResult.getData(); |
| | | if (item == null) { |
| | | throw new IllegalArgumentException("产品不存在:" + row.getProductCode()); |
| | | } |
| | | if (row.getCount() == null) { |
| | | throw new IllegalArgumentException("产品[" + row.getProductCode() + "]的数量不能为空"); |
| | | } |
| | | if (item.getUnitMeasureId() == null) { |
| | | throw new IllegalArgumentException("产品[" + row.getProductCode() + "]未配置计量单位,请先在物料中维护"); |
| | | } |
| | | ErpPurchaseOrderSaveReqVO.Item itemVO = new ErpPurchaseOrderSaveReqVO.Item(); |
| | | itemVO.setProductId(item.getId()); |
| | | itemVO.setProductUnitId(item.getUnitMeasureId()); |
| | | itemVO.setProductPrice(row.getProductPrice() == null ? BigDecimal.ZERO : row.getProductPrice()); |
| | | itemVO.setCount(row.getCount()); |
| | | itemVO.setTaxPercent(row.getTaxPercent()); |
| | | itemVO.setRemark(row.getRemark()); |
| | | itemVO.setQcCheckFlag("是".equals(StrUtil.trim(row.getQcCheckFlag()))); |
| | | items.add(itemVO); |
| | | } |
| | | |
| | | // 3. 单号:填了就用;留空时先不生成,等确认要新增时再生成,避免重复行被跳过也消耗流水号 |
| | | boolean generatedNo = StrUtil.isBlank(head.getNo()); |
| | | |
| | | // 4. 组装主表(状态由调用方决定) |
| | | ErpPurchaseOrderDO purchaseOrder = new ErpPurchaseOrderDO(); |
| | | purchaseOrder.setNo(generatedNo ? null : head.getNo().trim()); |
| | | purchaseOrder.setSupplierId(supplierId); |
| | | purchaseOrder.setOrderTime(parseImportOrderTime(head.getOrderTime())); |
| | | purchaseOrder.setRemark(head.getRemark()); |
| | | purchaseOrder.setDepositPrice(BigDecimal.ZERO); // deposit_price 为 NOT NULL 且无默认值,必须显式赋值 |
| | | |
| | | List<ErpPurchaseOrderItemDO> itemDOs = validatePurchaseOrderItems(items); |
| | | calculateTotalPrice(purchaseOrder, itemDOs); |
| | | return new ImportOrderData(purchaseOrder, itemDOs, |
| | | buildOrderFingerprint(supplierId, itemDOs), generatedNo, |
| | | StrUtil.isNotBlank(head.getOrderTime())); |
| | | } |
| | | |
| | | /** |
| | | * 新增导入订单(含明细)。 |
| | | */ |
| | | private void createPurchaseOrderByImport(ImportOrderData importData, Boolean directApprove) { |
| | | ErpPurchaseOrderDO purchaseOrder = importData.order; |
| | | if (StrUtil.isBlank(purchaseOrder.getNo())) { |
| | | purchaseOrder.setNo(noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX)); |
| | | } |
| | | purchaseOrder.setStatus(Boolean.TRUE.equals(directApprove) |
| | | ? ErpAuditStatus.APPROVE.getStatus() : ErpAuditStatus.PROCESS.getStatus()); |
| | | purchaseOrder.setInCount(BigDecimal.ZERO); |
| | | purchaseOrder.setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus()); |
| | | purchaseOrderMapper.insert(purchaseOrder); |
| | | importData.items.forEach(item -> item.setOrderId(purchaseOrder.getId())); |
| | | purchaseOrderItemMapper.insertBatch(importData.items); |
| | | } |
| | | |
| | | /** |
| | | * 覆盖已有导入订单(含明细)。 |
| | | */ |
| | | private void updatePurchaseOrderByImport(ErpPurchaseOrderDO existing, ImportOrderData importData, |
| | | Boolean directApprove) { |
| | | // 1. 有下游单据的订单不允许覆盖,避免把已入库/已开票的数据改掉 |
| | | if (existing.getInCount() != null && existing.getInCount().compareTo(BigDecimal.ZERO) > 0) { |
| | | throw new IllegalArgumentException("采购订单[" + existing.getNo() + "]已入库(已入库数量 " |
| | | + normalizeNumber(existing.getInCount()) + "),不能覆盖;请先删除对应入库记录后再导入"); |
| | | } |
| | | if (existing.getReturnCount() != null && existing.getReturnCount().compareTo(BigDecimal.ZERO) > 0) { |
| | | throw new IllegalArgumentException("采购订单[" + existing.getNo() + "]已退货(已退货数量 " |
| | | + normalizeNumber(existing.getReturnCount()) + "),不能覆盖;请先删除对应退货记录后再导入"); |
| | | } |
| | | if (CollUtil.isNotEmpty(invoiceOrderMapper.selectListByPurchaseOrderId(existing.getId()))) { |
| | | throw new IllegalArgumentException("采购订单[" + existing.getNo() |
| | | + "]已关联发票,不能覆盖;请先解除发票关联后再导入"); |
| | | } |
| | | if (StrUtil.isNotBlank(existing.getProcessInstanceId())) { |
| | | throw new IllegalArgumentException("采购订单[" + existing.getNo() |
| | | + "]已提交审批流程,不能覆盖;请先在协同办公撤回或作废该审批后再导入"); |
| | | } |
| | | List<ErpPurchaseOrderItemDO> oldItems = purchaseOrderItemMapper.selectListByOrderId(existing.getId()); |
| | | boolean linkedRequest = oldItems.stream().anyMatch(item -> item.getRequestItemId() != null); |
| | | if (linkedRequest) { |
| | | throw new IllegalArgumentException("采购订单[" + existing.getNo() |
| | | + "]已关联采购申请,不能覆盖;请先在采购申请中撤销或删除对应申请后再导入"); |
| | | } |
| | | |
| | | // 2. 更新主表(保留原单号、原入库状态) |
| | | ErpPurchaseOrderDO updateObj = importData.order; |
| | | updateObj.setId(existing.getId()); |
| | | updateObj.setNo(existing.getNo()); |
| | | // 文件没填采购时间时保留原订单时间,避免覆盖时把日期改成当前时间 |
| | | if (!importData.orderTimeProvided) { |
| | | updateObj.setOrderTime(existing.getOrderTime()); |
| | | } |
| | | updateObj.setStatus(Boolean.TRUE.equals(directApprove) |
| | | ? ErpAuditStatus.APPROVE.getStatus() : ErpAuditStatus.PROCESS.getStatus()); |
| | | updateObj.setInCount(existing.getInCount() == null ? BigDecimal.ZERO : existing.getInCount()); |
| | | updateObj.setInStatus(existing.getInStatus()); |
| | | purchaseOrderMapper.updateById(updateObj); |
| | | |
| | | // 3. 明细整组替换 |
| | | purchaseOrderItemMapper.deleteByOrderId(existing.getId()); |
| | | importData.items.forEach(item -> item.setOrderId(existing.getId())); |
| | | purchaseOrderItemMapper.insertBatch(importData.items); |
| | | } |
| | | |
| | | private ErpPurchaseOrderImportDuplicateVO buildImportDuplicate(String label, ErpPurchaseOrderDO existing, |
| | | String reason) { |
| | | return ErpPurchaseOrderImportDuplicateVO.builder() |
| | | .key(label) |
| | | .existingNo(existing.getNo()) |
| | | .existingId(existing.getId()) |
| | | .reason(reason) |
| | | .build(); |
| | | } |
| | | |
| | | /** |
| | | * 生成“供应商 + 完整明细”的订单指纹,用于识别重复导入。 |
| | | * 明细按物料、数量、单价、税率排序后比较,不受行顺序影响。 |
| | | */ |
| | | private String buildOrderFingerprint(Long supplierId, List<ErpPurchaseOrderItemDO> items) { |
| | | if (supplierId == null || CollUtil.isEmpty(items)) { |
| | | return null; |
| | | } |
| | | List<String> parts = items.stream() |
| | | .map(item -> String.join(":", |
| | | String.valueOf(item.getProductId()), |
| | | normalizeNumber(item.getCount()), |
| | | normalizeNumber(item.getProductPrice()), |
| | | normalizeNumber(item.getTaxPercent()))) |
| | | .sorted() |
| | | .toList(); |
| | | return supplierId + "|" + String.join(",", parts); |
| | | } |
| | | |
| | | private String normalizeNumber(BigDecimal value) { |
| | | return value == null ? "" : value.stripTrailingZeros().toPlainString(); |
| | | } |
| | | |
| | | private static class ImportOrderData { |
| | | |
| | | private final ErpPurchaseOrderDO order; |
| | | private final List<ErpPurchaseOrderItemDO> items; |
| | | private final String fingerprint; |
| | | /** |
| | | * 采购单编号是否由系统自动生成(文件里留空) |
| | | */ |
| | | private final boolean generatedNo; |
| | | /** |
| | | * 文件里是否填写了采购时间 |
| | | */ |
| | | private final boolean orderTimeProvided; |
| | | |
| | | private ImportOrderData(ErpPurchaseOrderDO order, List<ErpPurchaseOrderItemDO> items, String fingerprint, |
| | | boolean generatedNo, boolean orderTimeProvided) { |
| | | this.order = order; |
| | | this.items = items; |
| | | this.fingerprint = fingerprint; |
| | | this.generatedNo = generatedNo; |
| | | this.orderTimeProvided = orderTimeProvided; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解析导入的采购时间,兼容多种常见格式;为空取当前时间 |
| | | */ |
| | | private LocalDateTime parseImportOrderTime(String text) { |
| | | if (StrUtil.isBlank(text)) { |
| | | return LocalDateTime.now(); |
| | | } |
| | | 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"}) { |
| | | try { |
| | | return LocalDateTime.parse(value, DateTimeFormatter.ofPattern(pattern)); |
| | | } catch (Exception ignored) { |
| | | // 继续尝试下一个格式 |
| | | } |
| | | } |
| | | for (String pattern : new String[]{"yyyy-MM-dd", "yyyy/M/d"}) { |
| | | try { |
| | | return LocalDate.parse(value, DateTimeFormatter.ofPattern(pattern)).atStartOfDay(); |
| | | } catch (Exception ignored) { |
| | | // 继续尝试下一个格式 |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("采购时间格式不正确:" + text + ",示例 2026-01-01 10:00:00"); |
| | | } |
| | | |
| | | /** |
| | | * 提取导入失败原因(ServiceException 取业务提示) |
| | | */ |
| | | private String resolveImportErrorMessage(Exception ex) { |
| | | if (ex instanceof cn.iocoder.yudao.framework.common.exception.ServiceException serviceException) { |
| | | return serviceException.getMessage(); |
| | | } |
| | | return StrUtil.blankToDefault(ex.getMessage(), "导入失败"); |
| | | } |
| | | |
| | | private void calculateTotalPrice(ErpPurchaseOrderDO purchaseOrder, List<ErpPurchaseOrderItemDO> purchaseOrderItems) { |
| | |
| | | private List<ErpPurchaseOrderItemDO> validatePurchaseOrderItems(List<ErpPurchaseOrderSaveReqVO.Item> list) { |
| | | // 转化为 ErpPurchaseOrderItemDO 列表(产品验证已移除) |
| | | return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> { |
| | | item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())); |
| | | if (item.getTotalPrice() == null) { |
| | | // 单价为含税单价:含税金额 = 单价 × 数量;税额 = 含税金额 × 税率;不含税金额 = 含税金额 - 税额 |
| | | BigDecimal taxInclusivePrice = MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()); |
| | | if (taxInclusivePrice == null) { |
| | | return; |
| | | } |
| | | BigDecimal taxPrice = null; |
| | | if (item.getTaxPercent() != null) { |
| | | item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent())); |
| | | taxPrice = MoneyUtils.priceMultiplyPercent(taxInclusivePrice, item.getTaxPercent()); |
| | | } |
| | | item.setTaxPrice(taxPrice); |
| | | item.setTotalPrice(taxPrice != null ? taxInclusivePrice.subtract(taxPrice) : taxInclusivePrice); |
| | | })); |
| | | } |
| | | |