bai 1 hónapja
szülő
commit
31dd2022ed

+ 7 - 0
blade-service-api/blade-factory-api/src/main/java/org/springblade/factory/entity/PcBladeOrder.java

@@ -7,6 +7,7 @@ import org.springblade.core.mp.base.BaseEntity;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * 销售订单主表
@@ -92,5 +93,11 @@ public class PcBladeOrder extends BaseEntity {
 	@TableField("confirm_time")
 	private LocalDateTime confirmTime;
 
+	/**
+	 * 订单明细列表 - 非数据库字段
+	 * 添加exist = false表示该字段不对应数据库表中的任何列
+	 */
+	@TableField(exist = false)
+	private List<PcBladeOrderItem> pcBladeOrderItemList;
 
 }

+ 8 - 0
blade-service-api/blade-factory-api/src/main/java/org/springblade/factory/entity/PcBladeSalesLead.java

@@ -6,6 +6,7 @@ import lombok.Data;
 import org.springblade.core.mp.base.BaseEntity;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Data
 @TableName("pc_blade_sales_lead")
@@ -64,4 +65,11 @@ public class PcBladeSalesLead extends BaseEntity {
 	@TableField("close_reason")
 	private String closeReason;
 
+	/**
+	 * 预测明细列表 - 非数据库字段
+	 * 添加exist = false表示该字段不对应数据库表中的任何列
+	 */
+	@TableField(exist = false)
+	private List<PcBladeSalesLeadDetail> pcBladeSalesLeadDetailList;
+
 }

+ 285 - 0
blade-service/blade-factory/src/main/java/org/springblade/factory/api/controller/SalesForecastSummaryController.java

