Sfoglia il codice sorgente

MaterialStockOutTraceabilityReport Excel Version

master
B.E.N.S.O.N 13 ore fa
parent
commit
f8085826cb
1 ha cambiato i file con 306 aggiunte e 0 eliminazioni
  1. +306
    -0
      src/main/java/com/ffii/fpsms/modules/report/web/MaterialStockOutTraceabilityReportController.kt

+ 306
- 0
src/main/java/com/ffii/fpsms/modules/report/web/MaterialStockOutTraceabilityReportController.kt Vedi File

@@ -10,9 +10,21 @@ import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.apache.poi.ss.usermodel.BorderStyle
import org.apache.poi.ss.usermodel.CellStyle
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.Row
import org.apache.poi.ss.usermodel.VerticalAlignment
import org.apache.poi.ss.usermodel.DataFormat
import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.ss.util.WorkbookUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.io.ByteArrayOutputStream

@RestController
@RequestMapping("/report")
@@ -72,5 +84,299 @@ class MaterialStockOutTraceabilityReportController(

return ResponseEntity(pdfBytes, headers, HttpStatus.OK)
}

@GetMapping("/print-material-stock-out-traceability-excel")
fun exportMaterialStockOutTraceabilityReportExcel(
@RequestParam(required = false) stockCategory: String?,
@RequestParam(required = false) stockSubCategory: String?,
@RequestParam(required = false) itemCode: String?,
@RequestParam(required = false) year: String?,
@RequestParam(required = false) lastOutDateStart: String?,
@RequestParam(required = false) lastOutDateEnd: String?,
@RequestParam(required = false) handler: String?,
): ResponseEntity<ByteArray> {
val parameters = mutableMapOf<String, Any>()

// Set report header parameters (keep consistent with PDF endpoint)
parameters["stockCategory"] = stockCategory ?: "All"
parameters["stockSubCategory"] = stockSubCategory ?: "All"
parameters["itemNo"] = itemCode ?: "All"
parameters["year"] = year ?: LocalDate.now().year.toString()
parameters["reportDate"] = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
parameters["reportTime"] = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
parameters["lastOutDateStart"] = lastOutDateStart ?: ""
parameters["lastOutDateEnd"] = lastOutDateEnd ?: ""
parameters["deliveryPeriodStart"] = ""
parameters["deliveryPeriodEnd"] = ""

val dbData = materialStockOutTraceabilityReportService.searchMaterialStockOutTraceabilityReport(
stockCategory,
stockSubCategory,
itemCode,
year,
lastOutDateStart,
lastOutDateEnd,
handler,
)

val excelBytes = createMaterialStockOutTraceabilityExcel(
dbData = dbData,
year = year,
lastOutDateStart = lastOutDateStart,
lastOutDateEnd = lastOutDateEnd,
)

val headers = HttpHeaders().apply {
contentType = MediaType.parseMediaType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
setContentDispositionFormData(
"attachment",
"MaterialStockOutTraceabilityReport.xlsx",
)
set("filename", "MaterialStockOutTraceabilityReport.xlsx")
}

return ResponseEntity(excelBytes, headers, HttpStatus.OK)
}

private fun createMaterialStockOutTraceabilityExcel(
dbData: List<Map<String, Any>>,
year: String?,
lastOutDateStart: String?,
lastOutDateEnd: String?,
): ByteArray {
val workbook = XSSFWorkbook()
val reportTitle = "物料出倉追蹤報告"
val safeSheetName = WorkbookUtil.createSafeSheetName(reportTitle)
val sheet = workbook.createSheet(safeSheetName)

val totalColumns = 11
var rowIndex = 0

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

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

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

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

val headers = listOf(
"貨品編號",
"貨品名稱",
"單位",
"工單編號",
"提料單號",
"批號",
"到期日",
"出貨數量",
"提料人",
"存貨位置",
"提料備註",
)

// Header row
val headerRow = sheet.createRow(rowIndex++)
headers.forEachIndexed { i, h ->
val cell = headerRow.createCell(i)
cell.setCellValue(h)
cell.cellStyle = headerStyle
}

val summaryQtyThickBottomStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.RIGHT
verticalAlignment = VerticalAlignment.CENTER
val df: DataFormat = workbook.createDataFormat()
dataFormat = df.getFormat("#,##0")
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THICK
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
val font = workbook.createFont().apply { bold = true }
setFont(font)
}

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

val summaryLabelInExpiryColStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.RIGHT
verticalAlignment = VerticalAlignment.CENTER
val font = workbook.createFont().apply { bold = true }
setFont(font)
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THICK
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
}

