| | |
| | | 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.CrmReceivablePlanDO; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper; |
| | |
| | | import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; |
| | | 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 cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; |
| | | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum; |
| | | import com.mzt.logapi.context.LogRecordContext; |
| | | import com.mzt.logapi.service.impl.DiffParseFunction; |
| | | import com.mzt.logapi.starter.annotation.LogRecord; |
| | |
| | | private CrmReceivablePlanService receivablePlanService; |
| | | @Resource |
| | | private CrmPermissionService permissionService; |
| | | @Resource |
| | | private CrmInvoiceService invoiceService; |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private BpmProcessInstanceApi bpmProcessInstanceApi; |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @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 校验关联数据存在 |
| | | // 1.1 校验关联数据存在 |
| | | validateRelationDataExists(createReqVO); |
| | | // 1.3 生成回款编号 |
| | | // 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); |
| | |
| | | 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())); |
| | | } |
| | |
| | | // 2. 校验金额是否超过 |
| | | if (reqVO.getPrice().compareTo(notReceivablePrice) > 0) { |
| | | throw exception(RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT, notReceivablePrice); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 校验关联开票合格:存在、审批通过、已上传发票附件、与所选合同一致 |
| | | */ |
| | | private void validateInvoiceForReceivable(CrmReceivableSaveReqVO reqVO) { |
| | | // 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) { |
| | | // 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. 校验本次回款金额不超过该开票剩余可回款金额(开票金额 - 已回款金额) |
| | | BigDecimal availablePrice = invoice.getPrice().subtract(receivablePriceSum); |
| | | if (reqVO.getPrice().compareTo(availablePrice) > 0) { |
| | | 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.4 校验关联开票合格 |
| | | validateInvoiceForReceivable(updateReqVO); |
| | | // 1.5 校验可回款金额不超过该开票剩余可回款金额 |
| | | validateReceivablePriceExceedsInvoiceRemaining(updateReqVO); |
| | | |
| | | // 1.3 只有草稿、审批中,可以编辑; |
| | | // 1.6 只有草稿、审批中,可以编辑; |
| | | if (!ObjectUtils.equalsAny(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), |
| | | CrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Map<Long, BigDecimal> getReceivablePriceMapByInvoiceId(Collection<Long> invoiceIds) { |
| | | return receivableMapper.selectReceivablePriceMapByInvoiceId(invoiceIds); |
| | | } |
| | | |
| | | @Override |
| | | public Long getReceivableCountByContractId(Long contractId) { |
| | | return receivableMapper.selectCountByContractId(contractId); |
| | | } |