package com.ruoyi.sales.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.approve.bean.dto.ApprovalInstanceDto; import com.ruoyi.approve.bean.vo.ApproveGetAndUpdateVo; import com.ruoyi.approve.mapper.ApprovalTemplateMapper; import com.ruoyi.approve.pojo.ApprovalInstance; import com.ruoyi.approve.pojo.ApprovalTemplate; import com.ruoyi.approve.pojo.ApproveProcess; import com.ruoyi.approve.service.ApprovalInstanceService; import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl; import com.ruoyi.basic.mapper.CustomerMapper; import com.ruoyi.basic.pojo.Customer; import com.ruoyi.common.enums.IsDeleteEnum; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.OrderUtils; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.security.LoginUser; import com.ruoyi.sales.dto.SalesQuotationDto; import com.ruoyi.sales.dto.SalesQuotationImportDto; import com.ruoyi.sales.dto.SalesQuotationMainImportDto; import com.ruoyi.sales.dto.SalesQuotationProductImportDto; import com.ruoyi.sales.mapper.SalesQuotationImportLogMapper; import com.ruoyi.sales.mapper.SalesQuotationMapper; import com.ruoyi.sales.mapper.SalesQuotationPriceHistoryMapper; import com.ruoyi.sales.mapper.SalesQuotationProductMapper; import com.ruoyi.sales.pojo.*; import com.ruoyi.sales.service.SalesQuotationProductService; import com.ruoyi.sales.service.SalesQuotationService; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @Slf4j @Service @Transactional(rollbackFor = Exception.class) @RequiredArgsConstructor public class SalesQuotationServiceImpl extends ServiceImpl implements SalesQuotationService { private final SalesQuotationProductMapper salesQuotationProductMapper; private final SalesQuotationMapper salesQuotationMapper; private final SalesQuotationProductService salesQuotationProductService; private final SalesQuotationPriceHistoryMapper priceHistoryMapper; private final SalesQuotationImportLogMapper importLogMapper; private final ApproveProcessServiceImpl approveProcessService; private final CustomerMapper customerMapper; private final ApprovalTemplateMapper approvalTemplateMapper; private final ApprovalInstanceService approvalInstanceService; @Override public IPage listPage(Page page, SalesQuotationDto salesQuotationDto) { IPage salesQuotationDtoIPage = salesQuotationMapper.listPage(page, salesQuotationDto); if(CollectionUtils.isEmpty(salesQuotationDtoIPage.getRecords())){ return salesQuotationDtoIPage; } // 批量查询产品,避免 N+1 问题 List quotationIds = salesQuotationDtoIPage.getRecords().stream() .map(SalesQuotationDto::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); if (!quotationIds.isEmpty()) { List allProducts = salesQuotationProductMapper.selectList( new LambdaQueryWrapper() .in(SalesQuotationProduct::getSalesQuotationId, quotationIds) ); Map> productMap = allProducts.stream() .collect(Collectors.groupingBy(SalesQuotationProduct::getSalesQuotationId)); salesQuotationDtoIPage.getRecords().forEach(record -> record.setProducts(productMap.getOrDefault(record.getId(), new ArrayList<>())) ); } return salesQuotationDtoIPage; } @Override public boolean add(SalesQuotationDto salesQuotationDto) { LoginUser loginUser = SecurityUtils.getLoginUser(); SalesQuotation salesQuotation = new SalesQuotation(); BeanUtils.copyProperties(salesQuotationDto, salesQuotation); salesQuotation.setId(null); Customer customer = customerMapper.selectById(salesQuotationDto.getCustomerId()); if (ObjectUtils.isNotEmpty(customer)) { salesQuotation.setCustomer(customer.getCustomerName()); } String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT","quotation_no", salesQuotationDto.getCreateTime() != null ? salesQuotationDto.getCreateTime() : LocalDateTime.now()); salesQuotation.setQuotationNo(quotationNo); salesQuotation.setStatus("待审批"); salesQuotationMapper.insert(salesQuotation); if(CollectionUtils.isEmpty(salesQuotationDto.getProducts())){ return true; } List products = salesQuotationDto.getProducts().stream().map(product -> { SalesQuotationProduct salesQuotationProduct = new SalesQuotationProduct(); BeanUtils.copyProperties(product, salesQuotationProduct); salesQuotationProduct.setSalesQuotationId(salesQuotation.getId()); return salesQuotationProduct; }).collect(Collectors.toList()); salesQuotationProductService.saveBatch(products); // 报价审批 ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne( new LambdaQueryWrapper() .eq(ApprovalTemplate::getBusinessType, 6L) .eq(ApprovalTemplate::getDeleted, 0) .orderByDesc(ApprovalTemplate::getId) .last("LIMIT 1") ); if (approvalTemplate == null) { throw new RuntimeException("请先配置报价审批模板"); } ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto(); approvalInstance.setTemplateId(approvalTemplate.getId()); approvalInstance.setTemplateName(approvalTemplate.getTemplateName()); approvalInstance.setBusinessId(salesQuotation.getId()); approvalInstance.setBusinessType(6L); approvalInstance.setCurrentLevel(1); approvalInstance.setTitle(quotationNo+"审批"); approvalInstance.setApplicantId(loginUser.getUserId()); approvalInstance.setApplicantName(loginUser.getNickName()); approvalInstance.setApplyTime(LocalDateTime.now()); try { approvalInstanceService.add(approvalInstance); } catch (Exception e) { log.error("SalesQuotationServiceImpl approve error for quotationNo: {}", e); throw new RuntimeException("审批失败: " + e.getMessage(), e); } return true; } @Override public boolean edit(SalesQuotationDto salesQuotationDto) { SalesQuotation salesQuotation = new SalesQuotation(); BeanUtils.copyProperties(salesQuotationDto, salesQuotation); ApproveGetAndUpdateVo vo = new ApproveGetAndUpdateVo(); if("拒绝".equals(salesQuotationDto.getStatus())){ vo.setApproveStatus(0); salesQuotation.setStatus("待审批"); } if(salesQuotationMapper.updateById(salesQuotation)!=1){ return false; } salesQuotationProductMapper.delete(new LambdaQueryWrapper().eq(SalesQuotationProduct::getSalesQuotationId, salesQuotationDto.getId())); if(CollectionUtils.isEmpty(salesQuotationDto.getProducts())){ return true; } List products = salesQuotationDto.getProducts().stream().map(product -> { SalesQuotationProduct salesQuotationProduct = new SalesQuotationProduct(); BeanUtils.copyProperties(product, salesQuotationProduct); salesQuotationProduct.setSalesQuotationId(salesQuotation.getId()); return salesQuotationProduct; }).collect(Collectors.toList()); salesQuotationProductService.saveBatch(products); // 修改报价审批 // 先结束之前未结束的报价审批 approvalInstanceService.lambdaUpdate().set(ApprovalInstance::getStatus,"REJECTED").eq(ApprovalInstance::getBusinessId,salesQuotation.getId()).eq(ApprovalInstance::getBusinessType,6L).update(); ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne( new LambdaQueryWrapper() .eq(ApprovalTemplate::getBusinessType, 6L) .eq(ApprovalTemplate::getDeleted, 0) .orderByDesc(ApprovalTemplate::getId) .last("LIMIT 1") ); if (approvalTemplate == null) { throw new RuntimeException("请先配置报价审批模板"); } ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto(); approvalInstance.setTemplateId(approvalTemplate.getId()); approvalInstance.setTemplateName(approvalTemplate.getTemplateName()); approvalInstance.setBusinessId(salesQuotation.getId()); approvalInstance.setBusinessType(6L); approvalInstance.setCurrentLevel(1); approvalInstance.setTitle(salesQuotation.getQuotationNo()+"审批"); approvalInstance.setApplicantId(SecurityUtils.getUserId()); approvalInstance.setApplicantName(SecurityUtils.getLoginUser().getNickName()); approvalInstance.setApplyTime(LocalDateTime.now()); try { approvalInstanceService.add(approvalInstance); } catch (Exception e) { log.error("SalesQuotationServiceImpl approve error for quotationNo: {}", e); throw new RuntimeException("审批失败: " + e.getMessage(), e); } return true; } @Override public boolean delete(Long id) { SalesQuotation salesQuotation = salesQuotationMapper.selectById(id); if(salesQuotation==null) return false; salesQuotationMapper.deleteById(id); salesQuotationProductMapper.delete(new LambdaQueryWrapper().eq(SalesQuotationProduct::getSalesQuotationId, id)); // 删除报价审批 ApproveProcess one = approveProcessService.getOne(new LambdaQueryWrapper() .eq(ApproveProcess::getApproveType, 6) .eq(ApproveProcess::getApproveDelete, IsDeleteEnum.NOT_DELETED) .eq(ApproveProcess::getApproveReason, salesQuotation.getQuotationNo())); if(one != null){ approveProcessService.delByIds(Collections.singletonList(one.getId())); } return true; } @Override public void downloadTemplate(HttpServletResponse response) { // 报价单数据示例 List mainList = new ArrayList<>(); SalesQuotationMainImportDto mainExample = new SalesQuotationMainImportDto(); mainExample.setQuotationNo("QT202606120001"); mainExample.setCustomerName("示例客户"); mainExample.setSalesperson("张三"); mainExample.setPaymentMethod("月结30天"); mainExample.setDeliveryPeriod("7天"); mainExample.setRemark("示例报价单"); mainList.add(mainExample); // 报价产品数据示例 List productList = new ArrayList<>(); SalesQuotationProductImportDto productExample1 = new SalesQuotationProductImportDto(); productExample1.setQuotationNo("QT202606120001"); productExample1.setProductCategory("电池组件"); productExample1.setSpecificationModel("MODEL-A-100W"); productExample1.setUnit("片"); productExample1.setUnitPrice(new BigDecimal("150.00")); productList.add(productExample1); SalesQuotationProductImportDto productExample2 = new SalesQuotationProductImportDto(); productExample2.setQuotationNo("QT202606120001"); productExample2.setProductCategory("电池组件"); productExample2.setSpecificationModel("MODEL-B-200W"); productExample2.setUnit("片"); productExample2.setUnitPrice(new BigDecimal("200.00")); productList.add(productExample2); // 使用静态方法导出多Sheet模板 Map> sheetDataMap = new LinkedHashMap<>(); sheetDataMap.put("报价单数据", new ExcelUtil.SheetData<>(mainList, SalesQuotationMainImportDto.class)); sheetDataMap.put("报价产品数据", new ExcelUtil.SheetData<>(productList, SalesQuotationProductImportDto.class)); ExcelUtil.exportExcelMultiSheet(response, sheetDataMap, "销售报价导入模板"); } @Override @Transactional(rollbackFor = Exception.class) public SalesQuotationImportLog importQuotation(MultipartFile file) { LoginUser loginUser = SecurityUtils.getLoginUser(); // 检查审批模板是否存在 ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne( new LambdaQueryWrapper() .eq(ApprovalTemplate::getBusinessType, 6L) .eq(ApprovalTemplate::getDeleted, 0) .orderByDesc(ApprovalTemplate::getId) .last("LIMIT 1") ); if (approvalTemplate == null) { throw new ServiceException("请先配置报价审批模板,无法导入"); } // 解析多Sheet Excel文件 ExcelUtil mainUtil = new ExcelUtil<>(SalesQuotationMainImportDto.class); Map> sheetMap; try { sheetMap = mainUtil.importExcelMultiSheet(Arrays.asList("报价单数据", "报价产品数据"), file.getInputStream(), 0); } catch (IOException e) { throw new ServiceException("读取文件失败: " + e.getMessage()); } List mainList = sheetMap.get("报价单数据"); List productListRaw = sheetMap.get("报价产品数据"); if (CollectionUtils.isEmpty(mainList)) { throw new ServiceException("报价单数据为空,请检查模板内容"); } // 将产品数据转为正确的DTO类型 ExcelUtil productUtil = new ExcelUtil<>(SalesQuotationProductImportDto.class); Map> productSheetMap; try { productSheetMap = productUtil.importExcelMultiSheet(Arrays.asList("报价产品数据"), file.getInputStream(), 0); } catch (IOException e) { throw new ServiceException("读取产品数据失败: " + e.getMessage()); } List productList = productSheetMap.get("报价产品数据"); // 生成批次号 String batchNo = "QT_IMP_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); // 创建导入记录 SalesQuotationImportLog importLog = new SalesQuotationImportLog(); importLog.setBatchNo(batchNo); importLog.setFileName(file.getOriginalFilename()); importLog.setTotalCount(mainList.size()); importLog.setSuccessCount(0); importLog.setUpdateCount(0); importLog.setNewCount(0); importLog.setFailCount(0); importLog.setStatus("completed"); importLog.setCreateUser(loginUser.getUserId()); importLog.setCreateUserName(loginUser.getNickName()); importLogMapper.insert(importLog); // 查询相关数据 List customers = customerMapper.selectList( new LambdaQueryWrapper() .in(Customer::getCustomerName, mainList.stream() .map(SalesQuotationMainImportDto::getCustomerName) .filter(Objects::nonNull) .collect(Collectors.toList())) ); Map customerMap = customers.stream() .collect(Collectors.toMap(Customer::getCustomerName, c -> c, (a, b) -> a)); // 按报价单号分组产品 Map> productGroupMap = new HashMap<>(); if (!CollectionUtils.isEmpty(productList)) { productGroupMap = productList.stream() .filter(p -> p.getQuotationNo() != null && !p.getQuotationNo().isEmpty()) .collect(Collectors.groupingBy(SalesQuotationProductImportDto::getQuotationNo)); } int successCount = 0; int newCount = 0; int updateCount = 0; int failCount = 0; for (SalesQuotationMainImportDto mainDto : mainList) { try { String customerName = mainDto.getCustomerName(); if (customerName == null || customerName.isEmpty()) { failCount++; continue; } // 查找客户 Customer customer = customerMap.get(customerName); // 创建报价单 SalesQuotation quotation = new SalesQuotation(); quotation.setCustomer(customerName); quotation.setCustomerId(customer != null ? customer.getId() : null); // 生成报价单号 String quotationNo; if (mainDto.getQuotationNo() != null && !mainDto.getQuotationNo().isEmpty()) { quotationNo = mainDto.getQuotationNo(); } else { quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT", "quotation_no", LocalDateTime.now()); } quotation.setQuotationNo(quotationNo); // 检查报价单号是否已存在 SalesQuotation existing = salesQuotationMapper.selectOne( new LambdaQueryWrapper() .eq(SalesQuotation::getQuotationNo, quotationNo) .last("LIMIT 1") ); boolean isUpdate = existing != null; if (isUpdate) { quotation.setId(existing.getId()); quotation.setQuotationDate(existing.getQuotationDate()); quotation.setStatus(existing.getStatus()); } else { quotation.setQuotationDate(LocalDate.now()); quotation.setStatus("待审批"); } quotation.setSalesperson(mainDto.getSalesperson()); quotation.setPaymentMethod(mainDto.getPaymentMethod()); quotation.setDeliveryPeriod(mainDto.getDeliveryPeriod()); quotation.setRemark("导入批次号: " + batchNo + (mainDto.getRemark() != null ? "," + mainDto.getRemark() : "")); // 计算总金额并保存产品 BigDecimal totalAmount = BigDecimal.ZERO; List quotationProducts = new ArrayList<>(); List products = productGroupMap.get(quotationNo); if (!CollectionUtils.isEmpty(products)) { for (SalesQuotationProductImportDto productDto : products) { SalesQuotationProduct product = new SalesQuotationProduct(); product.setProduct(productDto.getProductCategory()); product.setSpecification(productDto.getSpecificationModel()); product.setUnit(productDto.getUnit()); product.setQuantity(0); product.setUnitPrice(productDto.getUnitPrice() != null ? productDto.getUnitPrice().doubleValue() : 0.0); BigDecimal amount = productDto.getUnitPrice() != null ? productDto.getUnitPrice() : BigDecimal.ZERO; product.setAmount(amount.doubleValue()); totalAmount = totalAmount.add(amount); quotationProducts.add(product); } } quotation.setTotalAmount(totalAmount); if (isUpdate) { salesQuotationMapper.updateById(quotation); // 删除旧产品数据 salesQuotationProductMapper.delete(new LambdaQueryWrapper() .eq(SalesQuotationProduct::getSalesQuotationId, existing.getId())); } else { salesQuotationMapper.insert(quotation); } // 保存产品并记录降价历史 for (SalesQuotationProduct product : quotationProducts) { product.setSalesQuotationId(quotation.getId()); salesQuotationProductMapper.insert(product); recordPriceHistory(quotation.getId(), product, batchNo); } if (!isUpdate) { // 创建审批实例 ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto(); approvalInstance.setTemplateId(approvalTemplate.getId()); approvalInstance.setTemplateName(approvalTemplate.getTemplateName()); approvalInstance.setBusinessId(quotation.getId()); approvalInstance.setBusinessType(6L); approvalInstance.setCurrentLevel(1); approvalInstance.setTitle(quotationNo + "审批"); approvalInstance.setApplicantId(loginUser.getUserId()); approvalInstance.setApplicantName(loginUser.getNickName()); approvalInstance.setApplyTime(LocalDateTime.now()); approvalInstanceService.add(approvalInstance); } successCount++; if (isUpdate) { updateCount++; } else { newCount++; } } catch (Exception e) { log.error("导入报价单失败: {}", e.getMessage()); failCount++; } } // 更新导入记录 importLog.setSuccessCount(successCount); importLog.setNewCount(newCount); importLog.setUpdateCount(updateCount); importLog.setFailCount(failCount); importLogMapper.updateById(importLog); return importLog; } /** * 记录降价历史 */ private void recordPriceHistory(Long quotationId, SalesQuotationProduct product, String batchNo) { LoginUser loginUser = SecurityUtils.getLoginUser(); // 查找相同项目名称的历史报价产品 List historyProducts = salesQuotationProductMapper.selectList( new LambdaQueryWrapper() .eq(SalesQuotationProduct::getProduct, product.getProduct()) .ne(SalesQuotationProduct::getId, product.getId()) .orderByDesc(SalesQuotationProduct::getCreateTime) .last("LIMIT 1") ); if (!historyProducts.isEmpty()) { SalesQuotationProduct historyProduct = historyProducts.get(0); BigDecimal oldPrice = historyProduct.getUnitPrice() != null ? new BigDecimal(historyProduct.getUnitPrice().toString()) : BigDecimal.ZERO; BigDecimal newPrice = product.getUnitPrice() != null ? new BigDecimal(product.getUnitPrice().toString()) : BigDecimal.ZERO; // 如果价格有变化,记录降价历史 if (oldPrice.compareTo(newPrice) != 0) { SalesQuotationPriceHistory priceHistory = new SalesQuotationPriceHistory(); priceHistory.setQuotationId(quotationId); priceHistory.setQuotationProductId(product.getId()); priceHistory.setProductName(product.getProduct()); priceHistory.setSpecification(product.getSpecification()); priceHistory.setOldPrice(oldPrice); priceHistory.setNewPrice(newPrice); priceHistory.setPriceChange(newPrice.subtract(oldPrice)); priceHistory.setImportBatch(batchNo); priceHistory.setImportTime(LocalDateTime.now()); priceHistory.setCreateUser(loginUser.getUserId()); priceHistory.setCreateUserName(loginUser.getNickName()); priceHistory.setChangeReason(newPrice.compareTo(oldPrice) < 0 ? "降价" : "涨价"); priceHistoryMapper.insert(priceHistory); } } } @Override public IPage listImportLog(Page page) { return importLogMapper.selectPage(page, new LambdaQueryWrapper() .orderByDesc(SalesQuotationImportLog::getCreateTime)); } @Override public List listPriceHistory(Long quotationId) { return priceHistoryMapper.selectList( new LambdaQueryWrapper() .eq(SalesQuotationPriceHistory::getQuotationId, quotationId) .orderByDesc(SalesQuotationPriceHistory::getImportTime)); } }