Commit 6431f675100b0be70270263c817486c4457913d5

Authored by 房远帅
1 parent 91e6b37f

采购计划:打印采购吨超协汇总

1 package com.lframework.xingyun.sc.procurement.controller.coordination; 1 package com.lframework.xingyun.sc.procurement.controller.coordination;
2 2
  3 +import cn.hutool.core.io.resource.ClassPathResource;
3 import com.lframework.starter.web.core.annotations.security.HasPermission; 4 import com.lframework.starter.web.core.annotations.security.HasPermission;
4 import com.lframework.starter.web.core.components.resp.InvokeResultBuilder; 5 import com.lframework.starter.web.core.components.resp.InvokeResultBuilder;
5 import com.lframework.starter.web.core.controller.DefaultBaseController; 6 import com.lframework.starter.web.core.controller.DefaultBaseController;
6 import com.lframework.starter.web.core.components.resp.InvokeResult; 7 import com.lframework.starter.web.core.components.resp.InvokeResult;
7 import com.lframework.starter.web.core.components.resp.PageResult; 8 import com.lframework.starter.web.core.components.resp.PageResult;
8 import com.lframework.xingyun.sc.bo.procurement.GetCoordinationSummaryApprovalBo; 9 import com.lframework.xingyun.sc.bo.procurement.GetCoordinationSummaryApprovalBo;
  10 +import com.lframework.xingyun.sc.bo.procurement.GetCoordinationSummaryApprovalLineBo;
9 import com.lframework.xingyun.sc.procurement.entity.coordination.CoordinationSummaryApproval; 11 import com.lframework.xingyun.sc.procurement.entity.coordination.CoordinationSummaryApproval;
10 import com.lframework.xingyun.sc.procurement.entity.summary.DomesticPlanSummary; 12 import com.lframework.xingyun.sc.procurement.entity.summary.DomesticPlanSummary;
11 import com.lframework.xingyun.sc.procurement.service.coordination.CoordinationSummaryApprovalService; 13 import com.lframework.xingyun.sc.procurement.service.coordination.CoordinationSummaryApprovalService;
@@ -17,12 +19,26 @@ import com.lframework.xingyun.sc.procurement.vo.summary.QueryDomesticPlanSummary @@ -17,12 +19,26 @@ import com.lframework.xingyun.sc.procurement.vo.summary.QueryDomesticPlanSummary
17 import io.swagger.annotations.Api; 19 import io.swagger.annotations.Api;
18 import io.swagger.annotations.ApiImplicitParam; 20 import io.swagger.annotations.ApiImplicitParam;
19 import io.swagger.annotations.ApiOperation; 21 import io.swagger.annotations.ApiOperation;
  22 +import java.io.FileNotFoundException;
  23 +import java.io.IOException;
  24 +import java.io.InputStream;
  25 +import java.math.BigDecimal;
  26 +import java.math.RoundingMode;
  27 +import java.net.URLEncoder;
  28 +import java.time.LocalDate;
  29 +import java.time.format.DateTimeFormatter;
  30 +import java.util.HashMap;
20 import java.util.List; 31 import java.util.List;
21 import java.util.Map; 32 import java.util.Map;
22 import javax.annotation.Resource; 33 import javax.annotation.Resource;
  34 +import javax.servlet.http.HttpServletResponse;
23 import javax.validation.Valid; 35 import javax.validation.Valid;
24 import javax.validation.constraints.NotBlank; 36 import javax.validation.constraints.NotBlank;
25 import lombok.extern.slf4j.Slf4j; 37 import lombok.extern.slf4j.Slf4j;
  38 +import org.apache.commons.collections4.CollectionUtils;
  39 +import org.apache.poi.ss.usermodel.*;
  40 +import org.apache.poi.util.IOUtils;
  41 +import org.apache.poi.xssf.usermodel.XSSFWorkbook;
