diff --git a/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java b/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java index 2e5808c9..cb99dd40 100644 --- a/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java +++ b/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java @@ -143,6 +143,17 @@ public class WmsMaterialCoilController extends BaseController { ExcelUtil.exportExcel(list, "退火报表", WmsMaterialCoilAnnealExportVo.class, response); } + /** + * 导出异常报表(按 coilIds,联查钢卷信息、异常信息、改判原因) + * 一个钢卷可能对应多个异常,钢卷信息合并居中,异常信息逐条显示 + * 改判原因放在钢卷信息和异常信息之间 + */ + @Log(title = "钢卷物料表-异常报表", businessType = BusinessType.EXPORT) + @PostMapping("/exportAbnormal") + public void exportAbnormal(WmsMaterialCoilBo bo, HttpServletResponse response) { + iWmsMaterialCoilService.exportAbnormalReport(bo, response); + } + /** * 查询钢卷物料表列表(POST请求,支持大量coilIds查询) * 功能与GET /list相同,但使用POST请求体传递参数,避免URL长度限制 diff --git a/klp-wms/src/main/java/com/klp/domain/WmsCoilAbnormalExportRow.java b/klp-wms/src/main/java/com/klp/domain/WmsCoilAbnormalExportRow.java new file mode 100644 index 00000000..2da24d07 --- /dev/null +++ b/klp-wms/src/main/java/com/klp/domain/WmsCoilAbnormalExportRow.java @@ -0,0 +1,30 @@ +package com.klp.domain; + +import com.klp.domain.vo.WmsMaterialCoilVo; +import lombok.Data; + +/** + * 钢卷异常报表导出数据行 + * 用于组织钢卷、改判原因和异常信息的导出数据结构 + * + * @author Joshi + * @date 2026-05-06 + */ +@Data +public class WmsCoilAbnormalExportRow { + + /** + * 钢卷信息 + */ + private WmsMaterialCoilVo coil; + + /** + * 改判原因 + */ + private String rejudgeReason; + + /** + * 异常信息 + */ + private WmsCoilAbnormal abnormal; +} diff --git a/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilExportVo.java b/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilExportVo.java index a55a7660..ec96486a 100644 --- a/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilExportVo.java +++ b/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilExportVo.java @@ -74,8 +74,7 @@ public class WmsMaterialCoilExportVo { * 用途 */ @ExcelProperty(value = "用途") - private String purpose; - + private String businessPurpose; /** * 切边要求 */ @@ -184,8 +183,6 @@ public class WmsMaterialCoilExportVo { // 班组 private String team; - // 业务用途 - private String businessPurpose; // 是否与订单相关 readConverterExp private Integer isRelatedToOrder; diff --git a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java index 146cbc5e..6e7aacde 100644 --- a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java +++ b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java @@ -8,6 +8,7 @@ import com.klp.common.core.domain.PageQuery; import com.klp.domain.vo.dashboard.CoilTrimStatisticsVo; import com.klp.domain.vo.dashboard.CategoryWidthStatisticsVo; +import javax.servlet.http.HttpServletResponse; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.util.Collection; @@ -304,5 +305,15 @@ public interface IWmsMaterialCoilService { * @return 退火报表导出数据列表 */ List queryAnnealExportList(WmsMaterialCoilBo bo); + + /** + * 导出异常报表(按 coilIds,联查钢卷信息、异常信息、改判原因) + * 一个钢卷可能对应多个异常,钢卷信息合并居中,异常信息逐条显示 + * 改判原因放在钢卷信息和异常信息之间 + * + * @param bo 查询条件(前端通过 POST 传 coilIds) + * @param response HTTP响应对象 + */ + void exportAbnormalReport(WmsMaterialCoilBo bo, HttpServletResponse response); } diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java index c130a850..9099e510 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java @@ -41,6 +41,15 @@ import java.util.*; import java.util.stream.Collectors; import java.math.BigDecimal; import java.util.Arrays; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; /** * 钢卷物料表Service业务层处理 @@ -73,6 +82,8 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService { private final WmsCoilWarehouseOperationLogMapper wmsCoilWarehouseOperationLogMapper; private final IWmsCoilAbnormalService coilAbnormalService; private final WmsCoilContractRelMapper coilContractRelMapper; + private final WmsCoilQualityRejudgeMapper wmsCoilQualityRejudgeMapper; + private final WmsCoilAbnormalMapper coilAbnormalMapper; /** * 查询钢卷物料表 @@ -5280,5 +5291,266 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService { return false; } + @Override + public void exportAbnormalReport(WmsMaterialCoilBo bo, HttpServletResponse response) { + // 1. 查询钢卷列表 + List coilList = queryList(bo); + if (coilList == null || coilList.isEmpty()) { + throw new RuntimeException("未找到符合条件的钢卷数据"); + } + + // 2. 收集所有钢卷ID + List coilIds = coilList.stream() + .map(WmsMaterialCoilVo::getCoilId) + .distinct() + .collect(Collectors.toList()); + + // 3. 批量查询异常信息 + Map> abnormalMap = new HashMap<>(); + if (!coilIds.isEmpty()) { + LambdaQueryWrapper abnormalQuery = new LambdaQueryWrapper<>(); + abnormalQuery.in(WmsCoilAbnormal::getCoilId, coilIds); + abnormalQuery.eq(WmsCoilAbnormal::getDelFlag, 0); + List allAbnormals = coilAbnormalMapper.selectList(abnormalQuery); + if (allAbnormals != null && !allAbnormals.isEmpty()) { + abnormalMap = allAbnormals.stream() + .collect(Collectors.groupingBy(WmsCoilAbnormal::getCoilId)); + } + } + + // 4. 批量查询改判原因(最新的一条) + Map rejudgeReasonMap = new HashMap<>(); + if (!coilIds.isEmpty()) { + LambdaQueryWrapper rejudgeQuery = new LambdaQueryWrapper<>(); + rejudgeQuery.in(WmsCoilQualityRejudge::getCoilId, coilIds); + rejudgeQuery.eq(WmsCoilQualityRejudge::getDelFlag, "0"); + rejudgeQuery.orderByDesc(WmsCoilQualityRejudge::getCreateTime); + List allRejudges = wmsCoilQualityRejudgeMapper.selectList(rejudgeQuery); + + if (allRejudges != null && !allRejudges.isEmpty()) { + // 按钢卷ID分组,每组取最新的一条 + Map> rejudgeGroupMap = allRejudges.stream() + .collect(Collectors.groupingBy(WmsCoilQualityRejudge::getCoilId)); + + for (Map.Entry> entry : rejudgeGroupMap.entrySet()) { + List rejudges = entry.getValue(); + if (!rejudges.isEmpty()) { + // 由于已按创建时间降序排列,第一条就是最新的 + WmsCoilQualityRejudge latestRejudge = rejudges.get(0); + if (latestRejudge.getRejudgeReason() != null) { + rejudgeReasonMap.put(entry.getKey(), latestRejudge.getRejudgeReason()); + } + } + } + } + } + + // 5. 构建导出数据(扁平化:一个钢卷+一个异常 = 一行) + List exportData = new ArrayList<>(); + for (WmsMaterialCoilVo coil : coilList) { + Long coilId = coil.getCoilId(); + List abnormalList = abnormalMap.getOrDefault(coilId, new ArrayList<>()); + String rejudgeReason = rejudgeReasonMap.get(coilId); + + if (abnormalList.isEmpty()) { + WmsCoilAbnormalExportRow row = new WmsCoilAbnormalExportRow(); + row.setCoil(coil); + row.setRejudgeReason(rejudgeReason); + row.setAbnormal(null); + exportData.add(row); + } else { + for (WmsCoilAbnormal abnormal : abnormalList) { + WmsCoilAbnormalExportRow row = new WmsCoilAbnormalExportRow(); + row.setCoil(coil); + row.setRejudgeReason(rejudgeReason); + row.setAbnormal(abnormal); + exportData.add(row); + } + } + } + + // 6. 导出Excel + try (Workbook wb = new XSSFWorkbook()) { + Sheet sheet = wb.createSheet("异常报表"); + int r = 0; + + // 标题行 + Row titleRow = sheet.createRow(r++); + titleRow.setHeightInPoints(36f); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellValue("钢卷异常报表"); + + CellStyle titleStyle = wb.createCellStyle(); + titleStyle.setAlignment(HorizontalAlignment.CENTER); + titleStyle.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setBold(true); + titleFont.setFontHeightInPoints((short) 15); + titleStyle.setFont(titleFont); + + CellRangeAddress titleRegion = new CellRangeAddress(0, 0, 0, 41); + sheet.addMergedRegion(titleRegion); + for (int i = titleRegion.getFirstColumn(); i <= titleRegion.getLastColumn(); i++) { + Cell cell = titleRow.getCell(i); + if (cell == null) cell = titleRow.createCell(i); + cell.setCellStyle(titleStyle); + } + + // 表头 + String[] headers = { + "类型", "逻辑库区", "实际库区", "入场卷号", "厂家卷号", "成品卷号", "日期", + "重量", "用途", "切边要求", "包装种类", "产品质量", "原料材质", + "库存状态", "备注", "名称", "规格", "长度", "材质", "厂家", + "表面处理", "锌层", "物品ID", "操作完成时间", "调拨类型", + "改判原因", + "产线", "位置", "长度坐标", "缺陷开始位置", "缺陷结束位置", + "缺陷代码", "缺陷类型", "缺陷率", "缺陷重量", "程度", "判级", + "判级人", "判级时间", "主标记", "整卷标记", "异常备注", "板面" + }; + + CellStyle headStyle = wb.createCellStyle(); + Font headFont = wb.createFont(); + headFont.setBold(true); + headStyle.setFont(headFont); + headStyle.setAlignment(HorizontalAlignment.CENTER); + headStyle.setVerticalAlignment(VerticalAlignment.CENTER); + headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + headStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex()); + headStyle.setWrapText(true); + + Row headRow = sheet.createRow(r++); + headRow.setHeightInPoints(24); + for (int i = 0; i < headers.length; i++) { + Cell cell = headRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headStyle); + } + + // 数据行样式 + CellStyle centerStyle = wb.createCellStyle(); + centerStyle.setAlignment(HorizontalAlignment.CENTER); + centerStyle.setVerticalAlignment(VerticalAlignment.CENTER); + + // 填充数据 + int dataStartRow = r; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + for (WmsCoilAbnormalExportRow rowData : exportData) { + WmsMaterialCoilVo coil = rowData.getCoil(); + WmsCoilAbnormal abnormal = rowData.getAbnormal(); + String rejudgeReason = rowData.getRejudgeReason(); + + Row row = sheet.createRow(r++); + int cc = 0; + + // 钢卷信息(前25列) + row.createCell(cc++).setCellValue(coil.getItemType() != null ? coil.getItemType() : ""); + row.createCell(cc++).setCellValue(coil.getWarehouseName() != null ? coil.getWarehouseName() : ""); + row.createCell(cc++).setCellValue(coil.getActualWarehouseName() != null ? coil.getActualWarehouseName() : ""); + row.createCell(cc++).setCellValue(coil.getEnterCoilNo() != null ? coil.getEnterCoilNo() : ""); + row.createCell(cc++).setCellValue(coil.getSupplierCoilNo() != null ? coil.getSupplierCoilNo() : ""); + row.createCell(cc++).setCellValue(coil.getCurrentCoilNo() != null ? coil.getCurrentCoilNo() : ""); + row.createCell(cc++).setCellValue(coil.getCreateTime() != null ? sdf.format(coil.getCreateTime()) : ""); + row.createCell(cc++).setCellValue(coil.getNetWeight() != null ? coil.getNetWeight().toString() : ""); + row.createCell(cc++).setCellValue(coil.getBusinessPurpose() != null ? coil.getBusinessPurpose() : ""); + row.createCell(cc++).setCellValue(coil.getTrimmingRequirement() != null ? coil.getTrimmingRequirement() : ""); + row.createCell(cc++).setCellValue(coil.getPackagingRequirement() != null ? coil.getPackagingRequirement() : ""); + row.createCell(cc++).setCellValue(coil.getQualityStatus() != null ? coil.getQualityStatus() : ""); + row.createCell(cc++).setCellValue(coil.getPackingStatus() != null ? coil.getPackingStatus() : ""); + row.createCell(cc++).setCellValue(coil.getStatus() != null ? coil.getStatus().toString() : ""); + row.createCell(cc++).setCellValue(coil.getRemark() != null ? coil.getRemark() : ""); + row.createCell(cc++).setCellValue(coil.getItemName() != null ? coil.getItemName() : ""); + row.createCell(cc++).setCellValue(coil.getSpecification() != null ? coil.getSpecification() : ""); + row.createCell(cc++).setCellValue(coil.getLength() != null ? coil.getLength().toString() : ""); + row.createCell(cc++).setCellValue(coil.getMaterial() != null ? coil.getMaterial() : ""); + row.createCell(cc++).setCellValue(coil.getManufacturer() != null ? coil.getManufacturer() : ""); + row.createCell(cc++).setCellValue(coil.getSurfaceTreatmentDesc() != null ? coil.getSurfaceTreatmentDesc() : ""); + row.createCell(cc++).setCellValue(coil.getZincLayer() != null ? coil.getZincLayer() : ""); + row.createCell(cc++).setCellValue(coil.getItemId() != null ? coil.getItemId().toString() : ""); + row.createCell(cc++).setCellValue(coil.getActionCompleteTime() != null ? sdf.format(coil.getActionCompleteTime()) : ""); + row.createCell(cc++).setCellValue(coil.getTransferType() != null ? coil.getTransferType() : ""); + + // 改判原因 + row.createCell(cc++).setCellValue(rejudgeReason != null ? rejudgeReason : ""); + + // 异常信息 + if (abnormal != null) { + row.createCell(cc++).setCellValue(abnormal.getProductionLine() != null ? abnormal.getProductionLine() : ""); + row.createCell(cc++).setCellValue(abnormal.getPosition() != null ? abnormal.getPosition() : ""); + row.createCell(cc++).setCellValue(abnormal.getLength() != null ? abnormal.getLength().toString() : ""); + row.createCell(cc++).setCellValue(abnormal.getStartPosition() != null ? abnormal.getStartPosition().toString() : ""); + row.createCell(cc++).setCellValue(abnormal.getEndPosition() != null ? abnormal.getEndPosition().toString() : ""); + row.createCell(cc++).setCellValue(abnormal.getDefectCode() != null ? abnormal.getDefectCode() : ""); + row.createCell(cc++).setCellValue(abnormal.getDefectType() != null ? abnormal.getDefectType() : ""); + row.createCell(cc++).setCellValue(abnormal.getDefectRate() != null ? abnormal.getDefectRate().toString() : ""); + row.createCell(cc++).setCellValue(abnormal.getDefectWeight() != null ? abnormal.getDefectWeight().toString() : ""); + row.createCell(cc++).setCellValue(abnormal.getDegree() != null ? abnormal.getDegree() : ""); + row.createCell(cc++).setCellValue(abnormal.getJudgeLevel() != null ? abnormal.getJudgeLevel() : ""); + row.createCell(cc++).setCellValue(abnormal.getJudgeBy() != null ? abnormal.getJudgeBy() : ""); + row.createCell(cc++).setCellValue(abnormal.getJudgeTime() != null ? sdf.format(abnormal.getJudgeTime()) : ""); + row.createCell(cc++).setCellValue(abnormal.getMainMark() != null ? abnormal.getMainMark().toString() : ""); + row.createCell(cc++).setCellValue(abnormal.getWholeCoilMark() != null ? abnormal.getWholeCoilMark().toString() : ""); + row.createCell(cc++).setCellValue(abnormal.getRemark() != null ? abnormal.getRemark() : ""); + row.createCell(cc++).setCellValue(abnormal.getPlateSurface() != null ? abnormal.getPlateSurface() : ""); + } else { + for (int j = 0; j < 17; j++) row.createCell(cc++).setCellValue(""); + } + } + + // 合并钢卷信息列(前26列:25列钢卷信息 + 1列改判原因) + int currentRow = dataStartRow; + while (currentRow < r) { + Long currentCoilId = exportData.get(currentRow - dataStartRow).getCoil().getCoilId(); + int startRow = currentRow; + int endRow = currentRow; + + while (endRow < r && exportData.get(endRow - dataStartRow).getCoil().getCoilId().equals(currentCoilId)) { + endRow++; + } + + if (endRow - startRow > 1) { + for (int col = 0; col < 26; col++) { + sheet.addMergedRegion(new CellRangeAddress(startRow, endRow - 1, col, col)); + } + } + + // 设置居中样式 + for (int rowIdx = startRow; rowIdx < endRow; rowIdx++) { + Row row = sheet.getRow(rowIdx); + if (row != null) { + for (int col = 0; col < 26; col++) { + Cell cell = row.getCell(col); + if (cell != null) cell.setCellStyle(centerStyle); + } + } + } + + currentRow = endRow; + } + + // 自适应列宽 + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i, true); + int w = sheet.getColumnWidth(i); + sheet.setColumnWidth(i, Math.min(Math.max(w, 3000), 12000)); + } + + // 输出 + String filename = "abnormal_report_" + System.currentTimeMillis() + ".xlsx"; + String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8.name()); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encoded); + + try (ServletOutputStream os = response.getOutputStream()) { + wb.write(os); + os.flush(); + } + } catch (IOException e) { + throw new RuntimeException("导出失败:" + e.getMessage()); + } + } + + }