From 88ccf9c2400cb8e357a90877cf651b9e57b12d9c Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 10 Feb 2026 15:43:45 +0800 Subject: [PATCH] update new report --- .../report/service/ItemQcFailReportService.kt | 183 ++++++ .../report/web/ItemQcFailReportController.kt | 67 +++ src/main/resources/jasper/ItemQCReport.jrxml | 561 ++++++++++++++++++ 3 files changed, 811 insertions(+) create mode 100644 src/main/java/com/ffii/fpsms/modules/report/service/ItemQcFailReportService.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt create mode 100644 src/main/resources/jasper/ItemQCReport.jrxml diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/ItemQcFailReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/ItemQcFailReportService.kt new file mode 100644 index 0000000..10061df --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/report/service/ItemQcFailReportService.kt @@ -0,0 +1,183 @@ +package com.ffii.fpsms.modules.report.service + +import com.ffii.core.support.JdbcDao +import org.springframework.stereotype.Service + +@Service +open class ItemQcFailReportService( + private val jdbcDao: JdbcDao, +) { + fun searchItemQcFailReport( + stockCategory: String?, // items.type (可逗号分隔) + itemCode: String?, // items.code (可逗号分隔, LIKE) + lastInDateStart: String?, // stock_in_line.receiptDate >= + lastInDateEnd: String?, // stock_in_line.receiptDate < + ): List> { + val args = mutableMapOf() + + val stockCategorySql = buildMultiValueExactClause(stockCategory, "it.type", "stockCategory", 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(it.type, '') AS stockSubCategory, + COALESCE(it.code, '') AS itemNo, + COALESCE(CONCAT( + it.name, + CASE WHEN it.description IS NULL OR it.description = '' THEN '' ELSE CONCAT('\n', it.description) END + ), '') AS itemName, + COALESCE(uc.udfudesc, uc.code, '') AS unitOfMeasure, + COALESCE(sil.lotNo, il.lotNo, '') AS lotNo, + COALESCE(DATE_FORMAT(COALESCE(sil.expiryDate, il.expiryDate), '%Y-%m-%d'), '') AS expiryDate, + + /* 用 stock_in_line.jobOrderId 来判 IQC/EPQC */ + CASE WHEN sil.jobOrderId IS NOT NULL THEN 'EPQC' ELSE 'IQC' END AS qcType, + + /* QC Template Used:取 qc_category.code (例如 A1/B4/X1) */ + COALESCE( + qc.name, + (SELECT name + FROM qc_category + WHERE isDefault = 1 + AND deleted = 0 + LIMIT 1), + qc.name, + '' + ) AS qcTemplate, + + /* QC Criteria with Defect:优先用 qc_item_category.description,否则用 qc_item */ + COALESCE(qic.description, qi.description, qi.name, '') AS qcDefectCriteria, + + /* Lot Qty / Defect Qty:按 jrxml 字段类型输出 String */ + TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(sil.acceptedQty, 0), 2))) AS lotQty, + TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(qr.failQty, 0), 2))) AS defectQty, + + /* Ref Data (e.g temp):目前库表只有 qc_result.remarks,可先放这里 */ + COALESCE(qr.remarks, '') AS refData, + + /* Remarks:若你之后有独立字段再补 */ + '' AS remark, + + /* Order Ref No:IQC 用 purchase_order.code;EPQC 用 job_order.code */ + CASE + WHEN sil.jobOrderId IS NOT NULL THEN COALESCE(jo.code, '') + ELSE COALESCE(po.code, '') + END AS orderRefNo + + FROM qc_result qr + INNER JOIN stock_in_line sil + ON qr.stockInLineId = sil.id + AND sil.deleted = 0 + INNER JOIN items it + ON qr.itemId = it.id + AND it.deleted = 0 + LEFT JOIN item_uom iu + ON it.id = iu.itemId + AND iu.stockUnit = 1 + AND iu.deleted = 0 + LEFT JOIN uom_conversion uc + ON iu.uomId = uc.id + LEFT JOIN inventory_lot il + ON sil.inventoryLotId = il.id + AND il.deleted = 0 + LEFT JOIN purchase_order po + ON sil.purchaseOrderId = po.id + AND po.deleted = 0 + LEFT JOIN job_order jo + ON sil.jobOrderId = jo.id + AND jo.deleted = 0 + + LEFT JOIN items_qc_category_mapping iqcm + ON iqcm.itemId = it.id + AND iqcm.type = (CASE WHEN sil.jobOrderId IS NOT NULL THEN 'EPQC' ELSE 'IQC' END) + LEFT JOIN qc_category qc + ON qc.id = iqcm.qcCategoryId + AND qc.deleted = 0 + + LEFT JOIN qc_item qi + ON qi.id = qr.qcItemId + AND qi.deleted = 0 + LEFT JOIN qc_item_category qic + ON qic.qcItemId = qi.id + AND qic.qcCategoryId = COALESCE( + iqcm.qcCategoryId, + (SELECT id + FROM qc_category + WHERE isDefault = 1 + AND deleted = 0 + LIMIT 1) + ) + + WHERE + qr.deleted = 0 + AND qr.qcPassed = 0 + $stockCategorySql + $itemCodeSql + $lastInDateStartSql + $lastInDateEndSql + + ORDER BY + it.type, + it.code, + COALESCE(sil.lotNo, il.lotNo, ''), + CASE WHEN sil.jobOrderId IS NOT NULL THEN 'EPQC' ELSE 'IQC' END, + COALESCE(qic.`order`, 9999), + COALESCE(qi.code, '') + """.trimIndent() + val result = jdbcDao.queryForList(sql, args) + println("qcTemplate: ${result[0]["qcTemplate"]}") + println("qcDefectCriteria: ${result[0]["qcDefectCriteria"]}") + println("lotQty: ${result[0]["lotQty"]}") + println("defectQty: ${result[0]["defectQty"]}") + println("refData: ${result[0]["refData"]}") + println("remark: ${result[0]["remark"]}") + println("orderRefNo: ${result[0]["orderRefNo"]}") + return result + } + + 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 ")})" + } + + private fun buildMultiValueExactClause( + 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 = :$paramName" + } + return "AND (${conditions.joinToString(" OR ")})" + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt new file mode 100644 index 0000000..15e7741 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt @@ -0,0 +1,67 @@ +package com.ffii.fpsms.modules.report.web + +import net.sf.jasperreports.engine.* +import org.springframework.http.* +import org.springframework.web.bind.annotation.* +import java.time.LocalDate +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import com.ffii.fpsms.modules.report.service.ItemQcFailReportService +import com.ffii.fpsms.modules.report.service.ReportService + +@RestController +@RequestMapping("/report") +class ItemQcFailReportController( + + private val reportService: ReportService, + private val itemQcFailReportService: ItemQcFailReportService, +) { + + @GetMapping("/print-item-qc-fail") + fun generateItemQcFailReport( + @RequestParam(required = false) stockCategory: String?, + @RequestParam(required = false) itemCode: String?, + @RequestParam(required = false) lastInDateStart: String?, + @RequestParam(required = false) lastInDateEnd: String?, + ): ResponseEntity { + val parameters = mutableMapOf() + + parameters["stockCategory"] = stockCategory ?: "All" + parameters["stockSubCategory"] = stockCategory ?: "All" // 你定义 stock sub category = items.type + parameters["itemNo"] = itemCode ?: "All" + parameters["year"] = java.time.LocalDate.now().year.toString() + parameters["reportDate"] = java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd")) + parameters["reportTime"] = java.time.LocalTime.now().format(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss")) + + // jrxml 里有这些参数,先给空 + parameters["storeLocation"] = "" + parameters["balanceFilterStart"] = "" + parameters["balanceFilterEnd"] = "" + parameters["lastInDateStart"] = lastInDateStart ?: "" + parameters["lastInDateEnd"] = lastInDateEnd ?: "" + parameters["lastOutDateStart"] = "" + parameters["lastOutDateEnd"] = "" + + val dbData = itemQcFailReportService.searchItemQcFailReport( + stockCategory = stockCategory, + itemCode = itemCode, + lastInDateStart = lastInDateStart, + lastInDateEnd = lastInDateEnd, + ) + + val pdfBytes = reportService.createPdfResponse( + "/jasper/ItemQCReport.jrxml", + parameters, + dbData + ) + + val headers = org.springframework.http.HttpHeaders().apply { + contentType = org.springframework.http.MediaType.APPLICATION_PDF + setContentDispositionFormData("attachment", "ItemQCFailReport.pdf") + set("filename", "ItemQCFailReport.pdf") + } + return org.springframework.http.ResponseEntity(pdfBytes, headers, org.springframework.http.HttpStatus.OK) + } +} + + diff --git a/src/main/resources/jasper/ItemQCReport.jrxml b/src/main/resources/jasper/ItemQCReport.jrxml new file mode 100644 index 0000000..d94d7ea --- /dev/null +++ b/src/main/resources/jasper/ItemQCReport.jrxml @@ -0,0 +1,561 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +