package cn.iocoder.yudao.module.erp.service.purchase; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoicePageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceSaveReqVO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInvoiceMapper; import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; import cn.iocoder.yudao.module.erp.enums.ErpPurchaseInvoiceAuditStatusEnum; import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue; import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; /** * ERP 采购来票 Service 实现类 * * @author 超级管理员 */ @Service @Validated @Slf4j public class ErpPurchaseInvoiceServiceImpl implements ErpPurchaseInvoiceService { /** * BPM 来票审批流程标识 */ public static final String BPM_PROCESS_DEFINITION_KEY = "erp-purchase-invoice-audit"; /** * 来票附件的业务记录类型,对应 {@link cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum#ERP_PURCHASE_INVOICE} */ public static final String PURCHASE_INVOICE_RECORD_TYPE = "erp_purchase_invoice"; @Resource private ErpPurchaseInvoiceMapper invoiceMapper; @Resource private ErpNoRedisDAO noRedisDAO; @Resource private ErpPurchaseOrderService purchaseOrderService; @Resource private BpmProcessInstanceApi bpmProcessInstanceApi; @Resource private StorageAttachmentApi storageAttachmentApi; @Override @Transactional(rollbackFor = Exception.class) public Long createPurchaseInvoice(ErpPurchaseInvoiceSaveReqVO createReqVO) { // 1.1 校验关联数据存在 validateRelationDataExists(createReqVO); // 1.2 校验可开票金额超过上限 validatePurchaseInvoicePriceExceedsLimit(createReqVO); // 1.3 生成来票编号 String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_INVOICE_NO_PREFIX); if (invoiceMapper.selectByNo(no) != null) { throw exception(PURCHASE_INVOICE_NO_EXISTS); } // 2.1 插入来票 ErpPurchaseInvoiceDO invoice = BeanUtils.toBean(createReqVO, ErpPurchaseInvoiceDO.class) .setNo(no).setAuditStatus(ErpPurchaseInvoiceAuditStatusEnum.DRAFT.getStatus()); invoiceMapper.insert(invoice); // 2.2 绑定发票附件 if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) { storageAttachmentApi.bindAttachments("file", PURCHASE_INVOICE_RECORD_TYPE, invoice.getId(), createReqVO.getBlobIds()); } return invoice.getId(); } private void validateRelationDataExists(ErpPurchaseInvoiceSaveReqVO reqVO) { // 校验采购订单,必须审核通过 ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(reqVO.getPurchaseOrderId()); // 校验供应商与采购订单一致(防御脏数据:来票必须属于该采购订单的供应商) if (reqVO.getSupplierId() != null && ObjectUtil.notEqual(purchaseOrder.getSupplierId(), reqVO.getSupplierId())) { throw exception(PURCHASE_INVOICE_CREATE_FAIL_SUPPLIER_MISMATCH); } reqVO.setSupplierId(purchaseOrder.getSupplierId()); // 供应商以采购订单为准 } private void validatePurchaseInvoicePriceExceedsLimit(ErpPurchaseInvoiceSaveReqVO reqVO) { // 1. 计算剩余可开票金额,不包括 reqVO 自身(审批中也占用额度,防止累计来票超过采购订单金额) ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrder(reqVO.getPurchaseOrderId()); List invoices = invoiceMapper.selectListByOrderIdAndStatus(reqVO.getPurchaseOrderId(), List.of(ErpPurchaseInvoiceAuditStatusEnum.DRAFT.getStatus(), ErpPurchaseInvoiceAuditStatusEnum.PROCESS.getStatus(), ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus())); if (reqVO.getId() != null) { invoices.removeIf(invoice -> ObjectUtil.equal(invoice.getId(), reqVO.getId())); } BigDecimal remainPrice = purchaseOrder.getTotalPrice().subtract( getSumValue(invoices, ErpPurchaseInvoiceDO::getPrice, BigDecimal::add, BigDecimal.ZERO)); // 2. 校验金额是否超过 if (reqVO.getPrice().compareTo(remainPrice) > 0) { throw exception(PURCHASE_INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT, remainPrice); } } @Override @Transactional(rollbackFor = Exception.class) public void updatePurchaseInvoice(ErpPurchaseInvoiceSaveReqVO updateReqVO) { Assert.notNull(updateReqVO.getId(), "来票编号不能为空"); // 1.1 校验存在 ErpPurchaseInvoiceDO oldInvoice = validatePurchaseInvoiceExists(updateReqVO.getId()); // 1.2 只有草稿可以编辑 if (ObjectUtil.notEqual(oldInvoice.getAuditStatus(), ErpPurchaseInvoiceAuditStatusEnum.DRAFT.getStatus())) { throw exception(PURCHASE_INVOICE_UPDATE_FAIL_EDITING_PROHIBITED, oldInvoice.getNo()); } // 1.3 校验关联数据 + 可开票金额 validateRelationDataExists(updateReqVO); validatePurchaseInvoicePriceExceedsLimit(updateReqVO); // 2. 更新来票 ErpPurchaseInvoiceDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseInvoiceDO.class); invoiceMapper.updateById(updateObj); // 3. 增量更新发票附件 storageAttachmentApi.updateAttachments("file", PURCHASE_INVOICE_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds()); } @Override @Transactional(rollbackFor = Exception.class) public void deletePurchaseInvoice(Long id) { // 1.1 校验存在 ErpPurchaseInvoiceDO invoice = validatePurchaseInvoiceExists(id); // 1.2 审批通过时,不允许删除 if (ObjectUtil.equal(invoice.getAuditStatus(), ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus())) { throw exception(PURCHASE_INVOICE_DELETE_FAIL_IS_APPROVE); } // 2.1 删除发票附件 storageAttachmentApi.deleteAttachmentsByRecord(PURCHASE_INVOICE_RECORD_TYPE, id); // 2.2 删除来票 invoiceMapper.deleteById(id); } @Override @Transactional(rollbackFor = Exception.class) public void submitPurchaseInvoice(Long id, Long userId) { // 1. 校验来票是否在审批 ErpPurchaseInvoiceDO invoice = validatePurchaseInvoiceExists(id); if (ObjectUtil.notEqual(invoice.getAuditStatus(), ErpPurchaseInvoiceAuditStatusEnum.DRAFT.getStatus())) { throw exception(PURCHASE_INVOICE_SUBMIT_FAIL_NOT_DRAFT); } // 1.1 重新校验可开票金额,防止累计来票超过采购订单金额(草稿期后可能有其他单进入审批中) validatePurchaseInvoicePriceExceedsLimit(new ErpPurchaseInvoiceSaveReqVO() .setId(invoice.getId()).setPurchaseOrderId(invoice.getPurchaseOrderId()) .setSupplierId(invoice.getSupplierId()).setPrice(invoice.getPrice())); // 2. 创建来票审批流程实例 String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id))); // 3. 更新来票工作流编号 invoiceMapper.updateById(new ErpPurchaseInvoiceDO().setId(id).setProcessInstanceId(processInstanceId) .setAuditStatus(ErpPurchaseInvoiceAuditStatusEnum.PROCESS.getStatus())); } @Override public void updatePurchaseInvoiceAuditStatus(Long id, Integer bpmResult) { // 1. 校验存在 + 审批中状态 ErpPurchaseInvoiceDO invoice = validatePurchaseInvoiceExists(id); if (ObjectUtil.notEqual(invoice.getAuditStatus(), ErpPurchaseInvoiceAuditStatusEnum.PROCESS.getStatus())) { log.warn("[updatePurchaseInvoiceAuditStatus][来票({}) 不处于审批中,无法更新审批结果({})]", id, bpmResult); return; } // 2. 根据审批结果更新状态 Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); if (auditStatus != null) { invoiceMapper.updateById(new ErpPurchaseInvoiceDO().setId(id).setAuditStatus(auditStatus)); } } private Integer convertBpmResultToAuditStatus(Integer bpmResult) { if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) { return ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus(); } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) { return ErpPurchaseInvoiceAuditStatusEnum.REJECT.getStatus(); } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) { return ErpPurchaseInvoiceAuditStatusEnum.CANCEL.getStatus(); } return null; } private ErpPurchaseInvoiceDO validatePurchaseInvoiceExists(Long id) { ErpPurchaseInvoiceDO invoice = invoiceMapper.selectById(id); if (invoice == null) { throw exception(PURCHASE_INVOICE_NOT_EXISTS); } return invoice; } @Override public ErpPurchaseInvoiceDO getPurchaseInvoice(Long id) { return invoiceMapper.selectById(id); } @Override public List getPurchaseInvoiceList(Collection ids) { if (CollUtil.isEmpty(ids)) { return List.of(); } return invoiceMapper.selectByIds(ids); } @Override public PageResult getPurchaseInvoicePage(ErpPurchaseInvoicePageReqVO pageReqVO) { return invoiceMapper.selectPage(pageReqVO); } @Override public List getPurchaseInvoiceListByOrderIdAndStatus(Long orderId, Collection auditStatuses) { return invoiceMapper.selectListByOrderIdAndStatus(orderId, auditStatuses); } @Override public List getPurchaseInvoiceListBySupplierIdAndStatus(Long supplierId, Collection auditStatuses) { return invoiceMapper.selectListBySupplierIdAndStatus(supplierId, auditStatuses); } @Override public Long getAuditInvoiceCount() { return invoiceMapper.selectCountByAudit(); } }