Prechádzať zdrojové kódy

工厂端接口开发,导出接口

bai 3 mesiacov pred
rodič
commit
d6760bee73

+ 3 - 0
blade-service-api/blade-factory-api/src/main/java/org/springblade/factory/entity/PcBladeSalesForecastMain.java

@@ -61,6 +61,9 @@ public class PcBladeSalesForecastMain extends BaseEntity {
 	@TableField("approved_time")
 	private LocalDateTime approvedTime;
 
+	@TableField("remark")
+	private String remark;
+
 	@TableField(exist = false)
 	private List<PcBladeSalesForecastSummary> PcBladeSalesForecastSummaryList;
 

+ 3 - 0
blade-service-api/blade-factory-api/src/main/java/org/springblade/factory/entity/PcBladeSalesForecastSummary.java

@@ -89,4 +89,7 @@ public class PcBladeSalesForecastSummary extends BaseEntity {
 	@TableField("approved_time")
 	private LocalDateTime approvedTime;
 
+	@TableField("remark")
+	private String remark;
+
 }

+ 456 - 28
blade-service/blade-factory/src/main/java/org/springblade/factory/api/controller/SalesForecastSummaryController.java

@@ -10,6 +10,8 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.secure.BladeUser;
@@ -24,8 +26,12 @@ import springfox.documentation.annotations.ApiIgnore;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
 import java.time.LocalDate;
 import java.time.format.DateTimeParseException;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -50,6 +56,7 @@ public class SalesForecastSummaryController {
 	private final PcBladeSalesForecastMainService salesForecastMainService;
 
 
+
 	/**
 	 * 根据ID查询销售预测主表
 	 */
@@ -411,48 +418,185 @@ public class SalesForecastSummaryController {
 
 	// TODO 导出预测填报的excel标准表格---导出的时候自动带入主键id使用系统生成的
 	/**
-	 * 导出预测填报的excel标准表格
+	 * 根据年月导出当前登录用户的销售预测数据到Excel
+	 * 数据权限基于当前登录用户的经销商ID或用户ID进行过滤
 	 */
-	@GetMapping("/exportTemplate")
-	public void exportTemplate(HttpServletResponse response) throws IOException {
-		forecastService.exportExcelTemplate(response);
+	@GetMapping("/user/export/{year}/{month}")
+	public void exportUserSalesForecastToExcel(
+		@PathVariable Integer year,
+		@PathVariable Integer month,
+		HttpServletResponse response,
+		Principal principal) {
+
+		// 1. 获取当前登录用户信息
+		String username = AuthUtil.getUserName(); // 获取用户名
+		// 实际应用中通常会有一个UserService来获取用户详细信息
+		// User currentUser = userService.getUserByUsername(username);
+		// Long dealerId = currentUser.getDealerId(); // 假设用户关联了经销商ID
+
+		// 这里为了演示,假设从安全上下文获取用户ID
+		Long currentUserId = AuthUtil.getUserId();
+
+		// 2. 根据年月和用户信息查询数据
+		List<PcBladeSalesForecastMain> mainList =
+			salesForecastMainService.findByUserAndYearAndMonth(currentUserId, year, month);
+
+		// 3. 创建Excel工作簿
+		try (Workbook workbook = new XSSFWorkbook()) {
+			// 4. 创建工作表
+			Sheet sheet = workbook.createSheet(username + "-" + year + "年" + month + "月销售预测数据");
+
+			// 5. 创建表头样式
+			CellStyle headerStyle = createHeaderStyle(workbook);
+
+			// 6. 创建表头
+			String[] headers = {
+				"年份", "月份", "经销商编码", "经销商名称",
+				"品牌编码", "品牌名称", "物料编码", "物料名称",
+				"规格", "花纹", "预测数量", "审批状态", "创建人"
+			};
+			Row headerRow = sheet.createRow(0);
+			for (int i = 0; i < headers.length; i++) {
+				Cell cell = headerRow.createCell(i);
+				cell.setCellValue(headers[i]);
+				cell.setCellStyle(headerStyle);
+				sheet.autoSizeColumn(i);
+			}
+
+			// 7. 填充数据
+			int rowNum = 1;
+			for (PcBladeSalesForecastMain main : mainList) {
+				if (main.getPcBladeSalesForecastSummaryList() != null && !main.getPcBladeSalesForecastSummaryList().isEmpty()) {
+					for (PcBladeSalesForecastSummary summary : main.getPcBladeSalesForecastSummaryList()) {
+						Row row = sheet.createRow(rowNum++);
+						fillUserDataRow(row, main, summary);
+					}
+				} else {
+					Row row = sheet.createRow(rowNum++);
+					fillUserDataRow(row, main, null);
+				}
+			}
+
+			// 8. 设置响应头,准备下载
+			String fileName = username + "-" + year + "年" + month + "月销售预测数据.xlsx";
+			response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+			response.setHeader("Content-Disposition",
+				"attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
+
+			// 9. 写入响应流
+			workbook.write(response.getOutputStream());
+		} catch (IOException e) {
+			e.printStackTrace();
+			// 实际应用中应使用日志框架记录异常
+		}
 	}
 
-	// TODO 导入预测填报的excel数据表格---注意导入的时候主键id使用系统生成的
 	/**
-	 * 导入预测填报的excel数据表格
+	 * 获取当前登录用户ID
+	 * 实际项目中应根据安全框架实现进行调整
 	 */
-	@PostMapping("/importData")
-	public R<String> importData(@RequestParam("file") MultipartFile file) throws IOException {
-		return forecastService.importExcelData(file);
+	private Long getCurrentUserId() {
+		// 示例实现,实际应从安全上下文获取
+		// 例如使用Spring Security:
+		// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+		// UserDetails userDetails = (UserDetails) authentication.getPrincipal();
+		// return userService.getUserIdByUsername(userDetails.getUsername());
+
+		// 这里仅为示例返回,实际应用需修改
+		return 1L;
 	}
 
-	/* ========== 工厂端审核 ========== */
-
+//	/**
+//	 * 创建表头样式
+//	 */
+//	private CellStyle createHeaderStyle(Workbook workbook) {
+//		CellStyle style = workbook.createCellStyle();
+//		style.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
+//		style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+//		style.setBorderTop(BorderStyle.THIN);
+//		style.setBorderRight(BorderStyle.THIN);
+//		style.setBorderBottom(BorderStyle.THIN);
+//		style.setBorderLeft(BorderStyle.THIN);
+//
+//		Font font = workbook.createFont();
+//		font.setBold(true);
+//		style.setFont(font);
+//
+//		style.setAlignment(HorizontalAlignment.CENTER);
+//		style.setVerticalAlignment(VerticalAlignment.CENTER);
+//		return style;
+//	}
 
 	/**
-	 * 审核预测数据
-	 * @param id 预测ID
-	 * @param approvalStatus 审批状态 1已通过 2已拒绝
-	 * @param approvalRemark 审批备注
-	 * @return 审核结果
+	 * 填充用户数据行
 	 */
-	@PostMapping("/approve")
-	public R<String> approve(@RequestParam Long id,
-							 @RequestParam Integer approvalStatus,
-							 @RequestParam(required = false) String approvalRemark) {
-		return forecastService.approveForecast(id, approvalStatus, approvalRemark);
+	private void fillUserDataRow(Row row, PcBladeSalesForecastMain main, PcBladeSalesForecastSummary summary) {
+		int cellNum = 0;
+
+		// 年份
+		row.createCell(cellNum++).setCellValue(main.getYear() != null ? main.getYear() : 0);
+		// 月份
+		row.createCell(cellNum++).setCellValue(main.getMonth() != null ? main.getMonth() : 0);
+		// 经销商编码
+		row.createCell(cellNum++).setCellValue(main.getCustomerCode() != null ? main.getCustomerCode() : "");
+		// 经销商名称
+		row.createCell(cellNum++).setCellValue(main.getCustomerName() != null ? main.getCustomerName() : "");
+
+		if (summary != null) {
+			// 品牌编码
+			row.createCell(cellNum++).setCellValue(summary.getBrandCode() != null ? summary.getBrandCode() : "");
+			// 品牌名称
+			row.createCell(cellNum++).setCellValue(summary.getBrandName() != null ? summary.getBrandName() : "");
+			// 物料编码
+			row.createCell(cellNum++).setCellValue(summary.getItemCode() != null ? summary.getItemCode() : "");
+			// 物料名称
+			row.createCell(cellNum++).setCellValue(summary.getItemName() != null ? summary.getItemName() : "");
+			// 规格
+			row.createCell(cellNum++).setCellValue(summary.getSpecs() != null ? summary.getSpecs() : "");
+			// 花纹
+			row.createCell(cellNum++).setCellValue(summary.getPattern() != null ? summary.getPattern() : "");
+			// 预测数量
+			if (summary.getForecastQuantity() != null) {
+				row.createCell(cellNum++).setCellValue(summary.getForecastQuantity().doubleValue());
+			} else {
+				row.createCell(cellNum++).setCellValue(0);
+			}
+		} else {
+			for (int i = 0; i < 7; i++) {
+				row.createCell(cellNum++).setCellValue("");
+			}
+		}
+
+		// 审批状态
+		String approvalStatus = "";
+		if (main.getApprovalStatus() != null) {
+			switch (main.getApprovalStatus()) {
+				case 0:
+					approvalStatus = "未审批";
+					break;
+				case 1:
+					approvalStatus = "已通过";
+					break;
+				case 2:
+					approvalStatus = "已拒绝";
+					break;
+				default:
+					approvalStatus = "未知";
+			}
+		}
+		row.createCell(cellNum++).setCellValue(approvalStatus);
+
+//		// 创建人(假设BaseEntity中有创建人字段)
+//		row.createCell(cellNum++).setCellValue((Date) (main.getCreateUser() != null ? main.getCreateUser() : ""));
 	}
 
+	// TODO 导入预测填报的excel数据表格---注意导入的时候主键id使用系统生成的
 	/**
-	 * 获取待审核的预测数据列表
+	 * 导入预测填报的excel数据表格
 	 */
-	@GetMapping("/pendingApproval")
-	public R<List<PcBladeSalesForecastSummary>> getPendingApproval() {
-		// 实际应用中可以根据需要添加分页和查询条件
-		PcBladeSalesForecastSummary query = new PcBladeSalesForecastSummary();
-		query.setApprovalStatus(0); // 0表示未审批
-		return R.data(forecastService.list(new QueryWrapper<>(query)));
+	@PostMapping("/importData")
+	public R<String> importData(@RequestParam("file") MultipartFile file) throws IOException {
+		return forecastService.importExcelData(file);
 	}
 
 
@@ -525,4 +669,288 @@ public class SalesForecastSummaryController {
 		return R.data(stock);
 	}
 
+
+
+
+	/* ========== 工厂端审核 ========== */
+
+	/**
+	 * 工厂端获取商品预测列表
+	 * @param params
+	 * @param query
+	 * @param pcBladeSalesForecastMain
+	 * @param startMonth
+	 * @param endMonth
+	 * @return
+	 */
+	@GetMapping("/forecast/list")
+	@ApiOperation(value = "工厂端获取列表", notes = "")
+	public R<IPage<PcBladeSalesForecastMain>> forecastList(
+		@ApiIgnore @RequestParam Map<String, Object> params,
+		Query query,
+		PcBladeSalesForecastMain pcBladeSalesForecastMain,
+		@RequestParam(required = false) @ApiParam("开始日期(格式:yyyy-MM)") String startMonth,
+		@RequestParam(required = false) @ApiParam("结束日期(格式:yyyy-MM)") String endMonth) {
+
+
+		// 1. 构建基础查询条件
+		QueryWrapper<PcBladeSalesForecastMain> queryWrapper = Condition.getQueryWrapper(params, PcBladeSalesForecastMain.class);
+
+		// 2. 处理年月区间查询
+		try {
+			if (startMonth != null && !startMonth.isEmpty()) {
+				String[] startParts = startMonth.split("-");
+				queryWrapper.ge("year", Integer.parseInt(startParts[0]));
+				if (startParts.length > 1) {
+					queryWrapper.ge("month", Integer.parseInt(startParts[1]));
+				}
+			}
+
+			if (endMonth != null && !endMonth.isEmpty()) {
+				String[] endParts = endMonth.split("-");
+				queryWrapper.le("year", Integer.parseInt(endParts[0]));
+				if (endParts.length > 1) {
+					queryWrapper.le("month", Integer.parseInt(endParts[1]));
+				}
+			}
+		} catch (Exception e) {
+			return R.fail("日期格式错误,请使用yyyy-MM格式");
+		}
+
+		// 3. 设置排序方式
+		queryWrapper.orderByDesc("year", "month", "create_time");
+
+		// 4. 执行分页查询
+		IPage<PcBladeSalesForecastMain> pages = salesForecastMainService.pagePro(
+			Condition.getPage(query),
+			queryWrapper
+		);
+
+		// 6. 返回结果
+		return R.data(pages);
+	}
+
+
+	/**
+	 * 根据ID查询销售预测主表
+	 */
+	@GetMapping("/forecast/detail")
+	@ApiOperation(value = "查询销售预测详情", notes = "根据ID查询销售预测主表及关联明细")
+	public R<PcBladeSalesForecastMain> getForecastDetail(
+		@ApiParam(value = "主表ID", required = true) @RequestParam Long id) {
+		if (id == null) {
+			return R.fail("ID不能为空");
+		}
+		PcBladeSalesForecastMain main = salesForecastMainService.selectPcBladeSalesForecastMainById(id);
+		return main != null ? R.data(main) : R.fail("数据不存在");
+	}
+
+
+	/**
+	 * 审核预测数据
+	 * @param pcBladeSalesForecastMain
+	 * @return
+	 */
+	@PostMapping("/approve")
+	public R<String> approve(@RequestBody PcBladeSalesForecastMain pcBladeSalesForecastMain) {
+		return salesForecastMainService.approve(pcBladeSalesForecastMain);
+	}
+
+	/**
+	 * 获取待审核的预测数据列表,默认显示当月数据,支持按年月查询
+	 */
+	@GetMapping("/pendingApproval")
+	public R<List<PcBladeSalesForecastSummary>> getPendingApproval(
+		@RequestParam(required = false) Integer year,
+		@RequestParam(required = false) Integer month) {
+
+		// 处理默认年月(如果未传参则使用当前年月)
+		LocalDate now = LocalDate.now();
+		if (year == null) {
+			year = now.getYear();
+		}
+		if (month == null) {
+			month = now.getMonthValue();
+		}
+
+		// 构建查询条件:查询指定年月的未审批数据
+		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
+		queryWrapper.eq("approval_status", 0) // 0表示未审批
+			.eq("year", year)         // 匹配年份
+			.eq("month", month);      // 匹配月份
+
+		// 如果需要查询日期范围(例如跨月查询),可以使用以下条件
+		// 示例:查询从2023年10月到2024年3月的未审批数据
+		// queryWrapper.between("concat(year, '-', lpad(month, 2, '0'))", "2023-10", "2024-03");
+
+		return R.data(forecastService.list(queryWrapper));
+	}
+
+
+	/**
+	 * 审核单挑明细预测数据
+	 * @param pcBladeSalesForecastSummary
+	 * @return
+	 */
+	@PostMapping("/particulars")
+	public R<String> approveFactoryForecast(@RequestBody PcBladeSalesForecastSummary pcBladeSalesForecastSummary) {
+		return forecastService.approveFactoryForecast(pcBladeSalesForecastSummary.getId(),pcBladeSalesForecastSummary.getForecastMainId(), pcBladeSalesForecastSummary.getApprovalStatus(), pcBladeSalesForecastSummary.getRemark());
+	}
+
+
+
+
+	/**
+	 * 根据年月导出销售预测数据到Excel
+	 */
+	@GetMapping("/export/{year}/{month}")
+	public void exportToExcel(@PathVariable Integer year, @PathVariable Integer month, HttpServletResponse response) {
+		// 1. 根据年月查询数据
+		List<PcBladeSalesForecastMain> mainList = salesForecastMainService.findByYearAndMonth(year, month);
+
+		// 2. 创建Excel工作簿
+		try (Workbook workbook = new XSSFWorkbook()) {
+			// 3. 创建工作表
+			Sheet sheet = workbook.createSheet("销售预测数据");
+
+			// 4. 创建表头样式
+			CellStyle headerStyle = createHeaderStyle(workbook);
+
+			// 5. 创建表头
+			String[] headers = {
+				"年份", "月份", "经销商编码", "经销商名称",
+				"品牌编码", "品牌名称", "物料编码", "物料名称",
+				"规格", "花纹", "预测数量", "审批状态"
+			};
+			Row headerRow = sheet.createRow(0);
+			for (int i = 0; i < headers.length; i++) {
+				Cell cell = headerRow.createCell(i);
+				cell.setCellValue(headers[i]);
+				cell.setCellStyle(headerStyle);
+				// 自动调整列宽
+				sheet.autoSizeColumn(i);
+			}
+
+			// 6. 填充数据
+			int rowNum = 1;
+			for (PcBladeSalesForecastMain main : mainList) {
+				// 如果有明细数据,遍历明细
+				if (main.getPcBladeSalesForecastSummaryList() != null && !main.getPcBladeSalesForecastSummaryList().isEmpty()) {
+					for (PcBladeSalesForecastSummary summary : main.getPcBladeSalesForecastSummaryList()) {
+						Row row = sheet.createRow(rowNum++);
+						fillDataRow(row, main, summary);
+					}
+				} else {
+					// 没有明细数据时,只填充主表信息
+					Row row = sheet.createRow(rowNum++);
+					fillDataRow(row, main, null);
+				}
+			}
+
+			// 7. 设置响应头,准备下载
+			String fileName = year + "年" + month + "月销售预测数据.xlsx";
+			response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+			response.setHeader("Content-Disposition",
+				"attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
+
+			// 8. 写入响应流
+			workbook.write(response.getOutputStream());
+		} catch (IOException e) {
+			// 实际应用中应该使用日志框架记录异常
+			e.printStackTrace();
+			// 可以考虑添加适当的异常处理逻辑
+		}
+	}
+
+	/**
+	 * 创建表头样式
+	 */
+	private CellStyle createHeaderStyle(Workbook workbook) {
+		CellStyle style = workbook.createCellStyle();
+		// 设置背景色
+		style.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex());
+		style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+		// 设置边框
+		style.setBorderTop(BorderStyle.THIN);
+		style.setBorderRight(BorderStyle.THIN);
+		style.setBorderBottom(BorderStyle.THIN);
+		style.setBorderLeft(BorderStyle.THIN);
+		// 设置字体
+		Font font = workbook.createFont();
+		font.setBold(true);
+		style.setFont(font);
+		// 设置居中
+		style.setAlignment(HorizontalAlignment.CENTER);
+		style.setVerticalAlignment(VerticalAlignment.CENTER);
+		return style;
+	}
+
+	/**
+	 * 填充数据行
+	 */
+	private void fillDataRow(Row row, PcBladeSalesForecastMain main, PcBladeSalesForecastSummary summary) {
+		int cellNum = 0;
+
+		// 年份
+		row.createCell(cellNum++).setCellValue(main.getYear() != null ? main.getYear() : 0);
+		// 月份
+		row.createCell(cellNum++).setCellValue(main.getMonth() != null ? main.getMonth() : 0);
+		// 经销商编码
+		row.createCell(cellNum++).setCellValue(main.getCustomerCode() != null ? main.getCustomerCode() : "");
+		// 经销商名称
+		row.createCell(cellNum++).setCellValue(main.getCustomerName() != null ? main.getCustomerName() : "");
+
+		// 如果有明细数据,填充明细信息
+		if (summary != null) {
+			// 品牌编码
+			row.createCell(cellNum++).setCellValue(summary.getBrandCode() != null ? summary.getBrandCode() : "");
+			// 品牌名称
+			row.createCell(cellNum++).setCellValue(summary.getBrandName() != null ? summary.getBrandName() : "");
+			// 物料编码
+			row.createCell(cellNum++).setCellValue(summary.getItemCode() != null ? summary.getItemCode() : "");
+			// 物料名称
+			row.createCell(cellNum++).setCellValue(summary.getItemName() != null ? summary.getItemName() : "");
+			// 规格
+			row.createCell(cellNum++).setCellValue(summary.getSpecs() != null ? summary.getSpecs() : "");
+			// 花纹
+			row.createCell(cellNum++).setCellValue(summary.getPattern() != null ? summary.getPattern() : "");
+			// 预测数量
+			if (summary.getForecastQuantity() != null) {
+				row.createCell(cellNum++).setCellValue(summary.getForecastQuantity().doubleValue());
+			} else {
+				row.createCell(cellNum++).setCellValue(0);
+			}
+		} else {
+			// 没有明细数据时填充空值
+			for (int i = 0; i < 7; i++) {
+				row.createCell(cellNum++).setCellValue("");
+			}
+		}
+
+		// 审批状态(转换为文字描述)
+		String approvalStatus = "";
+		if (main.getApprovalStatus() != null) {
+			switch (main.getApprovalStatus()) {
+				case 0:
+					approvalStatus = "未审批";
+					break;
+				case 1:
+					approvalStatus = "已通过";
+					break;
+				case 2:
+					approvalStatus = "已拒绝";
+					break;
+				default:
+					approvalStatus = "未知";
+			}
+		}
+		row.createCell(cellNum++).setCellValue(approvalStatus);
+	}
+
+
+
+
+
+
+
 }

+ 2 - 3
blade-service/blade-factory/src/main/java/org/springblade/factory/controller/PcBladeSalesForecastMainController.java

@@ -91,8 +91,7 @@ public class PcBladeSalesForecastMainController {
 	 */
 	@PutMapping("/approve")
 	@ApiOperation(value = "审批销售预测", notes = "传入ID和审批状态(0未审批 1已通过 2已拒绝)")
-	public R<Boolean> approve(@RequestBody PcBladeSalesForecastMain pcBladeSalesForecastMain) {
-		boolean result = salesForecastMainService.approve(pcBladeSalesForecastMain);
-		return R.data(200, result, result ? "审批成功" : "审批失败");
+	public R<String> approve(@RequestBody PcBladeSalesForecastMain pcBladeSalesForecastMain) {
+		return salesForecastMainService.approve(pcBladeSalesForecastMain);
 	}
 }

+ 16 - 1
blade-service/blade-factory/src/main/java/org/springblade/factory/service/PcBladeSalesForecastMainService.java

@@ -3,6 +3,7 @@ package org.springblade.factory.service;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.springblade.core.mp.base.BaseService;
+import org.springblade.core.tool.api.R;
 import org.springblade.factory.entity.PcBladeSalesForecastMain;
 
 import java.util.List;
@@ -43,7 +44,7 @@ public interface PcBladeSalesForecastMainService extends BaseService<PcBladeSale
 	 * @param pcBladeSalesForecastMain 包含ID和审批状态的实体
 	 * @return 是否成功
 	 */
-	boolean approve(PcBladeSalesForecastMain pcBladeSalesForecastMain);
+	R<String> approve(PcBladeSalesForecastMain pcBladeSalesForecastMain);
 
 
 	/**
@@ -77,4 +78,18 @@ public interface PcBladeSalesForecastMainService extends BaseService<PcBladeSale
 	 */
 	boolean batchDelete(PcBladeSalesForecastMain pcBladeSalesForecastMain);
 
+
+	/**
+	 * 根据年份和月份查询销售预测数据,包含明细
+	 * @param year 年份
+	 * @param month 月份
+	 * @return 销售预测主表列表,包含对应的明细表数据
+	 */
+	List<PcBladeSalesForecastMain> findByYearAndMonth(Integer year, Integer month);
+
+
+
+	List<PcBladeSalesForecastMain> findByUserAndYearAndMonth(Long currentUserId, Integer year, Integer month);
+
+
 }

+ 14 - 5
blade-service/blade-factory/src/main/java/org/springblade/factory/service/PcBladeSalesForecastSummaryService.java

@@ -102,14 +102,23 @@ public interface PcBladeSalesForecastSummaryService extends BaseService<PcBladeS
 
 	/**
 	 * 工厂端审核预测数据
-	 * @param id 预测ID
-	 * @param approvalStatus 审批状态 1已通过 2已拒绝
-	 * @param approvalRemark 审批备注
-	 * @return 审核结果
+	 * @param id
+	 * @param forecastMainId
+	 * @param approvalStatus
+	 * @param approvalRemark
+	 * @return
 	 */
-	R<String> approveForecast(Long id, Integer approvalStatus, String approvalRemark);
+	R<String> approveFactoryForecast(Long id, Long forecastMainId, Integer approvalStatus, String approvalRemark);
 
 
+	/**
+	 * 工厂端审核预测数据
+	 * @param id
+	 * @param approvalStatus
+	 * @param approvalRemark
+	 * @return
+	 */
+	R<String> approveForecast(Long id, Integer approvalStatus, String approvalRemark);
 
 	/**
 	 * 根据用户ID和日期条件查询销售预测列表(分页)

+ 98 - 74
blade-service/blade-factory/src/main/java/org/springblade/factory/service/impl/PcBladeSalesForecastMainServiceImpl.java

@@ -4,10 +4,10 @@ import com.alibaba.excel.util.CollectionUtils;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springblade.core.mp.base.BaseServiceImpl;
 import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.api.R;
 import org.springblade.factory.entity.PcBladeSalesForecastMain;
 import org.springblade.factory.entity.PcBladeSalesForecastSummary;
 import org.springblade.factory.entity.ViewCustomerSel;
@@ -16,6 +16,8 @@ import org.springblade.factory.mapper.PcBladeSalesForecastSummaryMapper;
 import org.springblade.factory.service.PcBladeSalesForecastMainService;
 import org.springblade.factory.service.PcBladeSalesForecastSummaryService;
 import org.springblade.factory.service.ZcrmViewCustomerSelService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -23,52 +25,52 @@ import java.time.LocalDateTime;
 import java.util.Date;
 import java.util.List;
 
-/**
- * <p>
- * 销售预测主表 服务实现类
- * </p>
- *
- * @author yourname
- * @since 2025-09-10
- */
 @Service
-@AllArgsConstructor
 @Slf4j
 public class PcBladeSalesForecastMainServiceImpl extends BaseServiceImpl<PcBladeSalesForecastMainMapper, PcBladeSalesForecastMain> implements PcBladeSalesForecastMainService {
 
-	private final PcBladeSalesForecastSummaryService pcBladeSalesForecastSummaryService;
-	private final ZcrmViewCustomerSelService customerSelService;
+	// 使用Setter注入+Lazy解决循环依赖
+	private PcBladeSalesForecastSummaryService pcBladeSalesForecastSummaryService;
+	private ZcrmViewCustomerSelService customerSelService;
+	private PcBladeSalesForecastSummaryMapper summaryMapper;
 
+	@Autowired
+	@Lazy
+	public void setPcBladeSalesForecastSummaryService(PcBladeSalesForecastSummaryService pcBladeSalesForecastSummaryService) {
+		this.pcBladeSalesForecastSummaryService = pcBladeSalesForecastSummaryService;
+	}
 
+	@Autowired
+	public void setCustomerSelService(ZcrmViewCustomerSelService customerSelService) {
+		this.customerSelService = customerSelService;
+	}
 
+	@Autowired
+	public void setSummaryMapper(PcBladeSalesForecastSummaryMapper summaryMapper) {
+		this.summaryMapper = summaryMapper;
+	}
 
 	@Override
 	public PcBladeSalesForecastMain selectPcBladeSalesForecastMainById(Long id) {
-		// 1. 查询主表数据
 		PcBladeSalesForecastMain main = baseMapper.selectById(id);
 		if (main == null) {
 			log.warn("销售预测主表数据不存在,ID: {}", id);
 			return null;
 		}
 
-		// 2. 关联查询明细数据
 		try {
-			// 创建查询条件,关联主表ID且状态为有效
 			PcBladeSalesForecastSummary querySummary = new PcBladeSalesForecastSummary();
 			querySummary.setForecastMainId(id);
-			querySummary.setStatus(1); // 假设1表示有效数据
+			querySummary.setStatus(1);
 
-			// 查询明细列表
 			List<PcBladeSalesForecastSummary> summaryList =
 				pcBladeSalesForecastSummaryService.selectPcBladeSalesForecastSummaryList(querySummary);
 
-			// 设置明细列表到主表对象
 			main.setPcBladeSalesForecastSummaryList(summaryList);
 			log.info("查询销售预测主表及明细成功,主表ID: {},明细数量: {}",
 				id, summaryList != null ? summaryList.size() : 0);
 		} catch (Exception e) {
 			log.error("查询销售预测明细数据时发生异常,主表ID: {}", id, e);
-			// 此处不抛出异常,避免因明细查询失败导致主表数据无法返回
 		}
 
 		return main;
@@ -76,7 +78,6 @@ public class PcBladeSalesForecastMainServiceImpl extends BaseServiceImpl<PcBlade
 
 	@Override
 	public boolean insertPcBladeSalesForecastMain(PcBladeSalesForecastMain pcBladeSalesForecastMain) {
-		// 可以在这里添加创建人、创建时间等默认值设置
 		pcBladeSalesForecastMain.setCreateTime(new Date());
 		pcBladeSalesForecastMain.setIsDeleted(0);
 		pcBladeSalesForecastMain.setStatus(1);
@@ -85,44 +86,62 @@ public class PcBladeSalesForecastMainServiceImpl extends BaseServiceImpl<PcBlade
 
 	@Override
 	public boolean updatePcBladeSalesForecastMain(PcBladeSalesForecastMain pcBladeSalesForecastMain) {
-		// 设置更新时间
 		pcBladeSalesForecastMain.setUpdateTime(new Date());
 		return updateById(pcBladeSalesForecastMain);
 	}
 
 	@Override
-	public boolean approve(PcBladeSalesForecastMain pcBladeSalesForecastMain) {
+	public R<String> approve(PcBladeSalesForecastMain pcBladeSalesForecastMain) {
 		if (pcBladeSalesForecastMain.getId() == null || pcBladeSalesForecastMain.getApprovalStatus() == null) {
-			return false;
+			return R.fail("数据不存在或审核状态未设置");
 		}
 
-		UpdateWrapper<PcBladeSalesForecastMain> updateWrapper = new UpdateWrapper<>();
-		updateWrapper.eq("id", pcBladeSalesForecastMain.getId())
-			.set("approval_status", pcBladeSalesForecastMain.getApprovalStatus())
-			.set("approved_time", LocalDateTime.now());
-		return update(updateWrapper);
+		Long currentUserId = AuthUtil.getUserId();
+		String currentUserName = AuthUtil.getUserName();
+		LocalDateTime now = LocalDateTime.now();
+
+		try {
+			UpdateWrapper<PcBladeSalesForecastMain> mainWrapper = new UpdateWrapper<>();
+			mainWrapper.eq("id", pcBladeSalesForecastMain.getId())
+				.set("approval_status", pcBladeSalesForecastMain.getApprovalStatus())
+				.set("approved_by", currentUserId)
+				.set("approved_name", currentUserName)
+				.set("approved_time", now);
+
+			boolean mainResult = update(mainWrapper);
+			if (!mainResult) {
+				return R.fail("主表审核失败");
+			}
+
+			UpdateWrapper<PcBladeSalesForecastSummary> summaryWrapper = new UpdateWrapper<>();
+			summaryWrapper.eq("forecast_main_id", pcBladeSalesForecastMain.getId())
+				.set("approval_status", pcBladeSalesForecastMain.getApprovalStatus())
+				.set("approved_by", currentUserId)
+				.set("approved_name", currentUserName)
+				.set("approved_time", now);
+
+			int summaryCount = summaryMapper.update(null, summaryWrapper);
+
+			return R.data(String.format("审核完成,主表更新成功,关联明细表共%d条记录", summaryCount));
+		} catch (Exception e) {
+			log.error("审核操作失败", e);
+			return R.fail("审核操作异常:" + e.getMessage());
+		}
 	}
 
 	@Override
 	public IPage<PcBladeSalesForecastMain> pagePro(IPage<PcBladeSalesForecastMain> page, QueryWrapper<PcBladeSalesForecastMain> queryWrapper) {
-		// 1. 执行基础分页查询
 		IPage<PcBladeSalesForecastMain> resultPage = this.page(page, queryWrapper);
-
-		// 2. 如果有需要,可以在这里处理查询结果
-		// 例如:关联查询子表数据、计算额外字段等
 		List<PcBladeSalesForecastMain> records = resultPage.getRecords();
 		if (records != null && !records.isEmpty()) {
 			for (PcBladeSalesForecastMain main : records) {
-				// 处理每条记录,例如设置额外计算的字段
-				// TODO 这个需要查询
-				PcBladeSalesForecastSummary pcBladeSalesForecastSummary = new  PcBladeSalesForecastSummary();
+				PcBladeSalesForecastSummary pcBladeSalesForecastSummary = new PcBladeSalesForecastSummary();
 				pcBladeSalesForecastSummary.setForecastMainId(main.getId());
 				pcBladeSalesForecastSummary.setStatus(1);
 				List<PcBladeSalesForecastSummary> pcBladeSalesForecastSummaryList = pcBladeSalesForecastSummaryService.selectPcBladeSalesForecastSummaryList(pcBladeSalesForecastSummary);
 				main.setPcBladeSalesForecastSummaryList(pcBladeSalesForecastSummaryList);
 			}
 		}
-		// 3. 返回处理后的分页结果
 		return resultPage;
 	}
 
@@ -130,21 +149,17 @@ public class PcBladeSalesForecastMainServiceImpl extends BaseServiceImpl<PcBlade
 	@Transactional(rollbackFor = Exception.class)
 	public boolean batchAdd(PcBladeSalesForecastMain pcBladeSalesForecastMain) {
 		try {
-			// 1. 验证主表数据是否为空
 			if (pcBladeSalesForecastMain == null) {
 				log.error("批量添加失败:主表数据不能为空");
 				return false;
 			}
 
-			// 2. 设置公共字段
 			Long currentUserId = AuthUtil.getUserId();
 
-			// 设置默认审批状态(0-未审批)
 			if (pcBladeSalesForecastMain.getApprovalStatus() == null) {
 				pcBladeSalesForecastMain.setApprovalStatus(0);
 			}
 
-			// 3. 保存主表数据,获取自动生成的ID
 			pcBladeSalesForecastMain.setCustomerId(currentUserId);
 			ViewCustomerSel zcrmViewCustomerSel = customerSelService.selectZcrmViewCustomerSelByCustomerId(pcBladeSalesForecastMain.getCustomerId());
 			pcBladeSalesForecastMain.setCustomerCode(zcrmViewCustomerSel.getCustomerCode());
@@ -156,39 +171,27 @@ public class PcBladeSalesForecastMainServiceImpl extends BaseServiceImpl<PcBlade
 				return false;
 			}
 
-			// 4. 处理明细表数据
 			List<PcBladeSalesForecastSummary> summaryList = pcBladeSalesForecastMain.getPcBladeSalesForecastSummaryList();
 			if (summaryList != null && !summaryList.isEmpty()) {
-				// 获取主表ID用于关联
 				Long mainId = pcBladeSalesForecastMain.getId();
 				if (mainId == null) {
 					log.error("批量添加失败:主表ID生成失败");
-					// 事务会自动回滚
 					return false;
 				}
 
-				// 设置明细表公共字段和关联ID
 				for (PcBladeSalesForecastSummary summary : summaryList) {
-					// 设置关联主表ID
 					summary.setForecastMainId(mainId);
-
-					// 同步主表的年份、月份和经销商信息
 					summary.setYear(pcBladeSalesForecastMain.getYear());
 					summary.setMonth(pcBladeSalesForecastMain.getMonth());
 					summary.setCustomerId(pcBladeSalesForecastMain.getCustomerId());
 					summary.setCustomerCode(pcBladeSalesForecastMain.getCustomerCode());
 					summary.setCustomerName(pcBladeSalesForecastMain.getCustomerName());
-					summary.setCustomerCode(pcBladeSalesForecastMain.getCustomerCode());
-					summary.setCustomerName(pcBladeSalesForecastMain.getCustomerName());
-					summary.setCustomerId(pcBladeSalesForecastMain.getCustomerId());
 
-					// 设置默认审批状态
 					if (summary.getApprovalStatus() == null) {
 						summary.setApprovalStatus(0);
 					}
 				}
 
-				// 批量保存明细表
 				boolean summarySaved = pcBladeSalesForecastSummaryService.saveBatch(summaryList);
 				if (!summarySaved) {
 					log.error("批量添加失败:明细表数据保存失败");
@@ -196,51 +199,41 @@ public class PcBladeSalesForecastMainServiceImpl extends BaseServiceImpl<PcBlade
 				}
 			}
 
-			// 5. 所有操作成功
 			return true;
 
 		} catch (Exception e) {
 			log.error("批量添加销售预测数据失败", e);
-			// 发生异常时事务会自动回滚
 			return false;
 		}
 	}
 
-
 	@Override
 	@Transactional(rollbackFor = Exception.class)
 	public boolean batchUpdate(PcBladeSalesForecastMain main) {
 		try {
-			// 1. 验证主表数据及ID
 			if (main == null || main.getId() == null) {
 				log.error("批量修改失败:主表数据或ID不能为空");
 				return false;
 			}
 
-			// 3. 更新主表数据
 			boolean mainUpdated = updateById(main);
 			if (!mainUpdated) {
 				log.error("批量修改失败:主表数据更新失败,主表ID: {}", main.getId());
 				return false;
 			}
 
-			// 4. 处理明细表数据
 			List<PcBladeSalesForecastSummary> summaryList = main.getPcBladeSalesForecastSummaryList();
 			if (summaryList != null && !summaryList.isEmpty()) {
 				Long mainId = main.getId();
 
-				// 遍历处理每条明细
 				for (PcBladeSalesForecastSummary summary : summaryList) {
-					// 验证明细ID
 					if (summary.getId() == null) {
 						log.warn("跳过无ID的明细表数据,主表ID: {}", mainId);
 						continue;
 					}
 
-					// 确保关联关系正确
 					summary.setForecastMainId(mainId);
 
-					// 更新明细数据
 					boolean summaryUpdated = pcBladeSalesForecastSummaryService.updateById(summary);
 					if (!summaryUpdated) {
 						log.error("明细表数据更新失败,明细ID: {}", summary.getId());
@@ -257,54 +250,85 @@ public class PcBladeSalesForecastMainServiceImpl extends BaseServiceImpl<PcBlade
 
 		} catch (Exception e) {
 			log.error("批量修改销售预测数据发生异常", e);
-			// 事务自动回滚
 			return false;
 		}
 	}
 
 	/**
-	 * 	批量软删除销售预测数据(主表+明细表)
-	 * 	假设实体类使用了MyBatis-Plus的逻辑删除注解@TableLogic
-	 * @param pcBladeSalesForecastMain
-	 * @return
+	 * 批量软删除销售预测数据(主表+明细表)
 	 */
 	@Override
 	@Transactional(rollbackFor = Exception.class)
 	public boolean batchDelete(PcBladeSalesForecastMain pcBladeSalesForecastMain) {
-		// 1. 验证参数
 		if (pcBladeSalesForecastMain == null || pcBladeSalesForecastMain.getId() == null) {
 			log.warn("批量删除失败:主表ID列表为空");
 			return false;
 		}
 
 		try {
-			// 2. 先删除关联的明细表数据
 			QueryWrapper<PcBladeSalesForecastSummary> summaryQuery = new QueryWrapper<>();
 			summaryQuery.in("forecast_main_id", pcBladeSalesForecastMain.getId());
 			boolean summaryDeleted = pcBladeSalesForecastSummaryService.remove(summaryQuery);
 
 			if (!summaryDeleted) {
 				log.warn("明细表数据删除失败,可能无对应明细记录");
-				// 这里不直接返回false,因为可能确实没有明细数据
 			}
 
-			// 3. 再删除主表数据(逻辑删除)
 			boolean mainDeleted = removeById(pcBladeSalesForecastMain.getId());
 			if (!mainDeleted) {
 				log.error("主表数据批量删除失败");
 				return false;
 			}
 
-			// 4. 记录删除结果
 			log.info("批量软删除销售预测数据成功,共处理{}条主表记录", pcBladeSalesForecastMain.getPcBladeSalesForecastSummaryList().size());
 			return true;
 
 		} catch (Exception e) {
 			log.error("批量软删除销售预测数据时发生异常", e);
-			// 事务会自动回滚
 			return false;
 		}
 	}
 
-}
+	@Override
+	public List<PcBladeSalesForecastMain> findByYearAndMonth(Integer year, Integer month) {
+		QueryWrapper<PcBladeSalesForecastMain> mainQuery = new QueryWrapper<>();
+		mainQuery.eq("year", year)
+			.eq("month", month);
+		List<PcBladeSalesForecastMain> mainList = baseMapper.selectList(mainQuery);
+
+		for (PcBladeSalesForecastMain main : mainList) {
+			QueryWrapper<PcBladeSalesForecastSummary> summaryQuery = new QueryWrapper<>();
+			summaryQuery.eq("forecast_main_id", main.getId())
+				.eq("year", year)
+				.eq("month", month);
+			List<PcBladeSalesForecastSummary> summaryList = summaryMapper.selectList(summaryQuery);
+			main.setPcBladeSalesForecastSummaryList(summaryList);
+		}
+
+		return mainList;
+	}
+
+	/**
+	 * 根据用户ID、年份和月份查询销售预测数据
+	 */
+	@Override
+	public List<PcBladeSalesForecastMain> findByUserAndYearAndMonth(Long userId, Integer year, Integer month) {
+		QueryWrapper<PcBladeSalesForecastMain> mainQuery = new QueryWrapper<>();
+		mainQuery.eq("year", year)
+			.eq("month", month)
+			.eq("CUSTOMER_ID", userId);
+
+		List<PcBladeSalesForecastMain> mainList = baseMapper.selectList(mainQuery);
 
+		for (PcBladeSalesForecastMain main : mainList) {
+			QueryWrapper<PcBladeSalesForecastSummary> summaryQuery = new QueryWrapper<>();
+			summaryQuery.eq("forecast_main_id", main.getId())
+				.eq("year", year)
+				.eq("month", month);
+			List<PcBladeSalesForecastSummary> summaryList = summaryMapper.selectList(summaryQuery);
+			main.setPcBladeSalesForecastSummaryList(summaryList);
+		}
+
+		return mainList;
+	}
+}

+ 72 - 127
blade-service/blade-factory/src/main/java/org/springblade/factory/service/impl/PcBladeSalesForecastSummaryServiceImpl.java

@@ -7,9 +7,7 @@ import com.alibaba.excel.event.AnalysisEventListener;
 import com.alibaba.excel.util.CollectionUtils;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
@@ -18,41 +16,52 @@ import org.springblade.core.mp.support.Query;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.tool.api.R;
 import org.springblade.factory.common.FactoryUitls;
+import org.springblade.factory.entity.PcBladeSalesForecastMain;
 import org.springblade.factory.entity.PcBladeSalesForecastSummary;
 import org.springblade.factory.mapper.PcBladeSalesForecastSummaryMapper;
+import org.springblade.factory.service.PcBladeSalesForecastMainService;
 import org.springblade.factory.service.PcBladeSalesForecastSummaryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
-import java.time.temporal.TemporalAdjusters;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.time.DateTimeException;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
 @Service
-@AllArgsConstructor
 @Slf4j
 public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBladeSalesForecastSummaryMapper, PcBladeSalesForecastSummary> implements PcBladeSalesForecastSummaryService {
 
-	private PcBladeSalesForecastSummaryMapper forecastMapper;
+	// 使用Setter注入+Lazy解决循环依赖
+	private PcBladeSalesForecastMainService salesForecastMainService;
+	private FactoryUitls factoryUitls;
+
+	@Autowired
+	@Lazy
+	public void setSalesForecastMainService(PcBladeSalesForecastMainService salesForecastMainService) {
+		this.salesForecastMainService = salesForecastMainService;
+	}
 
+	@Autowired
+	public void setFactoryUitls(FactoryUitls factoryUitls) {
+		this.factoryUitls = factoryUitls;
+	}
 
 	@Override
 	public List<PcBladeSalesForecastSummary> selectPcBladeSalesForecastSummaryList(PcBladeSalesForecastSummary pcBladeSalesForecastSummary) {
-		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
+		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>(pcBladeSalesForecastSummary);
 		return this.list(queryWrapper);
 	}
 
@@ -71,17 +80,12 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 		return this.updateById(pcBladeSalesForecastSummary);
 	}
 
-
 	/**
 	 * 批量保存销售预测明细表数据
-	 *
-	 * @param pcBladeSalesForecastSummaryList 销售预测明细表数据列表
-	 * @return 是否保存成功
 	 */
 	@Override
 	@Transactional(rollbackFor = Exception.class)
 	public boolean saveBatch(List<PcBladeSalesForecastSummary> pcBladeSalesForecastSummaryList) {
-		// 1. 验证参数
 		if (CollectionUtils.isEmpty(pcBladeSalesForecastSummaryList)) {
 			log.warn("批量保存失败:明细表数据列表为空");
 			return false;
@@ -91,7 +95,6 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			int total = pcBladeSalesForecastSummaryList.size();
 			log.info("开始批量保存销售预测明细表数据,共{}条记录", total);
 
-			// 2. 不限制批次,直接保存全部数据
 			boolean success = super.saveBatch(pcBladeSalesForecastSummaryList);
 
 			if (success) {
@@ -104,21 +107,16 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 
 		} catch (Exception e) {
 			log.error("批量保存销售预测明细表数据时发生异常", e);
-			// 事务会自动回滚
 			return false;
 		}
 	}
 
 	/**
 	 * 批量更新销售预测明细表数据
-	 *
-	 * @param pcBladeSalesForecastSummaryList 销售预测明细表数据列表
-	 * @return 是否更新成功
 	 */
 	@Override
 	@Transactional(rollbackFor = Exception.class)
 	public boolean updateById(List<PcBladeSalesForecastSummary> pcBladeSalesForecastSummaryList) {
-		// 1. 验证参数
 		if (CollectionUtils.isEmpty(pcBladeSalesForecastSummaryList)) {
 			log.warn("批量更新失败:明细表数据列表为空");
 			return false;
@@ -129,15 +127,12 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			int successCount = 0;
 			log.info("开始批量更新销售预测明细表数据,共{}条记录", total);
 
-			// 2. 遍历列表,逐条更新
 			for (PcBladeSalesForecastSummary summary : pcBladeSalesForecastSummaryList) {
-				// 验证ID是否存在
 				if (summary.getId() == null) {
 					log.warn("跳过无ID的明细表数据,无法更新");
 					continue;
 				}
 
-				// 执行更新操作
 				boolean updated = super.updateById(summary);
 				if (updated) {
 					successCount++;
@@ -146,36 +141,20 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 				}
 			}
 
-			// 3. 检查更新结果
 			if (successCount == 0) {
 				log.error("批量更新失败,所有数据未成功更新");
 				return false;
 			}
 
 			log.info("销售预测明细表数据批量更新完成,共{}条记录,成功更新{}条", total, successCount);
-			// 如果有部分更新成功,这里根据业务需求决定返回true还是false
-			// 这里返回true表示至少有一条更新成功
 			return true;
 
 		} catch (Exception e) {
 			log.error("批量更新销售预测明细表数据时发生异常", e);
-			// 事务会自动回滚
 			return false;
 		}
 	}
 
-
-
-
-
-
-
-
-	/* ========== 业务接口 ========== */
-
-
-	private FactoryUitls factoryUitls;
-
 	/**
 	 * 检查是否可以提报(当月21日为截止日期)
 	 */
@@ -183,13 +162,11 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 	public boolean checkCanSubmit() {
 		Calendar calendar = Calendar.getInstance();
 		int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
-		// 直接判断当前日期是否超过21日
 		return currentDay <= 21;
 	}
 
 	/**
 	 * 检查当前经销商是否已提报该月份的预测
-	 * 按经销商、年份、月份、品牌、物料维度检查
 	 */
 	@Override
 	public List<PcBladeSalesForecastSummary> checkAlreadySubmitted(PcBladeSalesForecastSummary forecast) {
@@ -198,12 +175,10 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			.eq("year", forecast.getYear())
 			.eq("month", forecast.getMonth());
 
-		// 如果有品牌ID,增加品牌维度检查
 		if (forecast.getBrandId() != null) {
 			queryWrapper.eq("BRAND_ID", forecast.getBrandId());
 		}
 
-		// 如果有物料ID,增加物料维度检查
 		if (forecast.getItemId() != null) {
 			queryWrapper.eq("ITEM_ID", forecast.getItemId());
 		}
@@ -217,7 +192,6 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 	@Override
 	@Transactional(rollbackFor = Exception.class)
 	public R<String> saveOrUpdateForecast(PcBladeSalesForecastSummary forecast) {
-		// 1. 检查日期是否超过21日
 		if (!checkCanSubmit()) {
 			return R.fail("本月21日后不能再提报预测");
 		}
@@ -227,39 +201,30 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			return R.fail("经销商ID不能为空");
 		}
 
-		// 2. 验证当前操作的经销商是否为登录用户,防止冒充填报
 		Long loginUserId = AuthUtil.getUserId();
 		if (!customerId.equals(loginUserId)) {
 			return R.fail("无权操作其他经销商的预测数据");
 		}
 
-		// 3. 检查是否已提报
 		List<PcBladeSalesForecastSummary> existingForecasts = checkAlreadySubmitted(forecast);
 		boolean isUpdate = !existingForecasts.isEmpty();
 
-		// 4. 设置公共字段
-		if (existingForecasts != null && existingForecasts.size() > 0) {
-			// 更新操作,获取已有记录的ID
+		if (isUpdate) {
 			forecast.setId(existingForecasts.get(0).getId());
 			forecast.setUpdateTime(new Date());
 			forecast.setUpdateUser(loginUserId);
-			// 重置审批状态为未审批
 			forecast.setApprovalStatus(0);
 			forecast.setApprovedBy(null);
 			forecast.setApprovedName(null);
 			forecast.setApprovedTime(null);
 		} else {
-			// 新增操作,生成ID
-//			forecast.setId(IdWorker.getId()); // MyBatis-Plus自带的雪花算法ID生成
 			forecast.setCreateTime(new Date());
 			forecast.setCreateUser(loginUserId);
 			forecast.setUpdateTime(new Date());
 			forecast.setUpdateUser(loginUserId);
-			// 初始审批状态为未审批
 			forecast.setApprovalStatus(0);
 		}
 
-		// 5. 保存信息
 		boolean success = this.saveOrUpdate(forecast);
 		if (!success) {
 			return R.fail(isUpdate ? "更新预测失败" : "新增预测失败");
@@ -278,7 +243,6 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			return R.fail("没有需要保存的数据");
 		}
 
-		// 1. 检查日期是否超过21日
 		if (!checkCanSubmit()) {
 			return R.fail("本月21日后不能再提报预测");
 		}
@@ -291,17 +255,14 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 				return R.fail("经销商ID不能为空");
 			}
 
-			// 2. 验证权限,防止冒充填报
 			if (!customerId.equals(loginUserId)) {
 				return R.fail("无权操作其他经销商的预测数据");
 			}
 
-			// 3. 检查是否已提报
 			List<PcBladeSalesForecastSummary> existingForecasts = checkAlreadySubmitted(forecast);
 			boolean isUpdate = !existingForecasts.isEmpty();
 
-			// 4. 设置公共字段
-			if (existingForecasts != null && existingForecasts.size() > 0) {
+			if (isUpdate) {
 				forecast.setId(existingForecasts.get(0).getId());
 				forecast.setUpdateTime(new Date());
 				forecast.setUpdateUser(loginUserId);
@@ -310,7 +271,6 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 				forecast.setApprovedName(null);
 				forecast.setApprovedTime(null);
 			} else {
-//				forecast.setId(IdWorker.getId()); // MyBatis-Plus自带的雪花算法ID生成
 				forecast.setCreateTime(new Date());
 				forecast.setCreateUser(loginUserId);
 				forecast.setUpdateTime(new Date());
@@ -319,7 +279,6 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			}
 		}
 
-		// 5. 批量保存
 		boolean success = this.saveOrUpdateBatch(forecasts);
 		return success ? R.success("批量操作成功") : R.fail("批量操作失败");
 	}
@@ -330,13 +289,11 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			return null;
 		}
 
-		// 获取当前年月
 		LocalDate now = LocalDate.now();
 		int currentYear = now.getYear();
 		int currentMonth = now.getMonthValue();
 
 		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
-		// 将customerId改为CUSTOMER_ID,与数据库实际列名保持一致
 		queryWrapper.eq("CUSTOMER_ID", customerId)
 			.eq("year", currentYear)
 			.eq("month", currentMonth)
@@ -350,23 +307,17 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 	 */
 	@Override
 	public void exportExcelTemplate(HttpServletResponse response) throws IOException {
-		// 创建Excel工作簿
 		Workbook workbook = new XSSFWorkbook();
-		// 创建工作表
 		Sheet sheet = workbook.createSheet("销售预测模板");
 
-		// 创建表头行
 		Row headerRow = sheet.createRow(0);
 
-		// 定义表头内容(与实体类字段对应)
 		String[] headers = {
 			"年份", "月份", "经销商ID", "经销商编码", "经销商名称",
 			"品牌ID", "品牌编码", "品牌名称", "物料ID", "物料编码",
 			"物料名称", "规格", "花纹", "预测数量"
 		};
 
-		// 创建单元格样式
-
 		CellStyle headerStyle = workbook.createCellStyle();
 		Font font = workbook.createFont();
 		font.setBold(true);
@@ -377,16 +328,13 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 		headerStyle.setBorderRight(BorderStyle.THIN);
 		headerStyle.setAlignment(HorizontalAlignment.CENTER);
 
-		// 设置表头
 		for (int i = 0; i < headers.length; i++) {
 			Cell cell = headerRow.createCell(i);
 			cell.setCellValue(headers[i]);
 			cell.setCellStyle(headerStyle);
-			// 自动调整列宽
 			sheet.autoSizeColumn(i);
 		}
 
-		// 添加一行示例数据(可选)
 		Row exampleRow = sheet.createRow(1);
 		exampleRow.createCell(0).setCellValue(2023);
 		exampleRow.createCell(1).setCellValue(10);
@@ -400,12 +348,10 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 		exampleRow.createCell(12).setCellValue("花纹示例");
 		exampleRow.createCell(13).setCellValue(100);
 
-		// 设置响应头
 		response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
 		String fileName = URLEncoder.encode("销售预测模板.xlsx", StandardCharsets.UTF_8.name());
 		response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
 
-		// 写入响应输出流
 		try (OutputStream os = response.getOutputStream()) {
 			workbook.write(os);
 		} finally {
@@ -419,17 +365,14 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 	@Override
 	@Transactional(rollbackFor = Exception.class)
 	public R<String> importExcelData(MultipartFile file) throws IOException {
-		// 检查是否可以提报
 		if (!checkCanSubmit()) {
 			return R.fail("本月21日后不能再提报预测");
 		}
 
-		// 检查文件是否为空
 		if (file.isEmpty()) {
 			return R.fail("导入文件不能为空");
 		}
 
-		// 检查文件格式
 		String fileName = file.getOriginalFilename();
 		if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
 			return R.fail("请上传Excel格式的文件");
@@ -440,37 +383,28 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			return R.fail("用户未登录");
 		}
 
-		// 使用EasyExcel读取Excel文件
 		List<PcBladeSalesForecastSummary> forecasts = EasyExcel.read(file.getInputStream())
 			.head(PcBladeSalesForecastSummary.class)
 			.registerReadListener(new AnalysisEventListener<PcBladeSalesForecastSummary>() {
-				// 可以在这里添加逐行验证逻辑
 				@Override
-				public void invoke(PcBladeSalesForecastSummary data, AnalysisContext context) {
-
-				}
+				public void invoke(PcBladeSalesForecastSummary data, AnalysisContext context) {}
 
 				@Override
-				public void doAfterAllAnalysed(AnalysisContext context) {
-					// 所有数据解析完成后执行
-				}
+				public void doAfterAllAnalysed(AnalysisContext context) {}
 			})
 			.sheet()
 			.doReadSync();
 
-		// 验证导入的数据
 		if (forecasts.isEmpty()) {
 			return R.fail("导入数据为空");
 		}
 
-		// 数据验证和处理
 		List<String> errorMessages = new ArrayList<>();
 
 		for (int i = 0; i < forecasts.size(); i++) {
 			PcBladeSalesForecastSummary forecast = forecasts.get(i);
-			int rowNum = i + 2; // 行号从2开始(表头+1)
+			int rowNum = i + 2;
 
-			// 基本数据验证
 			if (forecast.getYear() == null) {
 				errorMessages.add("第" + rowNum + "行:年份不能为空");
 			}
@@ -487,27 +421,22 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 				errorMessages.add("第" + rowNum + "行:预测数量必须大于0");
 			}
 
-			// 设置系统字段,覆盖可能手动填写的值
-			forecast.setCustomerId(loginUserId); // 强制设置为当前登录用户ID
-			forecast.setId(null); // 清除ID,由系统生成
-			forecast.setApprovalStatus(0); // 初始化为未审批状态
+			forecast.setCustomerId(loginUserId);
+			forecast.setId(null);
+			forecast.setApprovalStatus(0);
 			forecast.setCreateTime(new Date());
 			forecast.setCreateUser(AuthUtil.getUserId());
 			forecast.setUpdateTime(new Date());
 			forecast.setUpdateUser(AuthUtil.getUserId());
-
-			// 清除审批相关字段,避免导入时携带
 			forecast.setApprovedBy(null);
 			forecast.setApprovedName(null);
 			forecast.setApprovedTime(null);
 		}
 
-		// 如果有验证错误,返回错误信息
 		if (!errorMessages.isEmpty()) {
 			return R.fail("导入数据验证失败:\n" + String.join("\n", errorMessages));
 		}
 
-		// 调用批量保存方法
 		return batchSaveOrUpdateForecastsFun(forecasts);
 	}
 
@@ -516,15 +445,13 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 	 */
 	private R<String> batchSaveOrUpdateForecastsFun(List<PcBladeSalesForecastSummary> forecasts) {
 		try {
-			// 先删除当前用户已有的同月份数据,避免重复
 			PcBladeSalesForecastSummary example = new PcBladeSalesForecastSummary();
 			example.setCustomerId(forecasts.get(0).getCustomerId());
 			example.setYear(forecasts.get(0).getYear());
 			example.setMonth(forecasts.get(0).getMonth());
-			forecastMapper.deleteByExample(example);
+			baseMapper.delete(new QueryWrapper<>(example));
 
-			// 批量插入新数据
-			forecastMapper.batchInsert(forecasts);
+			this.saveBatch(forecasts);
 			return R.success("导入成功,共导入" + forecasts.size() + "条数据");
 		} catch (Exception e) {
 			log.error("批量保存预测数据失败", e);
@@ -551,7 +478,6 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			return R.fail("预测数据不存在");
 		}
 
-		// 设置审批信息
 		forecast.setApprovalStatus(approvalStatus);
 		forecast.setApprovedBy(AuthUtil.getUserId());
 		forecast.setApprovedName(AuthUtil.getUserName());
@@ -563,32 +489,64 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 		return success ? R.success("审核成功") : R.fail("审核失败");
 	}
 
+	/**
+	 * 工厂端审核预测数据
+	 */
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public R<String> approveFactoryForecast(Long id, Long forecastMainId, Integer approvalStatus, String approvalRemark) {
+		if (forecastMainId == null) {
+			return R.fail("预测销售主数据ID不能为空");
+		}
+
+		PcBladeSalesForecastMain pcBladeSalesForecastMain = salesForecastMainService.selectPcBladeSalesForecastMainById(forecastMainId);
+		if (pcBladeSalesForecastMain == null) {
+			return R.fail("预测销售主数据不存在");
+		}
+
+		if (id == null) {
+			return R.fail("预测ID不能为空");
+		}
+
+		if (approvalStatus == null || (approvalStatus != 1 && approvalStatus != 2)) {
+			return R.fail("审批状态不正确");
+		}
+
+		PcBladeSalesForecastSummary forecast = this.getById(id);
+		if (forecast == null) {
+			return R.fail("预测数据不存在");
+		}
+
+		forecast.setApprovalStatus(approvalStatus);
+		forecast.setApprovedBy(AuthUtil.getUserId());
+		forecast.setApprovedName(AuthUtil.getUserName());
+		forecast.setApprovedTime(LocalDateTime.now());
+		forecast.setUpdateTime(new Date());
+		forecast.setUpdateUser(AuthUtil.getUserId());
+
+		boolean success = this.updateById(forecast);
+		return success ? R.success("审核成功") : R.fail("审核失败");
+	}
 
 	/**
 	 * 根据用户ID和日期条件查询销售预测列表(分页)
 	 */
 	@Override
 	public IPage<PcBladeSalesForecastSummary> getForecastPageByUserId(Long customerId, LocalDate startDate, LocalDate endDate, Query query) {
-		// 创建分页对象
 		IPage<PcBladeSalesForecastSummary> page = new Page<>(query.getCurrent(), query.getSize());
-
-		// 构建查询条件
 		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
 		queryWrapper.eq("CUSTOMER_ID", customerId);
 
-		// 处理日期区间查询
 		if (startDate != null && endDate != null) {
 			queryWrapper.ge("year", startDate.getYear())
 				.le("year", endDate.getYear());
 
-			// 如果是同一年,添加月份条件
 			if (startDate.getYear() == endDate.getYear()) {
 				queryWrapper.and(w -> w
 					.ge("month", startDate.getMonthValue())
 					.le("month", endDate.getMonthValue())
 				);
 			} else if (startDate.getYear() < endDate.getYear()) {
-				// 跨年度查询
 				queryWrapper.and(w -> w
 					.or(qw -> qw.eq("year", startDate.getYear()).ge("month", startDate.getMonthValue()))
 					.or(qw -> qw.eq("year", endDate.getYear()).le("month", endDate.getMonthValue()))
@@ -596,23 +554,18 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 				);
 			}
 		} else if (startDate != null) {
-			// 只有开始日期
 			queryWrapper.ge("year", startDate.getYear());
-			if (queryWrapper.getEntity().getYear().equals(startDate.getYear())) {
+			if (queryWrapper.getEntity().getYear() != null && queryWrapper.getEntity().getYear().equals(startDate.getYear())) {
 				queryWrapper.ge("month", startDate.getMonthValue());
 			}
 		} else if (endDate != null) {
-			// 只有结束日期
 			queryWrapper.le("year", endDate.getYear());
-			if (queryWrapper.getEntity().getYear().equals(endDate.getYear())) {
+			if (queryWrapper.getEntity().getYear() != null && queryWrapper.getEntity().getYear().equals(endDate.getYear())) {
 				queryWrapper.le("month", endDate.getMonthValue());
 			}
 		}
 
-		// 按年份和月份降序排列
 		queryWrapper.orderByDesc("year", "month", "create_time");
-
-		// 执行分页查询
 		return this.page(page, queryWrapper);
 	}
 
@@ -621,11 +574,9 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 	 */
 	@Override
 	public List<PcBladeSalesForecastSummary> getForecastListByUserId(Long customerId, LocalDate startDate, LocalDate endDate) {
-		// 复用分页查询的条件构建逻辑
 		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
 		queryWrapper.eq("CUSTOMER_ID", customerId);
 
-		// 处理日期区间查询(与分页方法相同)
 		if (startDate != null && endDate != null) {
 			queryWrapper.ge("year", startDate.getYear())
 				.le("year", endDate.getYear());
@@ -644,19 +595,17 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 			}
 		} else if (startDate != null) {
 			queryWrapper.ge("year", startDate.getYear());
-			if (queryWrapper.getEntity().getYear().equals(startDate.getYear())) {
+			if (queryWrapper.getEntity().getYear() != null && queryWrapper.getEntity().getYear().equals(startDate.getYear())) {
 				queryWrapper.ge("month", startDate.getMonthValue());
 			}
 		} else if (endDate != null) {
 			queryWrapper.le("year", endDate.getYear());
-			if (queryWrapper.getEntity().getYear().equals(endDate.getYear())) {
+			if (queryWrapper.getEntity().getYear() != null && queryWrapper.getEntity().getYear().equals(endDate.getYear())) {
 				queryWrapper.le("month", endDate.getMonthValue());
 			}
 		}
 
 		queryWrapper.orderByDesc("year", "month", "create_time");
-
-		// 执行列表查询
 		return this.list(queryWrapper);
 	}
 
@@ -673,8 +622,4 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 
 		return this.list(queryWrapper);
 	}
-
-
-
-
 }