From cdf08be7cfd60be23cff4a41edf1f662bb6ee724 Mon Sep 17 00:00:00 2001 From: "vluk@2fi-solutions.com.hk" Date: Mon, 23 Feb 2026 15:40:55 +0800 Subject: [PATCH 1/2] tommy receive --- .../modules/report/service/ReportService.kt | 65 +++++++++++++++++++ .../modules/report/web/ReportController.kt | 35 +++------- 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt index 84b6483..a579b55 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt @@ -3,8 +3,14 @@ package com.ffii.fpsms.modules.report.service import org.springframework.stereotype.Service import net.sf.jasperreports.engine.* import net.sf.jasperreports.engine.data.JRMapCollectionDataSource +import java.io.ByteArrayOutputStream import java.io.InputStream import com.ffii.core.support.JdbcDao +import org.apache.poi.ss.usermodel.Cell +import org.apache.poi.ss.usermodel.CellType +import org.apache.poi.ss.usermodel.Row +import org.apache.poi.ss.usermodel.Sheet +import org.apache.poi.xssf.usermodel.XSSFWorkbook @Service open class ReportService( @@ -926,6 +932,65 @@ fun searchMaterialStockOutTraceabilityReport( } return jdbcDao.queryForList("$finalSql ORDER BY itemNo, lotNo", args) } + /** + * Builds Stock Balance Report as Excel (.xlsx) from the same data used for the PDF report. + */ + fun createStockBalanceExcel(dataList: List>): ByteArray { + val workbook = XSSFWorkbook() + val sheet: Sheet = workbook.createSheet("Stock Balance") + val columns = listOf( + "itemNo" to "Item No", + "itemName" to "Item Name", + "unitOfMeasure" to "Unit", + "lotNo" to "Lot No", + "expiryDate" to "Expiry Date", + "openingBalance" to "Opening Balance", + "cumStockIn" to "Cum Stock In", + "cumStockOut" to "Cum Stock Out", + "currentBalance" to "Current Balance", + "storeLocation" to "Store Location", + "lastInDate" to "Last In Date", + "lastOutDate" to "Last Out Date", + "lastMovementDate" to "Last Movement Date", + "misInputAndLost" to "Mis Input And Lost", + "defectiveGoods" to "Defective Goods", + "variance" to "Variance", + "cumStockInByPurchaseOrder" to "Cum Stock In (PO)", + "cumStockInByJobOrder" to "Cum Stock In (JO)", + "cumStockInByStockTake" to "Cum Stock In (Stock Take)", + "cumStockInByAdj" to "Cum Stock In (Adj)", + "cumStockOutMissQty" to "Cum Stock Out Miss", + "cumStockOutBadQty" to "Cum Stock Out Bad", + "cumStockOutAdj" to "Cum Stock Out Adj", + "cumStockOutAdjStockTake" to "Cum Stock Out Adj Stock Take", + "cumStockOutStockTake" to "Cum Stock Out Stock Take", + "cumStockOutByDO" to "Cum Stock Out (DO)", + "cumStockOutByJO" to "Cum Stock Out (JO)", + "cumStockOutByConsumable" to "Cum Stock Out (Consumable)" + ) + val headerRow: Row = sheet.createRow(0) + columns.forEachIndexed { index, (_, label) -> + val cell: Cell = headerRow.createCell(index, CellType.STRING) + cell.setCellValue(label) + } + dataList.forEachIndexed { rowIndex, rowData -> + val row: Row = sheet.createRow(rowIndex + 1) + columns.forEachIndexed { colIndex, (key, _) -> + val cell: Cell = row.createCell(colIndex) + val value = rowData[key] + when (value) { + null -> cell.setCellValue("") + is Number -> cell.setCellValue(value.toDouble()) + else -> cell.setCellValue(value.toString()) + } + } + } + columns.indices.forEach { sheet.autoSizeColumn(it) } + val out = ByteArrayOutputStream() + workbook.use { it.write(out) } + return out.toByteArray() + } + /** * Compiles and fills a Jasper Report, returning the PDF as a ByteArray. */ diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt index 87b2631..35e5872 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt @@ -268,21 +268,6 @@ class ReportController( @RequestParam(required = false) lastOutDateStart: String?, @RequestParam(required = false) lastOutDateEnd: String? ): ResponseEntity { - val parameters = mutableMapOf() - - // Set report header parameters - parameters["stockCategory"] = stockCategory ?: "All" - parameters["itemNo"] = itemCode ?: "All" - parameters["reportDate"] = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) - parameters["reportTime"] = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) - parameters["storeLocation"] = storeLocation ?: "" - parameters["balanceFilterStart"] = balanceFilterStart ?: "" - parameters["balanceFilterEnd"] = balanceFilterEnd ?: "" - parameters["lastInDateStart"] = lastInDateStart ?: "" - parameters["lastInDateEnd"] = lastInDateEnd ?: "" - parameters["lastOutDateStart"] = lastOutDateStart ?: "" - parameters["lastOutDateEnd"] = lastOutDateEnd ?: "" - val dbData = reportService.searchStockBalanceReport( stockCategory, itemCode, @@ -294,20 +279,16 @@ class ReportController( lastOutDateStart, lastOutDateEnd ) - - val pdfBytes = reportService.createPdfResponse( - "/jasper/StockBalanceReport.jrxml", - parameters, - dbData - ) - + + val excelBytes = reportService.createStockBalanceExcel(dbData) + val headers = HttpHeaders().apply { - contentType = MediaType.APPLICATION_PDF - setContentDispositionFormData("attachment", "StockBalanceReport.pdf") - set("filename", "StockBalanceReport.pdf") + contentType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + setContentDispositionFormData("attachment", "StockBalanceReport.xlsx") + set("filename", "StockBalanceReport.xlsx") } - - return ResponseEntity(pdfBytes, headers, HttpStatus.OK) + + return ResponseEntity(excelBytes, headers, HttpStatus.OK) } } \ No newline at end of file From e592845c41f0c19b2fbc14246e8d0529a398a38f Mon Sep 17 00:00:00 2001 From: "vluk@2fi-solutions.com.hk" Date: Mon, 23 Feb 2026 15:48:19 +0800 Subject: [PATCH 2/2] no message --- .../modules/report/service/ReportService.kt | 72 ++++--------------- .../modules/report/web/ReportController.kt | 19 ++++- 2 files changed, 32 insertions(+), 59 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt index a579b55..d46faa7 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt @@ -6,11 +6,9 @@ import net.sf.jasperreports.engine.data.JRMapCollectionDataSource import java.io.ByteArrayOutputStream import java.io.InputStream import com.ffii.core.support.JdbcDao -import org.apache.poi.ss.usermodel.Cell -import org.apache.poi.ss.usermodel.CellType -import org.apache.poi.ss.usermodel.Row -import org.apache.poi.ss.usermodel.Sheet -import org.apache.poi.xssf.usermodel.XSSFWorkbook +import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter +import net.sf.jasperreports.export.SimpleExporterInput +import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput @Service open class ReportService( @@ -933,61 +931,19 @@ fun searchMaterialStockOutTraceabilityReport( return jdbcDao.queryForList("$finalSql ORDER BY itemNo, lotNo", args) } /** - * Builds Stock Balance Report as Excel (.xlsx) from the same data used for the PDF report. + * Compiles and fills a Jasper Report, then exports to Excel (.xlsx). Same layout/columns as the report template. */ - fun createStockBalanceExcel(dataList: List>): ByteArray { - val workbook = XSSFWorkbook() - val sheet: Sheet = workbook.createSheet("Stock Balance") - val columns = listOf( - "itemNo" to "Item No", - "itemName" to "Item Name", - "unitOfMeasure" to "Unit", - "lotNo" to "Lot No", - "expiryDate" to "Expiry Date", - "openingBalance" to "Opening Balance", - "cumStockIn" to "Cum Stock In", - "cumStockOut" to "Cum Stock Out", - "currentBalance" to "Current Balance", - "storeLocation" to "Store Location", - "lastInDate" to "Last In Date", - "lastOutDate" to "Last Out Date", - "lastMovementDate" to "Last Movement Date", - "misInputAndLost" to "Mis Input And Lost", - "defectiveGoods" to "Defective Goods", - "variance" to "Variance", - "cumStockInByPurchaseOrder" to "Cum Stock In (PO)", - "cumStockInByJobOrder" to "Cum Stock In (JO)", - "cumStockInByStockTake" to "Cum Stock In (Stock Take)", - "cumStockInByAdj" to "Cum Stock In (Adj)", - "cumStockOutMissQty" to "Cum Stock Out Miss", - "cumStockOutBadQty" to "Cum Stock Out Bad", - "cumStockOutAdj" to "Cum Stock Out Adj", - "cumStockOutAdjStockTake" to "Cum Stock Out Adj Stock Take", - "cumStockOutStockTake" to "Cum Stock Out Stock Take", - "cumStockOutByDO" to "Cum Stock Out (DO)", - "cumStockOutByJO" to "Cum Stock Out (JO)", - "cumStockOutByConsumable" to "Cum Stock Out (Consumable)" - ) - val headerRow: Row = sheet.createRow(0) - columns.forEachIndexed { index, (_, label) -> - val cell: Cell = headerRow.createCell(index, CellType.STRING) - cell.setCellValue(label) - } - dataList.forEachIndexed { rowIndex, rowData -> - val row: Row = sheet.createRow(rowIndex + 1) - columns.forEachIndexed { colIndex, (key, _) -> - val cell: Cell = row.createCell(colIndex) - val value = rowData[key] - when (value) { - null -> cell.setCellValue("") - is Number -> cell.setCellValue(value.toDouble()) - else -> cell.setCellValue(value.toString()) - } - } - } - columns.indices.forEach { sheet.autoSizeColumn(it) } + fun createExcelResponse(templatePath: String, params: Map, dataList: List>): ByteArray { + val stream = this::class.java.getResourceAsStream(templatePath) + ?: throw RuntimeException("Report template not found: $templatePath") + val jasperReport = JasperCompileManager.compileReport(stream) + val dataSource = JRMapCollectionDataSource(dataList) + val jasperPrint = JasperFillManager.fillReport(jasperReport, params, dataSource) val out = ByteArrayOutputStream() - workbook.use { it.write(out) } + val exporter = JRXlsxExporter() + exporter.setExporterInput(SimpleExporterInput(jasperPrint)) + exporter.setExporterOutput(SimpleOutputStreamExporterOutput(out)) + exporter.exportReport() return out.toByteArray() } diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt index 35e5872..710fdac 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt @@ -268,6 +268,19 @@ class ReportController( @RequestParam(required = false) lastOutDateStart: String?, @RequestParam(required = false) lastOutDateEnd: String? ): ResponseEntity { + val parameters = mutableMapOf() + parameters["stockCategory"] = stockCategory ?: "All" + parameters["itemNo"] = itemCode ?: "All" + parameters["reportDate"] = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + parameters["reportTime"] = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + parameters["storeLocation"] = storeLocation ?: "" + parameters["balanceFilterStart"] = balanceFilterStart ?: "" + parameters["balanceFilterEnd"] = balanceFilterEnd ?: "" + parameters["lastInDateStart"] = lastInDateStart ?: "" + parameters["lastInDateEnd"] = lastInDateEnd ?: "" + parameters["lastOutDateStart"] = lastOutDateStart ?: "" + parameters["lastOutDateEnd"] = lastOutDateEnd ?: "" + val dbData = reportService.searchStockBalanceReport( stockCategory, itemCode, @@ -280,7 +293,11 @@ class ReportController( lastOutDateEnd ) - val excelBytes = reportService.createStockBalanceExcel(dbData) + val excelBytes = reportService.createExcelResponse( + "/jasper/StockBalanceReport.jrxml", + parameters, + dbData + ) val headers = HttpHeaders().apply { contentType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")