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/pojo/SalesQuotationImportLog.java | 74 +++
src/main/java/com/ruoyi/sales/dto/SalesQuotationImportDto.java | 44 +
src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java | 49 +
src/main/java/com/ruoyi/sales/pojo/SalesQuotationPriceHistory.java | 69 +++
docs/sales_quotation_price_history.sql | 56 ++
src/main/java/com/ruoyi/sales/dto/SalesQuotationProductImportDto.java | 38 +
src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java | 12
docs/sales_quotation_import_api.md | 223 +++++++++
src/main/resources/mapper/sales/SalesQuotationImportLogMapper.xml | 5
src/main/java/com/ruoyi/sales/mapper/SalesQuotationImportLogMapper.java | 9
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java | 328 ++++++++++++++
src/main/java/com/ruoyi/sales/mapper/SalesQuotationPriceHistoryMapper.java | 9
src/main/java/com/ruoyi/sales/dto/SalesQuotationMainImportDto.java | 50 ++
src/main/resources/mapper/sales/SalesQuotationPriceHistoryMapper.xml | 5
src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java | 340 ++++++++++++--
src/main/java/com/ruoyi/sales/service/SalesQuotationService.java | 30 +
16 files changed, 1,285 insertions(+), 56 deletions(-)
diff --git a/docs/sales_quotation_import_api.md b/docs/sales_quotation_import_api.md
new file mode 100644
index 0000000..8613ff2
--- /dev/null
+++ b/docs/sales_quotation_import_api.md
@@ -0,0 +1,223 @@
+# 閿�鍞姤浠峰鍏ュ姛鑳� - 鍓嶇鑱旇皟鏂囨。
+
+## 涓�銆佸姛鑳芥杩�
+
+閿�鍞姤浠锋ā鍧楁敮鎸佸Sheet妯℃澘瀵煎叆锛屾ā鏉跨粨鏋勫弬鑰冮攢鍞彴璐︼細
+1. **鎶ヤ环鍗曟暟鎹甋heet**锛氬寘鍚姤浠峰崟鍩烘湰淇℃伅
+2. **鎶ヤ环浜у搧鏁版嵁Sheet**锛氬寘鍚骇鍝佹槑缁嗕俊鎭�
+
+---
+
+## 浜屻�佹ā鏉跨粨鏋�
+
+### 2.1 鎶ヤ环鍗曟暟鎹甋heet
+
+| 瀛楁 | 璇存槑 | 鏄惁蹇呭~ |
+|------|------|----------|
+| 鎶ヤ环鍗曞彿 | 鎶ヤ环鍗曠紪鍙凤紙涓嶅~鑷姩鐢熸垚锛� | 鍚� |
+| 瀹㈡埛鍚嶇О | 瀹㈡埛鍚嶇О | 鏄� |
+| 涓氬姟鍛� | 閿�鍞汉鍛樺鍚� | 鍚� |
+| 鎶ヤ环鏃ユ湡 | 鏍煎紡yyyy-MM-dd | 鍚� |
+| 鏈夋晥鏈熻嚦 | 鏍煎紡yyyy-MM-dd | 鍚� |
+| 浠樻鏂瑰紡 | 濡�"鏈堢粨30澶�" | 鍚� |
+| 浜よ揣鍛ㄦ湡 | 濡�"7澶�" | 鍚� |
+| 澶囨敞 | 鍏朵粬璇存槑 | 鍚� |
+
+### 2.2 鎶ヤ环浜у搧鏁版嵁Sheet
+
+| 瀛楁 | 璇存槑 | 鏄惁蹇呭~ |
+|------|------|----------|
+| 鎶ヤ环鍗曞彿 | 鍏宠仈鎶ヤ环鍗曟暟鎹甋heet鐨勬姤浠峰崟鍙� | 鏄� |
+| 浜у搧澶х被 | 浜у搧鍒嗙被鍚嶇О | 鏄� |
+| 瑙勬牸鍨嬪彿 | 浜у搧瑙勬牸鍨嬪彿 | 鏄� |
+| 鍗曚綅 | 璁¢噺鍗曚綅 | 鍚� |
+| 鏁伴噺 | 鎶ヤ环鏁伴噺 | 鏄� |
+| 鍚◣鍗曚环 | 鍗曚环锛堝厓锛� | 鏄� |
+| 鍚◣鎬讳环 | 鎬讳环锛堝厓锛� | 鍚︼紙涓嶅~鑷姩璁$畻锛� |
+| 澶囨敞 | 浜у搧澶囨敞 | 鍚� |
+
+---
+
+## 涓夈�佹帴鍙h鎯�
+
+### 3.1 涓嬭浇鎶ヤ环瀵煎叆妯℃澘
+
+**璇锋眰**
+```
+GET /sales/quotation/downloadTemplate
+```
+
+**鍝嶅簲**
+- Excel鏂囦欢涓嬭浇锛屽寘鍚袱涓猄heet锛�
+ - 鎶ヤ环鍗曟暟鎹紙鍚ず渚嬫暟鎹級
+ - 鎶ヤ环浜у搧鏁版嵁锛堝惈绀轰緥鏁版嵁锛�
+
+### 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鏂囦欢鏍煎紡閿欒 |
+| 鎶ヤ环鍗曟暟鎹负绌猴紝璇锋鏌ユā鏉垮唴瀹� | 鎶ヤ环鍗昐heet鏃犳暟鎹� |
+
+### 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": "寮犱笁"
+ }
+ ]
+}
+```
+
+---
+
+## 鍥涖�佷笟鍔℃祦绋�
+
+```
+涓嬭浇妯℃澘
+ 鈹�
+ 鈻�
+濉啓鎶ヤ环鍗曟暟鎹甋heet锛堟瘡琛屼竴涓姤浠峰崟锛�
+濉啓鎶ヤ环浜у搧鏁版嵁Sheet锛堥�氳繃鎶ヤ环鍗曞彿鍏宠仈锛�
+ 鈹�
+ 鈻�
+涓婁紶鏂囦欢
+ 鈹�
+ 鈻�
+妫�鏌ュ鎵规ā鏉挎槸鍚﹀瓨鍦�
+ 鈹�
+ 鈹溾攢鈹� 涓嶅瓨鍦� 鈹�鈹�鈻� 閿欒锛�"璇峰厛閰嶇疆鎶ヤ环瀹℃壒妯℃澘锛屾棤娉曞鍏�"
+ 鈹�
+ 鈹斺攢鈹� 瀛樺湪 鈹�鈹�鈻� 瑙f瀽涓や釜Sheet鏁版嵁
+ 鈹�
+ 鈻�
+閬嶅巻鎶ヤ环鍗曟暟鎹�
+ 鈹�
+ 鈹溾攢鈹� 妫�鏌ユ姤浠峰崟鍙锋槸鍚﹀凡瀛樺湪 鈹�鈹�鈻� 宸插瓨鍦ㄥ垯璺宠繃
+ 鈹�
+ 鈹溾攢鈹� 鍒涘缓鎶ヤ环鍗曡褰�
+ 鈹溾攢鈹� 鍏宠仈浜у搧鏁版嵁锛堥�氳繃鎶ヤ环鍗曞彿鍖归厤锛�
+ 鈹溾攢鈹� 璁板綍闄嶄环鍘嗗彶锛堝姣斿巻鍙蹭环鏍硷級
+ 鈹斺攢鈹� 鍒涘缓瀹℃壒娴佺▼
+ 鈹�
+ 鈻�
+杩斿洖瀵煎叆缁撴灉
+```
+
+---
+
+## 浜斻�佺ず渚嬫暟鎹�
+
+**鎶ヤ环鍗曟暟鎹甋heet**
+| 鎶ヤ环鍗曞彿 | 瀹㈡埛鍚嶇О | 涓氬姟鍛� | 浠樻鏂瑰紡 | 浜よ揣鍛ㄦ湡 |
+|----------|----------|--------|----------|----------|
+| 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`
\ No newline at end of file
diff --git a/docs/sales_quotation_price_history.sql b/docs/sales_quotation_price_history.sql
new file mode 100644
index 0000000..e69de81
--- /dev/null
+++ b/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 '鎶ヤ环鍗旾D',
+ `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 '鎿嶄綔浜篒D',
+ `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 '鎿嶄綔浜篒D',
+ `create_user_name` VARCHAR(100) DEFAULT NULL COMMENT '鎿嶄綔浜哄鍚�',
+ `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '瀵煎叆鏃堕棿',
+ `audit_user` BIGINT DEFAULT NULL COMMENT '瀹℃牳浜篒D',
+ `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='閿�鍞姤浠峰鍏ヨ褰曡〃';
diff --git a/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
index d103265..167f4b4 100644
--- a/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
+++ b/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;
@@ -465,37 +466,37 @@
{
val = Convert.toBigDecimal(val);
}
- else if (Date.class == fieldType)
- {
- if (val instanceof String)
- {
- val = DateUtils.parseDate(val);
+ else if (Date.class == fieldType)
+ {
+ if (val instanceof String)
+ {
+ val = DateUtils.parseDate(val);
}
else if (val instanceof Double)
- {
- val = DateUtil.getJavaDate((Double) val);
- }
- }
- else if (LocalDate.class == fieldType)
- {
- if (val instanceof String)
- {
- Date date = DateUtils.parseDate(val);
- val = StringUtils.isNull(date) ? null : DateUtils.toLocalDate(date);
- }
- else if (val instanceof Date)
- {
- val = DateUtils.toLocalDate((Date) val);
- }
- else if (val instanceof Double)
- {
- val = DateUtils.toLocalDate(DateUtil.getJavaDate((Double) val));
- }
- }
- else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
- {
- val = Convert.toBool(val, false);
- }
+ {
+ val = DateUtil.getJavaDate((Double) val);
+ }
+ }
+ else if (LocalDate.class == fieldType)
+ {
+ if (val instanceof String)
+ {
+ Date date = DateUtils.parseDate(val);
+ val = StringUtils.isNull(date) ? null : DateUtils.toLocalDate(date);
+ }
+ else if (val instanceof Date)
+ {
+ val = DateUtils.toLocalDate((Date) val);
+ }
+ else if (val instanceof Double)
+ {
+ val = DateUtils.toLocalDate(DateUtil.getJavaDate((Double) val));
+ }
+ }
+ else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
+ {
+ val = Convert.toBool(val, false);
+ }
if (StringUtils.isNotNull(fieldType))
{
String propertyName = field.getName();
@@ -667,24 +668,24 @@
val = Convert.toFloat(val);
} else if (BigDecimal.class == fieldType) {
val = Convert.toBigDecimal(val);
- } else if (Date.class == fieldType) {
- if (val instanceof String) {
- val = DateUtils.parseDate(val);
- } else if (val instanceof Double) {
- val = DateUtil.getJavaDate((Double) val);
- }
- } else if (LocalDate.class == fieldType) {
- if (val instanceof String) {
- Date date = DateUtils.parseDate(val);
- val = StringUtils.isNull(date) ? null : DateUtils.toLocalDate(date);
- } else if (val instanceof Date) {
- val = DateUtils.toLocalDate((Date) val);
- } else if (val instanceof Double) {
- val = DateUtils.toLocalDate(DateUtil.getJavaDate((Double) val));
- }
- } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
- val = Convert.toBool(val, false);
- }
+ } else if (Date.class == fieldType) {
+ if (val instanceof String) {
+ val = DateUtils.parseDate(val);
+ } else if (val instanceof Double) {
+ val = DateUtil.getJavaDate((Double) val);
+ }
+ } else if (LocalDate.class == fieldType) {
+ if (val instanceof String) {
+ Date date = DateUtils.parseDate(val);
+ val = StringUtils.isNull(date) ? null : DateUtils.toLocalDate(date);
+ } else if (val instanceof Date) {
+ val = DateUtils.toLocalDate((Date) val);
+ } else if (val instanceof Double) {
+ val = DateUtils.toLocalDate(DateUtil.getJavaDate((Double) val));
+ }
+ } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
+ val = Convert.toBool(val, false);
+ }
if (StringUtils.isNotNull(fieldType)) {
String propertyName = field.getName();
@@ -2068,7 +2069,7 @@
/**
* 鑾峰彇瀵硅薄鐨勫瓙鍒楄〃鏂规硶
- *
+ *
* @param name 鍚嶇О
* @param pojoClass 绫诲璞�
* @return 瀛愬垪琛ㄦ柟娉�
@@ -2089,4 +2090,247 @@
}
return method;
}
+
+ /**
+ * 瀵煎嚭澶歋heet Excel妯℃澘锛堥潤鎬佹柟娉曪級
+ * 鏀寔涓嶅悓绫诲瀷鐨凞TO瀵煎嚭鍒颁笉鍚岀殑Sheet
+ *
+ * @param response HTTP鍝嶅簲
+ * @param sheetDataMap Map<Sheet鍚嶇О, SheetData>锛孲heetData鍖呭惈鏁版嵁鍒楄〃鍜屽搴旂殑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("瀵煎嚭澶歋heet 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 });
+ }
+ }
+ }
+
+ // 鎸塻ort鎺掑簭
+ 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);
+ }
+ }
}
diff --git a/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java b/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java
index f0467b3..1789bb3 100644
--- a/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java
+++ b/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)
diff --git a/src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java b/src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java
index f9251f8..9e174ef 100644
--- a/src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java
+++ b/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);
- util.exportExcel(response, listPage.getRecords() , "閿�鍞姤浠�");
+ 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));
+ }
}
diff --git a/src/main/java/com/ruoyi/sales/dto/SalesQuotationImportDto.java b/src/main/java/com/ruoyi/sales/dto/SalesQuotationImportDto.java
new file mode 100644
index 0000000..21e3238
--- /dev/null
+++ b/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;
+
+/**
+ * 閿�鍞姤浠峰鍏TO锛堝吋瀹瑰崟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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/sales/dto/SalesQuotationMainImportDto.java b/src/main/java/com/ruoyi/sales/dto/SalesQuotationMainImportDto.java
new file mode 100644
index 0000000..93d07ee
--- /dev/null
+++ b/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;
+
+/**
+ * 閿�鍞姤浠蜂富琛ㄥ鍏TO锛堟姤浠峰崟鏁版嵁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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/sales/dto/SalesQuotationProductImportDto.java b/src/main/java/com/ruoyi/sales/dto/SalesQuotationProductImportDto.java
new file mode 100644
index 0000000..9a86a9b
--- /dev/null
+++ b/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;
+
+/**
+ * 閿�鍞姤浠蜂骇鍝佸鍏TO锛堟姤浠蜂骇鍝佹暟鎹甋heet锛�
+ */
+@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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/sales/mapper/SalesQuotationImportLogMapper.java b/src/main/java/com/ruoyi/sales/mapper/SalesQuotationImportLogMapper.java
new file mode 100644
index 0000000..64899cf
--- /dev/null
+++ b/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> {
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/sales/mapper/SalesQuotationPriceHistoryMapper.java b/src/main/java/com/ruoyi/sales/mapper/SalesQuotationPriceHistoryMapper.java
new file mode 100644
index 0000000..3385450
--- /dev/null
+++ b/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> {
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/sales/pojo/SalesQuotationImportLog.java b/src/main/java/com/ruoyi/sales/pojo/SalesQuotationImportLog.java
new file mode 100644
index 0000000..e6a6acf
--- /dev/null
+++ b/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 = "鎿嶄綔浜篒D")
+ private Long createUser;
+
+ @Schema(description = "鎿嶄綔浜哄鍚�")
+ private String createUserName;
+
+ @Schema(description = "瀵煎叆鏃堕棿")
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createTime;
+
+ @Schema(description = "瀹℃牳浜篒D")
+ 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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/sales/pojo/SalesQuotationPriceHistory.java b/src/main/java/com/ruoyi/sales/pojo/SalesQuotationPriceHistory.java
new file mode 100644
index 0000000..e0c45fc
--- /dev/null
+++ b/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 = "鎶ヤ环鍗旾D")
+ 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 = "鎿嶄綔浜篒D")
+ 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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/sales/service/SalesQuotationService.java b/src/main/java/com/ruoyi/sales/service/SalesQuotationService.java
index 7f29a7f..805ab1e 100644
--- a/src/main/java/com/ruoyi/sales/service/SalesQuotationService.java
+++ b/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);
}
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 c2748f0..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,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("璇峰厛閰嶇疆鎶ヤ环瀹℃壒妯℃澘锛屾棤娉曞鍏�");
+ }
+
+ // 瑙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));
+ }
}
diff --git a/src/main/resources/mapper/sales/SalesQuotationImportLogMapper.xml b/src/main/resources/mapper/sales/SalesQuotationImportLogMapper.xml
new file mode 100644
index 0000000..19a487c
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/src/main/resources/mapper/sales/SalesQuotationPriceHistoryMapper.xml b/src/main/resources/mapper/sales/SalesQuotationPriceHistoryMapper.xml
new file mode 100644
index 0000000..69ece09
--- /dev/null
+++ b/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>
\ No newline at end of file
--
Gitblit v1.9.3