package cn.iocoder.yudao.module.erp.service.purchase; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; 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.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.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; 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.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.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.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.ErpPurchaseOrderInStatusEnum; import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; import cn.iocoder.yudao.module.mes.api.arrivalnotice.MesArrivalNoticeApi; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.repository.ProcessDefinition; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; /** * ERP 采购订单 Service 实现类 * * @author 芋道源码 */ @Service @Validated @Slf4j public class ErpPurchaseOrderServiceImpl implements ErpPurchaseOrderService { /** * BPM 采购订单审批分类编码 */ private static final String PURCHASE_ORDER_APPROVE_CATEGORY_CODE = "purchase_request_approve"; @Resource private ErpPurchaseOrderMapper purchaseOrderMapper; @Resource private ErpPurchaseOrderItemMapper purchaseOrderItemMapper; @Resource private ErpNoRedisDAO noRedisDAO; @Resource private ErpSupplierService supplierService; @Resource private ErpAccountService accountService; @Resource private BpmProcessInstanceApi processInstanceApi; @Resource private BpmCategoryService bpmCategoryService; @Resource private BpmProcessDefinitionService bpmProcessDefinitionService; @Resource @Lazy private MesArrivalNoticeApi arrivalNoticeApi; @Resource private ErpPurchaseRequestItemMapper purchaseRequestItemMapper; @Resource private ErpPurchaseRequestMapper purchaseRequestMapper; @Override @Transactional(rollbackFor = Exception.class) public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO) { // 1.1 校验订单项的有效性 List purchaseOrderItems = validatePurchaseOrderItems(createReqVO.getItems()); // 1.2 校验供应商 supplierService.validateSupplier(createReqVO.getSupplierId()); // 1.3 校验结算账户 if (createReqVO.getAccountId() != null) { 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); } // 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())); calculateTotalPrice(purchaseOrder, purchaseOrderItems); purchaseOrderMapper.insert(purchaseOrder); // 2.2 插入订单项 purchaseOrderItems.forEach(o -> o.setOrderId(purchaseOrder.getId())); purchaseOrderItemMapper.insertBatch(purchaseOrderItems); return purchaseOrder.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updatePurchaseOrder(ErpPurchaseOrderSaveReqVO updateReqVO) { // 1.1 校验存在 ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(updateReqVO.getId()); // 采购订单不允许修改(已审核状态) if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseOrder.getStatus())) { throw exception(PURCHASE_ORDER_UPDATE_FAIL_APPROVE, purchaseOrder.getNo()); } // 1.2 校验供应商 supplierService.validateSupplier(updateReqVO.getSupplierId()); // 1.3 校验结算账户 if (updateReqVO.getAccountId() != null) { accountService.validateAccount(updateReqVO.getAccountId()); } // 1.4 校验订单项的有效性 List purchaseOrderItems = validatePurchaseOrderItems(updateReqVO.getItems()); // 2.1 更新订单 ErpPurchaseOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseOrderDO.class); calculateTotalPrice(updateObj, purchaseOrderItems); purchaseOrderMapper.updateById(updateObj); // 2.2 更新订单项 updatePurchaseOrderItemList(updateReqVO.getId(), purchaseOrderItems); } private void calculateTotalPrice(ErpPurchaseOrderDO purchaseOrder, List 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())); // 计算优惠价格 if (purchaseOrder.getDiscountPercent() == null) { purchaseOrder.setDiscountPercent(BigDecimal.ZERO); } purchaseOrder.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseOrder.getTotalPrice(), purchaseOrder.getDiscountPercent())); purchaseOrder.setTotalPrice(purchaseOrder.getTotalPrice().subtract(purchaseOrder.getDiscountPrice())); } @Override @Transactional(rollbackFor = Exception.class) public void updatePurchaseOrderStatus(Long id, Integer status) { boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); // 1.1 校验存在 ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id); // 1.2 校验状态 if (purchaseOrder.getStatus().equals(status)) { throw exception(approve ? PURCHASE_ORDER_APPROVE_FAIL : PURCHASE_ORDER_PROCESS_FAIL); } // 1.3 存在采购入库单,无法反审核 BigDecimal inCount = purchaseOrder.getInCount(); if (!approve && inCount != null && inCount.compareTo(BigDecimal.ZERO) > 0) { throw exception(PURCHASE_ORDER_PROCESS_FAIL_EXISTS_IN); } // 1.4 存在采购退货单,无法反审核 BigDecimal returnCount = purchaseOrder.getReturnCount(); if (!approve && returnCount != null && returnCount.compareTo(BigDecimal.ZERO) > 0) { throw exception(PURCHASE_ORDER_PROCESS_FAIL_EXISTS_RETURN); } // 2. 更新状态 int updateCount = purchaseOrderMapper.updateByIdAndStatus(id, purchaseOrder.getStatus(), new ErpPurchaseOrderDO().setStatus(status)); if (updateCount == 0) { throw exception(approve ? PURCHASE_ORDER_APPROVE_FAIL : PURCHASE_ORDER_PROCESS_FAIL); } } @Override @Transactional(rollbackFor = Exception.class) public void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId) { // 1. 校验存在 + 未审核状态 ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id); if (!ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus())) { throw exception(PURCHASE_ORDER_SUBMIT_FAIL_STATUS); } // 2. 创建 BPM 流程实例 String processInstanceId = processInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() .setProcessDefinitionKey(processDefinitionKey) .setBusinessKey(String.valueOf(id))); // 3. 更新采购订单状态为审批中 purchaseOrderMapper.updateById(new ErpPurchaseOrderDO() .setId(id) .setStatus(ErpAuditStatus.APPROVING.getStatus()) .setProcessInstanceId(processInstanceId)); } @Override public List> getPurchaseOrderApproveProcessDefinitionList() { // 1. 校验分类和流程定义是否存在 validatePurchaseOrderApproveCategoryAndProcessDefinition(); // 2. 获取分类下的流程定义信息 List definitionInfoList = bpmProcessDefinitionService .getProcessDefinitionInfoListByCategory(PURCHASE_ORDER_APPROVE_CATEGORY_CODE); if (CollUtil.isEmpty(definitionInfoList)) { return Collections.emptyList(); } // 3. 遍历获取流程定义详情,过滤激活状态,保留最新版本 Map latestVersionMap = new HashMap<>(); for (BpmProcessDefinitionInfoDO info : definitionInfoList) { ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId()); if (pd == null || pd.isSuspended()) { continue; } ProcessDefinition existing = latestVersionMap.get(pd.getKey()); if (existing == null || pd.getVersion() > existing.getVersion()) { latestVersionMap.put(pd.getKey(), pd); } } // 4. 返回流程定义列表 List> result = new ArrayList<>(); for (ProcessDefinition pd : latestVersionMap.values()) { Map item = new HashMap<>(); item.put("id", pd.getId()); item.put("key", pd.getKey()); item.put("name", pd.getName()); result.add(item); } return result; } @Override @Transactional(rollbackFor = Exception.class) public void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult) { // 1. 校验存在 ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id); if (!ErpAuditStatus.APPROVING.getStatus().equals(purchaseOrder.getStatus())) { log.warn("[updatePurchaseOrderAuditStatus] 采购订单({}) 不处于审批中状态", id); return; } // 2. 根据审批结果更新状态 Integer newStatus = convertBpmResultToAuditStatus(bpmResult); if (newStatus != null) { purchaseOrderMapper.updateById(new ErpPurchaseOrderDO() .setId(id).setStatus(newStatus)); } } /** * 校验采购订单审批分类和流程定义是否存在 */ private void validatePurchaseOrderApproveCategoryAndProcessDefinition() { // 1. 校验分类是否存在 List categories = bpmCategoryService.getCategoryListByCode( Collections.singletonList(PURCHASE_ORDER_APPROVE_CATEGORY_CODE)); if (CollUtil.isEmpty(categories)) { throw exception(PURCHASE_ORDER_BPM_CATEGORY_NOT_EXISTS); } } /** * 转换 BPM 审批结果为审核状态 */ private Integer convertBpmResultToAuditStatus(Integer bpmResult) { if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) { return ErpAuditStatus.APPROVE.getStatus(); } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) { return ErpAuditStatus.PROCESS.getStatus(); } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) { return ErpAuditStatus.CANCEL.getStatus(); } return null; } private List validatePurchaseOrderItems(List 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())); } })); } private void updatePurchaseOrderItemList(Long id, List newList) { // 第一步,对比新老数据,获得添加、修改、删除的列表 List oldList = purchaseOrderItemMapper.selectListByOrderId(id); List> diffList = diffList(oldList, newList, (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); // 第二步,批量添加、修改、删除 if (CollUtil.isNotEmpty(diffList.get(0))) { diffList.get(0).forEach(o -> o.setOrderId(id)); purchaseOrderItemMapper.insertBatch(diffList.get(0)); } if (CollUtil.isNotEmpty(diffList.get(1))) { purchaseOrderItemMapper.updateBatch(diffList.get(1)); } if (CollUtil.isNotEmpty(diffList.get(2))) { purchaseOrderItemMapper.deleteByIds(convertList(diffList.get(2), ErpPurchaseOrderItemDO::getId)); } } @Override public void updatePurchaseOrderInCount(Long id, Map inCountMap) { List orderItems = purchaseOrderItemMapper.selectListByOrderId(id); // 1. 更新每个采购订单项 orderItems.forEach(item -> { BigDecimal inCount = inCountMap.getOrDefault(item.getId(), BigDecimal.ZERO); if (item.getInCount().equals(inCount)) { return; } if (inCount.compareTo(item.getCount()) > 0) { throw exception(PURCHASE_ORDER_ITEM_IN_FAIL_PRODUCT_EXCEED, "产品ID:" + item.getProductId(), item.getCount()); } purchaseOrderItemMapper.updateById(new ErpPurchaseOrderItemDO().setId(item.getId()).setInCount(inCount)); }); // 2. 更新采购订单 BigDecimal totalInCount = getSumValue(inCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO); purchaseOrderMapper.updateById(new ErpPurchaseOrderDO().setId(id).setInCount(totalInCount)); } @Override public void updatePurchaseOrderReturnCount(Long orderId, Map returnCountMap) { List orderItems = purchaseOrderItemMapper.selectListByOrderId(orderId); // 1. 更新每个采购订单项 orderItems.forEach(item -> { BigDecimal returnCount = returnCountMap.getOrDefault(item.getId(), BigDecimal.ZERO); if (item.getReturnCount().equals(returnCount)) { return; } if (returnCount.compareTo(item.getInCount()) > 0) { throw exception(PURCHASE_ORDER_ITEM_RETURN_FAIL_IN_EXCEED, "产品ID:" + item.getProductId(), item.getInCount()); } purchaseOrderItemMapper.updateById(new ErpPurchaseOrderItemDO().setId(item.getId()).setReturnCount(returnCount)); }); // 2. 更新采购订单 BigDecimal totalReturnCount = getSumValue(returnCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO); purchaseOrderMapper.updateById(new ErpPurchaseOrderDO().setId(orderId).setReturnCount(totalReturnCount)); } @Override @Transactional(rollbackFor = Exception.class) public void deletePurchaseOrder(List ids) { // 1. 校验不处于已审批 List purchaseOrders = purchaseOrderMapper.selectByIds(ids); if (CollUtil.isEmpty(purchaseOrders)) { return; } purchaseOrders.forEach(purchaseOrder -> { if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseOrder.getStatus())) { throw exception(PURCHASE_ORDER_DELETE_FAIL_APPROVE, purchaseOrder.getNo()); } }); // 2. 遍历删除 purchaseOrders.forEach(purchaseOrder -> { purchaseOrderMapper.deleteById(purchaseOrder.getId()); purchaseOrderItemMapper.deleteByOrderId(purchaseOrder.getId()); }); } private ErpPurchaseOrderDO validatePurchaseOrderExists(Long id) { ErpPurchaseOrderDO purchaseOrder = purchaseOrderMapper.selectById(id); if (purchaseOrder == null) { throw exception(PURCHASE_ORDER_NOT_EXISTS); } return purchaseOrder; } @Override public ErpPurchaseOrderDO getPurchaseOrder(Long id) { return purchaseOrderMapper.selectById(id); } @Override public ErpPurchaseOrderDO validatePurchaseOrder(Long id) { ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id); if (ObjectUtil.notEqual(purchaseOrder.getStatus(), ErpAuditStatus.APPROVE.getStatus())) { throw exception(PURCHASE_ORDER_NOT_APPROVE); } return purchaseOrder; } @Override public PageResult getPurchaseOrderPage(ErpPurchaseOrderPageReqVO pageReqVO) { return purchaseOrderMapper.selectPage(pageReqVO); } // ==================== 订单项 ==================== @Override public List getPurchaseOrderItemListByOrderId(Long orderId) { return purchaseOrderItemMapper.selectListByOrderId(orderId); } @Override public List getPurchaseOrderItemListByOrderIds(Collection orderIds) { if (CollUtil.isEmpty(orderIds)) { return Collections.emptyList(); } return purchaseOrderItemMapper.selectListByOrderIds(orderIds); } @Override public List getPurchaseOrderListBySupplierIdAndStatus(Long supplierId, Integer status) { if (supplierId == null) { return Collections.emptyList(); } return purchaseOrderMapper.selectList(supplierId, status); } @Override public List getPurchaseOrderList(List ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } return purchaseOrderMapper.selectByIds(ids); } @Override public void updatePurchaseOrderInStatus(Long orderId) { // 1. 获取采购订单 ErpPurchaseOrderDO order = validatePurchaseOrderExists(orderId); // 2. 获取采购订单明细 List items = purchaseOrderItemMapper.selectListByOrderId(orderId); if (CollUtil.isEmpty(items)) { return; } // 3. 计算入库状态和总入库数量 int inStatus = calculateInStatus(items); BigDecimal totalInCount = calculateTotalInCount(items); // 4. 更新采购订单入库状态和入库数量 purchaseOrderMapper.updateById(new ErpPurchaseOrderDO() .setId(orderId).setInStatus(inStatus).setInCount(totalInCount)); // 5. 回写采购申请明细的入库数量和采购申请的入库状态 updatePurchaseRequestInStatus(items, inStatus); } /** * 计算总入库数量 */ private BigDecimal calculateTotalInCount(List items) { BigDecimal total = BigDecimal.ZERO; for (ErpPurchaseOrderItemDO item : items) { if (item.getInCount() != null) { total = total.add(item.getInCount()); } } return total; } /** * 回写采购申请入库数量和入库状态 * * @param orderItems 采购订单明细列表 * @param orderInStatus 采购订单入库状态 */ private void updatePurchaseRequestInStatus(List orderItems, int orderInStatus) { // 获取所有关联的采购申请明细 ID Set requestItemIds = new HashSet<>(); for (ErpPurchaseOrderItemDO item : orderItems) { if (item.getRequestItemId() != null) { requestItemIds.add(item.getRequestItemId()); } } if (requestItemIds.isEmpty()) { return; } // 更新采购申请明细的入库数量 for (ErpPurchaseOrderItemDO orderItem : orderItems) { if (orderItem.getRequestItemId() != null && orderItem.getInCount() != null) { purchaseRequestItemMapper.updateById(new ErpPurchaseRequestItemDO() .setId(orderItem.getRequestItemId()) .setInCount(orderItem.getInCount())); } } // 获取采购申请明细,找到对应的采购申请 ID List requestItems = purchaseRequestItemMapper.selectBatchIds(requestItemIds); if (CollUtil.isEmpty(requestItems)) { return; } Set requestIds = convertSet(requestItems, ErpPurchaseRequestItemDO::getRequestId); // 更新采购申请的入库状态 for (Long requestId : requestIds) { purchaseRequestMapper.updateById(new ErpPurchaseRequestDO() .setId(requestId) .setInStatus(orderInStatus)); } } /** * 计算入库状态 * * @param items 采购订单明细列表 * @return 入库状态 */ private int calculateInStatus(List items) { boolean allIn = true; boolean anyIn = false; for (ErpPurchaseOrderItemDO item : items) { BigDecimal count = item.getCount() != null ? item.getCount() : BigDecimal.ZERO; BigDecimal inCount = item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO; if (inCount.compareTo(BigDecimal.ZERO) > 0) { anyIn = true; } if (inCount.compareTo(count) < 0) { allIn = false; } } if (allIn) { return ErpPurchaseOrderInStatusEnum.ALL_IN.getStatus(); } else if (anyIn) { return ErpPurchaseOrderInStatusEnum.PART_IN.getStatus(); } else { return ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus(); } } @Override @Transactional(rollbackFor = Exception.class) public Long confirmReceipt(Long id) { // 1. 校验采购订单存在且已审核 ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrder(id); // 2. 校验采购订单未全部入库 if (ErpPurchaseOrderInStatusEnum.ALL_IN.getStatus().equals(purchaseOrder.getInStatus())) { throw exception(PURCHASE_ORDER_ALL_IN); } // 3. 调用 MES 接口生成到货通知单 Long arrivalNoticeId = arrivalNoticeApi.generateArrivalNoticeFromPurchaseOrder(id); log.info("[confirmReceipt] 采购订单({}) 确认收货,生成到货通知单({})", id, arrivalNoticeId); return arrivalNoticeId; } }