docs/sales_quotation_import_api.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,223 @@ # é宿¥ä»·å¯¼å ¥åè½ - å端èè°ææ¡£ ## ä¸ãåè½æ¦è¿° é宿¥ä»·æ¨¡åæ¯æå¤Sheet模æ¿å¯¼å ¥ï¼æ¨¡æ¿ç»æåèéå®å°è´¦ï¼ 1. **æ¥ä»·åæ°æ®Sheet**ï¼å 嫿¥ä»·ååºæ¬ä¿¡æ¯ 2. **æ¥ä»·äº§åæ°æ®Sheet**ï¼å å«äº§åæç»ä¿¡æ¯ --- ## äºã模æ¿ç»æ ### 2.1 æ¥ä»·åæ°æ®Sheet | åæ®µ | 说æ | æ¯å¦å¿ å¡« | |------|------|----------| | æ¥ä»·åå· | æ¥ä»·åç¼å·ï¼ä¸å¡«èªå¨çæï¼ | å¦ | | 客æ·åç§° | 客æ·åç§° | æ¯ | | ä¸å¡å | éå®äººåå§å | å¦ | | æ¥ä»·æ¥æ | æ ¼å¼yyyy-MM-dd | å¦ | | æææè³ | æ ¼å¼yyyy-MM-dd | å¦ | | 仿¬¾æ¹å¼ | å¦"æç»30天" | å¦ | | äº¤è´§å¨æ | å¦"7天" | å¦ | | 夿³¨ | å ¶ä»è¯´æ | å¦ | ### 2.2 æ¥ä»·äº§åæ°æ®Sheet | åæ®µ | 说æ | æ¯å¦å¿ å¡« | |------|------|----------| | æ¥ä»·åå· | å ³èæ¥ä»·åæ°æ®Sheetçæ¥ä»·åå· | æ¯ | | 产å大类 | 产ååç±»åç§° | æ¯ | | è§æ ¼åå· | 产åè§æ ¼åå· | æ¯ | | åä½ | 计éåä½ | å¦ | | æ°é | æ¥ä»·æ°é | æ¯ | | å«ç¨åä»· | åä»·ï¼å ï¼ | æ¯ | | å«ç¨æ»ä»· | æ»ä»·ï¼å ï¼ | å¦ï¼ä¸å¡«èªå¨è®¡ç®ï¼ | | 夿³¨ | 产å夿³¨ | å¦ | --- ## ä¸ãæ¥å£è¯¦æ ### 3.1 ä¸è½½æ¥ä»·å¯¼å ¥æ¨¡æ¿ **请æ±** ``` GET /sales/quotation/downloadTemplate ``` **ååº** - Excelæä»¶ä¸è½½ï¼å å«ä¸¤ä¸ªSheetï¼ - æ¥ä»·åæ°æ®ï¼å«ç¤ºä¾æ°æ®ï¼ - æ¥ä»·äº§åæ°æ®ï¼å«ç¤ºä¾æ°æ®ï¼ ### 3.2 å¯¼å ¥æ¥ä»·å **请æ±** ``` POST /sales/quotation/import Content-Type: multipart/form-data file: [Excelæä»¶] ``` **ååº** ```json { "code": 200, "msg": "æä½æå", "data": { "id": 1, "batchNo": "QT_IMP_20260612143000", "fileName": "æ¥ä»·å.xlsx", "totalCount": 5, "successCount": 5, "newCount": 5, "failCount": 0, "status": "completed", "createTime": "2026-06-12 14:30:00", "createUserName": "å¼ ä¸" } } ``` **ä¸å¡é»è¾** 1. **æ¨¡æ¿æ£æ¥**ï¼å¯¼å ¥åæ£æ¥æ¥ä»·å®¡æ¹æ¨¡æ¿æ¯å¦åå¨ - ä¸åå¨ï¼è¿åé误"请å é ç½®æ¥ä»·å®¡æ¹æ¨¡æ¿ï¼æ æ³å¯¼å ¥" - åå¨ï¼ç»§ç»å¯¼å ¥ 2. **æ¥ä»·åå·å¤ç**ï¼ - 妿填åäºæ¥ä»·åå·ï¼ä½¿ç¨å¡«åçåå· - 妿æªå¡«åï¼èªå¨çæåå·ï¼æ ¼å¼ï¼QT + æ¥æ + åºå·ï¼ - æ¥ä»·åå·å·²åå¨åè·³è¿ 3. **产åå ³è**ï¼éè¿æ¥ä»·åå·å°äº§åæ°æ®å ³èå°å¯¹åºçæ¥ä»·å 4. **å®¡æ¹æµç¨**ï¼å¯¼å ¥æååèªå¨åå»ºå®¡æ¹æµç¨ 5. **éä»·è®°å½**ï¼ç¸åè§æ ¼åå·ç产å对æ¯åå²ä»·æ ¼ï¼èªå¨è®°å½éä»· **é误ç ** | éè¯¯ä¿¡æ¯ | 说æ | |----------|------| | 请å é ç½®æ¥ä»·å®¡æ¹æ¨¡æ¿ï¼æ æ³å¯¼å ¥ | ç³»ç»æªé ç½®æ¥ä»·å®¡æ¹æ¨¡æ¿ | | 读åæä»¶å¤±è´¥ | Excelæä»¶æ ¼å¼é误 | | æ¥ä»·åæ°æ®ä¸ºç©ºï¼è¯·æ£æ¥æ¨¡æ¿å 容 | æ¥ä»·åSheetæ æ°æ® | ### 3.3 æ¥è¯¢å¯¼å ¥è®°å½ **请æ±** ``` GET /sales/quotation/importLog/list?pageNum=1&pageSize=10 ``` **ååº** ```json { "code": 200, "data": { "total": 20, "records": [ { "id": 1, "batchNo": "QT_IMP_20260612143000", "fileName": "æ¥ä»·å.xlsx", "totalCount": 5, "successCount": 5, "newCount": 5, "failCount": 0, "status": "completed", "createUserName": "å¼ ä¸", "createTime": "2026-06-12 14:30:00" } ] } } ``` ### 3.4 æ¥è¯¢éä»·åå² **请æ±** ``` GET /sales/quotation/priceHistory/list?quotationProductId=123 ``` **ååº** ```json { "code": 200, "data": [ { "id": 1, "productName": "çµæ± ç»ä»¶A", "specification": "MODEL-A-100W", "oldPrice": 150.00, "newPrice": 120.00, "priceChange": -30.00, "changeReason": "éä»·", "importBatch": "QT_IMP_20260612143000", "importTime": "2026-06-12 14:30:00", "createUserName": "å¼ ä¸" } ] } ``` --- ## åãä¸å¡æµç¨ ``` ä¸è½½æ¨¡æ¿ â â¼ å¡«åæ¥ä»·åæ°æ®Sheetï¼æ¯è¡ä¸ä¸ªæ¥ä»·åï¼ å¡«åæ¥ä»·äº§åæ°æ®Sheetï¼éè¿æ¥ä»·åå·å ³èï¼ â â¼ ä¸ä¼ æä»¶ â â¼ æ£æ¥å®¡æ¹æ¨¡æ¿æ¯å¦åå¨ â âââ ä¸åå¨ ââ⺠é误ï¼"请å é ç½®æ¥ä»·å®¡æ¹æ¨¡æ¿ï¼æ æ³å¯¼å ¥" â âââ åå¨ ââ⺠解æä¸¤ä¸ªSheetæ°æ® â â¼ éåæ¥ä»·åæ°æ® â âââ æ£æ¥æ¥ä»·åå·æ¯å¦å·²åå¨ ââ⺠已åå¨åè·³è¿ â âââ å建æ¥ä»·åè®°å½ âââ å ³èäº§åæ°æ®ï¼éè¿æ¥ä»·åå·å¹é ï¼ âââ è®°å½éä»·åå²ï¼å¯¹æ¯åå²ä»·æ ¼ï¼ âââ åå»ºå®¡æ¹æµç¨ â â¼ è¿åå¯¼å ¥ç»æ ``` --- ## äºãç¤ºä¾æ°æ® **æ¥ä»·åæ°æ®Sheet** | æ¥ä»·åå· | 客æ·åç§° | ä¸å¡å | 仿¬¾æ¹å¼ | äº¤è´§å¨æ | |----------|----------|--------|----------|----------| | QT202606120001 | 示ä¾å®¢æ· | å¼ ä¸ | æç»30天 | 7天 | **æ¥ä»·äº§åæ°æ®Sheet** | æ¥ä»·åå· | 产å大类 | è§æ ¼åå· | åä½ | æ°é | å«ç¨åä»· | å«ç¨æ»ä»· | |----------|----------|----------|------|------|----------|----------| | QT202606120001 | çµæ± ç»ä»¶ | MODEL-A-100W | ç | 100 | 150.00 | 15000.00 | | QT202606120001 | çµæ± ç»ä»¶ | MODEL-B-200W | ç | 50 | 200.00 | 10000.00 | --- ## å ãæ°æ®åºåæ´ éæ§è¡ï¼`docs/sales_quotation_price_history.sql` docs/sales_quotation_price_history.sql
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,56 @@ -- ============================================================ -- é宿¥ä»·éä»·è®°å½è¡¨ -- éç¨åºæ¯ï¼è®°å½æ¥ä»·å项ç®çåå²ä»·æ ¼åå -- çææ¥æï¼2026-06-12 -- ============================================================ -- é宿¥ä»·éä»·è®°å½è¡¨ CREATE TABLE `sales_quotation_price_history` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主é®ID', `quotation_id` BIGINT NOT NULL COMMENT 'æ¥ä»·åID', `quotation_product_id` BIGINT NOT NULL COMMENT 'æ¥ä»·ååID', `product_name` VARCHAR(200) DEFAULT NULL COMMENT 'åååç§°ï¼åä½åå¨ï¼', `specification` VARCHAR(200) DEFAULT NULL COMMENT 'ååè§æ ¼ï¼åä½åå¨ï¼', `old_price` DECIMAL(24, 4) DEFAULT NULL COMMENT 'ååä»·', `new_price` DECIMAL(24, 4) DEFAULT NULL COMMENT 'æ°åä»·', `price_change` DECIMAL(24, 4) DEFAULT NULL COMMENT 'ä»·æ ¼åå¨ï¼æ°-æ§ï¼è´æ°è¡¨ç¤ºéä»·ï¼', `change_reason` VARCHAR(500) DEFAULT NULL COMMENT 'åå¨åå ', `import_batch` VARCHAR(50) DEFAULT NULL COMMENT 'å¯¼å ¥æ¹æ¬¡å·', `import_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'å¯¼å ¥æ¶é´', `create_user` BIGINT DEFAULT NULL COMMENT 'æä½äººID', `create_user_name` VARCHAR(100) DEFAULT NULL COMMENT 'æä½äººå§å', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', `tenant_id` BIGINT DEFAULT NULL COMMENT 'ç§æ·ID', `dept_id` BIGINT DEFAULT NULL COMMENT 'é¨é¨ID', PRIMARY KEY (`id`), KEY `idx_quotation_id` (`quotation_id`), KEY `idx_quotation_product_id` (`quotation_product_id`), KEY `idx_import_batch` (`import_batch`), KEY `idx_import_time` (`import_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='é宿¥ä»·éä»·è®°å½è¡¨'; -- é宿¥ä»·å¯¼å ¥è®°å½è¡¨ CREATE TABLE `sales_quotation_import_log` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主é®ID', `batch_no` VARCHAR(50) NOT NULL COMMENT 'å¯¼å ¥æ¹æ¬¡å·', `file_name` VARCHAR(200) DEFAULT NULL COMMENT 'å¯¼å ¥æä»¶å', `total_count` INT DEFAULT 0 COMMENT 'æ»è®°å½æ°', `success_count` INT DEFAULT 0 COMMENT 'æåè®°å½æ°', `update_count` INT DEFAULT 0 COMMENT 'æ´æ°è®°å½æ°', `new_count` INT DEFAULT 0 COMMENT 'æ°å¢è®°å½æ°', `fail_count` INT DEFAULT 0 COMMENT 'å¤±è´¥è®°å½æ°', `status` VARCHAR(20) DEFAULT 'pending' COMMENT 'ç¶æï¼pending-å¾ å®¡æ ¸, approved-å·²éè¿, rejected-å·²æç»', `remark` VARCHAR(500) DEFAULT NULL COMMENT '夿³¨', `create_user` BIGINT DEFAULT NULL COMMENT 'æä½äººID', `create_user_name` VARCHAR(100) DEFAULT NULL COMMENT 'æä½äººå§å', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'å¯¼å ¥æ¶é´', `audit_user` BIGINT DEFAULT NULL COMMENT 'å®¡æ ¸äººID', `audit_user_name` VARCHAR(100) DEFAULT NULL COMMENT 'å®¡æ ¸äººå§å', `audit_time` DATETIME DEFAULT NULL COMMENT 'å®¡æ ¸æ¶é´', `tenant_id` BIGINT DEFAULT NULL COMMENT 'ç§æ·ID', `dept_id` BIGINT DEFAULT NULL COMMENT 'é¨é¨ID', PRIMARY KEY (`id`), UNIQUE KEY `uk_batch_no` (`batch_no`), KEY `idx_status` (`status`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='é宿¥ä»·å¯¼å ¥è®°å½è¡¨'; src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -10,6 +10,7 @@ import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; @@ -2089,4 +2090,247 @@ } return method; } /** * 导åºå¤Sheet Excel模æ¿ï¼éææ¹æ³ï¼ * æ¯æä¸åç±»åçDTO导åºå°ä¸åçSheet * * @param response HTTPååº * @param sheetDataMap Map<Sheetåç§°, SheetData>ï¼SheetDataå 嫿°æ®å表å对åºçClassç±»å * @param fileName æä»¶å */ @SuppressWarnings("unchecked") public static void exportExcelMultiSheet(HttpServletResponse response, Map<String, SheetData<?>> sheetDataMap, String fileName) { try (SXSSFWorkbook workbook = new SXSSFWorkbook()) { // åå»ºæ ·å¼ CellStyle headerStyle = createHeaderStyle(workbook); CellStyle dataStyle = createDataStyle(workbook); // éåæ¯ä¸ªSheet for (Map.Entry<String, SheetData<?>> entry : sheetDataMap.entrySet()) { String sheetName = entry.getKey(); SheetData<?> sheetData = entry.getValue(); List<?> dataList = sheetData.getDataList(); Class<?> clazz = sheetData.getClazz(); // å建Sheet Sheet sheet = workbook.createSheet(sheetName); // è·ååæ®µä¿¡æ¯ List<Object[]> fields = getFieldsByClass(clazz, Type.IMPORT); if (fields.isEmpty()) { continue; } // å建表头 Row headerRow = sheet.createRow(0); int colIndex = 0; for (Object[] fieldObj : fields) { Field field = (Field) fieldObj[0]; Excel excel = (Excel) fieldObj[1]; Cell cell = headerRow.createCell(colIndex); cell.setCellValue(excel.name()); cell.setCellStyle(headerStyle); // 设置å宽 sheet.setColumnWidth(colIndex, (int) ((excel.width() + 0.72) * 256)); colIndex++; } // åå ¥æ°æ® if (dataList != null && !dataList.isEmpty()) { int rowIndex = 1; for (Object data : dataList) { Row dataRow = sheet.createRow(rowIndex); colIndex = 0; for (Object[] fieldObj : fields) { Field field = (Field) fieldObj[0]; Excel excel = (Excel) fieldObj[1]; field.setAccessible(true); Object value = field.get(data); Cell cell = dataRow.createCell(colIndex); setCellValueByType(cell, value, excel, dataStyle, workbook); colIndex++; } rowIndex++; } } } // è¾åºå°ååº response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String encodedFileName = new String(fileName.getBytes("GBK"), "ISO8859_1") + ".xlsx"; response.addHeader("Content-Disposition", "attachment;filename=" + encodedFileName); workbook.write(response.getOutputStream()); } catch (Exception e) { log.error("导åºå¤Sheet Excelå¼å¸¸: {}", e.getMessage()); throw new UtilException("导åºExcel失败: " + e.getMessage()); } } /** * Sheetæ°æ®å°è£ ç±» */ public static class SheetData<T> { private List<T> dataList; private Class<T> clazz; public SheetData(List<T> dataList, Class<T> clazz) { this.dataList = dataList; this.clazz = clazz; } public List<T> getDataList() { return dataList; } public Class<T> getClazz() { return clazz; } } /** * æ ¹æ®ç±»è·ååæ®µå表ï¼éææ¹æ³ï¼ */ private static List<Object[]> getFieldsByClass(Class<?> clazz, Type type) { List<Object[]> fields = new ArrayList<>(); List<Field> tempFields = new ArrayList<>(); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); for (Field field : tempFields) { if (field.isAnnotationPresent(Excel.class)) { Excel excel = field.getAnnotation(Excel.class); if (excel != null && (excel.type() == Type.ALL || excel.type() == type)) { fields.add(new Object[] { field, excel }); } } } // æsortæåº fields.sort(Comparator.comparing(objects -> ((Excel) objects[1]).sort())); return fields; } /** * åå»ºè¡¨å¤´æ ·å¼ */ private static CellStyle createHeaderStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setBorderRight(BorderStyle.THIN); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderLeft(BorderStyle.THIN); style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderTop(BorderStyle.THIN); style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderBottom(BorderStyle.THIN); style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); Font font = workbook.createFont(); font.setFontName("Arial"); font.setFontHeightInPoints((short) 10); font.setBold(true); style.setFont(font); DataFormat dataFormat = workbook.createDataFormat(); style.setDataFormat(dataFormat.getFormat("@")); return style; } /** * åå»ºæ°æ®æ ·å¼ */ private static CellStyle createDataStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setBorderRight(BorderStyle.THIN); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderLeft(BorderStyle.THIN); style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderTop(BorderStyle.THIN); style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderBottom(BorderStyle.THIN); style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); Font font = workbook.createFont(); font.setFontName("Arial"); font.setFontHeightInPoints((short) 10); style.setFont(font); return style; } /** * 设置åå æ ¼å¼ */ private static void setCellValueByType(Cell cell, Object value, Excel excel, CellStyle dataStyle, Workbook workbook) { cell.setCellStyle(dataStyle); if (value == null) { cell.setCellValue(""); return; } String dateFormat = excel.dateFormat(); if (StringUtils.isNotEmpty(dateFormat) && value instanceof Date) { cell.setCellValue(new SimpleDateFormat(dateFormat).format((Date) value)); } else if (value instanceof Date) { cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format((Date) value)); } else if (value instanceof BigDecimal) { cell.setCellValue(((BigDecimal) value).doubleValue()); } else if (value instanceof Number) { cell.setCellValue(((Number) value).doubleValue()); } else if (value instanceof Boolean) { cell.setCellValue((Boolean) value); } else { String strValue = Convert.toStr(value); // 鲿¢CSVæ³¨å ¥ if (StringUtils.startsWithAny(strValue, FORMULA_STR)) { strValue = RegExUtils.replaceFirst(strValue, FORMULA_REGEX_STR, "\t$0"); } cell.setCellValue(strValue); } } } src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java
@@ -8,6 +8,7 @@ import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingPathVariableException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; @@ -75,6 +76,17 @@ } /** * 请æ±åæ°ç¼ºå¤± */ @ExceptionHandler(MissingServletRequestParameterException.class) public AjaxResult handleMissingServletRequestParameterException(MissingServletRequestParameterException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请æ±å°å'{}',缺å°å¿ éç请æ±åæ°'{}'", requestURI, e.getParameterName()); return AjaxResult.error(String.format("缺å°å¿ éç请æ±åæ°[%s]", e.getParameterName())); } /** * 请æ±åæ°ç±»åä¸å¹é */ @ExceptionHandler(MethodArgumentTypeMismatchException.class) src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java
@@ -1,46 +1,85 @@ package com.ruoyi.sales.controller; import com.ruoyi.common.utils.poi.ExcelUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.framework.web.domain.R; import com.ruoyi.sales.dto.SalesQuotationDto; import com.ruoyi.sales.pojo.SalesQuotationImportLog; import com.ruoyi.sales.pojo.SalesQuotationPriceHistory; import com.ruoyi.sales.service.SalesQuotationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.List; @RestController @RequestMapping("/sales/quotation") @AllArgsConstructor @Tag(name = "é宿¥ä»·ç®¡ç") public class SalesQuotationController { private final SalesQuotationService salesQuotationService; @GetMapping("/list") @Operation(summary = "å页æ¥è¯¢æ¥ä»·åå表") public AjaxResult getList(Page page, SalesQuotationDto salesQuotationDto) { return AjaxResult.success(salesQuotationService.listPage(page, salesQuotationDto)); } @PostMapping("/export") @Operation(summary = "å¯¼åºæ¥ä»·å") public void export(HttpServletResponse response) { Page page = new Page(-1,-1); SalesQuotationDto afterSalesService = new SalesQuotationDto(); IPage<SalesQuotationDto> listPage = salesQuotationService.listPage(page, afterSalesService); ExcelUtil<SalesQuotationDto> util = new ExcelUtil<SalesQuotationDto>(SalesQuotationDto.class); ExcelUtil<SalesQuotationDto> util = new ExcelUtil<>(SalesQuotationDto.class); util.exportExcel(response, listPage.getRecords() , "é宿¥ä»·"); } @PostMapping("/add") @Operation(summary = "æ°å¢æ¥ä»·å") public AjaxResult add(@RequestBody SalesQuotationDto salesQuotationDto) { return AjaxResult.success(salesQuotationService.add(salesQuotationDto)); } @PostMapping("/update") @Operation(summary = "ä¿®æ¹æ¥ä»·å") public AjaxResult update(@RequestBody SalesQuotationDto salesQuotationDto) { return AjaxResult.success(salesQuotationService.edit(salesQuotationDto)); } @DeleteMapping("/delete") @Operation(summary = "å 餿¥ä»·å") public AjaxResult delete(@RequestBody Long id) { return AjaxResult.success(salesQuotationService.delete(id)); } @GetMapping("/downloadTemplate") @Operation(summary = "ä¸è½½æ¥ä»·å¯¼å ¥æ¨¡æ¿") public void downloadTemplate(HttpServletResponse response) { salesQuotationService.downloadTemplate(response); } @PostMapping("/import") @Operation(summary = "å¯¼å ¥æ¥ä»·å") public R<SalesQuotationImportLog> importQuotation(@RequestParam("file") MultipartFile file) { return R.ok(salesQuotationService.importQuotation(file)); } @GetMapping("/importLog/list") @Operation(summary = "æ¥è¯¢å¯¼å ¥è®°å½å表") public R<IPage<SalesQuotationImportLog>> listImportLog(Page page) { return R.ok(salesQuotationService.listImportLog(page)); } @GetMapping("/priceHistory/list") @Operation(summary = "æ¥è¯¢éä»·åå²è®°å½") public R<List<SalesQuotationPriceHistory>> listPriceHistory(@RequestParam("quotationProductId") Long quotationProductId) { return R.ok(salesQuotationService.listPriceHistory(quotationProductId)); } } src/main/java/com/ruoyi/sales/dto/SalesQuotationImportDto.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,44 @@ package com.ruoyi.sales.dto; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.framework.aspectj.lang.annotation.Excel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.math.BigDecimal; import java.util.Date; /** * é宿¥ä»·å¯¼å ¥DTOï¼å ¼å®¹åSheetå¯¼å ¥ï¼ */ @Data @EqualsAndHashCode(callSuper = true) public class SalesQuotationImportDto extends SalesQuotationProductImportDto { @Excel(name = "客æ·åç§°") @Schema(description = "客æ·åç§°") private String customerName; @Excel(name = "ä¸å¡å") @Schema(description = "ä¸å¡å") private String salesperson; @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "æ¥ä»·æ¥æ", width = 30, dateFormat = "yyyy-MM-dd") @Schema(description = "æ¥ä»·æ¥æ") private Date quotationDate; @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "æææè³", width = 30, dateFormat = "yyyy-MM-dd") @Schema(description = "æææè³") private Date validDate; @Excel(name = "仿¬¾æ¹å¼") @Schema(description = "仿¬¾æ¹å¼") private String paymentMethod; @Excel(name = "äº¤è´§å¨æ") @Schema(description = "äº¤è´§å¨æ") private String deliveryPeriod; } src/main/java/com/ruoyi/sales/dto/SalesQuotationMainImportDto.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,50 @@ package com.ruoyi.sales.dto; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.framework.aspectj.lang.annotation.Excel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; import java.util.Date; /** * é宿¥ä»·ä¸»è¡¨å¯¼å ¥DTOï¼æ¥ä»·åæ°æ®Sheetï¼ */ @Data public class SalesQuotationMainImportDto { @Excel(name = "æ¥ä»·åå·") @Schema(description = "æ¥ä»·åå·ï¼å¯éï¼ä¸å¡«èªå¨çæï¼") private String quotationNo; @Excel(name = "客æ·åç§°") @Schema(description = "客æ·åç§°") private String customerName; @Excel(name = "ä¸å¡å") @Schema(description = "ä¸å¡å") private String salesperson; @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "æ¥ä»·æ¥æ", width = 30, dateFormat = "yyyy-MM-dd") @Schema(description = "æ¥ä»·æ¥æ") private Date quotationDate; @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "æææè³", width = 30, dateFormat = "yyyy-MM-dd") @Schema(description = "æææè³") private Date validDate; @Excel(name = "仿¬¾æ¹å¼") @Schema(description = "仿¬¾æ¹å¼") private String paymentMethod; @Excel(name = "äº¤è´§å¨æ") @Schema(description = "äº¤è´§å¨æ") private String deliveryPeriod; @Excel(name = "夿³¨") @Schema(description = "夿³¨") private String remark; } src/main/java/com/ruoyi/sales/dto/SalesQuotationProductImportDto.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,38 @@ package com.ruoyi.sales.dto; import com.ruoyi.framework.aspectj.lang.annotation.Excel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; /** * é宿¥ä»·äº§åå¯¼å ¥DTOï¼æ¥ä»·äº§åæ°æ®Sheetï¼ */ @Data public class SalesQuotationProductImportDto { @Excel(name = "æ¥ä»·åå·") @Schema(description = "æ¥ä»·åå·ï¼å ³è主表ï¼") private String quotationNo; @Excel(name = "产å大类") @Schema(description = "产å大类") private String productCategory; @Excel(name = "è§æ ¼åå·") @Schema(description = "è§æ ¼åå·") private String specificationModel; @Excel(name = "åä½") @Schema(description = "åä½") private String unit; @Excel(name = "åä»·") @Schema(description = "åä»·") private BigDecimal unitPrice; @Excel(name = "夿³¨") @Schema(description = "夿³¨") private String remark; } src/main/java/com/ruoyi/sales/mapper/SalesQuotationImportLogMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,9 @@ package com.ruoyi.sales.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.sales.pojo.SalesQuotationImportLog; import org.apache.ibatis.annotations.Mapper; @Mapper public interface SalesQuotationImportLogMapper extends BaseMapper<SalesQuotationImportLog> { } src/main/java/com/ruoyi/sales/mapper/SalesQuotationPriceHistoryMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,9 @@ package com.ruoyi.sales.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.sales.pojo.SalesQuotationPriceHistory; import org.apache.ibatis.annotations.Mapper; @Mapper public interface SalesQuotationPriceHistoryMapper extends BaseMapper<SalesQuotationPriceHistory> { } src/main/java/com/ruoyi/sales/pojo/SalesQuotationImportLog.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,74 @@ package com.ruoyi.sales.pojo; import com.baomidou.mybatisplus.annotation.*; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; /** * é宿¥ä»·å¯¼å ¥è®°å½ */ @Data @TableName("sales_quotation_import_log") @Schema(description = "é宿¥ä»·å¯¼å ¥è®°å½") public class SalesQuotationImportLog { @TableId(value = "id", type = IdType.AUTO) @Schema(description = "主é®ID") private Long id; @Schema(description = "å¯¼å ¥æ¹æ¬¡å·") private String batchNo; @Schema(description = "å¯¼å ¥æä»¶å") private String fileName; @Schema(description = "æ»è®°å½æ°") private Integer totalCount; @Schema(description = "æåè®°å½æ°") private Integer successCount; @Schema(description = "æ´æ°è®°å½æ°") private Integer updateCount; @Schema(description = "æ°å¢è®°å½æ°") private Integer newCount; @Schema(description = "å¤±è´¥è®°å½æ°") private Integer failCount; @Schema(description = "ç¶æï¼pending-å¾ å®¡æ ¸, approved-å·²éè¿, rejected-å·²æç»") private String status; @Schema(description = "夿³¨") private String remark; @Schema(description = "æä½äººID") private Long createUser; @Schema(description = "æä½äººå§å") private String createUserName; @Schema(description = "å¯¼å ¥æ¶é´") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @Schema(description = "å®¡æ ¸äººID") private Long auditUser; @Schema(description = "å®¡æ ¸äººå§å") private String auditUserName; @Schema(description = "å®¡æ ¸æ¶é´") private LocalDateTime auditTime; @Schema(description = "ç§æ·ID") @TableField(fill = FieldFill.INSERT) private Long tenantId; @Schema(description = "é¨é¨ID") @TableField(fill = FieldFill.INSERT) private Long deptId; } src/main/java/com/ruoyi/sales/pojo/SalesQuotationPriceHistory.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,69 @@ package com.ruoyi.sales.pojo; import com.baomidou.mybatisplus.annotation.*; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; /** * é宿¥ä»·éä»·è®°å½ */ @Data @TableName("sales_quotation_price_history") @Schema(description = "é宿¥ä»·éä»·è®°å½") public class SalesQuotationPriceHistory { @TableId(value = "id", type = IdType.AUTO) @Schema(description = "主é®ID") private Long id; @Schema(description = "æ¥ä»·åID") private Long quotationId; @Schema(description = "æ¥ä»·ååID") private Long quotationProductId; @Schema(description = "åååç§°") private String productName; @Schema(description = "ååè§æ ¼") private String specification; @Schema(description = "ååä»·") private BigDecimal oldPrice; @Schema(description = "æ°åä»·") private BigDecimal newPrice; @Schema(description = "ä»·æ ¼åå¨ï¼æ°-æ§ï¼è´æ°è¡¨ç¤ºéä»·ï¼") private BigDecimal priceChange; @Schema(description = "åå¨åå ") private String changeReason; @Schema(description = "å¯¼å ¥æ¹æ¬¡å·") private String importBatch; @Schema(description = "å¯¼å ¥æ¶é´") private LocalDateTime importTime; @Schema(description = "æä½äººID") private Long createUser; @Schema(description = "æä½äººå§å") private String createUserName; @Schema(description = "å建æ¶é´") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @Schema(description = "ç§æ·ID") @TableField(fill = FieldFill.INSERT) private Long tenantId; @Schema(description = "é¨é¨ID") @TableField(fill = FieldFill.INSERT) private Long deptId; } src/main/java/com/ruoyi/sales/service/SalesQuotationService.java
@@ -3,8 +3,15 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.sales.dto.SalesQuotationDto; import com.ruoyi.sales.dto.SalesQuotationImportDto; import com.ruoyi.sales.pojo.SalesQuotation; import com.ruoyi.sales.pojo.SalesQuotationPriceHistory; import com.ruoyi.sales.pojo.SalesQuotationImportLog; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.web.multipart.MultipartFile; import jakarta.servlet.http.HttpServletResponse; import java.util.List; public interface SalesQuotationService extends IService<SalesQuotation> { IPage listPage(Page page, SalesQuotationDto salesQuotationDto); @@ -14,4 +21,27 @@ boolean delete(Long id); boolean edit(SalesQuotationDto salesQuotationDto); /** * ä¸è½½æ¥ä»·æ¨¡æ¿ */ void downloadTemplate(HttpServletResponse response); /** * å¯¼å ¥æ¥ä»·åï¼å¯¼å ¥åæ£æ¥å®¡æ¹æ¨¡æ¿æ¯å¦åå¨ï¼ * @param file å¯¼å ¥æä»¶ * @return å¯¼å ¥ç»æ */ SalesQuotationImportLog importQuotation(MultipartFile file); /** * æ¥è¯¢å¯¼å ¥è®°å½å表 */ IPage<SalesQuotationImportLog> listImportLog(Page page); /** * æ¥è¯¢éä»·åå²è®°å½ * @param quotationId æ¥ä»·ååID */ List<SalesQuotationPriceHistory> listPriceHistory(Long quotationId); } 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,7 +101,7 @@ 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()); } @@ -132,6 +148,7 @@ } return true; } @Override public boolean edit(SalesQuotationDto salesQuotationDto) { SalesQuotation salesQuotation = new SalesQuotation(); @@ -188,6 +205,7 @@ } return true; } @Override public boolean delete(Long id) { SalesQuotation salesQuotation = salesQuotationMapper.selectById(id); @@ -205,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("请å é ç½®æ¥ä»·å®¡æ¹æ¨¡æ¿ï¼æ æ³å¯¼å ¥"); } // è§£æå¤Sheet 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)); } } src/main/resources/mapper/sales/SalesQuotationImportLogMapper.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ruoyi.sales.mapper.SalesQuotationImportLogMapper"> </mapper> src/main/resources/mapper/sales/SalesQuotationPriceHistoryMapper.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ruoyi.sales.mapper.SalesQuotationPriceHistoryMapper"> </mapper>