| | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableItemDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableItemMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO; |
| | | import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableBizTypeEnum; |
| | | import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi; |
| | | import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO; |
| | | import cn.iocoder.yudao.module.mes.api.productsales.MesWmProductSalesApi; |
| | | import cn.iocoder.yudao.module.mes.api.productsales.dto.MesWmProductSalesRespDTO; |
| | | import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; |
| | | import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; |
| | | import cn.iocoder.yudao.module.crm.service.invoice.CrmInvoiceService; |
| | |
| | | import com.mzt.logapi.service.impl.DiffParseFunction; |
| | | import com.mzt.logapi.starter.annotation.LogRecord; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; |
| | | import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; |
| | | import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus; |
| | | |
| | | /** |
| | | * CRM 回款 Service 实现类 |
| | |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class CrmReceivableServiceImpl implements CrmReceivableService { |
| | | |
| | | /** |
| | | * BPM 合同审批流程标识 |
| | | */ |
| | | public static final String BPM_PROCESS_DEFINITION_KEY = "crm-receivable-audit"; |
| | | |
| | | @Resource |
| | | private CrmReceivableMapper receivableMapper; |
| | | @Resource |
| | | private CrmReceivableItemMapper receivableItemMapper; |
| | | |
| | | @Resource |
| | | private ErpSaleOrderApi saleOrderApi; |
| | | @Resource |
| | | private MesWmProductSalesApi productSalesApi; |
| | | |
| | | @Resource |
| | | private CrmNoRedisDAO noRedisDAO; |
| | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private BpmProcessInstanceApi bpmProcessInstanceApi; |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | |
| | | @Override |
| | |
| | | @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}", |
| | | success = CRM_RECEIVABLE_CREATE_SUCCESS) |
| | | public Long createReceivable(CrmReceivableSaveReqVO createReqVO) { |
| | | // 1.1 校验可回款金额超过上限 |
| | | validateReceivablePriceExceedsLimit(createReqVO); |
| | | // 1.2 校验可回款金额不超过累计已开票金额(先开票后回款) |
| | | validateReceivablePriceExceedsInvoiceLimit(createReqVO); |
| | | // 1.3 校验关联数据存在 |
| | | // 1.1 校验关联数据存在 |
| | | validateRelationDataExists(createReqVO); |
| | | // 1.4 生成回款编号 |
| | | // 1.2 校验关联开票合格(审批通过 + 已上传发票附件 + 与合同匹配) |
| | | validateInvoiceForReceivable(createReqVO); |
| | | // 1.3 校验可回款金额不超过合同剩余可回款金额 |
| | | validateReceivablePriceExceedsLimit(createReqVO); |
| | | // 1.4 校验可回款金额不超过该开票剩余可回款金额(先开票后回款) |
| | | validateReceivablePriceExceedsInvoiceRemaining(createReqVO); |
| | | // 1.5 生成回款编号 |
| | | String no = noRedisDAO.generate(CrmNoRedisDAO.RECEIVABLE_PREFIX); |
| | | if (receivableMapper.selectByNo(no) != null) { |
| | | throw exception(RECEIVABLE_NO_EXISTS); |
| | | } |
| | | |
| | | // 2.1 插入回款 |
| | | // 2.1 插入回款(创建后即为未审核状态) |
| | | CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class) |
| | | .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); |
| | | .setNo(no).setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()); |
| | | receivableMapper.insert(receivable); |
| | | // 2.2 |
| | | // 2.2 保存核销明细(未传时按关联开票自动生成一条) |
| | | syncReceivableItems(receivable, createReqVO.getItems()); |
| | | |
| | | // 3. 创建数据权限 |
| | | permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType()) |
| | |
| | | return receivable.getId(); |
| | | } |
| | | |
| | | /** |
| | | * 保存核销明细 |
| | | * |
| | | * 传 null 时:若关联开票,自动生成一条「开票」核销明细;否则不生成。 |
| | | */ |
| | | private void syncReceivableItems(CrmReceivableDO receivable, |
| | | List<CrmReceivableSaveReqVO.ReceivableItem> items) { |
| | | if (items == null) { |
| | | if (receivable.getInvoiceId() == null) { |
| | | return; |
| | | } |
| | | CrmReceivableSaveReqVO.ReceivableItem autoItem = new CrmReceivableSaveReqVO.ReceivableItem(); |
| | | autoItem.setBizType(CrmReceivableBizTypeEnum.SALE_INVOICE.getType()); |
| | | autoItem.setBizId(receivable.getInvoiceId()); |
| | | autoItem.setReceiptPrice(receivable.getPrice()); |
| | | items = List.of(autoItem); |
| | | } |
| | | // 1. 校验并解析每行关联单据 |
| | | List<ResolvedReceivableItem> resolvedItems = new ArrayList<>(items.size()); |
| | | BigDecimal itemPriceSum = BigDecimal.ZERO; |
| | | for (int i = 0; i < items.size(); i++) { |
| | | CrmReceivableSaveReqVO.ReceivableItem item = items.get(i); |
| | | int row = i + 1; |
| | | CrmReceivableBizTypeEnum bizType = item == null ? null |
| | | : CrmReceivableBizTypeEnum.valueOf(item.getBizType()); |
| | | if (bizType == null) { |
| | | throw exception(RECEIVABLE_ITEM_BIZ_TYPE_INVALID, row, |
| | | item == null ? "未填写" : item.getBizType()); |
| | | } |
| | | if (item.getReceiptPrice() == null || item.getReceiptPrice().compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw exception(RECEIVABLE_ITEM_PRICE_INVALID, row, bizType.getName(), |
| | | item.getBizId(), item.getReceiptPrice() == null ? "未填写" |
| | | : item.getReceiptPrice().stripTrailingZeros().toPlainString()); |
| | | } |
| | | ResolvedReceivableItem resolved = resolveReceivableItem(row, bizType, item.getBizId()); |
| | | // 1.1 已核销金额(不含本回款单,避免编辑时把自己算进去) |
| | | BigDecimal receiptedPrice = receivableItemMapper.selectListByBiz(bizType.getType(), item.getBizId()).stream() |
| | | .filter(old -> ObjectUtil.notEqual(old.getReceivableId(), receivable.getId())) |
| | | .map(old -> old.getReceiptPrice() == null ? BigDecimal.ZERO : old.getReceiptPrice()) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal remainPrice = resolved.totalPrice.subtract(receiptedPrice); |
| | | if (item.getReceiptPrice().compareTo(remainPrice) > 0) { |
| | | throw exception(RECEIVABLE_ITEM_PRICE_EXCEEDS, row, bizType.getName(), resolved.bizNo, |
| | | item.getReceiptPrice().stripTrailingZeros().toPlainString(), |
| | | remainPrice.stripTrailingZeros().toPlainString(), |
| | | resolved.totalPrice.stripTrailingZeros().toPlainString(), |
| | | receiptedPrice.stripTrailingZeros().toPlainString()); |
| | | } |
| | | resolved.receiptedPrice = receiptedPrice; |
| | | resolved.receiptPrice = item.getReceiptPrice(); |
| | | resolved.remark = item.getRemark(); |
| | | resolvedItems.add(resolved); |
| | | itemPriceSum = itemPriceSum.add(item.getReceiptPrice()); |
| | | } |
| | | // 2. 明细合计必须等于回款金额 |
| | | if (itemPriceSum.compareTo(receivable.getPrice()) != 0) { |
| | | throw exception(RECEIVABLE_ITEM_SUM_MISMATCH, |
| | | itemPriceSum.stripTrailingZeros().toPlainString(), |
| | | receivable.getPrice().stripTrailingZeros().toPlainString()); |
| | | } |
| | | // 3. 重建明细 |
| | | receivableItemMapper.deleteByReceivableId(receivable.getId()); |
| | | for (ResolvedReceivableItem resolved : resolvedItems) { |
| | | receivableItemMapper.insert(CrmReceivableItemDO.builder() |
| | | .receivableId(receivable.getId()).bizType(resolved.bizType.getType()) |
| | | .bizId(resolved.bizId).bizNo(resolved.bizNo).totalPrice(resolved.totalPrice) |
| | | .receiptedPrice(resolved.receiptedPrice).receiptPrice(resolved.receiptPrice) |
| | | .remark(resolved.remark).build()); |
| | | } |
| | | } |
| | | |
| | | private ResolvedReceivableItem resolveReceivableItem(int row, CrmReceivableBizTypeEnum bizType, Long bizId) { |
| | | ResolvedReceivableItem resolved = new ResolvedReceivableItem(); |
| | | resolved.bizType = bizType; |
| | | resolved.bizId = bizId; |
| | | switch (bizType) { |
| | | case SALE_ORDER -> { |
| | | ErpSaleOrderRespDTO saleOrder = bizId == null ? null : saleOrderApi.getSaleOrder(bizId).getCheckedData(); |
| | | if (saleOrder == null) { |
| | | throw exception(RECEIVABLE_ITEM_BIZ_NOT_EXISTS, row, bizType.getName(), bizId); |
| | | } |
| | | resolved.bizNo = saleOrder.getNo(); |
| | | resolved.totalPrice = saleOrder.getTotalPrice() == null ? BigDecimal.ZERO : saleOrder.getTotalPrice(); |
| | | } |
| | | case SALE_OUT -> { |
| | | MesWmProductSalesRespDTO saleOut = bizId == null ? null : productSalesApi.getProductSales(bizId); |
| | | if (saleOut == null) { |
| | | throw exception(RECEIVABLE_ITEM_BIZ_NOT_EXISTS, row, bizType.getName(), bizId); |
| | | } |
| | | resolved.bizNo = saleOut.getCode(); |
| | | resolved.totalPrice = saleOut.getTotalPrice() == null ? BigDecimal.ZERO : saleOut.getTotalPrice(); |
| | | } |
| | | case SALE_INVOICE -> { |
| | | CrmInvoiceDO invoice = bizId == null ? null : invoiceService.getInvoice(bizId); |
| | | if (invoice == null) { |
| | | throw exception(RECEIVABLE_ITEM_BIZ_NOT_EXISTS, row, bizType.getName(), bizId); |
| | | } |
| | | resolved.bizNo = invoice.getNo(); |
| | | resolved.totalPrice = invoice.getPrice() == null ? BigDecimal.ZERO : invoice.getPrice(); |
| | | } |
| | | default -> throw exception(RECEIVABLE_ITEM_BIZ_TYPE_INVALID, row, bizType.getType()); |
| | | } |
| | | return resolved; |
| | | } |
| | | |
| | | private static class ResolvedReceivableItem { |
| | | private CrmReceivableBizTypeEnum bizType; |
| | | private Long bizId; |
| | | private String bizNo; |
| | | private BigDecimal totalPrice; |
| | | private BigDecimal receiptedPrice; |
| | | private BigDecimal receiptPrice; |
| | | private String remark; |
| | | } |
| | | |
| | | private void validateReceivablePriceExceedsLimit(CrmReceivableSaveReqVO reqVO) { |
| | | // 1. 计算剩余可退款金额,不包括 reqVO 自身 |
| | | CrmContractDO contract = contractService.validateContract(reqVO.getContractId()); |
| | | // 统计口径:草稿 + 审批中 + 审批通过(与开票级校验、统计方法 selectReceivablePriceMapByContractId 保持一致) |
| | | List<CrmReceivableDO> receivables = receivableMapper.selectListByContractIdAndStatus(reqVO.getContractId(), |
| | | Arrays.asList(CrmAuditStatusEnum.APPROVE.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus())); |
| | | Arrays.asList(CrmAuditStatusEnum.DRAFT.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus(), |
| | | CrmAuditStatusEnum.APPROVE.getStatus())); |
| | | if (reqVO.getId() != null) { |
| | | receivables.removeIf(receivable -> ObjectUtil.equal(receivable.getId(), reqVO.getId())); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 校验累计回款金额不超过累计已开票且已上传发票附件的金额(先开票后回款) |
| | | * 校验关联开票合格:存在、审批通过、已上传发票附件、与所选合同一致 |
| | | */ |
| | | private void validateReceivablePriceExceedsInvoiceLimit(CrmReceivableSaveReqVO reqVO) { |
| | | // 1. 计算合同累计回款金额(不含本次,统计口径同合同金额校验:审批通过 + 审批中) |
| | | List<CrmReceivableDO> receivables = receivableMapper.selectListByContractIdAndStatus(reqVO.getContractId(), |
| | | Arrays.asList(CrmAuditStatusEnum.APPROVE.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus())); |
| | | private void validateInvoiceForReceivable(CrmReceivableSaveReqVO reqVO) { |
| | | // 未关联开票时直接跳过(允许先回款后开票) |
| | | if (reqVO.getInvoiceId() == null) { |
| | | return; |
| | | } |
| | | // 1. 校验开票存在 |
| | | CrmInvoiceDO invoice = invoiceService.getInvoice(reqVO.getInvoiceId()); |
| | | if (invoice == null) { |
| | | throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_NOT_EXISTS); |
| | | } |
| | | // 2. 校验开票审批通过 |
| | | if (ObjectUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) { |
| | | throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_NOT_APPROVE); |
| | | } |
| | | // 3. 校验开票已上传发票附件 |
| | | if (CollUtil.isEmpty(storageAttachmentApi.listAttachments( |
| | | StorageRecordTypeEnum.CRM_INVOICE.getType(), invoice.getId()))) { |
| | | throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_NOT_ATTACHMENT); |
| | | } |
| | | // 4. 校验开票与所选合同一致 |
| | | if (ObjectUtil.notEqual(invoice.getContractId(), reqVO.getContractId())) { |
| | | throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_CONTRACT_MISMATCH); |
| | | } |
| | | // 5. 校验开票与所选合同客户一致(防御脏数据:合同-开票-回款一对多对多,开票必须属于该合同客户) |
| | | if (reqVO.getCustomerId() != null && ObjectUtil.notEqual(invoice.getCustomerId(), reqVO.getCustomerId())) { |
| | | throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_CONTRACT_MISMATCH); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 校验累计回款金额不超过该开票剩余可回款金额(先开票后回款) |
| | | */ |
| | | private void validateReceivablePriceExceedsInvoiceRemaining(CrmReceivableSaveReqVO reqVO) { |
| | | // 未关联开票时直接跳过(金额上限仍由合同级校验 validateReceivablePriceExceedsLimit 兜底) |
| | | if (reqVO.getInvoiceId() == null) { |
| | | return; |
| | | } |
| | | // 1. 计算该开票累计回款金额(不含本次,统计口径同合同金额校验:草稿 + 审批中 + 审批通过) |
| | | CrmInvoiceDO invoice = invoiceService.getInvoice(reqVO.getInvoiceId()); |
| | | List<CrmReceivableDO> receivables = receivableMapper.selectListByInvoiceIdAndStatus(reqVO.getInvoiceId(), |
| | | Arrays.asList(CrmAuditStatusEnum.DRAFT.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus(), |
| | | CrmAuditStatusEnum.APPROVE.getStatus())); |
| | | if (reqVO.getId() != null) { |
| | | receivables.removeIf(receivable -> ObjectUtil.equal(receivable.getId(), reqVO.getId())); |
| | | } |
| | | BigDecimal receivablePriceSum = CollectionUtils.getSumValue(receivables, CrmReceivableDO::getPrice, |
| | | BigDecimal::add, BigDecimal.ZERO); |
| | | |
| | | // 2. 计算合同下「审批通过且已上传发票附件」的累计开票金额 |
| | | List<CrmInvoiceDO> invoices = invoiceService.getInvoiceListByContractIdAndStatus(reqVO.getContractId(), |
| | | List.of(CrmAuditStatusEnum.APPROVE.getStatus())); |
| | | BigDecimal invoicePriceSum = BigDecimal.ZERO; |
| | | for (CrmInvoiceDO invoice : invoices) { |
| | | if (CollUtil.isNotEmpty(storageAttachmentApi.listAttachments( |
| | | StorageRecordTypeEnum.CRM_INVOICE.getType(), invoice.getId()))) { |
| | | invoicePriceSum = invoicePriceSum.add(invoice.getPrice()); |
| | | } |
| | | } |
| | | |
| | | // 3. 校验本次回款金额不超过剩余可回款金额(已开票金额 - 已回款金额) |
| | | BigDecimal availablePrice = invoicePriceSum.subtract(receivablePriceSum); |
| | | // 2. 校验本次回款金额不超过该开票剩余可回款金额(开票金额 - 已回款金额) |
| | | BigDecimal availablePrice = invoice.getPrice().subtract(receivablePriceSum); |
| | | if (reqVO.getPrice().compareTo(availablePrice) > 0) { |
| | | throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_NOT_ENOUGH, availablePrice); |
| | | throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_PRICE_EXCEEDS, availablePrice); |
| | | } |
| | | } |
| | | |
| | |
| | | @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) |
| | | public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) { |
| | | Assert.notNull(updateReqVO.getId(), "回款编号不能为空"); |
| | | updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段 |
| | | updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段(invoiceId 允许修改) |
| | | // 1.1 校验存在 |
| | | CrmReceivableDO oldReceivable = validateReceivableExists(updateReqVO.getId()); |
| | | updateReqVO.setOwnerUserId(oldReceivable.getOwnerUserId()).setCustomerId(oldReceivable.getCustomerId()) |
| | | .setContractId(oldReceivable.getContractId()).setPlanId(oldReceivable.getPlanId()); // 设置已存在的值 |
| | | // 1.2 校验可回款金额超过上限 |
| | | // 1.2 若未传递关联开票,沿用旧值 |
| | | if (updateReqVO.getInvoiceId() == null) { |
| | | updateReqVO.setInvoiceId(oldReceivable.getInvoiceId()); |
| | | } |
| | | // 1.3 校验可回款金额不超过合同剩余可回款金额 |
| | | validateReceivablePriceExceedsLimit(updateReqVO); |
| | | // 1.3 校验可回款金额不超过累计已开票金额(先开票后回款) |
| | | validateReceivablePriceExceedsInvoiceLimit(updateReqVO); |
| | | // 1.4 校验关联开票合格 |
| | | validateInvoiceForReceivable(updateReqVO); |
| | | // 1.5 校验可回款金额不超过该开票剩余可回款金额 |
| | | validateReceivablePriceExceedsInvoiceRemaining(updateReqVO); |
| | | |
| | | // 1.4 只有草稿、审批中,可以编辑; |
| | | if (!ObjectUtils.equalsAny(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), |
| | | CrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | // 1.6 只有未审核,可以编辑; |
| | | if (ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED); |
| | | } |
| | | |
| | | // 2. 更新回款 |
| | | CrmReceivableDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivableDO.class); |
| | | receivableMapper.updateById(updateObj); |
| | | // 2.1 更新核销明细:传了 items 以 items 为准;未传且关联开票时按开票重建;否则保持不变 |
| | | if (updateReqVO.getItems() != null |
| | | || (updateReqVO.getInvoiceId() != null && ObjectUtil.notEqual(updateReqVO.getInvoiceId(), oldReceivable.getInvoiceId()))) { |
| | | syncReceivableItems(updateObj, updateReqVO.getItems()); |
| | | } |
| | | |
| | | // 3. 记录操作日志上下文 |
| | | updateReqVO.setOwnerUserId(oldReceivable.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况 |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void updateReceivableAuditStatus(Long id, Integer bpmResult) { |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateReceivableStatus(Long id, Integer status) { |
| | | boolean approve = CrmAuditStatusEnum.APPROVE.getStatus().equals(status); |
| | | // 1.1 校验存在 |
| | | CrmReceivableDO receivable = validateReceivableExists(id); |
| | | // 1.2 只有审批中,可以更新审批结果 |
| | | if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | log.error("[updateReceivableAuditStatus][receivable({}) 不处于审批中,无法更新审批结果({})]", |
| | | receivable.getId(), bpmResult); |
| | | throw exception(RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); |
| | | // 1.2 校验状态 |
| | | if (receivable.getAuditStatus().equals(status)) { |
| | | throw exception(approve ? RECEIVABLE_APPROVE_FAIL : RECEIVABLE_PROCESS_FAIL); |
| | | } |
| | | |
| | | // 2. 更新回款审批状态 |
| | | Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); |
| | | receivableMapper.updateById(new CrmReceivableDO().setId(id).setAuditStatus(auditStatus)); |
| | | // 2. 更新状态 |
| | | int updateCount = receivableMapper.updateByIdAndStatus(id, receivable.getAuditStatus(), |
| | | new CrmReceivableDO().setAuditStatus(status)); |
| | | if (updateCount == 0) { |
| | | throw exception(approve ? RECEIVABLE_APPROVE_FAIL : RECEIVABLE_PROCESS_FAIL); |
| | | } |
| | | |
| | | // 3. 回款计划回写实际回款金额与状态 |
| | | if (receivable.getPlanId() != null) { |
| | | if (approve) { |
| | | receivablePlanService.updateReceivablePlanReceipted(receivable.getPlanId(), |
| | | receivable.getPrice(), receivable.getReturnTime()); |
| | | } else { |
| | | receivablePlanService.resetReceivablePlanReceipted(receivable.getPlanId()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | // 2.1 删除回款 |
| | | receivableMapper.deleteById(id); |
| | | // 2.2 删除数据权限 |
| | | // 2.2 删除核销明细 |
| | | receivableItemMapper.deleteByReceivableId(id); |
| | | // 2.3 删除数据权限 |
| | | permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id); |
| | | |
| | | // 3. 记录操作日志上下文 |
| | | LogRecordContext.putVariable("receivable", receivable); |
| | | LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId())); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_SUBMIT_SUB_TYPE, bizNo = "{{#id}}", |
| | | success = CRM_RECEIVABLE_SUBMIT_SUCCESS) |
| | | public void submitReceivable(Long id, Long userId) { |
| | | // 1. 校验回款是否在审批 |
| | | CrmReceivableDO receivable = validateReceivableExists(id); |
| | | if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) { |
| | | throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT); |
| | | } |
| | | |
| | | // 2. 创建回款审批流程实例 |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() |
| | | .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id))); |
| | | |
| | | // 3. 更新回款工作流编号 |
| | | receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId(processInstanceId) |
| | | .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); |
| | | |
| | | // 4. 记录日志 |
| | | LogRecordContext.putVariable("receivableNo", receivable.getNo()); |
| | | } |
| | | |
| | | private CrmReceivableDO validateReceivableExists(Long id) { |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<CrmReceivableItemDO> getReceivableItemList(Long receivableId) { |
| | | return receivableItemMapper.selectListByReceivableId(receivableId); |
| | | } |
| | | |
| | | @Override |
| | | public List<CrmReceivableItemDO> getReceivableItemListByCustomerId(Long customerId) { |
| | | List<CrmReceivableDO> receivables = receivableMapper.selectListByCustomerId(customerId); |
| | | if (CollUtil.isEmpty(receivables)) { |
| | | return ListUtil.empty(); |
| | | } |
| | | return receivableItemMapper.selectListByReceivableIds( |
| | | receivables.stream().map(CrmReceivableDO::getId).toList()); |
| | | } |
| | | |
| | | @Override |
| | | @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) |
| | | public PageResult<CrmReceivableDO> getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO) { |
| | | return receivableMapper.selectPageByCustomerId(pageReqVO); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Map<Long, BigDecimal> getReceivablePriceMapByInvoiceId(Collection<Long> invoiceIds) { |
| | | return receivableMapper.selectReceivablePriceMapByInvoiceId(invoiceIds); |
| | | } |
| | | |
| | | @Override |
| | | public Long getReceivableCountByContractId(Long contractId) { |
| | | return receivableMapper.selectCountByContractId(contractId); |
| | | } |