| @@ -62,7 +62,10 @@ class SemiFGProductionAnalysisReportService( | |||
| * - Include only stock_in_line rows with non-null jobOrderId | |||
| * - Exclude stock_in_line rows with status = 'Pending' | |||
| * - stockCategory → items.type (exact, comma-separated); itemCode → items.code (LIKE, comma-separated) | |||
| * - Date range / year on productionDate (with IS NOT NULL when range bound is set) | |||
| * - Date path: | |||
| * stock_in_line.productLotNo (must be present) -> | |||
| * stock_in_line.inventoryLotId -> inventory_lot.id -> | |||
| * inventory_lot.stockInDate (used for month/year/date filters) | |||
| * - Quantity source: stock_in_line.acceptedQty | |||
| * - QC any fail → line qty 0 (same as traceability stockInQty) | |||
| * - One row per stockInLineId per month before pivot; all lines counted (not only job orders) | |||
| @@ -89,7 +92,7 @@ class SemiFGProductionAnalysisReportService( | |||
| val yearSql = if (!year.isNullOrBlank() && year != "All") { | |||
| args["year"] = year | |||
| "AND YEAR(si.productionDate) = :year" | |||
| "AND YEAR(CASE WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate ELSE si.productionDate END) = :year" | |||
| } else { | |||
| "" | |||
| } | |||
| @@ -97,13 +100,13 @@ class SemiFGProductionAnalysisReportService( | |||
| val lastOutDateStartSql = if (!lastOutDateStart.isNullOrBlank()) { | |||
| val formattedDate = lastOutDateStart.replace("/", "-") | |||
| args["lastOutDateStart"] = formattedDate | |||
| "AND si.productionDate IS NOT NULL AND DATE(si.productionDate) >= DATE(:lastOutDateStart)" | |||
| "AND DATE(CASE WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate ELSE si.productionDate END) >= DATE(:lastOutDateStart)" | |||
| } else "" | |||
| val lastOutDateEndSql = if (!lastOutDateEnd.isNullOrBlank()) { | |||
| val formattedDate = lastOutDateEnd.replace("/", "-") | |||
| args["lastOutDateEnd"] = formattedDate | |||
| "AND si.productionDate IS NOT NULL AND DATE(si.productionDate) <= DATE(:lastOutDateEnd)" | |||
| "AND DATE(CASE WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate ELSE si.productionDate END) <= DATE(:lastOutDateEnd)" | |||
| } else "" | |||
| val sql = """ | |||
| @@ -121,7 +124,12 @@ class SemiFGProductionAnalysisReportService( | |||
| COALESCE(it.name, '') AS itemName, | |||
| COALESCE(ic.sub, '') AS stockSubCategory, | |||
| COALESCE(uc.udfudesc, '') AS unitOfMeasure, | |||
| MONTH(si.productionDate) AS mon, | |||
| MONTH( | |||
| CASE | |||
| WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate | |||
| ELSE si.productionDate | |||
| END | |||
| ) AS mon, | |||
| si.id AS stockInLineId, | |||
| CASE WHEN COALESCE(qr_agg.qcFailed, 0) = 1 THEN 0 | |||
| ELSE COALESCE(si.acceptedQty, 0) | |||
| @@ -129,12 +137,20 @@ class SemiFGProductionAnalysisReportService( | |||
| FROM stock_in_line si | |||
| INNER JOIN items it ON si.itemId = it.id | |||
| INNER JOIN bom b ON b.code = it.code AND b.deleted = false | |||
| LEFT JOIN inventory_lot il ON il.id = si.inventoryLotId AND il.deleted = false | |||
| LEFT JOIN qr_agg ON qr_agg.stockInLineId = si.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 | |||
| WHERE si.deleted = false | |||
| AND si.productionDate IS NOT NULL | |||
| AND si.productLotNo IS NOT NULL | |||
| AND TRIM(si.productLotNo) <> '' | |||
| AND ( | |||
| CASE | |||
| WHEN si.productLotNo IS NOT NULL AND TRIM(si.productLotNo) <> '' THEN il.stockInDate | |||
| ELSE si.productionDate | |||
| END | |||
| ) IS NOT NULL | |||
| AND si.jobOrderId IS NOT NULL | |||
| AND (si.status IS NULL OR si.status <> 'Pending') | |||
| $stockCategorySql | |||