Fai Luk 3 дні тому
джерело
коміт
74e5bdabbc
2 змінених файлів з 46 додано та 19 видалено
  1. +39
    -16
      src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt
  2. +7
    -3
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt

+ 39
- 16
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). * 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). * Optional multi-filters: supplierIds, itemCodes (line itemNo), purchaseOrderNos (po.code).
*/ */
fun getPurchaseOrderByStatus( fun getPurchaseOrderByStatus(
@@ -78,10 +79,7 @@ open class ChartService(
purchaseOrderNos: List<String>?, purchaseOrderNos: List<String>?,
): List<Map<String, Any>> { ): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>() val args = mutableMapOf<String, Any>()
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 multiSql = buildPoMultiFiltersSql(supplierIds, itemCodes, purchaseOrderNos, null, args)
val sql = """ val sql = """
SELECT SELECT
@@ -96,9 +94,7 @@ open class ChartService(
return jdbcDao.queryForList(sql, args) 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<String, Any> { fun getPurchaseOrderFilterOptions(targetDate: LocalDate?): Map<String, Any> {
if (targetDate == null) { if (targetDate == null) {
return mapOf( return mapOf(
@@ -107,12 +103,13 @@ open class ChartService(
"poNos" to emptyList<Any>(), "poNos" to emptyList<Any>(),
) )
} }
val args = mutableMapOf<String, Any>("targetDate" to targetDate.toString())
val args = mutableMapOf<String, Any>()
val dateSql = buildPurchaseOrderActualSearchDateSql(targetDate, args)
val suppliersSql = """ val suppliersSql = """
SELECT DISTINCT po.supplierId AS supplierId, s.code AS code, COALESCE(s.name, '') AS name SELECT DISTINCT po.supplierId AS supplierId, s.code AS code, COALESCE(s.name, '') AS name
FROM purchase_order po FROM purchase_order po
LEFT JOIN shop s ON s.id = po.supplierId AND s.deleted = 0 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 ORDER BY s.code
""".trimIndent() """.trimIndent()
val itemsSql = """ val itemsSql = """
@@ -120,13 +117,13 @@ open class ChartService(
FROM purchase_order_line pol FROM purchase_order_line pol
INNER JOIN purchase_order po ON po.id = pol.purchaseOrderId AND po.deleted = 0 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 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 ORDER BY pol.itemNo
""".trimIndent() """.trimIndent()
val poNosSql = """ val poNosSql = """
SELECT DISTINCT po.code AS poNo SELECT DISTINCT po.code AS poNo
FROM purchase_order po FROM purchase_order po
WHERE po.deleted = 0 AND DATE(po.orderDate) = :targetDate
WHERE po.deleted = 0 $dateSql
ORDER BY po.code ORDER BY po.code
""".trimIndent() """.trimIndent()
return mapOf( 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, Any>,
): 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). * Drill-down rows for 預計送貨 must match [getPurchaseOrderEstimatedArrivalSummary] (estimated arrival date only).
* If we also applied [buildPurchaseOrderTargetDateSql], many POs (orderDate ≠ 預計到貨日) would drop out and * If we also applied [buildPurchaseOrderTargetDateSql], many POs (orderDate ≠ 預計到貨日) would drop out and
* supplier / item charts would collapse incorrectly. * 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( private fun buildPurchaseOrderDrillOrderDateSql(
estimatedArrivalBucket: String?, estimatedArrivalBucket: String?,
@@ -395,7 +418,7 @@ open class ChartService(
if (!estimatedArrivalBucket.isNullOrBlank() && targetDate != null) { if (!estimatedArrivalBucket.isNullOrBlank() && targetDate != null) {
return "" return ""
} }
return buildPurchaseOrderTargetDateSql(targetDate, "order", args)
return buildPurchaseOrderActualSearchDateSql(targetDate, args)
} }


/** /**


+ 7
- 3
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 java.util.zip.ZipOutputStream
import javax.imageio.ImageIO import javax.imageio.ImageIO
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.qrcode.QRCodeWriter
import java.net.Socket import java.net.Socket
import java.net.InetSocketAddress import java.net.InetSocketAddress
@@ -205,8 +206,10 @@ open class PlasticBagPrinterService(
val imageTemplate = loadOnPackImageTemplateOrNull(codeLower) ?: return@forEach val imageTemplate = loadOnPackImageTemplateOrNull(codeLower) ?: return@forEach


val qrContent = """{"itemId": $itemId, "stockInLineId": $stockInLineId}""" 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 qrBmpFileName = "${codeLower}qr.bmp"
val imageFileName = "$codeLower.image" val imageFileName = "$codeLower.image"
val imageContent = withOnPackLogo4Bmp(imageTemplate, qrBmpFileName) val imageContent = withOnPackLogo4Bmp(imageTemplate, qrBmpFileName)
@@ -388,7 +391,8 @@ open class PlasticBagPrinterService(
val totalHeight = contentSize + (verticalPadding * 2) val totalHeight = contentSize + (verticalPadding * 2)


val writer = QRCodeWriter() 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 image = BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_BYTE_BINARY)
val g = image.createGraphics() val g = image.createGraphics()


Завантаження…
Відмінити
Зберегти