Procházet zdrojové kódy

ItemQCFailReport Excel Version

master
B.E.N.S.O.N před 3 dny
rodič
revize
ea3a56dfe4
1 změnil soubory, kde provedl 279 přidání a 3 odebrání
  1. +279
    -3
      src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt

+ 279
- 3
src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt Zobrazit soubor

@@ -1,13 +1,22 @@
package com.ffii.fpsms.modules.report.web

import net.sf.jasperreports.engine.*
import org.springframework.http.*
import org.springframework.web.bind.annotation.*
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.math.BigDecimal
import com.ffii.fpsms.modules.report.service.ItemQcFailReportService
import com.ffii.fpsms.modules.report.service.ReportService
import org.apache.poi.ss.usermodel.BorderStyle
import org.apache.poi.ss.usermodel.FillPatternType
import org.apache.poi.ss.usermodel.HorizontalAlignment
import org.apache.poi.ss.usermodel.IndexedColors
import org.apache.poi.ss.usermodel.VerticalAlignment
import org.apache.poi.ss.usermodel.DataFormat
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.ss.util.WorkbookUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook

@RestController
@RequestMapping("/report")
@@ -27,13 +36,12 @@ class ItemQcFailReportController(
val parameters = mutableMapOf<String, Any>()

parameters["stockCategory"] = stockCategory ?: "All"
parameters["stockSubCategory"] = stockCategory ?: "All" // 你定义 stock sub category = items.type
parameters["stockSubCategory"] = stockCategory ?: "All"
parameters["itemNo"] = itemCode ?: "All"
parameters["year"] = java.time.LocalDate.now().year.toString()
parameters["reportDate"] = java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"))
parameters["reportTime"] = java.time.LocalTime.now().format(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss"))

// jrxml 里有这些参数,先给空
parameters["storeLocation"] = ""
parameters["balanceFilterStart"] = ""
parameters["balanceFilterEnd"] = ""
@@ -62,6 +70,274 @@ class ItemQcFailReportController(
}
return org.springframework.http.ResponseEntity(pdfBytes, headers, org.springframework.http.HttpStatus.OK)
}

@GetMapping("/print-item-qc-fail-excel")
fun exportItemQcFailReportExcel(
@RequestParam(required = false) stockCategory: String?,
@RequestParam(required = false) itemCode: String?,
@RequestParam(required = false) lastInDateStart: String?,
@RequestParam(required = false) lastInDateEnd: String?,
): ResponseEntity<ByteArray> {
val dbData = itemQcFailReportService.searchItemQcFailReport(
stockCategory = stockCategory,
itemCode = itemCode,
lastInDateStart = lastInDateStart,
lastInDateEnd = lastInDateEnd,
)

val reportTitle = "庫存品質檢測報告"
val excelBytes = createItemQcFailExcel(
dbData = dbData,
reportTitle = reportTitle,
lastInDateStart = lastInDateStart,
lastInDateEnd = lastInDateEnd
)

val headers = org.springframework.http.HttpHeaders().apply {
contentType = org.springframework.http.MediaType.parseMediaType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
setContentDispositionFormData("attachment", "ItemQCFailReport.xlsx")
set("filename", "ItemQCFailReport.xlsx")
}
return org.springframework.http.ResponseEntity(excelBytes, headers, org.springframework.http.HttpStatus.OK)
}

private fun createItemQcFailExcel(
dbData: List<Map<String, Any>>,
reportTitle: String,
lastInDateStart: String?,
lastInDateEnd: String?
): ByteArray {
val workbook: Workbook = XSSFWorkbook()
val safeSheetName = WorkbookUtil.createSafeSheetName(reportTitle)
val sheet = workbook.createSheet(safeSheetName)

var rowIndex = 0
val columnCount = 14

val titleStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.CENTER
verticalAlignment = VerticalAlignment.CENTER
}
val titleFont = workbook.createFont().apply {
bold = true
fontHeightInPoints = 16
}
titleStyle.setFont(titleFont)

// Title row
run {
val titleRowIndex = rowIndex++
val row = sheet.createRow(titleRowIndex)
val cell = row.createCell(0)
cell.setCellValue(reportTitle)
cell.cellStyle = titleStyle
// Merge title across columns so the text won't be blocked by narrow col A.
sheet.addMergedRegion(
org.apache.poi.ss.util.CellRangeAddress(
titleRowIndex,
titleRowIndex,
0,
columnCount - 1
)
)
row.heightInPoints = 24f
}

// Info row
run {
val row = sheet.createRow(rowIndex++)
val cell = row.createCell(0)
val startTxt = lastInDateStart ?: ""
val endTxt = lastInDateEnd ?: ""
cell.setCellValue("QC 不合格日期: ${startTxt} - ${endTxt}")
}

val headerStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.CENTER
verticalAlignment = VerticalAlignment.TOP
wrapText = true
fillForegroundColor = IndexedColors.GREY_25_PERCENT.index
fillPattern = FillPatternType.SOLID_FOREGROUND
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THIN
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
}
val headerFont = workbook.createFont().apply { bold = true }
headerStyle.setFont(headerFont)

val textStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.LEFT
verticalAlignment = VerticalAlignment.CENTER
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THIN
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
}

val wrappedTextStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.LEFT
verticalAlignment = VerticalAlignment.TOP
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THIN
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
wrapText = true
}

val integerNumberStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.RIGHT
verticalAlignment = VerticalAlignment.CENTER
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THIN
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
val df = workbook.creationHelper.createDataFormat()
dataFormat = df.getFormat("#,##0")
}

val decimalNumberStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.RIGHT
verticalAlignment = VerticalAlignment.CENTER
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THIN
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
val df = workbook.creationHelper.createDataFormat()
dataFormat = df.getFormat("#,##0.##")
}

val headers = listOf(
"庫存類別",
"物料編號",
"物料名稱",
"單位",
"批號",
"到期日",
"QC 類型",
"QC 範本",
"不合格標準",
"批量",
"不合格數量",
"參考資料",
"備註",
"訂單/工單"
)

// Header row
run {
val row = sheet.createRow(rowIndex++)
headers.forEachIndexed { col, h ->
val cell = row.createCell(col)
cell.setCellValue(h)
cell.cellStyle = headerStyle
}
// Increase header row height so wrapped header text won't be blocked/truncated.
row.heightInPoints = 35f
}

dbData.forEach { r ->
val row = sheet.createRow(rowIndex++)
fun writeText(col: Int, value: Any?) {
val cell = row.createCell(col)
cell.setCellValue(value?.toString() ?: "")
cell.cellStyle = textStyle
}

fun writeNumber(col: Int, value: Any?) {
val raw = value?.toString()?.trim() ?: ""
// Some SQL strings may end with "."; Excel would display it as "4." otherwise.
val cleaned = raw.removeSuffix(".")
val bd = cleaned.toBigDecimalOrNull()
val cell = row.createCell(col)

if (bd == null) {
cell.setCellValue(cleaned)
cell.cellStyle = textStyle
} else {
val stripped = bd.stripTrailingZeros()
if (stripped.scale() <= 0) {
// Render integer without decimal dot.
cell.setCellValue(stripped.toDouble())
cell.cellStyle = integerNumberStyle
} else {
cell.setCellValue(stripped.toDouble())
cell.cellStyle = decimalNumberStyle
}
}
}

// Keys must match ItemQcFailReportService SQL aliases
writeText(0, r["stockSubCategory"])
writeText(1, r["itemNo"])
writeText(2, r["itemName"])
writeText(3, r["unitOfMeasure"])
writeText(4, r["lotNo"])
writeText(5, r["expiryDate"])
writeText(6, r["qcType"])
writeText(7, r["qcTemplate"])
val defectCriteria = r["qcDefectCriteria"]
// Wrap this column because values can be long.
run {
val cell = row.createCell(8)
cell.setCellValue(defectCriteria?.toString() ?: "")
cell.cellStyle = wrappedTextStyle
}
writeNumber(9, r["lotQty"])
writeNumber(10, r["defectQty"])
writeText(11, r["refData"])
writeText(12, r["remark"])
writeText(13, r["orderRefNo"])

// If "不合格標準" is long, increase row height so wrapped text is visible.
val defectText = defectCriteria?.toString() ?: ""
// Estimate wrap lines using the known column width (approx chars per line).
// This avoids ugly/uneven row heights from simple length thresholds.
val charsPerLine = 38.0
val lines = maxOf(
1,
kotlin.math.ceil(defectText.length / charsPerLine).toInt()
)
val baseHeight = 18f // single-line
val lineHeight = 13.5f // additional lines
row.heightInPoints = (baseHeight + (lines - 1) * lineHeight).coerceIn(18f, 110f)
}

// Column widths: keep reasonable defaults, then auto-size a bit (optional)
run {
// Set explicit column widths (Excel units = 1/256th of a character width).
val widths = intArrayOf(
12, // stockSubCategory
16, // itemNo
18, // itemName
10, // unit
14, // lotNo
12, // expiryDate
10, // qcType
20, // qcTemplate
42, // qcDefectCriteria (wrap)
12, // lotQty
14, // defectQty
22, // refData
16, // remark
18, // orderRefNo
)
for (col in 0 until widths.size) {
sheet.setColumnWidth(col, widths[col] * 256)
}
}

return workbookToByteArray(workbook)
}

private fun workbookToByteArray(workbook: Workbook): ByteArray {
val out = java.io.ByteArrayOutputStream()
workbook.write(out)
workbook.close()
return out.toByteArray()
}
}



Načítá se…
Zrušit
Uložit