From 74e5bdabbc49f18fdc484d234ef3613e1d13d291 Mon Sep 17 00:00:00 2001 From: Fai Luk Date: Tue, 24 Mar 2026 15:48:25 +0800 Subject: [PATCH] no message --- .../modules/chart/service/ChartService.kt | 55 +++++++++++++------ .../service/PlasticBagPrinterService.kt | 10 +++- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt b/src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt index cf5bc35..e03f200 100644 --- a/src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt +++ b/src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt @@ -68,7 +68,8 @@ open class ChartService( /** * Purchase orders: count by status (pending, receiving, completed). - * targetDate: when set, only POs whose orderDate is on that date; when null, all POs. + * targetDate: when set, include POs whose estimatedArrivalDate is on that date OR have stock-in + * activity (completeDate/receiptDate/created) on that date; when null, all POs. * Optional multi-filters: supplierIds, itemCodes (line itemNo), purchaseOrderNos (po.code). */ fun getPurchaseOrderByStatus( @@ -78,10 +79,7 @@ open class ChartService( purchaseOrderNos: List?, ): List> { val args = mutableMapOf() - val dateSql = if (targetDate != null) { - args["targetDate"] = targetDate.toString() - "AND DATE(po.orderDate) = :targetDate" - } else "" + val dateSql = buildPurchaseOrderActualSearchDateSql(targetDate, args) val multiSql = buildPoMultiFiltersSql(supplierIds, itemCodes, purchaseOrderNos, null, args) val sql = """ SELECT @@ -96,9 +94,7 @@ open class ChartService( return jdbcDao.queryForList(sql, args) } - /** - * Filter options for purchase chart (distinct suppliers / items / PO numbers on orderDate). - */ + /** Filter options for purchase chart on the same date scope as 實際已送貨. */ fun getPurchaseOrderFilterOptions(targetDate: LocalDate?): Map { if (targetDate == null) { return mapOf( @@ -107,12 +103,13 @@ open class ChartService( "poNos" to emptyList(), ) } - val args = mutableMapOf("targetDate" to targetDate.toString()) + val args = mutableMapOf() + val dateSql = buildPurchaseOrderActualSearchDateSql(targetDate, args) val suppliersSql = """ SELECT DISTINCT po.supplierId AS supplierId, s.code AS code, COALESCE(s.name, '') AS name FROM purchase_order po LEFT JOIN shop s ON s.id = po.supplierId AND s.deleted = 0 - WHERE po.deleted = 0 AND DATE(po.orderDate) = :targetDate AND po.supplierId IS NOT NULL + WHERE po.deleted = 0 $dateSql AND po.supplierId IS NOT NULL ORDER BY s.code """.trimIndent() val itemsSql = """ @@ -120,13 +117,13 @@ open class ChartService( FROM purchase_order_line pol INNER JOIN purchase_order po ON po.id = pol.purchaseOrderId AND po.deleted = 0 LEFT JOIN items it ON it.id = pol.itemId AND it.deleted = 0 - WHERE pol.deleted = 0 AND DATE(po.orderDate) = :targetDate + WHERE pol.deleted = 0 $dateSql ORDER BY pol.itemNo """.trimIndent() val poNosSql = """ SELECT DISTINCT po.code AS poNo FROM purchase_order po - WHERE po.deleted = 0 AND DATE(po.orderDate) = :targetDate + WHERE po.deleted = 0 $dateSql ORDER BY po.code """.trimIndent() return mapOf( @@ -377,14 +374,40 @@ open class ChartService( } } + /** + * Date scope for 實際已送貨: + * - planned side: PO estimatedArrivalDate = targetDate + * - actual side: any stock-in activity date = targetDate + */ + private fun buildPurchaseOrderActualSearchDateSql( + targetDate: LocalDate?, + args: MutableMap, + ): String { + if (targetDate == null) return "" + args["targetDate"] = targetDate.toString() + return """ + AND ( + (po.estimatedArrivalDate IS NOT NULL AND DATE(po.estimatedArrivalDate) = :targetDate) + OR EXISTS ( + SELECT 1 + FROM purchase_order_line polDate + INNER JOIN stock_in_line silDate ON silDate.purchaseOrderLineId = polDate.id AND silDate.deleted = 0 + INNER JOIN stock_in siDate ON siDate.id = silDate.stockInId AND siDate.deleted = 0 + WHERE polDate.purchaseOrderId = po.id + AND polDate.deleted = 0 + AND DATE(COALESCE(siDate.completeDate, silDate.receiptDate, siDate.created)) = :targetDate + ) + ) + """.trimIndent() + } + /** * Drill-down rows for 預計送貨 must match [getPurchaseOrderEstimatedArrivalSummary] (estimated arrival date only). * If we also applied [buildPurchaseOrderTargetDateSql], many POs (orderDate ≠ 預計到貨日) would drop out and * supplier / item charts would collapse incorrectly. * - * For 實際已送貨 (no bucket), [getPurchaseOrderByStatus] is always filtered by **order date** only. If drill used - * [dateFilter] = complete, rows would be restricted to completeDate = targetDate and supplier/item charts would - * collapse to a tiny set (often one supplier) vs the donut counts. + * For 實際已送貨 (no bucket), drill must follow [buildPurchaseOrderActualSearchDateSql] to keep lower charts + * consistent with the top donut data scope. */ private fun buildPurchaseOrderDrillOrderDateSql( estimatedArrivalBucket: String?, @@ -395,7 +418,7 @@ open class ChartService( if (!estimatedArrivalBucket.isNullOrBlank() && targetDate != null) { return "" } - return buildPurchaseOrderTargetDateSql(targetDate, "order", args) + return buildPurchaseOrderActualSearchDateSql(targetDate, args) } /** diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt index 6a06d35..08eeb6f 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt @@ -17,6 +17,7 @@ import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import javax.imageio.ImageIO import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType import com.google.zxing.qrcode.QRCodeWriter import java.net.Socket import java.net.InetSocketAddress @@ -205,8 +206,10 @@ open class PlasticBagPrinterService( val imageTemplate = loadOnPackImageTemplateOrNull(codeLower) ?: return@forEach val qrContent = """{"itemId": $itemId, "stockInLineId": $stockInLineId}""" - // Reduce top/bottom whitespace by 90% for exported QR images (40px -> 4px). - val bmp = createQrCodeBitmap(qrContent, contentSize = 600, horizontalPadding = 40, verticalPadding = 4) + // Target approximately 470x389 BMP, but with larger visible QR and very little vertical whitespace. + // Width = 386 + (42 * 2) = 470 + // Height = 386 + (1 * 2) = 388 (~389) + val bmp = createQrCodeBitmap(qrContent, contentSize = 386, horizontalPadding = 42, verticalPadding = 1) val qrBmpFileName = "${codeLower}qr.bmp" val imageFileName = "$codeLower.image" val imageContent = withOnPackLogo4Bmp(imageTemplate, qrBmpFileName) @@ -388,7 +391,8 @@ open class PlasticBagPrinterService( val totalHeight = contentSize + (verticalPadding * 2) val writer = QRCodeWriter() - val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, contentSize, contentSize) + val hints = mapOf(EncodeHintType.MARGIN to 0) + val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, contentSize, contentSize, hints) val image = BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_BYTE_BINARY) val g = image.createGraphics()