fun addItemSummaryRow(totalQty: Double, itemCode: String, itemName: String) {
val summaryRow = sheet.createRow(rowIndex++)
summaryRow.createCell(0).apply {
setCellValue(itemCode)
cellStyle = summaryEmptyThickBottomStyle
}
summaryRow.createCell(1).apply {
setCellValue(itemName)
cellStyle = summaryEmptyThickBottomStyle
}
for (col in 2..5) {
summaryRow.createCell(col).apply {
setCellValue("")
cellStyle = summaryEmptyThickBottomStyle
}
}
summaryRow.createCell(6).apply {
setCellValue("總出倉量 :")
cellStyle = summaryLabelInExpiryColStyle
}
summaryRow.createCell(7).apply {
setCellValue(totalQty)
cellStyle = summaryQtyThickBottomStyle
}
for (col in 8 until totalColumns) {
summaryRow.createCell(col).apply {
setCellValue("")
cellStyle = summaryEmptyThickBottomStyle
}
}
}

fun addBlankSeparatorRow() {
sheet.createRow(rowIndex++)
}

if (dbData.isEmpty()) {
val dataRow = sheet.createRow(rowIndex++)
for (col in 0 until totalColumns) {
val cell = dataRow.createCell(col)
cell.setCellValue("-")
cell.cellStyle = textStyle
}
addItemSummaryRow(0.0, "", "")
} else {
var currentItemNo: String? = null
var currentItemName: String = ""
var currentItemTotalQty: Double = 0.0

dbData.forEach { rowMap ->
val itemNo = rowMap["itemNo"]?.toString() ?: ""
if (currentItemNo != null && itemNo != currentItemNo) {
addItemSummaryRow(currentItemTotalQty, currentItemNo!!, currentItemName)
addBlankSeparatorRow()
currentItemTotalQty = 0.0
}

val dataRow = sheet.createRow(rowIndex++)
setTextCell(dataRow, 0, rowMap["itemNo"], textStyle)
setTextCell(dataRow, 1, rowMap["itemName"], textStyle)
setTextCell(dataRow, 2, rowMap["unitOfMeasure"], textStyle)
setTextCell(dataRow, 3, rowMap["jobOrderNo"], textStyle)
setTextCell(dataRow, 4, rowMap["stockReqNo"], textStyle)
setTextCell(dataRow, 5, rowMap["lotNo"], textStyle)
setTextCell(dataRow, 6, rowMap["expiryDate"], textStyle)
setNumberCell(dataRow, 7, rowMap["stockOutQty"], numberStyle, dashStyle)
setTextCell(dataRow, 8, rowMap["handler"], textStyle)
setTextCell(dataRow, 9, rowMap["storeLocation"], textStyle)
setTextCell(dataRow, 10, rowMap["pickRemark"], textStyle)

currentItemNo = itemNo
currentItemName = rowMap["itemName"]?.toString() ?: ""
currentItemTotalQty += parseNullableNumber(rowMap["stockOutQty"]) ?: 0.0
}

// last item
addItemSummaryRow(
currentItemTotalQty,
currentItemNo ?: "",
currentItemName,
)
}


val lastRowIndex = rowIndex - 1
if (lastRowIndex >= 0) {
sheet.setAutoFilter(CellRangeAddress(0, lastRowIndex, 0, 0))
}

val widths = intArrayOf(18, 26, 10, 20, 18, 14, 14, 12, 18, 16, 18)
widths.forEachIndexed { idx, w -> sheet.setColumnWidth(idx, w * 256) }

val output = ByteArrayOutputStream()
workbook.use { it.write(output) }
return output.toByteArray()
}

private fun setTextCell(row: Row, col: Int, value: Any?, style: CellStyle) {
val cell = row.createCell(col)
cell.setCellValue(value?.toString() ?: "")
cell.cellStyle = style
}

private fun parseNullableNumber(value: Any?): Double? {
val s = value?.toString()?.replace(",", "")?.trim()
if (s.isNullOrBlank()) return null
if (s == "-" || s == "null") return null
return s.toDoubleOrNull()
}

private fun setNumberCell(
row: Row,
col: Int,
value: Any?,
numberStyle: CellStyle,
dashStyle: CellStyle,
) {
val cell = row.createCell(col)
val parsed = parseNullableNumber(value)
if (parsed == null) {
cell.setCellValue("-")
cell.cellStyle = dashStyle
} else {
cell.setCellValue(parsed)
cell.cellStyle = numberStyle
}
}
}


Caricamento…
Annulla
Salva