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 74b8ca6..c57fc55 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 @@ -2,7 +2,7 @@ package com.ffii.fpsms.modules.report.service import org.springframework.stereotype.Service import net.sf.jasperreports.engine.* -import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource +import net.sf.jasperreports.engine.data.JRMapCollectionDataSource import java.io.InputStream import com.ffii.core.support.JdbcDao @@ -73,6 +73,109 @@ open class ReportService( return jdbcDao.queryForList(sql, args) } + /** + * Helper function to build SQL clause for comma-separated values. + * Supports multiple values like "val1, val2, val3" and generates OR conditions with LIKE. + */ + private fun buildMultiValueLikeClause( + paramValue: String?, + columnName: String, + paramPrefix: String, + args: MutableMap + ): String { + if (paramValue.isNullOrBlank()) return "" + + val values = paramValue.split(",").map { it.trim() }.filter { it.isNotBlank() } + if (values.isEmpty()) return "" + + val conditions = values.mapIndexed { index, value -> + val paramName = "${paramPrefix}_$index" + args[paramName] = "%$value%" + "$columnName LIKE :$paramName" + } + + return "AND (${conditions.joinToString(" OR ")})" + } + + /** + * Queries the database for Stock In Traceability Report data. + * Joins stock_in_line, stock_in, items, item_category, qc_result, inventory_lot, inventory_lot_line, warehouse, and shop tables. + * Supports comma-separated values for stockCategory, stockSubCategory, and itemCode. + */ + fun searchStockInTraceabilityReport( + stockCategory: String?, + stockSubCategory: String?, + itemCode: String?, + lastInDateStart: String?, + lastInDateEnd: String? + ): List> { + val args = mutableMapOf() + + val stockCategorySql = buildMultiValueLikeClause(stockCategory, "ic.parent", "stockCategory", args) + val stockSubCategorySql = buildMultiValueLikeClause(stockSubCategory, "ic.sub", "stockSubCategory", args) + val itemCodeSql = buildMultiValueLikeClause(itemCode, "it.code", "itemCode", args) + + val lastInDateStartSql = if (!lastInDateStart.isNullOrBlank()) { + args["lastInDateStart"] = lastInDateStart + "AND sil.receiptDate >= :lastInDateStart" + } else "" + + val lastInDateEndSql = if (!lastInDateEnd.isNullOrBlank()) { + args["lastInDateEnd"] = lastInDateEnd + "AND sil.receiptDate < :lastInDateEnd" + } else "" + + val sql = """ + SELECT + COALESCE(ic.sub, '') as stockSubCategory, + COALESCE(it.code, '') as itemNo, + COALESCE(CONCAT(COALESCE(it.name, ''), ' ', COALESCE(it.description, '')), '') as itemName, + COALESCE(uc.code, '') as unitOfMeasure, + COALESCE(sil.dnNo, '') as dnNo, + COALESCE(sil.lotNo, il.lotNo, '') as lotNo, + COALESCE(DATE_FORMAT(COALESCE(sil.expiryDate, il.expiryDate), '%Y-%m-%d'), '') as expiryDate, + CAST(COALESCE(sil.acceptedQty, 0) AS CHAR) as stockInQty, + CAST(COALESCE(sil.acceptedQty, 0) AS CHAR) as iqcSampleQty, + CAST(COALESCE(qr.failQty, 0) AS CHAR) as iqcDefectQty, + CAST(CASE + WHEN COALESCE(sil.acceptedQty, 0) > 0 + THEN ROUND((COALESCE(qr.failQty, 0) / sil.acceptedQty) * 100, 2) + ELSE 0 + END AS CHAR) as iqcDefectPercentage, + CASE + WHEN qr.qcPassed = true OR qr.qcPassed IS NULL THEN 'Accept' + ELSE 'Reject' + END as iqcResult, + COALESCE(qr.remarks, '') as iqcRemarks, + COALESCE(wh.code, '') as storeLocation, + COALESCE(sp.code, '') as supplierID, + COALESCE(sp.name, '') as supplierName, + CAST(SUM(COALESCE(sil.acceptedQty, 0)) OVER (PARTITION BY it.id) AS CHAR) as totalStockInQty, + CAST(SUM(COALESCE(sil.acceptedQty, 0)) OVER (PARTITION BY it.id) AS CHAR) as totalIqcSampleQty, + CAST(SUM(COALESCE(qr.failQty, 0)) OVER (PARTITION BY it.id) AS CHAR) as totalIqcDefectQty + FROM stock_in_line sil + LEFT JOIN stock_in si ON sil.stockInId = si.id + LEFT JOIN items it ON sil.itemId = it.id + LEFT JOIN item_category ic ON it.categoryId = ic.id + LEFT JOIN item_uom iu ON it.id = iu.itemId AND iu.stockUnit = true + LEFT JOIN uom_conversion uc ON iu.uomId = uc.id + LEFT JOIN qc_result qr ON sil.id = qr.stockInLineId + LEFT JOIN inventory_lot il ON sil.inventoryLotId = il.id + LEFT JOIN inventory_lot_line ill ON il.id = ill.inventoryLotId + LEFT JOIN warehouse wh ON ill.warehouseId = wh.id + LEFT JOIN shop sp ON si.supplierId = sp.id + WHERE sil.deleted = false + $stockCategorySql + $stockSubCategorySql + $itemCodeSql + $lastInDateStartSql + $lastInDateEndSql + ORDER BY ic.sub, it.code, sil.lotNo + """.trimIndent() + + return jdbcDao.queryForList(sql, args) + } + /** * Compiles and fills a Jasper Report, returning the PDF as a ByteArray. */ @@ -82,7 +185,7 @@ open class ReportService( val jasperReport = JasperCompileManager.compileReport(stream) - val dataSource = JRBeanCollectionDataSource(dataList) + val dataSource = JRMapCollectionDataSource(dataList) val jasperPrint = JasperFillManager.fillReport(jasperReport, params, dataSource) return JasperExportManager.exportReportToPdf(jasperPrint) 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 82ed31f..ddfa12f 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 @@ -5,6 +5,9 @@ import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource import org.springframework.http.* import org.springframework.web.bind.annotation.* import java.io.InputStream +import java.time.LocalDate +import java.time.LocalTime +import java.time.format.DateTimeFormatter import com.ffii.fpsms.modules.report.service.ReportService @RestController @@ -76,4 +79,47 @@ class ReportController( return ResponseEntity(pdfBytes, headers, HttpStatus.OK) } + + @GetMapping("/print-stock-in-traceability") + fun generateStockInTraceabilityReport( + @RequestParam(required = false) stockCategory: String?, + @RequestParam(required = false) stockSubCategory: String?, + @RequestParam(required = false) itemCode: String?, + @RequestParam(required = false) lastInDateStart: String?, + @RequestParam(required = false) lastInDateEnd: String? + ): ResponseEntity { + val parameters = mutableMapOf() + + // Set report header parameters + parameters["stockCategory"] = stockCategory ?: "All" + parameters["stockSubCategory"] = stockSubCategory ?: "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["lastInDateStart"] = lastInDateStart ?: "" + parameters["lastInDateEnd"] = lastInDateEnd ?: "" + + // Query the DB to get a list of data + val dbData = reportService.searchStockInTraceabilityReport( + stockCategory, + stockSubCategory, + itemCode, + lastInDateStart, + lastInDateEnd + ) + + val pdfBytes = reportService.createPdfResponse( + "/jasper/StockInTraceabilityReport.jrxml", + parameters, + dbData + ) + + val headers = HttpHeaders().apply { + contentType = MediaType.APPLICATION_PDF + setContentDispositionFormData("attachment", "StockInTraceabilityReport.pdf") + set("filename", "StockInTraceabilityReport.pdf") + } + + return ResponseEntity(pdfBytes, headers, HttpStatus.OK) + } } \ No newline at end of file diff --git a/src/main/resources/jasper/StockInTraceabilityReport.jrxml b/src/main/resources/jasper/StockInTraceabilityReport.jrxml new file mode 100644 index 0000000..febf02f --- /dev/null +++ b/src/main/resources/jasper/StockInTraceabilityReport.jrxml @@ -0,0 +1,646 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <band height="85" splitType="Stretch"> + <staticText> + <reportElement x="288" y="0" width="226" height="23" uuid="15ab756b-0e92-466c-be47-112c2ee1aad1"> + <property name="com.jaspersoft.studio.unit.y" value="px"/> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + <textElement textAlignment="Left" verticalAlignment="Middle"> + <font fontName="微軟正黑體" size="16" isBold="true"/> + </textElement> + <text><![CDATA[Stock In Traceability Report +]]></text> + </staticText> + <staticText> + <reportElement x="0" y="35" width="90" height="23" uuid="7338c712-b4ee-4194-9c37-07af197eee92"> + <property name="com.jaspersoft.studio.unit.y" value="px"/> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + <textElement textAlignment="Left" verticalAlignment="Middle"> + <font fontName="微軟正黑體" size="16" isBold="true"/> + </textElement> + <text><![CDATA[報告日期:]]></text> + </staticText> + <staticText> + <reportElement x="0" y="58" width="90" height="23" uuid="84d51895-1f38-40bc-a2f8-ad5979628d51"> + <property name="com.jaspersoft.studio.unit.y" value="px"/> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + <textElement textAlignment="Left" verticalAlignment="Middle"> + <font fontName="微軟正黑體" size="16" isBold="true"/> + </textElement> + <text><![CDATA[貨物分類:]]></text> + </staticText> + <textField> + <reportElement x="90" y="35" width="390" height="23" uuid="ff7dd53c-f505-4615-bed4-107d0f053ffe"/> + <textElement verticalAlignment="Middle"> + <font fontName="微軟正黑體" size="16" isBold="true"/> + </textElement> + <textFieldExpression><![CDATA[$P{reportDate}]]></textFieldExpression> + </textField> + <textField> + <reportElement x="90" y="58" width="708" height="23" uuid="451b69eb-de5f-49e6-b8ba-d579f51e9658"/> + <textElement> + <font fontName="微軟正黑體" size="16" isBold="true"/> + </textElement> + <textFieldExpression><![CDATA[$P{stockCategory}]]></textFieldExpression> + </textField> + <staticText> + <reportElement x="480" y="35" width="90" height="23" uuid="465d0d50-d1f6-49e2-966a-86c87294e1c3"> + <property name="com.jaspersoft.studio.unit.y" value="px"/> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + <textElement textAlignment="Left" verticalAlignment="Middle"> + <font fontName="微軟正黑體" size="16" isBold="true"/> + </textElement> + <text><![CDATA[報告時間:]]></text> + </staticText> + <textField> + <reportElement x="570" y="35" width="228" height="23" uuid="bf131b2e-1da1-411a-b247-97280b89de21"/> + <textElement verticalAlignment="Middle"> + <font fontName="微軟正黑體" size="16" isBold="true"/> + </textElement> + <textFieldExpression><![CDATA[$P{reportTime}]]></textFieldExpression> + </textField> + <textField evaluationTime="Report"> + <reportElement x="780" y="12" width="20" height="18" uuid="ad15e154-51b2-4775-8b7e-3cf2d3bc8da9"> + <property name="com.jaspersoft.studio.unit.width" value="px"/> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + <textElement textAlignment="Center" verticalAlignment="Middle"> + <font fontName="微軟正黑體"/> + </textElement> + <textFieldExpression><![CDATA[$V{PAGE_NUMBER}]]></textFieldExpression> + </textField> + <staticText> + <reportElement x="760" y="12" width="20" height="18" uuid="c612993d-f309-41f0-bda2-abc0a7d5a3b5"> + <property name="com.jaspersoft.studio.unit.y" value="px"/> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + <textElement textAlignment="Center" verticalAlignment="Middle"> + <font fontName="微軟正黑體" size="12"/> + </textElement> + <text><![CDATA[/]]></text> + </staticText> + <staticText> + <reportElement x="700" y="12" width="40" height="18" uuid="375abc61-d5a7-4b06-8f95-b2ce305302c6"> + <property name="com.jaspersoft.studio.unit.y" value="px"/> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + <textElement textAlignment="Center" verticalAlignment="Middle"> + <font fontName="微軟正黑體" size="12"/> + </textElement> + <text><![CDATA[頁數]]></text> + </staticText> + <textField> + <reportElement x="740" y="12" width="20" height="18" uuid="c58d1f01-d047-414b-8436-b173dcb58d48"> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + <textElement textAlignment="Center" verticalAlignment="Middle"> + <font fontName="微軟正黑體"/> + </textElement> + <textFieldExpression><![CDATA[$V{PAGE_NUMBER}]]></textFieldExpression> + </textField> + <line> + <reportElement x="0" y="84" width="800" height="1" uuid="51721ea2-ec7b-4532-9e60-4903d8400cc8"> + <property name="com.jaspersoft.studio.unit.height" value="px"/> + </reportElement> + </line> + </band> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +