diff --git a/src/main/java/com/ffii/core/utils/QrCodeUtil.kt b/src/main/java/com/ffii/core/utils/QrCodeUtil.kt index 11f05544..e7b052f5 100644 --- a/src/main/java/com/ffii/core/utils/QrCodeUtil.kt +++ b/src/main/java/com/ffii/core/utils/QrCodeUtil.kt @@ -1,18 +1,21 @@ package com.ffii.core.utils import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType import java.awt.image.BufferedImage -import com.google.zxing.client.j2se.MatrixToImageWriter; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.client.j2se.MatrixToImageWriter +import com.google.zxing.qrcode.QRCodeWriter open class QrCodeUtil { companion object { fun generateQRCodeImage(barcodeText: String?): BufferedImage { - val barcodeWriter = QRCodeWriter() - val bitMatrix: BitMatrix = barcodeWriter.encode(barcodeText, BarcodeFormat.QR_CODE, 250, 250) - val img: BufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix) - return img + return generateQRCodeImage(barcodeText, 250, 4) + } + + fun generateQRCodeImage(barcodeText: String?, size: Int, margin: Int): BufferedImage { + val hints = hashMapOf(EncodeHintType.MARGIN to margin) + val bitMatrix = QRCodeWriter().encode(barcodeText, BarcodeFormat.QR_CODE, size, size, hints) + return MatrixToImageWriter.toBufferedImage(bitMatrix) } } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java b/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java index 2a485305..8df20baa 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java +++ b/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java @@ -135,4 +135,7 @@ public abstract class SettingNames { /** JSON: last laser TCP send where printer returned receive (job order no., lot, itemId/stockInLineId, etc.) */ public static final String LASER_PRINT_LAST_RECEIVE_SUCCESS = "LASER_PRINT.lastReceiveSuccess"; + /** PO / stock-in item label: 1 = original, 2 = enlarged, 3 = smaller QR, 4 = version 3 with the version 1 QR size. */ + public static final String LABEL_PO_ITEM_VERSION = "LABEL.poItem.version"; + } diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt index 377e8b48..ec914f52 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt @@ -2,7 +2,6 @@ package com.ffii.fpsms.modules.stock.service import com.ffii.core.response.RecordsRes import com.ffii.core.utils.PdfUtils -import com.ffii.core.utils.QrCodeUtil import com.ffii.fpsms.modules.master.entity.ItemUomRespository import com.ffii.fpsms.modules.master.entity.WarehouseRepository import com.ffii.fpsms.modules.stock.entity.InventoryLotLine @@ -44,6 +43,7 @@ import com.ffii.fpsms.modules.stock.web.model.WorkbenchItemLotsResponse import com.ffii.fpsms.modules.jobOrder.service.JobOrderService import com.ffii.fpsms.modules.jobOrder.web.model.ExportFGStockInLabelRequest import com.ffii.fpsms.modules.master.service.PrinterService +import com.ffii.fpsms.modules.settings.service.SettingsService import com.ffii.fpsms.modules.stock.web.model.PrintLabelForInventoryLotLineRequest import com.ffii.fpsms.modules.jobOrder.web.model.PrintFGStockInLabelRequest import com.ffii.core.utils.ZebraPrinterUtil @@ -60,6 +60,7 @@ open class InventoryLotLineService( private val stockInLineRepository: StockInLineRepository, private val inventoryRepository: InventoryRepository, private val printerService: PrinterService, + private val settingsService: SettingsService, @Lazy private val jobOrderService: JobOrderService ) { @@ -247,7 +248,8 @@ open class InventoryLotLineService( @Throws(IOException::class) @Transactional open fun exportStockInLineQrcode(request: LotLineToQrcode): Map { - val QRCODE_PDF = "qrCodeLabel/poItemPDF.jrxml" + val labelVersion = PoItemLabelTemplate.version(settingsService) + val QRCODE_PDF = PoItemLabelTemplate.path(labelVersion) val resource = ClassPathResource(QRCODE_PDF) if (!resource.exists()) { println("here") @@ -287,13 +289,18 @@ open class InventoryLotLineService( field["acceptedQty"] = "%.2f".format(info.acceptedQty) val stockItemUom = itemUomRespository.findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse(info.itemId) - field["uom"] = stockItemUom?.uom?.udfudesc ?: info.uom?.udfudesc?.toString() ?: "N/A" + val fullUom = stockItemUom?.uom?.udfudesc ?: info.uom?.udfudesc?.toString() ?: "N/A" + val shortUom = stockItemUom?.uom?.udfShortDesc ?: info.uom?.udfShortDesc + field["uom"] = fullUom + val (qtyLabel, qtyDetail) = PoItemLabelTemplate.qtyLines(field["acceptedQty"] as String, fullUom, shortUom) + field["qtyLabel"] = qtyLabel + field["qtyDetail"] = qtyDetail field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" field["expiryDate"] = info.expiryDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" field["lotNo"] = info.lotNo!! field["supplier"] = info.supplier ?: "N/A" - val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) + val image = PoItemLabelTemplate.qrImage(qrCodeContent, labelVersion) field["qrCode"] = image fields.add(field) } diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/PoItemLabelTemplate.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/PoItemLabelTemplate.kt new file mode 100644 index 00000000..f4dcf00e --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/PoItemLabelTemplate.kt @@ -0,0 +1,66 @@ +package com.ffii.fpsms.modules.stock.service + +import com.ffii.core.utils.QrCodeUtil +import com.ffii.fpsms.modules.common.SettingNames +import com.ffii.fpsms.modules.settings.service.SettingsService +import java.awt.image.BufferedImage + +object PoItemLabelTemplate { + const val V1 = "qrCodeLabel/poItemPDF.jrxml" + const val V2 = "qrCodeLabel/poItemPDF_v2.jrxml" + const val V3 = "qrCodeLabel/poItemPDF_v3.jrxml" + const val V4 = "qrCodeLabel/poItemPDF_v4.jrxml" + + fun version(settingsService: SettingsService): Int { + val raw = settingsService.findByName(SettingNames.LABEL_PO_ITEM_VERSION) + .map { it.value } + .orElse("1") + ?.trim() + return when (raw) { + "2" -> 2 + "3" -> 3 + "4" -> 4 + else -> 1 + } + } + + fun path(version: Int): String = when (version) { + 2 -> V2 + 3 -> V3 + 4 -> V4 + else -> V1 + } + + fun qrImage(content: String, version: Int): BufferedImage = + if (version == 1) QrCodeUtil.generateQRCodeImage(content) + else QrCodeUtil.generateQRCodeImage(content, 500, 1) + + /** First line is qty + short unit. Second line is the full unit only when it differs. */ + fun qtyLines(qtyRaw: String?, fullRaw: String?, shortRaw: String?): Pair { + val qty = stripTrailingZeros(qtyRaw) + val full = blankUnit(fullRaw) + val short = blankUnit(shortRaw) + val same = short.isEmpty() || full.isEmpty() || short.equals(full, ignoreCase = true) + if (same) { + val unit = short.ifEmpty { full } + val line = when { + unit.isEmpty() -> qty + unit.first().isDigit() -> listOf(qty, unit).filter { it.isNotEmpty() }.joinToString(" ") + else -> qty + unit + } + return line to "" + } + return (qty + short) to full + } + + private fun blankUnit(raw: String?): String { + val text = raw?.trim().orEmpty() + return if (text.isEmpty() || text.equals("N/A", ignoreCase = true)) "" else text + } + + private fun stripTrailingZeros(raw: String?): String { + val text = raw?.trim().orEmpty() + if (!text.contains('.')) return text + return text.replace(Regex("0+$"), "").removeSuffix(".") + } +} diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index fa4c1abc..7d141fc2 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -3,7 +3,6 @@ package com.ffii.fpsms.modules.stock.service import com.ffii.core.exception.BadRequestException import com.ffii.core.support.AbstractBaseEntityService import com.ffii.core.support.JdbcDao -import com.ffii.core.utils.QrCodeUtil import com.ffii.fpsms.modules.common.CodeGenerator import com.ffii.fpsms.modules.master.entity.ItemsRepository import com.ffii.fpsms.modules.master.entity.QcItemRepository @@ -33,6 +32,7 @@ import com.ffii.fpsms.modules.master.entity.WarehouseRepository import com.ffii.fpsms.modules.master.service.ItemUomService //import com.ffii.fpsms.modules.master.service.BomOutputQtyService import com.ffii.fpsms.modules.master.service.PrinterService +import com.ffii.fpsms.modules.settings.service.SettingsService import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository @@ -98,6 +98,7 @@ open class StockInLineService( private val itemUomRepository: ItemUomRespository, private val itemUomService: ItemUomService, private val printerService: PrinterService, + private val settingsService: SettingsService, private val stockTakeLineRepository: StockTakeLineRepository, private val inventoryLotLineService: InventoryLotLineService, private val deliveryOrderRepository: DeliveryOrderRepository, @@ -445,6 +446,8 @@ open class StockInLineService( } dnNo = request.dnNo receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now() + // The item label prints this field under 收貨日期. + productionDate = request.productionDate?.atStartOfDay() ?: receiptDate productLotNo = request.productLotNo this.type = "NOR" status = StockInLineStatus.PENDING.status @@ -1208,7 +1211,8 @@ open class StockInLineService( @Throws(IOException::class) @Transactional open fun exportStockInLineQrcode(request: ExportQrCodeRequest): Map { - val QRCODE_PDF = "qrCodeLabel/poItemPDF.jrxml" + val labelVersion = PoItemLabelTemplate.version(settingsService) + val QRCODE_PDF = PoItemLabelTemplate.path(labelVersion) val resource = ClassPathResource(QRCODE_PDF) if (!resource.exists()) { println("here") @@ -1242,12 +1246,17 @@ open class StockInLineService( field["poCode"] = info.poCode ?: "N/A" field["itemType"] = info.itemType ?: "N/A" field["acceptedQty"] = "%.2f".format(acceptedQty) - field["uom"] = stockItemUom?.uom?.udfudesc ?: info.uom?.udfudesc?.toString() ?: "N/A" - field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" + val fullUom = stockItemUom?.uom?.udfudesc ?: info.uom?.udfudesc?.toString() ?: "N/A" + val shortUom = stockItemUom?.uom?.udfShortDesc ?: info.uom?.udfShortDesc + field["uom"] = fullUom + val (qtyLabel, qtyDetail) = PoItemLabelTemplate.qtyLines(field["acceptedQty"] as String, fullUom, shortUom) + field["qtyLabel"] = qtyLabel + field["qtyDetail"] = qtyDetail + field["productionDate"] = (info.productionDate ?: info.receiptDate)?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" field["expiryDate"] = info.expiryDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" field["lotNo"] = info.lotNo ?: "N/A" field["supplier"] = info.supplier ?: "N/A" - val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) + val image = PoItemLabelTemplate.qrImage(qrCodeContent, labelVersion) field["qrCode"] = image fields.add(field) } diff --git a/src/main/resources/db/changelog/changes/20261001_po_item_label_version/01_setting.sql b/src/main/resources/db/changelog/changes/20261001_po_item_label_version/01_setting.sql new file mode 100644 index 00000000..8af0d109 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20261001_po_item_label_version/01_setting.sql @@ -0,0 +1,9 @@ +--liquibase formatted sql +--changeset fpsms:20261001_po_item_label_version + +INSERT INTO `settings` (`name`, `value`, `category`, `type`) +SELECT 'LABEL.poItem.version', '1', 'LABEL', '1/2' +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `settings` WHERE `name` = 'LABEL.poItem.version' +); diff --git a/src/main/resources/db/changelog/changes/20261001_po_item_label_version/02_allow_version_3.sql b/src/main/resources/db/changelog/changes/20261001_po_item_label_version/02_allow_version_3.sql new file mode 100644 index 00000000..c704aa81 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20261001_po_item_label_version/02_allow_version_3.sql @@ -0,0 +1,7 @@ +--liquibase formatted sql +--changeset fpsms:20261001_po_item_label_version_allow_3 + +UPDATE `settings` +SET `type` = '1/2/3' +WHERE `name` = 'LABEL.poItem.version' + AND `type` = '1/2'; diff --git a/src/main/resources/db/changelog/changes/20261001_po_item_label_version/03_allow_version_4.sql b/src/main/resources/db/changelog/changes/20261001_po_item_label_version/03_allow_version_4.sql new file mode 100644 index 00000000..30cafc5c --- /dev/null +++ b/src/main/resources/db/changelog/changes/20261001_po_item_label_version/03_allow_version_4.sql @@ -0,0 +1,7 @@ +--liquibase formatted sql +--changeset fpsms:20261002_po_item_label_version_allow_4 + +UPDATE `settings` +SET `type` = '1/2/3/4' +WHERE `name` = 'LABEL.poItem.version' + AND `type` IN ('1/2', '1/2/3'); diff --git a/src/main/resources/qrCodeLabel/poItemPDF_v2.jrxml b/src/main/resources/qrCodeLabel/poItemPDF_v2.jrxml new file mode 100644 index 00000000..d4a57f3d --- /dev/null +++ b/src/main/resources/qrCodeLabel/poItemPDF_v2.jrxml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/qrCodeLabel/poItemPDF_v3.jrxml b/src/main/resources/qrCodeLabel/poItemPDF_v3.jrxml new file mode 100644 index 00000000..47c4aec3 --- /dev/null +++ b/src/main/resources/qrCodeLabel/poItemPDF_v3.jrxml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/qrCodeLabel/poItemPDF_v4.jrxml b/src/main/resources/qrCodeLabel/poItemPDF_v4.jrxml new file mode 100644 index 00000000..31e29cd3 --- /dev/null +++ b/src/main/resources/qrCodeLabel/poItemPDF_v4.jrxml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +