| | |
| | | import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper; |
| | | import com.ruoyi.account.pojo.sales.AccountInvoiceApplication; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.approve.bean.dto.ApprovalInstanceDto; |
| | | import com.ruoyi.approve.mapper.ApprovalTemplateMapper; |
| | | import com.ruoyi.approve.pojo.ApprovalInstance; |
| | | import com.ruoyi.approve.pojo.ApprovalTemplate; |
| | | import com.ruoyi.approve.service.ApprovalInstanceService; |
| | | import com.ruoyi.basic.enums.ApplicationTypeEnum; |
| | | import com.ruoyi.basic.enums.RecordTypeEnum; |
| | | import com.ruoyi.basic.mapper.CustomerMapper; |
| | | import com.ruoyi.basic.mapper.ProductMapper; |
| | | import com.ruoyi.basic.mapper.ProductModelMapper; |
| | | import com.ruoyi.basic.mapper.OilDepotMapper; |
| | | import com.ruoyi.basic.mapper.TankInfoMapper; |
| | | import com.ruoyi.basic.pojo.Customer; |
| | | import com.ruoyi.basic.pojo.OilDepot; |
| | | import com.ruoyi.basic.pojo.TankInfo; |
| | | import com.ruoyi.basic.utils.FileUtil; |
| | | import com.ruoyi.common.enums.FileNameType; |
| | | import com.ruoyi.common.enums.SaleEnum; |
| | | import com.ruoyi.common.enums.SalesQuotationStatusEnum; |
| | | import com.ruoyi.common.enums.TypeEnums; |
| | | import com.ruoyi.common.exception.base.BaseException; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.EnumUtil; |
| | |
| | | private static final String LOCK_PREFIX = "contract_no_lock:"; |
| | | private static final long LOCK_WAIT_TIMEOUT = 10; // 锁等待超时时间(秒) |
| | | private static final long LOCK_EXPIRE_TIME = 30; // 锁自动过期时间(秒) |
| | | /** 销售类型:卖油(走产品明细) */ |
| | | private static final String LEDGER_TYPE_OIL = "卖油"; |
| | | /** 销售类型:代储(卖存储位置,走油库+储罐+手工合同金额) */ |
| | | private static final String LEDGER_TYPE_DEPOSIT = "代储"; |
| | | private final SalesLedgerMapper salesLedgerMapper; |
| | | private final CustomerMapper customerMapper; |
| | | private final SalesLedgerProductMapper salesLedgerProductMapper; |
| | |
| | | private final FileUtil fileUtil; |
| | | private final AccountInvoiceApplicationMapper accountInvoiceApplicationMapper; |
| | | private final AccountSalesCollectionMapper accountSalesCollectionMapper; |
| | | private final ApprovalTemplateMapper approvalTemplateMapper; |
| | | private final ApprovalInstanceService approvalInstanceService; |
| | | private final OilDepotMapper oilDepotMapper; |
| | | private final TankInfoMapper tankInfoMapper; |
| | | |
| | | @Autowired |
| | | private SysDeptMapper sysDeptMapper; |
| | |
| | | |
| | | @Override |
| | | public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) { |
| | | return salesLedgerMapper.selectSalesLedgerList(salesLedgerDto); |
| | | List<SalesLedger> list = salesLedgerMapper.selectSalesLedgerList(salesLedgerDto); |
| | | fillTrustFund(list); |
| | | return list; |
| | | } |
| | | |
| | | public List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(Long relateId, SaleEnum type) { |
| | |
| | | |
| | | @Override |
| | | public IPage<SalesLedgerVo> selectSalesLedgerListPage(Page page, SalesLedgerDto salesLedgerDto) { |
| | | return salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto); |
| | | IPage<SalesLedgerVo> result = salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto); |
| | | fillTrustFund(result.getRecords()); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 回填客户的信托基金,并判断本笔合同金额是否已超出。 |
| | | * 超出与否由后端算好返回,前端只负责标红 |
| | | */ |
| | | private void fillTrustFund(List<? extends SalesLedger> records) { |
| | | if (CollectionUtils.isEmpty(records)) { |
| | | return; |
| | | } |
| | | List<Long> customerIds = records.stream() |
| | | .map(SalesLedger::getCustomerId) |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | if (customerIds.isEmpty()) { |
| | | return; |
| | | } |
| | | Map<Long, BigDecimal> trustFundMap = customerMapper.selectBatchIds(customerIds).stream() |
| | | .filter(item -> item.getTrustFund() != null) |
| | | .collect(Collectors.toMap(Customer::getId, Customer::getTrustFund, (existing, replacement) -> existing)); |
| | | |
| | | records.forEach(ledger -> { |
| | | BigDecimal trustFund = trustFundMap.get(ledger.getCustomerId()); |
| | | ledger.setTrustFund(trustFund); |
| | | ledger.setExceedsTrustFund(trustFund != null && ledger.getContractAmount() != null |
| | | && ledger.getContractAmount().compareTo(trustFund) > 0); |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | |
| | | salesLedger.setContractAmount(salesLedgerProductImportDtos.stream() |
| | | .map(SalesLedgerProductImportDto::getTaxInclusiveTotalPrice) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add)); |
| | | salesLedger.setNetContractAmount(salesLedger.getContractAmount()); |
| | | salesLedgerMapper.insert(salesLedger); |
| | | |
| | | |
| | |
| | | // 删除附件表 |
| | | commonFileService.deleteByBusinessIds(idList, FileNameType.SALE.getValue()); |
| | | |
| | | // 删除台账对应的审批实例(同一台账可能反复提交/重审,会有多条) |
| | | List<ApprovalInstance> approvalInstances = approvalInstanceService.list( |
| | | new LambdaQueryWrapper<ApprovalInstance>() |
| | | .in(ApprovalInstance::getBusinessId, idList) |
| | | .eq(ApprovalInstance::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode()) |
| | | .eq(ApprovalInstance::getDeleted, 0)); |
| | | if (CollectionUtils.isNotEmpty(approvalInstances)) { |
| | | approvalInstanceService.delete(approvalInstances.stream() |
| | | .map(ApprovalInstance::getId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList())); |
| | | } |
| | | |
| | | // 删除生产管控数据 |
| | | //查询生产报工id |
| | | ArrayList<Long> mainIdList = productionProductMainService.listMain(idList); |
| | |
| | | SalesLedger salesLedger = convertToEntity(salesLedgerDto); |
| | | salesLedger.setCustomerName(customer.getCustomerName()); |
| | | salesLedger.setTenantId(customer.getTenantId()); |
| | | applyLedgerType(salesLedger, salesLedgerDto); |
| | | // 录入人:如果未传则使用当前登录用户 |
| | | if (StringUtils.isEmpty(salesLedger.getEntryPerson())) { |
| | | salesLedger.setEntryPerson(String.valueOf(SecurityUtils.getUserId())); |
| | | } |
| | | // 新增和修改都要重新走审批 |
| | | salesLedger.setApprovalStatus(SalesQuotationStatusEnum.PENDING.getCode()); |
| | | // 3. 新增或更新主表 |
| | | if (salesLedger.getId() == null) { |
| | | String contractNo = salesLedger.getSalesContractNo(); |
| | |
| | | salesLedger.setSalesContractNo(contractNo); |
| | | salesLedgerMapper.insert(salesLedger); |
| | | } else { |
| | | // 改单会重新走一遍审批:先作废此前未结束的审批实例,事务提交前还会重新发起 |
| | | approvalInstanceService.lambdaUpdate() |
| | | .set(ApprovalInstance::getStatus, "REJECTED") |
| | | .eq(ApprovalInstance::getBusinessId, salesLedger.getId()) |
| | | .eq(ApprovalInstance::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode()) |
| | | .update(); |
| | | salesLedgerMapper.updateById(salesLedger); |
| | | } |
| | | |
| | | |
| | | // 4. 处理子表数据 |
| | | List<SalesLedgerProduct> productList = salesLedgerDto.getProductData(); |
| | | // 4. 处理子表数据。代储没有产品明细,合同金额以手工填写的为准,不能被子表汇总覆盖 |
| | | List<SalesLedgerProduct> productList = LEDGER_TYPE_OIL.equals(salesLedger.getLedgerType()) |
| | | ? salesLedgerDto.getProductData() : null; |
| | | if (productList != null && !productList.isEmpty()) { |
| | | handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType())); |
| | | updateMainContractAmount( |
| | |
| | | ); |
| | | } |
| | | |
| | | // 5. 保存销售台账附件 |
| | | // 5. 兜底:确保实际合同金额有初始值等于合同金额 |
| | | if (salesLedger.getNetContractAmount() == null) { |
| | | SalesLedger dbLedger = salesLedgerMapper.selectById(salesLedger.getId()); |
| | | if (dbLedger != null && dbLedger.getNetContractAmount() == null && dbLedger.getContractAmount() != null) { |
| | | SalesLedger updateEntity = new SalesLedger(); |
| | | updateEntity.setId(salesLedger.getId()); |
| | | updateEntity.setNetContractAmount(dbLedger.getContractAmount()); |
| | | salesLedgerMapper.updateById(updateEntity); |
| | | } |
| | | } |
| | | |
| | | // 6. 保存销售台账附件 |
| | | fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, salesLedger.getId(), salesLedgerDto.getStorageBlobDTOs()); |
| | | |
| | | // 7. 发起审批。合同金额要取落库后的值(卖油的金额由第 4 步的产品明细汇总写回) |
| | | SalesLedger savedLedger = salesLedgerMapper.selectById(salesLedger.getId()); |
| | | startSalesLedgerApproval(savedLedger, customer.getTrustFund()); |
| | | |
| | | return 1; |
| | | } |
| | | |
| | | /** |
| | | * 校验销售类型并补齐代储的油库/储罐信息。 |
| | | * 卖油:合同金额只能由产品明细汇总产生;代储:没有明细,必须选油库、储罐并手工填合同金额 |
| | | */ |
| | | private void applyLedgerType(SalesLedger salesLedger, SalesLedgerDto salesLedgerDto) { |
| | | String ledgerType = salesLedgerDto.getLedgerType(); |
| | | if (StringUtils.isEmpty(ledgerType)) { |
| | | throw new BaseException("销售类型不能为空"); |
| | | } |
| | | if (LEDGER_TYPE_OIL.equals(ledgerType)) { |
| | | salesLedger.setOilDepotId(null); |
| | | salesLedger.setOilDepotName(null); |
| | | salesLedger.setTankId(null); |
| | | salesLedger.setTankNo(null); |
| | | // updateById 会跳过 null,对修改是空操作;新增时与改造前一致(本就为 null) |
| | | salesLedger.setContractAmount(null); |
| | | return; |
| | | } |
| | | if (!LEDGER_TYPE_DEPOSIT.equals(ledgerType)) { |
| | | throw new BaseException("销售类型不合法:" + ledgerType); |
| | | } |
| | | if (salesLedgerDto.getOilDepotId() == null) { |
| | | throw new BaseException("代储必须选择油库"); |
| | | } |
| | | if (salesLedgerDto.getTankId() == null) { |
| | | throw new BaseException("代储必须选择储罐"); |
| | | } |
| | | if (salesLedgerDto.getContractAmount() == null |
| | | || salesLedgerDto.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new BaseException("代储必须填写合同金额"); |
| | | } |
| | | OilDepot oilDepot = oilDepotMapper.selectById(salesLedgerDto.getOilDepotId()); |
| | | if (oilDepot == null) { |
| | | throw new BaseException("油库不存在:" + salesLedgerDto.getOilDepotId()); |
| | | } |
| | | TankInfo tankInfo = tankInfoMapper.selectById(salesLedgerDto.getTankId()); |
| | | if (tankInfo == null) { |
| | | throw new BaseException("储罐不存在:" + salesLedgerDto.getTankId()); |
| | | } |
| | | // 名称以基础数据为准,不采信前端传值 |
| | | salesLedger.setOilDepotName(oilDepot.getDepotName()); |
| | | salesLedger.setTankNo(tankInfo.getTankNo()); |
| | | } |
| | | |
| | | /** |
| | | * 发起销售台账审批,走系统已有的 OA 审批流程。 |
| | | * 合同金额超出客户信托基金时,审批标题带上提醒字眼 |
| | | */ |
| | | private void startSalesLedgerApproval(SalesLedger ledger, BigDecimal trustFund) { |
| | | if (ledger == null) { |
| | | return; |
| | | } |
| | | ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne( |
| | | new LambdaQueryWrapper<ApprovalTemplate>() |
| | | .eq(ApprovalTemplate::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode()) |
| | | .eq(ApprovalTemplate::getDeleted, 0) |
| | | .orderByDesc(ApprovalTemplate::getId) |
| | | .last("LIMIT 1") |
| | | ); |
| | | if (approvalTemplate == null) { |
| | | throw new RuntimeException("请先配置销售台账审批模板"); |
| | | } |
| | | boolean exceedsTrustFund = trustFund != null && ledger.getContractAmount() != null |
| | | && ledger.getContractAmount().compareTo(trustFund) > 0; |
| | | String title = ledger.getSalesContractNo() + "审批"; |
| | | if (exceedsTrustFund) { |
| | | title += "(合同金额超出客户信托基金,请重点审核)"; |
| | | } |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto(); |
| | | approvalInstance.setTemplateId(approvalTemplate.getId()); |
| | | approvalInstance.setTemplateName(approvalTemplate.getTemplateName()); |
| | | approvalInstance.setBusinessId(ledger.getId()); |
| | | approvalInstance.setBusinessType(TypeEnums.SALES_LEDGER_APPROVAL.getCode()); |
| | | approvalInstance.setCurrentLevel(1); |
| | | approvalInstance.setTitle(title); |
| | | approvalInstance.setApplicantId(loginUser.getUserId()); |
| | | approvalInstance.setApplicantName(loginUser.getNickName()); |
| | | approvalInstance.setApplyTime(LocalDateTime.now()); |
| | | try { |
| | | approvalInstanceService.add(approvalInstance); |
| | | } catch (Exception e) { |
| | | log.error("SalesLedgerServiceImpl approve error for salesContractNo: {}", ledger.getSalesContractNo(), e); |
| | | throw new RuntimeException("审批失败: " + e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | amountField.setAccessible(true); |
| | | amountField.set(entity, totalAmount); |
| | | |
| | | // 实际合同金额初始值等于合同金额 |
| | | try { |
| | | Field netAmountField = mainEntityClass.getDeclaredField("netContractAmount"); |
| | | netAmountField.setAccessible(true); |
| | | netAmountField.set(entity, totalAmount); |
| | | } catch (NoSuchFieldException ignored) { |
| | | // 子类可能没有该字段,忽略 |
| | | } |
| | | |
| | | mainMapper.updateById(entity); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException("动态更新主表金额失败", e); |