4 Commits 2f4aef77eb ... a58acde86f

Author SHA1 Message Date
  bai a58acde86f Merge remote-tracking branch 'origin/dev' into dev 1 day ago
  bai 8019408137 修复销售预测重复提交的处理,判断销售预测提交报错,订单添加返回ID 1 day ago
  bai 7ad21676d6 更新代码 6 days ago
  bai b8045e1477 更新代码 1 week ago

+ 58 - 25
blade-service/blade-factory/src/main/java/org/springblade/factory/api/controller/SalesForecastSummaryController.java

@@ -12,6 +12,8 @@ import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.secure.BladeUser;
@@ -38,6 +40,7 @@ import java.security.Principal;
 import java.time.LocalDate;
 import java.time.format.DateTimeParseException;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -61,6 +64,10 @@ public class SalesForecastSummaryController {
 
 	@Autowired
 	private IUserClient userClient;
+
+	@Autowired
+	private RedissonClient redissonClient; // 如需处理高并发,引入Redisson分布式锁
+
 	/**
 	 * 根据ID查询销售预测主表
 	 */
@@ -131,19 +138,29 @@ public class SalesForecastSummaryController {
 		return R.data(pages);
 	}
 
-	/**
-	 * 销售预测汇总列表 --添加接口
-	 */
 	@PostMapping("/main-add")
-	@ApiOperation(value = "销售预测汇总添加", notes = "添加销售预测主表及明细数据")
+	@ApiOperation(value = "销售预测汇总添加", notes = "添加销售预测主表及明细数据,同一客户同年同月仅允许一条记录")
 	public R<String> mainAdd(@RequestBody PcBladeSalesForecastMain pcBladeSalesForecastMain) {
 		try {
-			// 1. 基础参数验证
+			// 1. 基础参数验证(强化年份和月份的联合校验)
 			if (pcBladeSalesForecastMain == null) {
 				return R.fail("提交数据不能为空");
 			}
-			if (pcBladeSalesForecastMain.getYear() == null || pcBladeSalesForecastMain.getMonth() == null) {
-				return R.fail("年份和月份为必填项");
+			Integer year = pcBladeSalesForecastMain.getYear();
+			Integer month = pcBladeSalesForecastMain.getMonth();
+			if (year == null) {
+				return R.fail("年份为必填项");
+			}
+			if (month == null) {
+				return R.fail("月份为必填项");
+			}
+			// 验证年份合理性(可根据业务调整范围,这里限制为2000-2100年)
+			if (year < 2000 || year > 2100) {
+				return R.fail("年份必须在2000-2100之间");
+			}
+			// 验证月份合法性
+			if (month < 1 || month > 12) {
+				return R.fail("月份必须在1-12之间");
 			}
 
 			List<PcBladeSalesForecastSummary> summaryList = pcBladeSalesForecastMain.getPcBladeSalesForecastSummaryList();
@@ -151,29 +168,45 @@ public class SalesForecastSummaryController {
 				return R.fail("明细数据不能为空");
 			}
 
-			// 2. 获取当前用户ID(客户ID)
+			// 2. 获取当前用户关联的客户ID
 			Long currentUserId = AuthUtil.getUserId();
+			if (currentUserId == null) {
+				return R.fail("请先登录");
+			}
+			R<User> userInfo = userClient.userInfoById(currentUserId);
+			if (userInfo.getData() == null || userInfo.getData().getCustomerId() == null) {
+				return R.fail("用户未关联客户,请联系管理员");
+			}
+			Long customerId = userInfo.getData().getCustomerId();
+
+			// 3. 分布式锁:锁键明确包含 客户ID+年份+月份(三者联合唯一)
+			String lockKey = "sales_forecast:add:" + customerId + ":" + year + ":" + month;
+			RLock lock = redissonClient.getLock(lockKey);
+			try {
+				boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
+				if (!locked) {
+					return R.fail("操作太频繁,请稍后再试");
+				}
 
-			// 3. 检查主表是否已存在相同记录(核心修改:仅判断主表重复)
-			// 这里假设主表的唯一判断条件是:年份+月份+客户ID
-			boolean mainExists = salesForecastMainService.checkMainDuplicate(
-				pcBladeSalesForecastMain.getYear(),
-				pcBladeSalesForecastMain.getMonth(),
-				currentUserId
-			);
-			if (mainExists) {
-				return R.fail("提交失败:该年月的销售预测主记录已存在");
+				// 4. 第一次校验:检查 客户ID+年份+月份 是否已存在记录
+				boolean mainExists = salesForecastMainService.checkMainDuplicate(year, month, customerId);
+				if (mainExists) {
+					return R.fail(String.format("提交失败:%d年%d月的销售预测记录已存在", year, month));
+				}
+
+				// 5. 传递客户ID到Service,避免重复查询
+				pcBladeSalesForecastMain.setCustomerId(customerId);
+				return salesForecastMainService.batchAdd(pcBladeSalesForecastMain);
+			} finally {
+				if (lock.isHeldByCurrentThread()) {
+					lock.unlock();
+				}
 			}
 
-			// 4. 调用Service层批量添加方法
-			return salesForecastMainService.batchAdd(pcBladeSalesForecastMain);
-		} catch (DuplicateKeyException e) {
-			// 最终防线:捕获数据库唯一键冲突
-			log.error("销售预测数据重复提交", e);
-			return R.fail("提交失败:已存在相同的销售预测记录,请避免重复提交");
 		} catch (Exception e) {
-			log.error("销售预测添加接口发生异常", e);
-			return R.fail("系统异常,请联系管理员");
+			log.error("销售预测添加接口异常", e);
+			String errorMsg = e.getMessage() != null ? e.getMessage() : "系统异常,请联系管理员";
+			return R.fail(errorMsg);
 		}
 	}
 

+ 6 - 6
blade-service/blade-factory/src/main/java/org/springblade/factory/api/controller/SalesOrderController.java

@@ -327,10 +327,7 @@ public class SalesOrderController {
 		} else {
 			// 3. 非物料查询时,应用个人订单过滤(仅查看自己的订单)
 			Long userId = AuthUtil.getUserId();
-
-			R<User> user = userClient.userInfoById(userId);
-
-			queryWrapper.eq("CUSTOMER_ID", user.getData().getCustomerId());
+			queryWrapper.eq("CUSTOMER_ID", userId);
 		}
 
 		// 4. 处理通用查询参数
@@ -438,7 +435,7 @@ public class SalesOrderController {
 	 */
 	@PostMapping("/addOrder")
 	@Transactional
-	public R<Boolean> save(@RequestBody PcBladeOrder pcBladeOrder) {
+	public R<?> save(@RequestBody PcBladeOrder pcBladeOrder) {
 		if (pcBladeOrder.getStatus() > 1) {
 			return R.fail("状态提交错误");
 		}
@@ -451,7 +448,7 @@ public class SalesOrderController {
 		if (id == null) {
 			return R.fail("添加失败");
 		}
-		return R.data(200,true,"添加成功");
+		return R.data(200,id,"添加成功");
 	}
 
 	/**
@@ -759,6 +756,9 @@ public class SalesOrderController {
 		// TODO 修改---重要修复   userID需要修改成CUSTOMER_ID传参
 
 
+
+
+
 		// 构建查询条件
 		QueryWrapper<PcBladeUserLinkGoods> queryWrapper = new QueryWrapper<>();
 		queryWrapper.eq("CUSTOMER_ID", userId)

+ 54 - 32
blade-service/blade-factory/src/main/java/org/springblade/factory/service/impl/PcBladeSalesForecastMainServiceImpl.java

@@ -165,65 +165,87 @@ public class PcBladeSalesForecastMainServiceImpl extends BaseServiceImpl<PcBlade
 	@Transactional(rollbackFor = Exception.class)
 	public R<String> batchAdd(PcBladeSalesForecastMain pcBladeSalesForecastMain) {
 		try {
+			// 1. 基础参数二次校验(确保年份、月份、客户ID存在)
 			if (pcBladeSalesForecastMain == null) {
-				log.error("批量添加失败:主表数据不能为空");
-				return R.fail("批量添加失败:主表数据不能为空");
+				return R.fail("主表数据不能为空");
 			}
-
-			Long id = AuthUtil.getUserId();
-			R<User> userInfo = userClient.userInfoById(id);
-			if (userInfo.getData() == null || userInfo.getData().getCustomerId() == null) {
-				return R.fail("用户不存在");
+			Integer year = pcBladeSalesForecastMain.getYear();
+			Integer month = pcBladeSalesForecastMain.getMonth();
+			Long customerId = pcBladeSalesForecastMain.getCustomerId();
+			if (year == null) {
+				return R.fail("年份为必填项");
+			}
+			if (month == null) {
+				return R.fail("月份为必填项");
+			}
+			if (customerId == null) {
+				return R.fail("客户ID缺失,请重新登录");
 			}
 
-			if (pcBladeSalesForecastMain.getApprovalStatus() == null) {
-				pcBladeSalesForecastMain.setApprovalStatus(0);
+			// 2. 第二次校验:事务内再次检查 客户ID+年份+月份 唯一性(极端并发防护)
+			boolean mainExists = checkMainDuplicate(year, month, customerId);
+			if (mainExists) {
+				String msg = String.format("提交失败:%d年%d月的销售预测记录已存在", year, month);
+				log.warn(msg + "(事务内二次校验拦截)");
+				return R.fail(msg);
 			}
 
-			pcBladeSalesForecastMain.setCustomerId(userInfo.getData().getCustomerId());
-			ViewCustomerSel zcrmViewCustomerSel = customerSelService.selectZcrmViewCustomerSelByCustomerId(pcBladeSalesForecastMain.getCustomerId());
-			pcBladeSalesForecastMain.setCustomerCode(zcrmViewCustomerSel.getCustomerCode());
-			pcBladeSalesForecastMain.setCustomerName(zcrmViewCustomerSel.getCustomerName());
+			// 3. 完善主表信息
+			R<User> userInfo = userClient.userInfoById(AuthUtil.getUserId());
+			if (userInfo.getData() == null) {
+				return R.fail("用户信息不存在");
+			}
+			// 查询客户详情
+			ViewCustomerSel customer = customerSelService.selectZcrmViewCustomerSelByCustomerId(customerId);
+			if (customer == null) {
+				return R.fail("客户信息不存在");
+			}
+			// 设置主表字段
+			pcBladeSalesForecastMain.setCustomerCode(customer.getCustomerCode());
+			pcBladeSalesForecastMain.setCustomerName(customer.getCustomerName());
+			pcBladeSalesForecastMain.setApprovalStatus(pcBladeSalesForecastMain.getApprovalStatus() == null ? 0 : pcBladeSalesForecastMain.getApprovalStatus());
 
+			// 4. 插入主表
 			int mainSaved = baseMapper.insert(pcBladeSalesForecastMain);
 			if (mainSaved == 0) {
-				log.error("批量添加失败:主表数据保存失败");
-				return R.fail("批量添加失败:主表数据保存失败");
+				log.error("主表插入失败:客户ID={}, 年份={}, 月份={}", customerId, year, month);
+				return R.fail("添加失败:主表数据保存失败");
+			}
+			Long mainId = pcBladeSalesForecastMain.getId();
+			if (mainId == null) {
+				log.error("主表ID生成失败:客户ID={}, 年份={}, 月份={}", customerId, year, month);
+				return R.fail("添加失败:主表ID生成失败");
 			}
 
+			// 5. 处理明细表(确保明细与主表的年份、月份一致)
 			List<PcBladeSalesForecastSummary> summaryList = pcBladeSalesForecastMain.getPcBladeSalesForecastSummaryList();
 			if (summaryList != null && !summaryList.isEmpty()) {
-				Long mainId = pcBladeSalesForecastMain.getId();
-				if (mainId == null) {
-					log.error("批量添加失败:主表ID生成失败");
-					return R.fail("批量添加失败:主表ID生成失败");
-				}
-
 				for (PcBladeSalesForecastSummary summary : summaryList) {
+					// 强制明细的年份、月份与主表一致(防止数据不一致)
+					summary.setYear(year);
+					summary.setMonth(month);
 					summary.setForecastMainId(mainId);
-					summary.setYear(pcBladeSalesForecastMain.getYear());
-					summary.setMonth(pcBladeSalesForecastMain.getMonth());
-					summary.setCustomerId(pcBladeSalesForecastMain.getCustomerId());
-					summary.setCustomerCode(pcBladeSalesForecastMain.getCustomerCode());
-					summary.setCustomerName(pcBladeSalesForecastMain.getCustomerName());
-					if (summary.getApprovalStatus() == null) {
-						summary.setApprovalStatus(0);
-					}
+					summary.setCustomerId(customerId);
+					summary.setCustomerCode(customer.getCustomerCode());
+					summary.setCustomerName(customer.getCustomerName());
+					summary.setApprovalStatus(summary.getApprovalStatus() == null ? 0 : summary.getApprovalStatus());
 				}
 
 				boolean summarySaved = pcBladeSalesForecastSummaryService.saveBatch(summaryList);
 				if (!summarySaved) {
-					log.error("批量添加失败:明细表数据保存失败");
-					return R.fail("批量添加失败:明细表数据保存失败");
+					log.error("明细表插入失败:主表ID={}, 年份={}, 月份={}", mainId, year, month);
+					throw new RuntimeException("添加失败:明细表数据保存失败"); // 触发事务回滚
 				}
 			}
+
 			return R.data("添加成功");
 		} catch (Exception e) {
 			log.error("批量添加销售预测数据失败", e);
-			return R.fail("批量添加销售预测数据失败" + e.getMessage());
+			return R.fail(e.getMessage() != null ? e.getMessage() : "添加失败:系统异常");
 		}
 	}
 
+
 	@Override
 	@Transactional(rollbackFor = Exception.class)
 	public boolean batchUpdate(PcBladeSalesForecastMain main) {