| | |
| | | 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; |
| | |
| | | @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 校验结算账户 |
| | |
| | | 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); |
| | |
| | | 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(); |
| | | } |
| | |
| | | } |
| | | // 1.4 校验订单项的有效性 |
| | | List<ErpPurchaseOrderItemDO> purchaseOrderItems = validatePurchaseOrderItems(updateReqVO.getItems()); |
| | | // 1.5 校验采购台账字段 |
| | | validateLedgerFields(updateReqVO); |
| | | |
| | | // 2.1 更新订单 |
| | | ErpPurchaseOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseOrderDO.class); |
| | |
| | | } |
| | | 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()); |
| | |
| | | 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()); |
| | |
| | | * 解析导入的采购时间,兼容多种常见格式;为空取当前时间 |
| | | */ |
| | | 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"}) { |
| | |
| | | // 继续尝试下一个格式 |
| | | } |
| | | } |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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); |
| | |
| | | @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) |
| | |
| | | |
| | | 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) { |