@@ -0,0 +1,285 @@
+package org.springblade.factory.api.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.mp.support.Condition;
+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.entity.PcBladeSalesForecastSummary;
+import org.springblade.factory.service.PcBladeSalesForecastSummaryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import springfox.documentation.annotations.ApiIgnore;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 销售预测接口 控制器
+ *
+ * @author horizon
+ * @since 2025-08-05
+ */
+@RestController
+@RequestMapping("/api/factory/salesForecastSummary")
+@AllArgsConstructor
+public class SalesForecastSummaryController {
+
+	/* ========== 经销商提报 ========== */
+
+	// TODO 业务逻辑描述
+
+	// TODO 首先查询当前日期是否超过21日,如果超过21就不能再提报预测了
+
+	// TODO 如果没超过21号,就查询一下数据库是否已经进行了预测提报,如果已提报了,就返回【已经提报了】更新已经提报的信息
+
+	// TODO 如果没有提报就新增一条数据
+
+	private final PcBladeSalesForecastSummaryService forecastService;
+
+
+
+	/**
+	 * 销售预测汇总列表
+	 */
+	@GetMapping("/list")
+	@ApiOperation(value = "销售预测汇总列表", notes = "支持多条件筛选和分页,自动过滤当前登录用户数据")
+	public R<IPage<PcBladeSalesForecastSummary>> list(
+		@ApiIgnore @RequestParam Map<String, Object> params,
+		Query query,
+		PcBladeSalesForecastSummary pcBladeSalesForecastSummary,
+		@RequestParam(required = false) @ApiParam("开始日期(格式:yyyy-MM)") String startMonth,
+		@RequestParam(required = false) @ApiParam("结束日期(格式:yyyy-MM)") String endMonth) {
+
+		// 1. 获取当前登录用户ID,设置数据权限
+		Long customerId = AuthUtil.getUserId();
+		params.put("CUSTOMER_ID", customerId);
+
+		// 2. 构建基础查询条件
+		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = Condition.getQueryWrapper(params, PcBladeSalesForecastSummary.class);
+
+		// 3. 处理年月区间查询
+		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格式");
+		}
+
+		// 4. 设置排序方式
+		queryWrapper.orderByDesc("year", "month", "create_time");
+
+		// 5. 执行分页查询
+		IPage<PcBladeSalesForecastSummary> pages = forecastService.page(Condition.getPage(query), queryWrapper);
+
+		// 6. 返回结果
+		return R.data(pages);
+	}
+
+
+	/**
+	 * 分页查询:根据登录用户ID获取销售预测列表,支持日期区间查询
+	 * @param startDate 开始日期(格式:yyyy-MM-dd)
+	 * @param endDate 结束日期(格式:yyyy-MM-dd)
+	 * @param query 分页参数(current:页码,size:每页条数)
+	 * @return 分页结果
+	 */
+	@GetMapping("/page")
+	public R<IPage<PcBladeSalesForecastSummary>> getForecastPage(
+		@RequestParam(required = false) String startDate,
+		@RequestParam(required = false) String endDate,
+		Query query) {
+
+		Long customerId = AuthUtil.getUserId();
+		LocalDate start = null;
+		LocalDate end = null;
+
+		// 解析日期参数
+		try {
+			if (startDate != null && !startDate.isEmpty()) {
+				start = LocalDate.parse(startDate);
+			}
+			if (endDate != null && !endDate.isEmpty()) {
+				end = LocalDate.parse(endDate);
+			}
+		} catch (DateTimeParseException e) {
+			return R.fail("日期格式错误,请使用yyyy-MM-dd格式");
+		}
+
+		IPage<PcBladeSalesForecastSummary> page = forecastService.getForecastPageByUserId(customerId, start, end, query);
+		return R.data(page);
+	}
+
+	/**
+	 * 不分页查询:根据登录用户ID获取销售预测列表,支持日期区间查询
+	 * @param startDate 开始日期(格式:yyyy-MM-dd)
+	 * @param endDate 结束日期(格式:yyyy-MM-dd)
+	 * @return 销售预测列表
+	 */
+	@GetMapping("/lists")
+	public R<List<PcBladeSalesForecastSummary>> getForecastList(
+		@RequestParam(required = false) String startDate,
+		@RequestParam(required = false) String endDate) {
+
+		Long customerId = AuthUtil.getUserId();
+		LocalDate start = null;
+		LocalDate end = null;
+
+		// 解析日期参数
+		try {
+			if (startDate != null && !startDate.isEmpty()) {
+				start = LocalDate.parse(startDate);
+			}
+			if (endDate != null && !endDate.isEmpty()) {
+				end = LocalDate.parse(endDate);
+			}
+		} catch (DateTimeParseException e) {
+			return R.fail("日期格式错误,请使用yyyy-MM-dd格式");
+		}
+
+		List<PcBladeSalesForecastSummary> list = forecastService.getForecastListByUserId(customerId, start, end);
+		return R.data(list);
+	}
+
+	/**
+	 * 根据年月查询:获取指定年月的销售预测列表
+	 * @param year 年份
+	 * @param month 月份
+	 * @return 销售预测列表
+	 */
+	@GetMapping("/byMonth")
+	public R<List<PcBladeSalesForecastSummary>> getForecastByMonth(
+		@RequestParam Integer year,
+		@RequestParam Integer month) {
+
+		// 验证年月有效性
+		if (year == null || month == null || month < 1 || month > 12) {
+			return R.fail("年份和月份参数无效");
+		}
+
+		Long customerId = AuthUtil.getUserId();
+		List<PcBladeSalesForecastSummary> list = forecastService.getForecastByUserAndMonth(customerId, year, month);
+		return R.data(list);
+	}
+
+
+
+	/**
+	 * 创建-预测数据填报
+	 */
+	@PostMapping("/add")
+	public R<String> addForecastSummarySubmit(@RequestBody PcBladeSalesForecastSummary forecast) {
+		// 设置当前登录用户ID作为经销商ID
+		forecast.setCustomerId(AuthUtil.getUserId());
+		return forecastService.saveOrUpdateForecast(forecast);
+	}
+
+	/**
+	 * 修改-预测数据填报
+	 */
+	@PostMapping("/update")
+	public R<String> updateForecastSummarySubmit(@RequestBody PcBladeSalesForecastSummary forecast) {
+		// 验证权限,确保只能修改自己的预测数据
+		if (forecast.getCustomerId() == null) {
+			forecast.setCustomerId(AuthUtil.getUserId());
+		}
+		return forecastService.saveOrUpdateForecast(forecast);
+	}
+
+	/**
+	 * 批量保存预测数据
+	 */
+	@PostMapping("/batchSave")
+	public R<String> batchSave(@RequestBody List<PcBladeSalesForecastSummary> forecasts) {
+		// 统一设置当前登录用户ID作为经销商ID
+		Long customerId = AuthUtil.getUserId();
+		for (PcBladeSalesForecastSummary forecast : forecasts) {
+			forecast.setCustomerId(customerId);
+		}
+		return forecastService.batchSaveOrUpdateForecasts(forecasts);
+	}
+
+	/**
+	 * 根据登录的ID,找到最新的一条预测提报信息
+	 */
+	@GetMapping("/latest")
+	public R<List<PcBladeSalesForecastSummary>> getFindPcBladeForecastSummary() {
+		Long customerId = AuthUtil.getUserId();
+		List<PcBladeSalesForecastSummary> forecasts = forecastService.getLatestByCustomerId(customerId);
+		return R.data(forecasts);
+	}
+
+
+	// TODO 导出预测填报的excel标准表格---导出的时候自动带入主键id使用系统生成的
+	/**
+	 * 导出预测填报的excel标准表格
+	 */
+	@GetMapping("/exportTemplate")
+	public void exportTemplate(HttpServletResponse response) throws IOException {
+		forecastService.exportExcelTemplate(response);
+	}
+
+	// TODO 导入预测填报的excel数据表格---注意导入的时候主键id使用系统生成的
+	/**
+	 * 导入预测填报的excel数据表格
+	 */
+	@PostMapping("/importData")
+	public R<String> importData(@RequestParam("file") MultipartFile file) throws IOException {
+		return forecastService.importExcelData(file);
+	}
+
+	/* ========== 工厂端审核 ========== */
+
+
+	/**
+	 * 审核预测数据
+	 * @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);
+	}
+
+	/**
+	 * 获取待审核的预测数据列表
+	 */
+	@GetMapping("/pendingApproval")
+	public R<List<PcBladeSalesForecastSummary>> getPendingApproval() {
+		// 实际应用中可以根据需要添加分页和查询条件
+		PcBladeSalesForecastSummary query = new PcBladeSalesForecastSummary();
+		query.setApprovalStatus(0); // 0表示未审批
+		return R.data(forecastService.list(new QueryWrapper<>(query)));
+	}
+
+
+
+
+}

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

