2 天以前 684e4306a08feeae188070f264148e6765da8dcf
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -12,15 +12,26 @@
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;
@@ -89,6 +100,10 @@
    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;
@@ -106,6 +121,10 @@
    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;
@@ -123,7 +142,9 @@
    @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) {
@@ -303,7 +324,37 @@
    @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
@@ -536,6 +587,19 @@
        // 删除附件表
        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);
@@ -561,10 +625,13 @@
        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();
@@ -574,12 +641,19 @@
            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(
@@ -605,9 +679,100 @@
        // 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
    public void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type) {
        // 按ID分组,区分新增和更新的记录