|
@@ -1,9 +1,16 @@
|
|
|
package org.springblade.factory.service.impl;
|
|
|
|
|
|
+import com.alibaba.cloud.commons.lang.StringUtils;
|
|
|
+import com.alibaba.excel.EasyExcel;
|
|
|
+import com.alibaba.excel.context.AnalysisContext;
|
|
|
+import com.alibaba.excel.event.AnalysisEventListener;
|
|
|
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 org.apache.poi.ss.usermodel.*;
|
|
|
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
import org.springblade.core.mp.base.BaseServiceImpl;
|
|
|
import org.springblade.core.mp.support.Query;
|
|
|
import org.springblade.core.secure.utils.AuthUtil;
|
|
@@ -15,6 +22,11 @@ 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.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;
|
|
@@ -22,13 +34,19 @@ 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
|
|
|
public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBladeSalesForecastSummaryMapper, PcBladeSalesForecastSummary> implements PcBladeSalesForecastSummaryService {
|
|
|
|
|
|
+ private PcBladeSalesForecastSummaryMapper forecastMapper;
|
|
|
+
|
|
|
+
|
|
|
@Override
|
|
|
public List<PcBladeSalesForecastSummary> selectPcBladeSalesForecastSummaryList(PcBladeSalesForecastSummary pcBladeSalesForecastSummary) {
|
|
|
QueryWrapper<PcBladeSalesForecastSummary> queryWrapper = new QueryWrapper<>();
|
|
@@ -234,27 +252,67 @@ 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);
|
|
|
+ headerStyle.setFont(font);
|
|
|
+ headerStyle.setBorderTop(BorderStyle.THIN);
|
|
|
+ headerStyle.setBorderBottom(BorderStyle.THIN);
|
|
|
+ headerStyle.setBorderLeft(BorderStyle.THIN);
|
|
|
+ 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);
|
|
|
+ exampleRow.createCell(3).setCellValue("CUST001");
|
|
|
+ exampleRow.createCell(4).setCellValue("示例经销商");
|
|
|
+ exampleRow.createCell(6).setCellValue("BRAND001");
|
|
|
+ exampleRow.createCell(7).setCellValue("示例品牌");
|
|
|
+ exampleRow.createCell(9).setCellValue("ITEM001");
|
|
|
+ exampleRow.createCell(10).setCellValue("示例物料");
|
|
|
+ exampleRow.createCell(11).setCellValue("规格示例");
|
|
|
+ exampleRow.createCell(12).setCellValue("花纹示例");
|
|
|
+ exampleRow.createCell(13).setCellValue(100);
|
|
|
+
|
|
|
// 设置响应头
|
|
|
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);
|
|
|
- */
|
|
|
+ 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 {
|
|
|
+ workbook.close();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -268,33 +326,112 @@ public class PcBladeSalesForecastSummaryServiceImpl extends BaseServiceImpl<PcBl
|
|
|
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格式的文件");
|
|
|
+ }
|
|
|
+
|
|
|
Long loginUserId = AuthUtil.getUserId();
|
|
|
+ if (loginUserId == null) {
|
|
|
+ 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) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ 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)
|
|
|
+
|
|
|
+ // 基本数据验证
|
|
|
+ if (forecast.getYear() == null) {
|
|
|
+ errorMessages.add("第" + rowNum + "行:年份不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (forecast.getMonth() == null || forecast.getMonth() < 1 || forecast.getMonth() > 12) {
|
|
|
+ errorMessages.add("第" + rowNum + "行:月份格式不正确");
|
|
|
+ }
|
|
|
|
|
|
- // 这里实现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("导入成功");
|
|
|
+ if (StringUtils.isEmpty(forecast.getItemCode())) {
|
|
|
+ errorMessages.add("第" + rowNum + "行:物料编码不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (forecast.getForecastQuantity() == null || forecast.getForecastQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ errorMessages.add("第" + rowNum + "行:预测数量必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置系统字段,覆盖可能手动填写的值
|
|
|
+ forecast.setCustomerId(loginUserId); // 强制设置为当前登录用户ID
|
|
|
+ forecast.setId(null); // 清除ID,由系统生成
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量保存或更新预测数据
|
|
|
+ */
|
|
|
+ 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);
|
|
|
+
|
|
|
+ // 批量插入新数据
|
|
|
+ forecastMapper.batchInsert(forecasts);
|
|
|
+ return R.success("导入成功,共导入" + forecasts.size() + "条数据");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量保存预测数据失败", e);
|
|
|
+ return R.fail("导入失败:" + e.getMessage());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|