26 import org.springframework.validation.annotation.Validated; 42 import org.springframework.validation.annotation.Validated;
27 import org.springframework.web.bind.annotation.*; 43 import org.springframework.web.bind.annotation.*;
28 import org.springframework.web.bind.annotation.GetMapping; 44 import org.springframework.web.bind.annotation.GetMapping;
@@ -100,11 +116,204 @@ public class CoordinationSummaryApprovalController extends DefaultBaseController @@ -100,11 +116,204 @@ public class CoordinationSummaryApprovalController extends DefaultBaseController
100 } 116 }
101 117
102 @ApiOperation("打印采购吨超协汇总审批表") 118 @ApiOperation("打印采购吨超协汇总审批表")
103 - @ApiImplicitParam(value = "ID", name = "id", paramType = "path", required = true)  
104 - @HasPermission({"plan-manage:approval-form:print"})  
105 @GetMapping("/print") 119 @GetMapping("/print")
106 - public InvokeResult<GetCoordinationSummaryApprovalBo> print(@NotBlank(message = "ID不能为空!") String id) {  
107 - return InvokeResultBuilder.success(coordinationSummaryApprovalService.findDetail(id)); 120 + public void print(@NotBlank(message = "id不能为空") String id, HttpServletResponse response) throws IOException {
  121 + GetCoordinationSummaryApprovalBo data = coordinationSummaryApprovalService.findDetail(id);
  122 + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  123 + setupResponse(response, data.getSummaryDate() == null ? "采购吨超协汇总.xlsx"
  124 + : data.getSummaryDate().format(dateFormatter) + "-采购吨超协汇总.xlsx");
  125 +
  126 + List<GetCoordinationSummaryApprovalLineBo> lines = data.getLines();
  127 + final int DATA_START_ROW = 3; // 第4行(Excel) = 索引3,模板预留空行,多出来的明细复制这一行样式
  128 + final int COL_TOTAL_QTY = 6; // 第7列(Excel) G列 = 0基索引6,合计数量
  129 + final int COL_WEIGHTED_AVG = 9;// 第10列(Excel) J列 = 0基索引9,吨超协加权平均
  130 +
  131 + try {
  132 + ClassPathResource templateResource = new ClassPathResource("templates/coordinationSummaryTemplate.xlsx");
  133 + try (InputStream inputStream = templateResource.getStream();
  134 + Workbook workbook = new XSSFWorkbook(inputStream)) {
  135 + Sheet sheet = workbook.getSheetAt(0);
  136 + CellStyle borderedStyle = workbook.createCellStyle();
  137 + borderedStyle.setBorderTop(BorderStyle.THIN);
  138 + borderedStyle.setBorderBottom(BorderStyle.THIN);
  139 + borderedStyle.setBorderLeft(BorderStyle.THIN);
  140 + borderedStyle.setBorderRight(BorderStyle.THIN);
  141 +
  142 + int dataSize = lines == null ? 0 : lines.size();
  143 + int needInsert = Math.max(0, dataSize - 1); // 模板已占1行(索引3),超过1条数据要在后面插入行
  144 +
  145 + // 1) 如果明细 >1 条,就把"合计行 + 制单人总经理行"整体往下移 needInsert 行,避免覆盖
  146 + if (needInsert > 0) {
  147 + int shiftStart = DATA_START_ROW + 1; // 原第5行(索引4=合计行)开始下移
  148 + int shiftEnd = sheet.getLastRowNum();
  149 + if (shiftEnd >= shiftStart) {
  150 + sheet.shiftRows(shiftStart, shiftEnd, needInsert, true, true);
  151 + }
  152 + // 2) 把模板行(索引3)的样式复制到新插入的每一行
  153 + Row templateRow = sheet.getRow(DATA_START_ROW);
  154 + for (int k = 1; k <= needInsert; k++) {
  155 + int newRowIdx = DATA_START_ROW + k;
  156 + Row newRow = sheet.getRow(newRowIdx);
  157 + if (newRow == null) newRow = sheet.createRow(newRowIdx);
  158 + if (templateRow != null) {
  159 + newRow.setHeight(templateRow.getHeight());
  160 + for (int c = 0; c < 12; c++) {
  161 + Cell tc = templateRow.getCell(c);
  162 + Cell nc = newRow.getCell(c);
  163 + if (nc == null) nc = newRow.createCell(c);
  164 + if (tc != null) {
  165 + nc.setCellStyle(tc.getCellStyle());
  166 + } else {
  167 + nc.setCellStyle(borderedStyle);
  168 + }
  169 + }
  170 + }
  171 + }
  172 + }
  173 +
  174 + // 3) 填充明细行(从 DATA_START_ROW 开始写 dataSize 行)
  175 + BigDecimal totalQuantity = BigDecimal.ZERO;
  176 + for (int i = 0; i < dataSize; i++) {
  177 + GetCoordinationSummaryApprovalLineBo bo = lines.get(i);
  178 + int rowIdx = DATA_START_ROW + i;
  179 + setCellValue(sheet, rowIdx, 0, i + 1, borderedStyle);
  180 + setCellValue(sheet, rowIdx, 1, bo.getPlanCreateDate() == null ? "" : bo.getPlanCreateDate().format(dateFormatter), borderedStyle);
  181 + setCellValue(sheet, rowIdx, 2, bo.getPurchaseDeptName(), borderedStyle);
  182 + setCellValue(sheet, rowIdx, 3, bo.getSupplierName(), borderedStyle);
  183 + setCellValue(sheet, rowIdx, 4, bo.getPlanCode(), borderedStyle);
  184 + setCellValue(sheet, rowIdx, 5, bo.getProductName(), borderedStyle);
  185 + setCellValue(sheet, rowIdx, 6, bo.getQuantity(), borderedStyle);
  186 + totalQuantity = totalQuantity.add(bo.getQuantity() == null ? BigDecimal.ZERO : bo.getQuantity());
  187 + setCellValue(sheet, rowIdx, 7, bo.getPriceWithShipping(), borderedStyle);
  188 + setCellValue(sheet, rowIdx, 8, bo.getWorkshopName(), borderedStyle);
  189 + setCellValue(sheet, rowIdx, 9, bo.getCoordinationRange(), borderedStyle);
  190 + setCellValue(sheet, rowIdx, 10, bo.getCompareWithAvg(), borderedStyle);
  191 + setCellValue(sheet, rowIdx, 11, bo.getRemarkLabel(), borderedStyle);
  192 + }
  193 +
  194 + // 4) 合计行:最后一条明细的下一行(索引 = DATA_START_ROW + dataSize)
  195 + int totalRowIdx = DATA_START_ROW + dataSize;
  196 + Row totalRow = sheet.getRow(totalRowIdx);
  197 + if (totalRow == null) totalRow = sheet.createRow(totalRowIdx);
  198 + // 合计行所有格子先补齐边框(避免表格断裂)
  199 + for (int col = 0; col < 12; col++) {
  200 + Cell cell = totalRow.getCell(col);
  201 + if (cell == null) cell = totalRow.createCell(col);
  202 + cell.setCellStyle(borderedStyle);
  203 + }
  204 + // G列(索引6)合计数量
  205 + Cell totalCell = totalRow.getCell(COL_TOTAL_QTY);
  206 + if (totalCell == null) totalCell = totalRow.createCell(COL_TOTAL_QTY);
  207 + totalCell.setCellValue(totalQuantity.setScale(4, RoundingMode.HALF_UP).doubleValue());
  208 + totalCell.setCellStyle(borderedStyle);
  209 + // J列(索引9)吨超协加权平均
  210 + Cell avgCell = totalRow.getCell(COL_WEIGHTED_AVG);
  211 + if (avgCell == null) avgCell = totalRow.createCell(COL_WEIGHTED_AVG);
  212 + BigDecimal avg = data.getWeightedAvgCoordination() == null ? BigDecimal.ZERO : data.getWeightedAvgCoordination();
  213 + avgCell.setCellValue(avg.setScale(4, RoundingMode.HALF_UP).doubleValue());
  214 + avgCell.setCellStyle(borderedStyle);
  215 +
  216 + // 5) 模板占位符替换(标题行 summaryDate / 编号 code / 总经理 generalManagerName / 制表 createBy)
  217 + Map<String, Object> dataMap = new HashMap<>();
  218 + dataMap.put("code", data.getCode() == null ? "" : data.getCode());
  219 + dataMap.put("summaryDate", data.getSummaryDate() == null ? "" : data.getSummaryDate().format(dateFormatter));
  220 + dataMap.put("createBy", data.getCreateBy() == null ? "" : data.getCreateBy());
  221 + dataMap.put("generalManagerName", data.getGeneralManagerName() == null ? "" : data.getGeneralManagerName());
  222 + processTemplate(workbook, dataMap);
  223 +
  224 + workbook.write(response.getOutputStream());
  225 + response.getOutputStream().flush();
  226 + } catch (FileNotFoundException e) {
  227 + throw new RuntimeException("模板文件不存在: templates/coordinationSummaryTemplate.xlsx", e);
  228 + }
  229 + } catch (Exception e) {
  230 + log.error("采购吨超协汇总打印失败 id={}, msg={}", id, e.getMessage(), e);
  231 + throw e;
  232 + }
  233 + }
  234 +
  235 + /**
  236 + * 设置HTTP响应头
  237 + */
  238 + private void setupResponse(HttpServletResponse response, String fileName) throws IOException {
  239 + String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
  240 +
  241 + response.setContentType("application/vnd.ms-excel");
  242 + response.setCharacterEncoding("UTF-8");
  243 + response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
  244 + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
  245 + }
  246 +
  247 + /**
  248 + * 设置单元格值
  249 + */
  250 + private static void setCellValue(Sheet sheet, int rowNum, int colNum, Object value, CellStyle style) {
  251 + Row row = sheet.getRow(rowNum);
  252 + if (row == null) {
  253 + row = sheet.createRow(rowNum);
  254 + }
  255 + Cell cell = row.getCell(colNum);
  256 + if (cell == null) {
  257 + cell = row.createCell(colNum);
  258 + }
  259 + cell.setCellStyle(style); // 应用边框样式
  260 +
  261 + if (value == null) {
  262 + cell.setCellValue("");
  263 + } else if (value instanceof String) {
  264 + cell.setCellValue((String) value);
  265 + } else if (value instanceof Integer) {
  266 + cell.setCellValue((Integer) value);
  267 + } else if (value instanceof Double || value instanceof BigDecimal) {
  268 + cell.setCellValue(((Number) value).doubleValue());
  269 + } else if (value instanceof LocalDate) {
  270 + cell.setCellValue(((LocalDate) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
  271 + } else {
  272 + cell.setCellValue(value.toString());
  273 + }
  274 + }
  275 +
  276 + private void processTemplate(Workbook workbook, Map<String, Object> dataMap) {
  277 + Sheet sheet = workbook.getSheetAt(0);
  278 +
  279 + for (Row row : sheet) {
  280 + for (Cell cell : row) {
  281 + if (cell.getCellTypeEnum() == CellType.STRING) {
  282 + String cellValue = cell.getStringCellValue();
  283 + String newValue = replacePlaceholders(cellValue, dataMap);
  284 + if (!cellValue.equals(newValue)) {
  285 + cell.setCellValue(newValue);
  286 + }
  287 + }
  288 + }
  289 + }
  290 + }
  291 +
  292 + private String replacePlaceholders(String text, Map<String, Object> dataMap) {
  293 + if (text == null || text.isEmpty()) {
  294 + return text;
  295 + }
  296 +
  297 + String result = text;
  298 + for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
  299 + String placeholder = "${" + entry.getKey() + "}";
  300 + String value = entry.getValue() != null ? entry.getValue().toString() : "";
  301 + // 将数据库中的 \n 转换为 Excel 识别的换行符
  302 + value = value.replace("\\n", "\n");
  303 +
  304 + result = result.replace(placeholder, value);
  305 + }
  306 +
  307 + for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
  308 + String placeholder = "#{" + entry.getKey() + "}";
  309 + String value = entry.getValue() != null ? entry.getValue().toString() : "";
  310 + // 将数据库中的 \n 转换为 Excel 识别的换行符
  311 + value = value.replace("\\n", "\n");
  312 +
  313 + result = result.replace(placeholder, value);
  314 + }
  315 +
  316 + return result;
108 } 317 }
109 318
110 } 319 }