Procházet zdrojové kódy

FGStockOutTraceabilityReport Excel Version

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

+ 309
- 0
src/main/java/com/ffii/fpsms/modules/report/web/FGStockOutTraceabilityReportController.kt Zobrazit soubor

@@ -10,6 +10,18 @@ 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.DataFormat
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.util.CellRangeAddress
import org.apache.poi.ss.util.WorkbookUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.io.ByteArrayOutputStream
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
@@ -71,4 +83,301 @@ class FGStockOutTraceabilityReportController(

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

@GetMapping("/print-fg-stock-out-traceability-excel")
fun exportFGStockOutTraceabilityReportExcel(
@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 dbData = fgStockOutTraceabilityReportService.searchFGStockOutTraceabilityReport(
stockCategory,
stockSubCategory,
itemCode,
year,
lastOutDateStart,
lastOutDateEnd,
handler,
)

val excelBytes = createFGStockOutTraceabilityExcel(dbData)

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

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

private fun createFGStockOutTraceabilityExcel(dbData: List<Map<String, Any>>): 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(
"貨品編號",
"貨品名稱",
"單位",
"出貨單號",
"提料單號",
"批號",
"到期日",
"出貨數量",
"提料人",
"存貨位置",
"提料備註",
)

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

// Excel heading row
val titleRow = sheet.createRow(rowIndex++)
titleRow.createCell(0).apply {
setCellValue(reportTitle)
cellStyle = titleStyle
}
sheet.addMergedRegion(CellRangeAddress(0, 0, 0, totalColumns - 1))
sheet.createRow(rowIndex++)

val headerRowIndex = rowIndex
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.THICK
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.THICK
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.THICK
borderBottom = BorderStyle.THICK
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
}

val summaryHiddenKeyStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.LEFT
verticalAlignment = VerticalAlignment.CENTER
borderTop = BorderStyle.THICK
borderBottom = BorderStyle.THICK
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
// Keep value for Excel filtering/grouping while visually hiding text.
val font = workbook.createFont().apply {
color = IndexedColors.WHITE.index
}
setFont(font)
}

fun addItemSummaryRow(totalQty: Double, _itemCode: String, _itemName: String) {
val summaryRow = sheet.createRow(rowIndex++)
summaryRow.createCell(0).apply {
setCellValue(_itemCode)
cellStyle = summaryHiddenKeyStyle
}
summaryRow.createCell(1).apply {
setCellValue(_itemName)
cellStyle = summaryHiddenKeyStyle
}
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 = ""
var currentItemTotalQty = 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["deliveryOrderNo"], 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
}

addItemSummaryRow(currentItemTotalQty, currentItemNo ?: "", currentItemName)
}

val lastRowIndex = rowIndex - 1
if (lastRowIndex >= headerRowIndex) {
sheet.setAutoFilter(CellRangeAddress(headerRowIndex, 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
}
}
}

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