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<ErpPurchaseInvoiceDO> 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<ErpPurchaseInvoiceDO> getPurchaseInvoiceList(Collection<Long> ids) {
|
if (CollUtil.isEmpty(ids)) {
|
return List.of();
|
}
|
return invoiceMapper.selectByIds(ids);
|
}
|
|
@Override
|
public PageResult<ErpPurchaseInvoiceDO> getPurchaseInvoicePage(ErpPurchaseInvoicePageReqVO pageReqVO) {
|
return invoiceMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListByOrderIdAndStatus(Long orderId, Collection<Integer> auditStatuses) {
|
return invoiceMapper.selectListByOrderIdAndStatus(orderId, auditStatuses);
|
}
|
|
@Override
|
public List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListBySupplierIdAndStatus(Long supplierId, Collection<Integer> auditStatuses) {
|
return invoiceMapper.selectListBySupplierIdAndStatus(supplierId, auditStatuses);
|
}
|
|
@Override
|
public Long getAuditInvoiceCount() {
|
return invoiceMapper.selectCountByAudit();
|
}
|
|
}
|