| | |
| | | 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.framework.operatelog.core.util.OperateLogUtils; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| | |
| | | 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 com.mzt.logapi.starter.annotation.LogRecord; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.flowable.engine.repository.ProcessDefinition; |
| | |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; |
| | | import static cn.iocoder.yudao.module.erp.enums.LogRecordConstants.*; |
| | | |
| | | /** |
| | | * ERP 采购订单 Service 实现类 |
| | |
| | | private ErpPurchaseOrderMapper purchaseOrderMapper; |
| | | @Resource |
| | | private ErpPurchaseOrderItemMapper purchaseOrderItemMapper; |
| | | @Resource |
| | | private ErpSupplierContractService supplierContractService; |
| | | |
| | | @Resource |
| | | private ErpNoRedisDAO noRedisDAO; |
| | |
| | | if (createReqVO.getAccountId() != null) { |
| | | accountService.validateAccount(createReqVO.getAccountId()); |
| | | } |
| | | // 1.3.1 校验并解析关联合同(订单必须与合同属于同一供应商) |
| | | String contractNo = resolveContractNo(createReqVO.getContractId(), createReqVO.getSupplierId()); |
| | | // 1.4 生成订单号,并校验唯一性 |
| | | String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX); |
| | | if (purchaseOrderMapper.selectByNo(no) != null) { |
| | |
| | | // 2.1 插入订单(采购订单创建后为未审核状态,需要提交审批) |
| | | ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in |
| | | .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()) |
| | | .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus())); |
| | | .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus()) |
| | | .setContractNo(contractNo)); |
| | | calculateTotalPrice(purchaseOrder, purchaseOrderItems); |
| | | purchaseOrderMapper.insert(purchaseOrder); |
| | | // 2.2 插入订单项 |
| | |
| | | } |
| | | // 1.4 校验订单项的有效性 |
| | | List<ErpPurchaseOrderItemDO> purchaseOrderItems = validatePurchaseOrderItems(updateReqVO.getItems()); |
| | | // 1.5 校验并解析关联合同 |
| | | String contractNo = resolveContractNo(updateReqVO.getContractId(), updateReqVO.getSupplierId()); |
| | | |
| | | // 2.1 更新订单 |
| | | ErpPurchaseOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseOrderDO.class); |
| | | updateObj.setContractNo(contractNo); |
| | | calculateTotalPrice(updateObj, purchaseOrderItems); |
| | | purchaseOrderMapper.updateById(updateObj); |
| | | // 2.2 更新订单项 |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @LogRecord(type = ERP_PURCHASE_ORDER_TYPE, subType = ERP_PURCHASE_ORDER_IMPORT_SUB_TYPE, bizNo = "0", |
| | | success = ERP_PURCHASE_ORDER_IMPORT_SUCCESS) |
| | | public ErpPurchaseOrderImportRespVO importPurchaseOrderList(List<ErpPurchaseOrderImportExcelVO> importList, |
| | | Boolean directApprove) { |
| | | ErpPurchaseOrderImportRespVO respVO = ErpPurchaseOrderImportRespVO.builder() |
| | |
| | | .failureList(new LinkedHashMap<>()) |
| | | .build(); |
| | | if (CollUtil.isEmpty(importList)) { |
| | | // 记录导入结果汇总,供 @LogRecord 的 success 文案引用 |
| | | OperateLogUtils.putImportSummary(respVO.getCreateList(), respVO.getUpdateList(), respVO.getFailureList()); |
| | | return respVO; |
| | | } |
| | | |
| | |
| | | respVO.getFailureList().put(label, resolveImportErrorMessage(ex)); |
| | | } |
| | | } |
| | | // 记录导入结果汇总,供 @LogRecord 的 success 文案引用 |
| | | OperateLogUtils.putImportSummary(respVO.getCreateList(), respVO.getUpdateList(), respVO.getFailureList()); |
| | | return respVO; |
| | | } |
| | | |
| | |
| | | |
| | | private void calculateTotalPrice(ErpPurchaseOrderDO purchaseOrder, List<ErpPurchaseOrderItemDO> purchaseOrderItems) { |
| | | 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())); |
| | | // 明细的 totalPrice 是「数量 × 含税单价」,即含税总价;不含税金额 = 含税总价 − 税额 |
| | | BigDecimal totalPriceWithTax = getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO); |
| | | BigDecimal totalTaxPrice = getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO); |
| | | purchaseOrder.setTotalTaxPrice(totalTaxPrice); |
| | | purchaseOrder.setTotalPrice(totalPriceWithTax); |
| | | purchaseOrder.setTotalProductPrice(totalPriceWithTax.subtract(totalTaxPrice)); |
| | | // 计算优惠价格 |
| | | if (purchaseOrder.getDiscountPercent() == null) { |
| | | purchaseOrder.setDiscountPercent(BigDecimal.ZERO); |
| | |
| | | } |
| | | |
| | | // 2. 创建 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 -> { |
| | | // 产品单价为含税单价:行总价 = 数量 × 含税单价,税额再从含税总价里倒算 |
| | | 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.priceCalculateTaxIncluded(item.getTotalPrice(), item.getTaxPercent())); |
| | | })); |
| | | } |
| | | |
| | |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 校验关联合同,返回冗余的合同编码;未关联合同时返回 null |
| | | */ |
| | | private String resolveContractNo(Long contractId, Long supplierId) { |
| | | if (contractId == null) { |
| | | return null; |
| | | } |
| | | cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierContractDO contract = |
| | | supplierContractService.validateContractExists(contractId); |
| | | if (!Objects.equals(contract.getSupplierId(), supplierId)) { |
| | | throw exception(SUPPLIER_CONTRACT_SUPPLIER_MISMATCH); |
| | | } |
| | | return contract.getCode(); |
| | | } |
| | | |
| | | private ErpPurchaseOrderDO validatePurchaseOrderExists(Long id) { |
| | | ErpPurchaseOrderDO purchaseOrder = purchaseOrderMapper.selectById(id); |
| | | if (purchaseOrder == null) { |