Browse Source

导入销售预测

bai 3 weeks ago
parent
commit
e22acaaf21

+ 80 - 22
blade-service/blade-factory/src/main/java/org/springblade/factory/api/controller/SalesForecastSummaryController.java

@@ -208,7 +208,7 @@ public class SalesForecastSummaryController {
 
 
 	@PostMapping("/importForecastData/{id}")
-	@ApiOperation(value = "导入销售预测数据(不删除原有数据)")
+	@ApiOperation(value = "导入销售预测数据(不删除原有数据,跳过重复数据)")
 	public R<List<PcBladeSalesForecastSummary>> importForecastData(@PathVariable("id") Long id, @RequestParam("file") MultipartFile file) {
 
 		// 1. 基础参数校验
@@ -248,19 +248,45 @@ public class SalesForecastSummaryController {
 		}
 
 		try {
-			// ========== 移除删除数据的逻辑 ==========
-			// 不再执行删除forecast_main_id = id的同年同月数据
-
-			// 5. 定义列表存储导入数据(无需线程安全,同步读取无并发)
-			List<PcBladeSalesForecastSummary> saveList = new ArrayList<>();
-
+			// ========== 核心修改1:先读取Excel数据,提取物料编码 ==========
 			// 6. 同步读取Excel数据
 			List<SalesForecastImportDTO> dtoList = EasyExcel.read(file.getInputStream())
 				.head(SalesForecastImportDTO.class)
 				.sheet()
 				.doReadSync();
 
-			// 7. 提前查询经销商信息(避免循环内重复查询,提升性能)
+			// ========== 核心修改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. 定义列表存储导入数据
+			List<PcBladeSalesForecastSummary> saveList = new ArrayList<>();
+			// 用于记录Excel内重复的Key,避免同一Excel内重复
+			Set<String> excelUniqueKeySet = new HashSet<>();
+
+			// 10. 提前查询经销商信息(避免循环内重复查询)
 			QueryWrapper<ViewCustomerSel> customerQueryWrapper = new QueryWrapper<>();
 			customerQueryWrapper.eq("customer_id", user.getData().getCustomerId());
 			List<ViewCustomerSel> customerList = zcrmViewCustomerSelService.list(customerQueryWrapper);
@@ -269,13 +295,32 @@ public class SalesForecastSummaryController {
 			}
 			ViewCustomerSel customerInfo = customerList.get(0);
 
-			// 8. 遍历解析的数据,转换为入库实体
+			// 11. 遍历解析的数据,转换为入库实体(添加重复校验)
+			int skipCount = 0; // 记录跳过的重复数据条数
 			for (SalesForecastImportDTO dto : dtoList) {
-				// 跳过空行(防止Excel空行导致无效数据)
+				// 跳过空行
 				if (dto.getItemCode() == null && dto.getItemName() == null && dto.getForecastQuantity() == null) {
 					continue;
 				}
 
+				// 处理物料编码
+				String itemCode = dto.getItemCode() == null ? "" : dto.getItemCode().trim();
+				// 生成唯一Key(与数据库唯一索引字段对应)
+				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);
+
 				PcBladeSalesForecastSummary summary = new PcBladeSalesForecastSummary();
 
 				// 基础字段赋值
@@ -283,23 +328,23 @@ public class SalesForecastSummaryController {
 				summary.setMonth(forecastMonth);
 				summary.setForecastMainId(id); // 关联主预测表的ID
 
-				// 经销商信息赋值(统一使用提前查询的经销商信息)
+				// 经销商信息赋值
 				summary.setCustomerId(customerInfo.getId());
 				summary.setCustomerCode(customerInfo.getCustomerCode());
 				summary.setCustomerName(customerInfo.getCustomerName());
 
-				// Excel导入字段映射(处理空值和首尾空格)
-				summary.setItemCode(dto.getItemCode() == null ? "" : dto.getItemCode().trim());
+				// Excel导入字段映射
+				summary.setItemCode(itemCode);
 				summary.setItemName(dto.getItemName() == null ? "" : dto.getItemName().trim());
 				summary.setBrandName(dto.getBrandName() == null ? "" : dto.getBrandName().trim());
 				summary.setSpecs(dto.getSpecs() == null ? "" : dto.getSpecs().trim());
 				summary.setPattern(dto.getPattern() == null ? "" : dto.getPattern().trim());
 				summary.setForecastQuantity(dto.getForecastQuantity() == null ? BigDecimal.ZERO : dto.getForecastQuantity());
 
-				// 补充物料ID/品牌ID(关联库存表查询)
-				if (StringUtils.isNotBlank(dto.getItemCode())) {
+				// 补充物料ID/品牌ID
+				if (StringUtils.isNotBlank(itemCode)) {
 					ViewWhqohSel stock = zcrmViewWhqohSelService.getOne(
-						new LambdaQueryWrapper<ViewWhqohSel>().eq(ViewWhqohSel::getItemCode, dto.getItemCode().trim())
+						new LambdaQueryWrapper<ViewWhqohSel>().eq(ViewWhqohSel::getItemCode, itemCode)
 					);
 					if (stock != null) {
 						summary.setItemId(stock.getItemId());
@@ -313,29 +358,42 @@ public class SalesForecastSummaryController {
 				saveList.add(summary);
 			}
 
-			// 9. 批量保存数据(仅当有有效数据时保存)
+			// 12. 批量保存数据(仅当有有效数据时保存)
+			int importCount = 0;
 			if (!saveList.isEmpty()) {
 				boolean saveSuccess = forecastService.saveBatch(saveList);
 				if (!saveSuccess) {
 					return R.fail("数据保存失败");
 				}
+				importCount = saveList.size();
 			}
 
-			// 10. 查询该forecast_main_id下的所有汇总明细数据并返回
+			// 13. 查询该forecast_main_id下的所有汇总明细数据并返回
 			LambdaQueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new LambdaQueryWrapper<>();
-			queryWrapper.eq(PcBladeSalesForecastSummary::getForecastMainId, id);
-			// 可选:按物料号排序,返回数据更规整
-			queryWrapper.orderByAsc(PcBladeSalesForecastSummary::getItemCode);
+			queryWrapper.eq(PcBladeSalesForecastSummary::getForecastMainId, id)
+				.orderByAsc(PcBladeSalesForecastSummary::getItemCode);
 
 			List<PcBladeSalesForecastSummary> lists = forecastService.list(queryWrapper);
 
+			// 返回导入结果(包含导入数量和跳过数量)
 			return R.data(lists);
+
 		} catch (Exception e) {
 			e.printStackTrace();
 			return R.fail("导入失败:" + e.getMessage());
 		}
 	}
-
+	/**
+	 * 构建唯一Key(与数据库唯一索引uk_year_month_customer_item对应)
+	 * @param year 年份
+	 * @param month 月份
+	 * @param customerId 客户ID
+	 * @param itemCode 物料编码
+	 * @return 唯一标识字符串
+	 */
+	private String buildUniqueKey(Integer year, Integer month, Long customerId, String itemCode) {
+		return year + "_" + month + "_" + customerId + "_" + (itemCode == null ? "" : itemCode);
+	}
 
 	@GetMapping("/exportTemplate")
 	@ApiOperation(value="导出提交数据模板")