Browse Source

导入销售预测

bai 3 weeks ago
parent
commit
db207c718b

+ 24 - 51
blade-service/blade-factory/src/main/java/org/springblade/factory/api/controller/SalesForecastSummaryController.java

@@ -1,6 +1,7 @@
 package org.springblade.factory.api.controller;
 
 
+import cn.hutool.core.collection.CollectionUtil;
 import com.alibaba.excel.EasyExcel;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -205,10 +206,8 @@ public class SalesForecastSummaryController {
 
 
 
-
-
 	@PostMapping("/importForecastData/{id}")
-	@ApiOperation(value = "导入销售预测数据(不删除原有数据,跳过重复数据)")
+	@ApiOperation(value = "导入销售预测数据(先删除原有数据,再保存新数据)")
 	public R<List<PcBladeSalesForecastSummary>> importForecastData(@PathVariable("id") Long id, @RequestParam("file") MultipartFile file) {
 
 		// 1. 基础参数校验
@@ -216,12 +215,13 @@ public class SalesForecastSummaryController {
 			return R.fail("预测ID不能为空");
 		}
 
+		// 2. 校验主表数据是否存在(保留原有逻辑)
 		PcBladeSalesForecastMain pcBladeDealerForecast = forecastMainService.getById(id);
 		if (pcBladeDealerForecast == null) {
 			return R.fail("预测主数据不存在");
 		}
 
-		// 2. 文件校验
+		// 3. 文件校验
 		if (file.isEmpty()) {
 			return R.fail("导入文件不能为空");
 		}
@@ -230,14 +230,14 @@ public class SalesForecastSummaryController {
 			return R.fail("仅支持.xlsx格式的Excel文件导入");
 		}
 
-		// 3. 从主表获取年月
+		// 4. 从主表获取年月
 		Integer forecastYear = pcBladeDealerForecast.getYear();
 		Integer forecastMonth = pcBladeDealerForecast.getMonth();
 		if (forecastYear == null || forecastMonth == null) {
 			return R.fail("预测主数据的年月不能为空");
 		}
 
-		// 4. 用户信息校验
+		// 5. 用户信息校验
 		Long userId = AuthUtil.getUserId();
 		if (userId == null) {
 			return R.fail("用户未登录");
@@ -248,55 +248,34 @@ public class SalesForecastSummaryController {
 		}
 
 		try {
-			// ========== 核心修改1:先读取Excel数据,提取物料编码 ==========
+			// ========== 核心修改:导入前先删除该forecast_main_id下的所有历史数据 ==========
+			boolean deleteSuccess = forecastService.physicallyDeleteByForecastMainId(id);
+			if (!deleteSuccess) {
+				// 这里可以根据业务选择:是抛出异常还是继续(建议继续,因为可能原本就没有数据)
+				// return R.fail("删除历史数据失败");
+			}
+
 			// 6. 同步读取Excel数据
 			List<SalesForecastImportDTO> dtoList = EasyExcel.read(file.getInputStream())
 				.head(SalesForecastImportDTO.class)
 				.sheet()
 				.doReadSync();
 
-			// ========== 核心修改2:查询数据库中已存在的重复数据 ==========
-			// 7. 提取Excel中的物料编码(去重)
-			Set<String> itemCodeSet = new HashSet<>();
-			for (SalesForecastImportDTO dto : dtoList) {
-				if (dto.getItemCode() != null && !dto.getItemCode().trim().isEmpty()) {
-					itemCodeSet.add(dto.getItemCode().trim());
-				}
-			}
-
-			// 8. 查询数据库中已存在的组合数据(年份+月份+客户ID+物料编码)
-			LambdaQueryWrapper<PcBladeSalesForecastSummary> existWrapper = new LambdaQueryWrapper<>();
-			existWrapper.eq(PcBladeSalesForecastSummary::getYear, forecastYear)
-				.eq(PcBladeSalesForecastSummary::getMonth, forecastMonth)
-				.eq(PcBladeSalesForecastSummary::getCustomerId, user.getData().getCustomerId())
-				.eq(PcBladeSalesForecastSummary::getForecastMainId, id)
-				.in(!itemCodeSet.isEmpty(), PcBladeSalesForecastSummary::getItemCode, itemCodeSet);
-			List<PcBladeSalesForecastSummary> existList = forecastService.list(existWrapper);
-
-			// 9. 构建已存在的唯一Key集合(年份+月份+客户ID+物料编码)
-			Set<String> existUniqueKeySet = new HashSet<>();
-			for (PcBladeSalesForecastSummary exist : existList) {
-				String uniqueKey = buildUniqueKey(exist.getYear(), exist.getMonth(), exist.getCustomerId(), exist.getItemCode());
-				existUniqueKeySet.add(uniqueKey);
-			}
-
-			// ========== 原有逻辑保留,新增去重校验 ==========
-			// 5. 定义列表存储导入数据
+			// 7. 定义列表存储导入数据
 			List<PcBladeSalesForecastSummary> saveList = new ArrayList<>();
 			// 用于记录Excel内重复的Key,避免同一Excel内重复
 			Set<String> excelUniqueKeySet = new HashSet<>();
 
-			// 10. 提前查询经销商信息(避免循环内重复查询)
+			// 8. 提前查询经销商信息(避免循环内重复查询)
 			QueryWrapper<ViewCustomerSel> customerQueryWrapper = new QueryWrapper<>();
 			customerQueryWrapper.eq("customer_id", user.getData().getCustomerId());
 			List<ViewCustomerSel> customerList = zcrmViewCustomerSelService.list(customerQueryWrapper);
-			if (customerList.isEmpty()) {
+			if (CollectionUtil.isEmpty(customerList)) {
 				return R.fail("经销商数据不存在");
 			}
 			ViewCustomerSel customerInfo = customerList.get(0);
 
-			// 11. 遍历解析的数据,转换为入库实体(添加重复校验)
-			int skipCount = 0; // 记录跳过的重复数据条数
+			// 9. 遍历解析的数据,转换为入库实体
 			for (SalesForecastImportDTO dto : dtoList) {
 				// 跳过空行
 				if (dto.getItemCode() == null && dto.getItemName() == null && dto.getForecastQuantity() == null) {
@@ -305,18 +284,11 @@ public class SalesForecastSummaryController {
 
 				// 处理物料编码
 				String itemCode = dto.getItemCode() == null ? "" : dto.getItemCode().trim();
-				// 生成唯一Key(与数据库唯一索引字段对应
+				// 生成唯一Key(用于Excel内去重
 				String uniqueKey = buildUniqueKey(forecastYear, forecastMonth, user.getData().getCustomerId(), itemCode);
 
-				// ========== 核心修改3:跳过重复数据 ==========
-				// 跳过数据库中已存在的记录
-				if (existUniqueKeySet.contains(uniqueKey)) {
-					skipCount++;
-					continue;
-				}
 				// 跳过Excel内重复的记录
 				if (excelUniqueKeySet.contains(uniqueKey)) {
-					skipCount++;
 					continue;
 				}
 				excelUniqueKeySet.add(uniqueKey);
@@ -342,7 +314,7 @@ public class SalesForecastSummaryController {
 				summary.setForecastQuantity(dto.getForecastQuantity() == null ? BigDecimal.ZERO : dto.getForecastQuantity());
 
 				// 补充物料ID/品牌ID
-				if (StringUtils.isNotBlank(itemCode)) {
+				if (org.springframework.util.StringUtils.hasText(itemCode)) {
 					ViewWhqohSel stock = zcrmViewWhqohSelService.getOne(
 						new LambdaQueryWrapper<ViewWhqohSel>().eq(ViewWhqohSel::getItemCode, itemCode)
 					);
@@ -358,9 +330,9 @@ public class SalesForecastSummaryController {
 				saveList.add(summary);
 			}
 
-			// 12. 批量保存数据(仅当有有效数据时保存)
+			// 10. 批量保存数据(仅当有有效数据时保存)
 			int importCount = 0;
-			if (!saveList.isEmpty()) {
+			if (!CollectionUtil.isEmpty(saveList)) {
 				boolean saveSuccess = forecastService.saveBatch(saveList);
 				if (!saveSuccess) {
 					return R.fail("数据保存失败");
@@ -368,14 +340,14 @@ public class SalesForecastSummaryController {
 				importCount = saveList.size();
 			}
 
-			// 13. 查询该forecast_main_id下的所有汇总明细数据并返回
+			// 11. 查询该forecast_main_id下的所有汇总明细数据并返回
 			LambdaQueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new LambdaQueryWrapper<>();
 			queryWrapper.eq(PcBladeSalesForecastSummary::getForecastMainId, id)
 				.orderByAsc(PcBladeSalesForecastSummary::getItemCode);
 
 			List<PcBladeSalesForecastSummary> lists = forecastService.list(queryWrapper);
 
-			// 返回导入结果(包含导入数量和跳过数量
+			// 返回导入结果(包含导入数量)
 			return R.data(lists);
 
 		} catch (Exception e) {
@@ -383,6 +355,7 @@ public class SalesForecastSummaryController {
 			return R.fail("导入失败:" + e.getMessage());
 		}
 	}
+
 	/**
 	 * 构建唯一Key(与数据库唯一索引uk_year_month_customer_item对应)
 	 * @param year 年份

+ 12 - 0
blade-service/blade-factory/src/main/java/org/springblade/factory/mapper/PcBladeSalesForecastSummaryMapper.java

@@ -30,4 +30,16 @@ public interface PcBladeSalesForecastSummaryMapper extends BaseMapper<PcBladeSal
 		"</script>"
 	})
 	int deleteByIds(@Param("ids") List<Long> ids);
+
+
+
+	/**
+	 * 物理删除指定forecastMainId的所有明细数据
+	 * @param forecastMainId 预测主表ID
+	 * @return 删除的行数
+	 */
+	@Delete("DELETE FROM pc_blade_sales_forecast_summary WHERE forecast_main_id = #{forecastMainId}")
+	int physicallyDeleteByForecastMainId(@Param("forecastMainId") Long forecastMainId);
+
+
 }

+ 8 - 0
blade-service/blade-factory/src/main/java/org/springblade/factory/service/PcBladeSalesForecastSummaryService.java

@@ -195,4 +195,12 @@ public interface PcBladeSalesForecastSummaryService extends BaseService<PcBladeS
 	void exportMainData(Long userId, Integer year, Integer month, HttpServletResponse response) throws IOException;
 
 
+
+	/**
+	 * 物理删除指定forecastMainId的所有明细数据
+	 * @param forecastMainId 预测主表ID
+	 * @return 是否删除成功
+	 */
+	boolean physicallyDeleteByForecastMainId(Long forecastMainId);
+
 }

+ 12 - 0
blade-service/blade-factory/src/main/java/org/springblade/factory/service/impl/PcBladeSalesForecastSummaryServiceImpl.java

@@ -71,6 +71,18 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 	}
 
 
+	@Override
+	public boolean physicallyDeleteByForecastMainId(Long forecastMainId) {
+		if (forecastMainId == null) {
+			return false;
+		}
+		// 执行自定义的物理删除SQL
+		int deleteCount = baseMapper.physicallyDeleteByForecastMainId(forecastMainId);
+		// 只要删除行数 >= 0 就认为成功(0表示没有数据可删)
+		return deleteCount >= 0;
+	}
+
+
 	/**
 	 * 根据条件批量硬删除预测明细(先查ID再删)
 	 * @param wrapper 删除条件(年、月、客户ID、主表ID)