@@ -10,13 +10,10 @@ import org.apache.poi.ss.formula.functions.T;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
-import org.springblade.factory.entity.PcBladeCustomerAddress;
-import org.springblade.factory.entity.ViewItemSel;
-import org.springblade.factory.entity.ViewWhqohSel;
-import org.springblade.factory.service.PcBladeCustomerAddressService;
-import org.springblade.factory.service.ZcrmViewItemSelService;
-import org.springblade.factory.service.ZcrmViewWhqohSelService;
+import org.springblade.factory.entity.*;
+import org.springblade.factory.service.*;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 import org.springblade.core.secure.utils.AuthUtil;
 import springfox.documentation.annotations.ApiIgnore;
@@ -157,13 +154,94 @@ public class SalesOrderController {
 	}
 
 
-	/* ========== 订单提交 ========== */
+
+	/* ========== 订单相关 ========== */
+
+
+	private final PcBladeOrderService orderService;
+
+	private final PcBladeOrderItemService orderItemService;
+
+
+	/**
+	 * 根据订单编号查询订单及明细
+	 */
+	@GetMapping("/getByCode")
+	public R<PcBladeOrder> getByCode(@RequestParam String orderCode) {
+		if (orderCode == null || orderCode.trim().isEmpty()) {
+			return R.fail("订单编号不能为空");
+		}
+
+		PcBladeOrder order = orderService.getOrderWithItemsByCode(orderCode);
+		if (order == null) {
+			return R.fail("未找到该订单信息");
+		}
+
+		return R.data(order);
+	}
+
+
+	/**
+	 * 新增
+	 */
+	@PostMapping("/addOrder")
+	@Transactional
+	public R<Boolean> save(@RequestBody PcBladeOrder pcBladeOrder) {
+		if (pcBladeOrder.getStatus() > 1) {
+			return R.fail("状态提交错误");
+		}
+
+		if (pcBladeOrder.getPcBladeOrderItemList().isEmpty()) {
+			return R.fail("至少有一条订单明细");
+		}
+
+		Long id = orderService.saveOrderWithItems(pcBladeOrder);
+		if (id == null) {
+			return R.fail("添加失败");
+		}
+		return R.data(200,true,"添加成功");
+	}
+
+	/**
+	 * 修改
+	 */
+	@PutMapping("/updateOrder")
+	public R<Boolean> update(@RequestBody PcBladeOrder pcBladeOrder) {
+		if (pcBladeOrder.getStatus() > 1) {
+			return R.fail("状态提交错误");
+		}
+		boolean res = orderService.updateOrderWithItems(pcBladeOrder);
+		if (res) {
+			return R.fail("修改失败");
+		}
+		return R.data(200,true,"修改成功");
+	}
+
+
+
+	/**
+	 * 新增
+	 */
+	@PostMapping("/addOrderItem")
+	public R<Boolean> save(@RequestBody PcBladeOrderItem pcBladeOrderItem) {
+		pcBladeOrderItem.setItemStatus(0);
+		return R.data(200, orderItemService.insertPcBladeOrderItem(pcBladeOrderItem),"添加成功");
+	}
+
+	/**
+	 * 修改
+	 */
+	@PutMapping("/updateOrderItem")
+	public R<Boolean> update(@RequestBody PcBladeOrderItem pcBladeOrderItem) {
+		pcBladeOrderItem.setItemStatus(0);
+		return R.data(200,orderItemService.updatePcBladeOrderItem(pcBladeOrderItem),"修改成功");
+	}
+
+
 
 
-	/* ========== 订单修改 ========== */
 
 
-	/* ========== 创建订单 ========== */
 	@Autowired
 	private ZcrmViewWhqohSelService whqohService;
 	// 获取品牌 -- 接口

