|
|
|
@ -60,8 +60,11 @@ import java.util.Objects; |
|
|
|
import java.util.Set; |
|
|
|
|
|
|
|
import org.apache.poi.ss.usermodel.FillPatternType; |
|
|
|
import org.apache.poi.ss.usermodel.IndexedColors; |
|
|
|
import org.apache.poi.ss.usermodel.Workbook; |
|
|
|
import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap; |
|
|
|
import org.apache.poi.xssf.usermodel.XSSFCellStyle; |
|
|
|
import org.apache.poi.xssf.usermodel.XSSFColor; |
|
|
|
import org.apache.poi.xssf.usermodel.XSSFFont; |
|
|
|
|
|
|
|
/** |
|
|
|
* 产品打样记录跟踪 Service 实现 |
|
|
|
@ -157,6 +160,8 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic |
|
|
|
buProcessConfigMap, |
|
|
|
exportHeaderRowCount |
|
|
|
); |
|
|
|
Map<Long, ExportCellAppearance> cellAppearances = collectExportCellAppearances( |
|
|
|
exportRows, trackingColumns, bodyRows, completedProcessCellKeySet, exportHeaderRowCount); |
|
|
|
try { |
|
|
|
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); |
|
|
|
String fileName = URLEncoder.encode("项目打样跟踪_" + timestamp, StandardCharsets.UTF_8.name()) |
|
|
|
@ -193,9 +198,8 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic |
|
|
|
.autoCloseStream(false) |
|
|
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) |
|
|
|
.registerWriteHandler(noHeaderBackgroundStyle); |
|
|
|
if (!completedProcessCellKeySet.isEmpty()) { |
|
|
|
// 已完成工序单元格灰底高亮,导出视觉与前端表格“完成态”保持一致。 |
|
|
|
writerBuilder.registerWriteHandler(new CompletedProcessCellStyleHandler(completedProcessCellKeySet)); |
|
|
|
if (!cellAppearances.isEmpty()) { |
|
|
|
writerBuilder.registerWriteHandler(new ProofTrackingCellStyleHandler(cellAppearances)); |
|
|
|
} |
|
|
|
writerBuilder |
|
|
|
.head(headerRows) |
|
|
|
@ -398,6 +402,67 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic |
|
|
|
return rows; |
|
|
|
} |
|
|
|
|
|
|
|
private Map<Long, ExportCellAppearance> collectExportCellAppearances( |
|
|
|
List<ProofTrackingData> exportRows, |
|
|
|
List<ProofTrackingExportColumnData> trackingColumns, |
|
|
|
List<List<String>> bodyRows, |
|
|
|
Set<Long> completedProcessCellKeySet, |
|
|
|
int headerRowCount) { |
|
|
|
Map<Long, ExportCellAppearance> appearances = new HashMap<>(); |
|
|
|
for (Long cellKey : completedProcessCellKeySet) { |
|
|
|
appearances.put(cellKey, ExportCellAppearance.COMPLETED_PROCESS); |
|
|
|
} |
|
|
|
for (int rowIndex = 0; rowIndex < exportRows.size(); rowIndex++) { |
|
|
|
ProofTrackingData row = exportRows.get(rowIndex); |
|
|
|
if (row == null) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
for (int columnIndex = 0; columnIndex < trackingColumns.size(); columnIndex++) { |
|
|
|
String columnProp = trackingColumns.get(columnIndex).getColumnProp(); |
|
|
|
String displayValue = bodyRows.get(rowIndex).get(columnIndex); |
|
|
|
ExportCellAppearance appearance = resolveExportCellAppearance(row, columnProp, displayValue); |
|
|
|
if (appearance != null) { |
|
|
|
appearances.put(buildExportCellCoordinateKey(headerRowCount + rowIndex, columnIndex), appearance); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return appearances; |
|
|
|
} |
|
|
|
|
|
|
|
private ExportCellAppearance resolveExportCellAppearance(ProofTrackingData row, |
|
|
|
String columnProp, |
|
|
|
String displayValue) { |
|
|
|
// 与 projectProofTracking.vue 的链接、状态和预警样式保持一致,不给未同步的编码着色。 |
|
|
|
switch (columnProp) { |
|
|
|
case "projectNo": |
|
|
|
return !isBlank(displayValue) && isNpiAlignedId(row.getProjectId()) |
|
|
|
? ExportCellAppearance.NPI_LINK : null; |
|
|
|
case "testPartNo": |
|
|
|
return !isBlank(displayValue) && isNpiAlignedId(row.getProjectPartId()) |
|
|
|
? ExportCellAppearance.NPI_LINK : null; |
|
|
|
case "proofingStatus": |
|
|
|
return TRACKING_PROOFING_STATUS_FINISHED.equals(row.getProofingStatus()) |
|
|
|
? ExportCellAppearance.PROOF_FINISHED : ExportCellAppearance.PROOF_IN_PROGRESS; |
|
|
|
case "trackingStatus": |
|
|
|
// 复用已生成的导出文案,避免颜色与日期回退、完成态优先级的判断不一致。 |
|
|
|
if ("Complete".equals(displayValue)) { |
|
|
|
return ExportCellAppearance.TRACKING_COMPLETE; |
|
|
|
} |
|
|
|
if ("Trial prepare".equals(displayValue) || (displayValue != null && displayValue.startsWith("Delay-"))) { |
|
|
|
return ExportCellAppearance.TRACKING_DELAY; |
|
|
|
} |
|
|
|
if (displayValue != null && displayValue.startsWith("On schedule-")) { |
|
|
|
return ExportCellAppearance.TRACKING_ON_SCHEDULE; |
|
|
|
} |
|
|
|
return ExportCellAppearance.TRACKING_UNKNOWN; |
|
|
|
case "deliveryVariance": |
|
|
|
return row.getDeliveryVariance() != null && row.getDeliveryVariance() < -7 |
|
|
|
? ExportCellAppearance.DELIVERY_ALERT : null; |
|
|
|
default: |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static long buildExportCellCoordinateKey(int rowIndex, int columnIndex) { |
|
|
|
return (((long) rowIndex) << 32) | (columnIndex & 0xffffffffL); |
|
|
|
} |
|
|
|
@ -457,11 +522,15 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic |
|
|
|
if (rowData == null) { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
String packageDate = resolveTrackingPackageDateForExport(rowData); |
|
|
|
// 与页面一致:打样单号为空且 Delivery&Package 有值时,才显示准备阶段。 |
|
|
|
if (isBlank(rowData.getProofingNo()) && !isBlank(packageDate)) { |
|
|
|
return "Trial prepare"; |
|
|
|
} |
|
|
|
// 业务口径:打样状态为“打样完成”时,Tracking Status 固定展示 Complete,不再展示 Delay/On schedule。 |
|
|
|
if (isTrackingProofingCompleted(rowData.getProofingStatus())) { |
|
|
|
return "Complete"; |
|
|
|
} |
|
|
|
String packageDate = resolveTrackingPackageDateForExport(rowData); |
|
|
|
String baseline = toDateString(rowData.getBaseline()); |
|
|
|
if (isBlank(packageDate) || isBlank(baseline)) { |
|
|
|
return ""; |
|
|
|
@ -2987,17 +3056,42 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic |
|
|
|
data.setProofingStatusList(normalizedStatusList.isEmpty() ? null : normalizedStatusList); |
|
|
|
} |
|
|
|
|
|
|
|
private static class CompletedProcessCellStyleHandler implements CellWriteHandler { |
|
|
|
/** 页面使用的 RGB 色值;不使用近似的 Excel 索引色。 */ |
|
|
|
private enum ExportCellAppearance { |
|
|
|
NPI_LINK(null, "0C4DBB", false), |
|
|
|
PROOF_FINISHED(null, "67C23A", false), |
|
|
|
PROOF_IN_PROGRESS(null, "046E97", false), |
|
|
|
TRACKING_ON_SCHEDULE("E8F5E9", "303133", false), |
|
|
|
TRACKING_DELAY("FDE2E2", "303133", false), |
|
|
|
TRACKING_UNKNOWN("F4F4F5", "303133", false), |
|
|
|
TRACKING_COMPLETE("F4F4F5", "909399", false), |
|
|
|
DELIVERY_ALERT(null, "F56C6C", true), |
|
|
|
COMPLETED_PROCESS("ECECEC", "8C8C8C", false); |
|
|
|
|
|
|
|
private final String background; |
|
|
|
private final String foreground; |
|
|
|
private final boolean bold; |
|
|
|
|
|
|
|
ExportCellAppearance(String background, String foreground, boolean bold) { |
|
|
|
this.background = background; |
|
|
|
this.foreground = foreground; |
|
|
|
this.bold = bold; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static class ProofTrackingCellStyleHandler implements CellWriteHandler { |
|
|
|
|
|
|
|
private final Set<Long> completedCellKeySet; |
|
|
|
private final Map<Integer, CellStyle> completedStyleCache = new HashMap<>(); |
|
|
|
private final Map<Long, ExportCellAppearance> cellAppearances; |
|
|
|
private final Map<String, CellStyle> styleCache = new HashMap<>(); |
|
|
|
private final Map<String, XSSFFont> fontCache = new HashMap<>(); |
|
|
|
|
|
|
|
private CompletedProcessCellStyleHandler(Set<Long> completedCellKeySet) { |
|
|
|
this.completedCellKeySet = completedCellKeySet == null ? new LinkedHashSet<>() : completedCellKeySet; |
|
|
|
private ProofTrackingCellStyleHandler(Map<Long, ExportCellAppearance> cellAppearances) { |
|
|
|
this.cellAppearances = cellAppearances; |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public int order() { |
|
|
|
// 晚于默认样式策略执行,避免灰底被后续样式覆盖。 |
|
|
|
// 晚于默认样式策略执行,避免颜色被后续样式覆盖。 |
|
|
|
return Integer.MAX_VALUE; |
|
|
|
} |
|
|
|
|
|
|
|
@ -3009,28 +3103,49 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic |
|
|
|
Head head, |
|
|
|
Integer relativeRowIndex, |
|
|
|
Boolean isHead) { |
|
|
|
if (cell == null || Boolean.TRUE.equals(isHead) || completedCellKeySet.isEmpty()) { |
|
|
|
if (cell == null || Boolean.TRUE.equals(isHead)) { |
|
|
|
return; |
|
|
|
} |
|
|
|
long cellKey = buildExportCellCoordinateKey(cell.getRowIndex(), cell.getColumnIndex()); |
|
|
|
if (!completedCellKeySet.contains(cellKey)) { |
|
|
|
ExportCellAppearance appearance = cellAppearances.get( |
|
|
|
buildExportCellCoordinateKey(cell.getRowIndex(), cell.getColumnIndex())); |
|
|
|
if (appearance == null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
CellStyle sourceStyle = cell.getCellStyle(); |
|
|
|
Workbook workbook = cell.getSheet().getWorkbook(); |
|
|
|
int sourceStyleKey = sourceStyle == null ? -1 : sourceStyle.getIndex(); |
|
|
|
CellStyle completedStyle = completedStyleCache.get(sourceStyleKey); |
|
|
|
if (completedStyle == null) { |
|
|
|
completedStyle = workbook.createCellStyle(); |
|
|
|
if (sourceStyle != null) { |
|
|
|
completedStyle.cloneStyleFrom(sourceStyle); |
|
|
|
String styleKey = sourceStyle.getIndex() + ":" + appearance.name(); |
|
|
|
CellStyle styledCell = styleCache.get(styleKey); |
|
|
|
if (styledCell == null) { |
|
|
|
// 导出固定为 xlsx;流式 SXSSF 工作簿也使用 XSSF 样式和字体。 |
|
|
|
XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle(); |
|
|
|
style.cloneStyleFrom(sourceStyle); |
|
|
|
if (appearance.background != null) { |
|
|
|
style.setFillPattern(FillPatternType.SOLID_FOREGROUND); |
|
|
|
style.setFillForegroundColor(toExcelColor(appearance.background)); |
|
|
|
} |
|
|
|
completedStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); |
|
|
|
completedStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); |
|
|
|
completedStyle.setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); |
|
|
|
completedStyleCache.put(sourceStyleKey, completedStyle); |
|
|
|
String fontKey = sourceStyle.getFontIndex() + ":" + appearance.foreground + ":" + appearance.bold; |
|
|
|
XSSFFont font = fontCache.get(fontKey); |
|
|
|
if (font == null) { |
|
|
|
XSSFFont sourceFont = (XSSFFont) workbook.getFontAt(sourceStyle.getFontIndex()); |
|
|
|
font = (XSSFFont) workbook.createFont(); |
|
|
|
font.getCTFont().set(sourceFont.getCTFont()); |
|
|
|
font.setColor(toExcelColor(appearance.foreground)); |
|
|
|
if (appearance.bold) { |
|
|
|
font.setBold(true); |
|
|
|
} |
|
|
|
fontCache.put(fontKey, font); |
|
|
|
} |
|
|
|
style.setFont(font); |
|
|
|
styledCell = style; |
|
|
|
styleCache.put(styleKey, styledCell); |
|
|
|
} |
|
|
|
cell.setCellStyle(completedStyle); |
|
|
|
cell.setCellStyle(styledCell); |
|
|
|
} |
|
|
|
|
|
|
|
private static XSSFColor toExcelColor(String rgb) { |
|
|
|
int color = Integer.parseInt(rgb, 16); |
|
|
|
return new XSSFColor(new byte[]{(byte) (color >> 16), (byte) (color >> 8), (byte) color}, |
|
|
|
new DefaultIndexedColorMap()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|