From 11214e3074266a23fe61e8eebbce647fdb7305ef Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 12 六月 2026 18:02:03 +0800
Subject: [PATCH] 报价单修改-优化,增加导入记录,降价历史
---
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java | 358 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 350 insertions(+), 8 deletions(-)
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
index cb7b485..c1084b3 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
@@ -18,25 +18,39 @@
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.SalesQuotation;
-import com.ruoyi.sales.pojo.SalesQuotationProduct;
+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
@@ -44,6 +58,8 @@
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;
@@ -85,11 +101,11 @@
SalesQuotation salesQuotation = new SalesQuotation();
BeanUtils.copyProperties(salesQuotationDto, salesQuotation);
salesQuotation.setId(null);
- Customer customer = customerMapper.selectById(Long.valueOf(salesQuotationDto.getCustomerId()));
+ Customer customer = customerMapper.selectById(salesQuotationDto.getCustomerId());
if (ObjectUtils.isNotEmpty(customer)) {
salesQuotation.setCustomer(customer.getCustomerName());
}
- String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT","quotation_no");
+ String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT","quotation_no", salesQuotationDto.getCreateTime() != null ? salesQuotationDto.getCreateTime() : LocalDateTime.now());
salesQuotation.setQuotationNo(quotationNo);
salesQuotation.setStatus("寰呭鎵�");
salesQuotationMapper.insert(salesQuotation);
@@ -104,9 +120,19 @@
}).collect(Collectors.toList());
salesQuotationProductService.saveBatch(products);
// 鎶ヤ环瀹℃壒
+ ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne(
+ new LambdaQueryWrapper<ApprovalTemplate>()
+ .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(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getId());
- approvalInstance.setTemplateName(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getTemplateName());
+ approvalInstance.setTemplateId(approvalTemplate.getId());
+ approvalInstance.setTemplateName(approvalTemplate.getTemplateName());
approvalInstance.setBusinessId(salesQuotation.getId());
approvalInstance.setBusinessType(6L);
approvalInstance.setCurrentLevel(1);
@@ -122,6 +148,7 @@
}
return true;
}
+
@Override
public boolean edit(SalesQuotationDto salesQuotationDto) {
SalesQuotation salesQuotation = new SalesQuotation();
@@ -150,9 +177,19 @@
// 鍏堢粨鏉熶箣鍓嶆湭缁撴潫鐨勬姤浠峰鎵�
approvalInstanceService.lambdaUpdate().set(ApprovalInstance::getStatus,"REJECTED").eq(ApprovalInstance::getBusinessId,salesQuotation.getId()).eq(ApprovalInstance::getBusinessType,6L).update();
+ ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne(
+ new LambdaQueryWrapper<ApprovalTemplate>()
+ .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(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getId());
- approvalInstance.setTemplateName(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getTemplateName());
+ approvalInstance.setTemplateId(approvalTemplate.getId());
+ approvalInstance.setTemplateName(approvalTemplate.getTemplateName());
approvalInstance.setBusinessId(salesQuotation.getId());
approvalInstance.setBusinessType(6L);
approvalInstance.setCurrentLevel(1);
@@ -168,6 +205,7 @@
}
return true;
}
+
@Override
public boolean delete(Long id) {
SalesQuotation salesQuotation = salesQuotationMapper.selectById(id);
@@ -185,5 +223,309 @@
return true;
}
+ @Override
+ public void downloadTemplate(HttpServletResponse response) {
+ // 鎶ヤ环鍗曟暟鎹ず渚�
+ List<SalesQuotationMainImportDto> 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<SalesQuotationProductImportDto> 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<String, ExcelUtil.SheetData<?>> 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<ApprovalTemplate>()
+ .eq(ApprovalTemplate::getBusinessType, 6L)
+ .eq(ApprovalTemplate::getDeleted, 0)
+ .orderByDesc(ApprovalTemplate::getId)
+ .last("LIMIT 1")
+ );
+ if (approvalTemplate == null) {
+ throw new ServiceException("璇峰厛閰嶇疆鎶ヤ环瀹℃壒妯℃澘锛屾棤娉曞鍏�");
+ }
+
+ // 瑙f瀽澶歋heet Excel鏂囦欢
+ ExcelUtil<SalesQuotationMainImportDto> mainUtil = new ExcelUtil<>(SalesQuotationMainImportDto.class);
+ Map<String, List<SalesQuotationMainImportDto>> sheetMap;
+ try {
+ sheetMap = mainUtil.importExcelMultiSheet(Arrays.asList("鎶ヤ环鍗曟暟鎹�", "鎶ヤ环浜у搧鏁版嵁"), file.getInputStream(), 0);
+ } catch (IOException e) {
+ throw new ServiceException("璇诲彇鏂囦欢澶辫触: " + e.getMessage());
+ }
+
+ List<SalesQuotationMainImportDto> mainList = sheetMap.get("鎶ヤ环鍗曟暟鎹�");
+ List<SalesQuotationMainImportDto> productListRaw = sheetMap.get("鎶ヤ环浜у搧鏁版嵁");
+
+ if (CollectionUtils.isEmpty(mainList)) {
+ throw new ServiceException("鎶ヤ环鍗曟暟鎹负绌猴紝璇锋鏌ユā鏉垮唴瀹�");
+ }
+
+ // 灏嗕骇鍝佹暟鎹浆涓烘纭殑DTO绫诲瀷
+ ExcelUtil<SalesQuotationProductImportDto> productUtil = new ExcelUtil<>(SalesQuotationProductImportDto.class);
+ Map<String, List<SalesQuotationProductImportDto>> productSheetMap;
+ try {
+ productSheetMap = productUtil.importExcelMultiSheet(Arrays.asList("鎶ヤ环浜у搧鏁版嵁"), file.getInputStream(), 0);
+ } catch (IOException e) {
+ throw new ServiceException("璇诲彇浜у搧鏁版嵁澶辫触: " + e.getMessage());
+ }
+ List<SalesQuotationProductImportDto> 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<Customer> customers = customerMapper.selectList(
+ new LambdaQueryWrapper<Customer>()
+ .in(Customer::getCustomerName, mainList.stream()
+ .map(SalesQuotationMainImportDto::getCustomerName)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()))
+ );
+ Map<String, Customer> customerMap = customers.stream()
+ .collect(Collectors.toMap(Customer::getCustomerName, c -> c, (a, b) -> a));
+
+ // 鎸夋姤浠峰崟鍙峰垎缁勪骇鍝�
+ Map<String, List<SalesQuotationProductImportDto>> 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<SalesQuotation>()
+ .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<SalesQuotationProduct> quotationProducts = new ArrayList<>();
+
+ List<SalesQuotationProductImportDto> 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<SalesQuotationProduct>()
+ .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<SalesQuotationProduct> historyProducts = salesQuotationProductMapper.selectList(
+ new LambdaQueryWrapper<SalesQuotationProduct>()
+ .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<SalesQuotationImportLog> listImportLog(Page page) {
+ return importLogMapper.selectPage(page,
+ new LambdaQueryWrapper<SalesQuotationImportLog>()
+ .orderByDesc(SalesQuotationImportLog::getCreateTime));
+ }
+
+ @Override
+ public List<SalesQuotationPriceHistory> listPriceHistory(Long quotationId) {
+ return priceHistoryMapper.selectList(
+ new LambdaQueryWrapper<SalesQuotationPriceHistory>()
+ .eq(SalesQuotationPriceHistory::getQuotationId, quotationId)
+ .orderByDesc(SalesQuotationPriceHistory::getImportTime));
+ }
}
--
Gitblit v1.9.3