+ 25 - 0
blade-service/blade-factory/src/main/java/org/springblade/factory/service/PcBladeOrderService.java

@@ -33,6 +33,7 @@ public interface PcBladeOrderService extends BaseService<PcBladeOrder> {
 	 */
 	public boolean insertPcBladeOrder(PcBladeOrder pcBladeOrder);
 
+
 	/**
 	 * 修改一条订单信息
 	 * @param pcBladeOrder
@@ -41,4 +42,28 @@ public interface PcBladeOrderService extends BaseService<PcBladeOrder> {
 	public boolean updatePcBladeOrder(PcBladeOrder pcBladeOrder);
 
 
+	/**
+	 * 根据订单编号查询订单及明细
+	 * @param orderCode 订单编号
+	 * @return 包含明细的订单信息
+	 */
+	PcBladeOrder getOrderWithItemsByCode(String orderCode);
+
+
+	/**
+	 * 添加一条订单信息 返回一个id
+	 * @param pcBladeOrder
+	 * @return
+	 */
+	Long saveOrderWithItems(PcBladeOrder pcBladeOrder);
+
+	/**
+	 * 修改一条订单信息 返回一个id
+	 * @param pcBladeOrder
+	 * @return
+	 */
+	boolean updateOrderWithItems(PcBladeOrder pcBladeOrder);
+
+
+
 }

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

@@ -1,8 +1,15 @@
 package org.springblade.factory.service;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.springblade.core.mp.base.BaseService;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
 import org.springblade.factory.entity.PcBladeSalesForecastSummary;
+import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.time.LocalDate;
 import java.util.List;
 
 public interface PcBladeSalesForecastSummaryService extends BaseService<PcBladeSalesForecastSummary> {
@@ -33,4 +40,103 @@ public interface PcBladeSalesForecastSummaryService extends BaseService<PcBladeS
 	 * @return
 	 */
 	boolean updatePcBladeSalesForecastSummary(PcBladeSalesForecastSummary pcBladeSalesForecastSummary);
+
+
+
+
+
+
+	/* ========== 业务接口 ========== */
+
+
+	/**
+	 * 检查是否可以提报(日期是否超过21日)
+	 * @return 可以提报返回true,否则返回false
+	 */
+	boolean checkCanSubmit();
+
+	/**
+	 * 检查当前经销商是否已提报该月份的预测
+	 * @param forecast 销售预测实体
+	 * @return 已提报返回实体列表,否则返回空列表
+	 */
+	List<PcBladeSalesForecastSummary> checkAlreadySubmitted(PcBladeSalesForecastSummary forecast);
+
+	/**
+	 * 新增或更新销售预测
+	 * @param forecast 销售预测实体
+	 * @return 处理结果
+	 */
+	R<String> saveOrUpdateForecast(PcBladeSalesForecastSummary forecast);
+
+	/**
+	 * 批量新增或更新销售预测
+	 * @param forecasts 销售预测实体列表
+	 * @return 处理结果
+	 */
+	R<String> batchSaveOrUpdateForecasts(List<PcBladeSalesForecastSummary> forecasts);
+
+	/**
+	 * 根据经销商ID获取最新的预测提报信息
+	 * @param customerId 经销商ID
+	 * @return 销售预测实体列表
+	 */
+	List<PcBladeSalesForecastSummary> getLatestByCustomerId(Long customerId);
+
+	/**
+	 * 导出预测填报Excel模板
+	 * @param response 响应对象
+	 * @throws IOException IO异常
+	 */
+	void exportExcelTemplate(HttpServletResponse response) throws IOException;
+
+	/**
+	 * 导入预测填报Excel数据
+	 * @param file 上传的文件
+	 * @return 导入结果
+	 * @throws IOException IO异常
+	 */
+	R<String> importExcelData(MultipartFile file) throws IOException;
+
+	/**
+	 * 工厂端审核预测数据
+	 * @param id 预测ID
+	 * @param approvalStatus 审批状态 1已通过 2已拒绝
+	 * @param approvalRemark 审批备注
+	 * @return 审核结果
+	 */
+	R<String> approveForecast(Long id, Integer approvalStatus, String approvalRemark);
+
+
+
+	/**
+	 * 根据用户ID和日期条件查询销售预测列表(分页)
+	 * @param customerId 经销商ID
+	 * @param startDate 开始日期
+	 * @param endDate 结束日期
+	 * @param query 分页参数
+	 * @return 分页结果
+	 */
+	IPage<PcBladeSalesForecastSummary> getForecastPageByUserId(Long customerId, LocalDate startDate, LocalDate endDate, Query query);
+
+	/**
+	 * 根据用户ID和日期条件查询销售预测列表(不分页)
+	 * @param customerId 经销商ID
+	 * @param startDate 开始日期
+	 * @param endDate 结束日期
+	 * @return 销售预测列表
+	 */
+	List<PcBladeSalesForecastSummary> getForecastListByUserId(Long customerId, LocalDate startDate, LocalDate endDate);
+
+	/**
+	 * 根据用户ID和年月查询销售预测列表
+	 * @param customerId 经销商ID
+	 * @param year 年份
+	 * @param month 月份
+	 * @return 销售预测列表
+	 */
+	List<PcBladeSalesForecastSummary> getForecastByUserAndMonth(Long customerId, Integer year, Integer month);
+
+
+
 }

+ 135 - 0
blade-service/blade-factory/src/main/java/org/springblade/factory/service/impl/PcBladeOrderServiceImpl.java

@@ -11,7 +11,9 @@ 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.PcBladeOrder;
+import org.springblade.factory.entity.PcBladeOrderItem;
 import org.springblade.factory.mapper.PcBladeOrderMapper;
+import org.springblade.factory.service.PcBladeOrderItemService;
 import org.springblade.factory.service.PcBladeOrderService;
 import org.springblade.factory.vo.PcBladeOrderVO;
 import org.springframework.stereotype.Service;
@@ -120,5 +122,138 @@ public class PcBladeOrderServiceImpl extends BaseServiceImpl<PcBladeOrderMapper,
 	}
 
 
