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 5d9d980..123a9c0 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 @@ -815,6 +815,107 @@ fun searchMaterialStockOutTraceabilityReport( return jdbcDao.queryForList(sql, args) } + /** + * GRN (Goods Received Note) report: stock-in lines with PO/delivery note, filterable by receipt date range and item code. + * Returns rows for Excel export: poCode, deliveryNoteNo, receiptDate, itemCode, itemName, acceptedQty, demandQty, uom, etc. + */ + fun searchGrnReport( + receiptDateStart: String?, + receiptDateEnd: String?, + itemCode: String? + ): List> { + val args = mutableMapOf() + val receiptDateStartSql = if (!receiptDateStart.isNullOrBlank()) { + val formatted = receiptDateStart.replace("/", "-") + args["receiptDateStart"] = formatted + "AND DATE(sil.receiptDate) >= DATE(:receiptDateStart)" + } else "" + val receiptDateEndSql = if (!receiptDateEnd.isNullOrBlank()) { + val formatted = receiptDateEnd.replace("/", "-") + args["receiptDateEnd"] = formatted + "AND DATE(sil.receiptDate) <= DATE(:receiptDateEnd)" + } else "" + val itemCodeSql = buildMultiValueLikeClause(itemCode, "it.code", "itemCode", args) + + val sql = """ + SELECT + po.code AS poCode, + CASE + WHEN sil.dnNo = 'DN00000' OR sil.dnNo IS NULL THEN '' + ELSE sil.dnNo + END AS deliveryNoteNo, + DATE_FORMAT(sil.receiptDate, '%Y-%m-%d') AS receiptDate, + COALESCE(it.code, '') AS itemCode, + COALESCE(it.name, '') AS itemName, + COALESCE(sil.acceptedQty, 0) AS acceptedQty, + COALESCE(sil.demandQty, 0) AS demandQty, + COALESCE(uc_stock.udfudesc, uc_pol.udfudesc, '') AS uom, + COALESCE(uc_pol.udfudesc, '') AS purchaseUomDesc, + COALESCE(uc_stock.udfudesc, '') AS stockUomDesc, + COALESCE(sil.productLotNo, '') AS productLotNo, + DATE_FORMAT(sil.expiryDate, '%Y-%m-%d') AS expiryDate, + COALESCE(sp.code, '') AS supplierCode, + COALESCE(sp.name, '') AS supplier, + COALESCE(sil.status, '') AS status, + MAX(grn.m18_record_id) AS grnId + FROM stock_in_line sil + LEFT JOIN items it ON sil.itemId = it.id + LEFT JOIN purchase_order po ON sil.purchaseOrderId = po.id + LEFT JOIN shop sp ON po.supplierId = sp.id + LEFT JOIN purchase_order_line pol ON sil.purchaseOrderLineId = pol.id + LEFT JOIN uom_conversion uc_pol ON pol.uomId = uc_pol.id + LEFT JOIN item_uom iu_stock ON it.id = iu_stock.itemId AND iu_stock.stockUnit = true AND iu_stock.deleted = false + LEFT JOIN uom_conversion uc_stock ON iu_stock.uomId = uc_stock.id + LEFT JOIN m18_goods_receipt_note_log grn + ON grn.stock_in_line_id = sil.id + WHERE sil.deleted = false + AND sil.receiptDate IS NOT NULL + AND sil.purchaseOrderId IS NOT NULL + $receiptDateStartSql + $receiptDateEndSql + $itemCodeSql + GROUP BY + po.code, + deliveryNoteNo, + receiptDate, + itemCode, + itemName, + acceptedQty, + demandQty, + uom, + purchaseUomDesc, + stockUomDesc, + productLotNo, + expiryDate, + supplierCode, + supplier, + status + ORDER BY sil.receiptDate, po.code, sil.id + """.trimIndent() + val rows = jdbcDao.queryForList(sql, args) + return rows.map { row -> + mapOf( + "poCode" to row["poCode"], + "deliveryNoteNo" to row["deliveryNoteNo"], + "receiptDate" to row["receiptDate"], + "itemCode" to row["itemCode"], + "itemName" to row["itemName"], + "acceptedQty" to (row["acceptedQty"]?.let { n -> (n as? Number)?.toDouble() } ?: 0.0), + "receivedQty" to (row["acceptedQty"]?.let { n -> (n as? Number)?.toDouble() } ?: 0.0), + "demandQty" to (row["demandQty"]?.let { n -> (n as? Number)?.toDouble() } ?: 0.0), + "uom" to row["uom"], + "purchaseUomDesc" to row["purchaseUomDesc"], + "stockUomDesc" to row["stockUomDesc"], + "productLotNo" to row["productLotNo"], + "expiryDate" to row["expiryDate"], + "supplierCode" to row["supplierCode"], + "supplier" to row["supplier"], + "status" to row["status"], + "grnId" to row["grnId"] + ) + } + } + /** * Queries the database for Stock Balance Report data (one summarized row per item). * Uses stock_ledger with report period (fromDate/toDate): opening = before fromDate, cum in/out = in period, current = up to toDate. 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 008eaa9..6579462 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 @@ -320,4 +320,18 @@ class ReportController( return ResponseEntity(pdfBytes, headers, HttpStatus.OK) } + /** + * GRN (Goods Received Note) report data for Excel export. + * Query by receipt date range and optional item code. Returns JSON { "rows": [ ... ] }. + */ + @GetMapping("/grn-report") + fun getGrnReport( + @RequestParam(required = false) receiptDateStart: String?, + @RequestParam(required = false) receiptDateEnd: String?, + @RequestParam(required = false) itemCode: String? + ): Map { + val rows = reportService.searchGrnReport(receiptDateStart, receiptDateEnd, itemCode) + return mapOf("rows" to rows) + } + } \ No newline at end of file