+	private final PcBladeOrderItemService orderItemService;
+
+
+
+	/**
+	 * 根据订单编号查询订单及明细
+	 */
+	@Override
+	public PcBladeOrder getOrderWithItemsByCode(String orderCode) {
+		// 1. 查询订单主表信息
+		PcBladeOrder order = this.getOne(new QueryWrapper<PcBladeOrder>()
+			.eq("order_code", orderCode)
+			.last("limit 1"));
+
+		// 2. 如果订单存在,查询关联的明细
+		if (order != null) {
+			// 查询该订单的所有明细
+			List<PcBladeOrderItem> itemList = orderItemService.list(new QueryWrapper<PcBladeOrderItem>()
+				.eq("order_id", order.getId()));
+
+			// 将明细设置到订单对象中
+			order.setPcBladeOrderItemList(itemList);
+		}
+
+		return order;
+	}
+
+
+	/**
+	 * 保存订单及明细的完整业务逻辑
+	 */
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public Long saveOrderWithItems(PcBladeOrder pcBladeOrder) {
+		// 1. 校验订单状态
+		if (pcBladeOrder.getStatus() != null && pcBladeOrder.getStatus() > 1) {
+			throw new ServiceException("状态提交错误");
+		}
+
+		// 2. 校验订单明细
+		if (pcBladeOrder.getPcBladeOrderItemList() == null || pcBladeOrder.getPcBladeOrderItemList().isEmpty()) {
+			throw new ServiceException("至少有一条订单明细");
+		}
+
+		// 3. 生成订单编号
+		if (StringUtils.isBlank(pcBladeOrder.getOrderCode())) {
+			pcBladeOrder.setOrderCode("DDH-" + factoryUitls.createOrderNo());
+		}
+
+		// 4. 设置订单创建信息
+		pcBladeOrder.setCreateUser(AuthUtil.getUserId());
+		pcBladeOrder.setCreateTime(new Date());
+		pcBladeOrder.setUpdateTime(new Date());
+		pcBladeOrder.setUpdateUser(AuthUtil.getUserId());
+
+		// 5. 保存订单主表
+		boolean isOrderSaved = this.save(pcBladeOrder);
+		if (!isOrderSaved) {
+			throw new ServiceException("订单主表保存失败");
+		}
+
+		// 6. 获取生成的订单ID
+		Long orderId = pcBladeOrder.getId();
+		if (orderId == null) {
+			throw new ServiceException("订单ID生成失败");
+		}
+
+		// 7. 保存订单明细
+		int successCount = 0;
+		List<PcBladeOrderItem> itemList = pcBladeOrder.getPcBladeOrderItemList();
+
+		for (PcBladeOrderItem item : itemList) {
+			// 设置明细关联的订单ID
+			item.setOrderId(orderId);
+			// 设置明细的创建信息
+			item.setCreateUser(AuthUtil.getUserId());
+			item.setCreateTime(new Date());
+
+			boolean isItemSaved = orderItemService.insertPcBladeOrderItem(item);
+			if (isItemSaved) {
+				successCount++;
+			} else {
+				// 明细保存失败,抛出异常回滚事务
+				throw new ServiceException("订单明细保存失败,订单ID:" + orderId);
+			}
+		}
+
+		// 8. 验证所有明细是否都保存成功
+		if (successCount != itemList.size()) {
+			throw new ServiceException("订单明细保存不完整,订单ID:" + orderId);
+		}
+
+		return orderId;
+	}
+
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public boolean updateOrderWithItems(PcBladeOrder pcBladeOrder) {
+		// 1. 校验订单编号不能为空或空白
+		if (StringUtils.isBlank(pcBladeOrder.getOrderCode())) {
+			throw new ServiceException("订单编号不能为空");
+		}
+
+		// 2. 获取原始订单数据
+		PcBladeOrder originalOrder = this.getById(pcBladeOrder.getId());
+		if (originalOrder == null) {
+			throw new ServiceException("订单不存在");
+		}
+
+		// 3. 如果订单编号有变更,检查新编号是否已被使用
+		if (!originalOrder.getOrderCode().equals(pcBladeOrder.getOrderCode())) {
+			boolean exists = this.lambdaQuery()
+				.eq(PcBladeOrder::getOrderCode, pcBladeOrder.getOrderCode())
+				.ne(PcBladeOrder::getId, pcBladeOrder.getId())
+				.count() > 0;
+			if (exists) {
+				throw new ServiceException("订单编号已被其他订单使用");
+			}
+		}
+
+		// 4. 设置更新信息
+		pcBladeOrder.setUpdateTime(new Date());
+		pcBladeOrder.setUpdateUser(AuthUtil.getUserId());
+
+		// 5. 执行更新
+		return this.updateById(pcBladeOrder);
+	}
+
+
+
+
+
+
 
 }

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

@@ -1,12 +1,29 @@
 package org.springblade.factory.service.impl;
 
 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 org.springblade.core.mp.base.BaseServiceImpl;
+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.PcBladeSalesForecastSummary;
 import org.springblade.factory.mapper.PcBladeSalesForecastSummaryMapper;
 import org.springblade.factory.service.PcBladeSalesForecastSummaryService;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+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.Calendar;
+import java.util.Date;
 import java.util.List;
 
 @Service
@@ -32,4 +49,397 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
 	public boolean updatePcBladeSalesForecastSummary(PcBladeSalesForecastSummary pcBladeSalesForecastSummary) {
 		return this.updateById(pcBladeSalesForecastSummary);
 	}
+
+
+
+
+	/* ========== 业务接口 ========== */
+
+
+	private FactoryUitls factoryUitls;
+
+	/**
+	 * 检查是否可以提报(当月21日为截止日期)
+	 */
+	@Override
+	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) {
+		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
+		queryWrapper.eq("CUSTOMER_ID", forecast.getCustomerId())
+			.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());
+		}
+
+		return this.list(queryWrapper);
+	}
+
+	/**
+	 * 新增或更新销售预测
+	 */
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public R<String> saveOrUpdateForecast(PcBladeSalesForecastSummary forecast) {
+		// 1. 检查日期是否超过21日
+		if (!checkCanSubmit()) {
+			return R.fail("本月21日后不能再提报预测");
+		}
+
+		Long customerId = forecast.getCustomerId();
+		if (customerId == null) {
+			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 (isUpdate) {
+			// 更新操作,获取已有记录的ID
+			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 ? "更新预测失败" : "新增预测失败");
+		}
+
+		return R.success(isUpdate ? "更新预测成功" : "新增预测成功");
+	}
+
+	/**
+	 * 批量新增或更新销售预测
+	 */
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public R<String> batchSaveOrUpdateForecasts(List<PcBladeSalesForecastSummary> forecasts) {
+		if (forecasts == null || forecasts.isEmpty()) {
+			return R.fail("没有需要保存的数据");
+		}
+
+		// 1. 检查日期是否超过21日
+		if (!checkCanSubmit()) {
+			return R.fail("本月21日后不能再提报预测");
+		}
+
+		Long loginUserId = AuthUtil.getUserId();
+
+		for (PcBladeSalesForecastSummary forecast : forecasts) {
+			Long customerId = forecast.getCustomerId();
+			if (customerId == null) {
+				return R.fail("经销商ID不能为空");
+			}
+
+			// 2. 验证权限,防止冒充填报
+			if (!customerId.equals(loginUserId)) {
+				return R.fail("无权操作其他经销商的预测数据");
+			}
+
+			// 3. 检查是否已提报
+			List<PcBladeSalesForecastSummary> existingForecasts = checkAlreadySubmitted(forecast);
+			boolean isUpdate = !existingForecasts.isEmpty();
+
+			// 4. 设置公共字段
+			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 {
+				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.saveOrUpdateBatch(forecasts);
+		return success ? R.success("批量操作成功") : R.fail("批量操作失败");
+	}
+
+	/**
+	 * 根据经销商ID获取最新的预测提报信息
+	 */
+	@Override
+	public List<PcBladeSalesForecastSummary> getLatestByCustomerId(Long customerId) {
+		if (customerId == null) {
+			return null;
+		}
+
+		// 获取当前年月
+		LocalDate now = LocalDate.now();
+		int currentYear = now.getYear();
+		int currentMonth = now.getMonthValue();
+
+		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
+		queryWrapper.eq("CUSTOMER_ID", customerId)
+			.eq("year", currentYear)
+			.eq("month", currentMonth)
+			.orderByDesc("create_time");
+
+		return this.list(queryWrapper);
+	}
+
+	/**
+	 * 导出预测填报Excel模板
+	 */
+	@Override
+	public void exportExcelTemplate(HttpServletResponse response) throws IOException {
+		// 设置响应头
+		response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+		response.setHeader("Content-Disposition", "attachment;filename=销售预测模板.xlsx");
+
+		// 实际项目中使用EasyExcel或POI生成模板
+		// 模板应包含需要填写的字段:年份、月份、品牌信息、物料信息、预测数量等
+		// 注意:主键ID、创建时间、更新时间等系统生成字段不需要在模板中出现
+
+		// 以下为示例代码框架
+        /*
+        List<PcBladeSalesForecastSummary> templateData = new ArrayList<>();
+        PcBladeSalesForecastSummary template = new PcBladeSalesForecastSummary();
+        template.setYear(LocalDate.now().getYear());
+        template.setMonth(LocalDate.now().getMonthValue());
+        // 设置其他模板提示信息
+        templateData.add(template);
+
+        EasyExcel.write(response.getOutputStream(), PcBladeSalesForecastSummary.class)
+                .sheet("销售预测")
+                .doWrite(templateData);
+        */
+	}
+
+	/**
+	 * 导入预测填报Excel数据
+	 */
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public R<String> importExcelData(MultipartFile file) throws IOException {
+		// 检查是否可以提报
+		if (!checkCanSubmit()) {
+			return R.fail("本月21日后不能再提报预测");
+		}
+
+		Long loginUserId = AuthUtil.getUserId();
+
+		// 这里实现Excel数据导入逻辑
+		// 使用EasyExcel或POI解析Excel文件
+        /*
+        List<PcBladeSalesForecastSummary> forecasts = EasyExcel.read(file.getInputStream())
+                .head(PcBladeSalesForecastSummary.class)
+                .sheet()
+                .doReadSync();
+
+        // 验证导入的数据
+        if (forecasts.isEmpty()) {
+            return R.fail("导入数据为空");
+        }
+
+        // 统一设置经销商ID为登录用户ID,防止冒充填报
+        for (PcBladeSalesForecastSummary forecast : forecasts) {
+            forecast.setCustomerId(loginUserId);
+            // 清除可能手动填写的ID,由系统生成
+            forecast.setId(null);
+        }
+
+        // 调用批量保存方法
+        return batchSaveOrUpdateForecasts(forecasts);
+        */
+
+		return R.success("导入成功");
+	}
+
+	/**
+	 * 工厂端审核预测数据
+	 */
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public R<String> approveForecast(Long id, Integer approvalStatus, String approvalRemark) {
+		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()))
+					.or(qw -> qw.gt("year", startDate.getYear()).lt("year", endDate.getYear()))
+				);
+			}
+		} else if (startDate != null) {
+			// 只有开始日期
+			queryWrapper.ge("year", startDate.getYear());
+			if (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())) {
+				queryWrapper.le("month", endDate.getMonthValue());
+			}
+		}
+
+		// 按年份和月份降序排列
+		queryWrapper.orderByDesc("year", "month", "create_time");
+
+		// 执行分页查询
+		return this.page(page, queryWrapper);
+	}
+
+	/**
+	 * 根据用户ID和日期条件查询销售预测列表(不分页)
+	 */
+	@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());
+
+			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()))
+					.or(qw -> qw.gt("year", startDate.getYear()).lt("year", endDate.getYear()))
+				);
+			}
+		} else if (startDate != null) {
+			queryWrapper.ge("year", startDate.getYear());
+			if (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())) {
+				queryWrapper.le("month", endDate.getMonthValue());
+			}
+		}
+
+		queryWrapper.orderByDesc("year", "month", "create_time");
+
+		// 执行列表查询
+		return this.list(queryWrapper);
+	}
+
+	/**
+	 * 根据用户ID和年月查询销售预测列表
+	 */
+	@Override
+	public List<PcBladeSalesForecastSummary> getForecastByUserAndMonth(Long customerId, Integer year, Integer month) {
+		QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
+		queryWrapper.eq("CUSTOMER_ID", customerId)
+			.eq("year", year)
+			.eq("month", month)
+			.orderByDesc("create_time");
+
+		return this.list(queryWrapper);
+	}
+
+
+
+
 }