added version 4 of PO item labelproduction
| @@ -1,10 +1,20 @@ | |||||
| package com.ffii.fpsms.m18.model | package com.ffii.fpsms.m18.model | ||||
| import java.time.LocalDateTime | |||||
| data class SyncResult( | data class SyncResult( | ||||
| val totalProcessed: Int, | val totalProcessed: Int, | ||||
| val totalSuccess: Int, | val totalSuccess: Int, | ||||
| val totalFail: Int, | val totalFail: Int, | ||||
| val query: String? = null | val query: String? = null | ||||
| ) | |||||
| ) | |||||
| /** | |||||
| * Bounded suffix for [scheduler_sync_log.query]. Listing every skipped document | |||||
| * overflows VARCHAR(2000) on a multi-day PO window. | |||||
| */ | |||||
| fun skippedNotPendingQuerySuffix(entries: List<String>, previewLimit: Int = 20): String { | |||||
| if (entries.isEmpty()) return "" | |||||
| val shown = entries.take(previewLimit) | |||||
| val hidden = entries.size - shown.size | |||||
| val more = if (hidden > 0) "; ... +$hidden more" else "" | |||||
| return " | skipped (not pending, only pending may be overwritten) count=${entries.size}: ${shown.joinToString("; ")}$more" | |||||
| } | |||||
| @@ -649,11 +649,7 @@ open class M18DeliveryOrderService( | |||||
| logger.info("--------------------------------------------End - Saving M18 Delivery Order--------------------------------------------") | logger.info("--------------------------------------------End - Saving M18 Delivery Order--------------------------------------------") | ||||
| val skippedExistingSuffix = if (skippedList.isNotEmpty()) " | skipped=${skippedList.size}" else "" | val skippedExistingSuffix = if (skippedList.isNotEmpty()) " | skipped=${skippedList.size}" else "" | ||||
| val skippedNotPendingSuffix = if (skippedNotPendingList.isNotEmpty()) { | |||||
| " | skipped (not pending, only pending may be overwritten): ${skippedNotPendingList.joinToString("; ")}" | |||||
| } else { | |||||
| "" | |||||
| } | |||||
| val skippedNotPendingSuffix = skippedNotPendingQuerySuffix(skippedNotPendingList) | |||||
| return SyncResult( | return SyncResult( | ||||
| totalProcessed = successList.size + failList.size + skippedList.size + skippedNotPendingList.size, | totalProcessed = successList.size + failList.size + skippedList.size + skippedNotPendingList.size, | ||||
| totalSuccess = successList.size, | totalSuccess = successList.size, | ||||
| @@ -745,11 +745,7 @@ open class M18PurchaseOrderService( | |||||
| logger.info("--------------------------------------------End - Saving M18 Purchase Order--------------------------------------------") | logger.info("--------------------------------------------End - Saving M18 Purchase Order--------------------------------------------") | ||||
| val baseQuery = purchaseOrdersWithType?.query ?: "" | val baseQuery = purchaseOrdersWithType?.query ?: "" | ||||
| val skippedSuffix = if (skippedNotPendingList.isNotEmpty()) { | |||||
| " | skipped (not pending, only pending may be overwritten): ${skippedNotPendingList.joinToString("; ")}" | |||||
| } else { | |||||
| "" | |||||
| } | |||||
| val skippedSuffix = skippedNotPendingQuerySuffix(skippedNotPendingList) | |||||
| return SyncResult( | return SyncResult( | ||||
| totalProcessed = successList.size + failList.size + skippedNotPendingList.size, | totalProcessed = successList.size + failList.size + skippedNotPendingList.size, | ||||
| @@ -135,7 +135,7 @@ public abstract class SettingNames { | |||||
| /** JSON: last laser TCP send where printer returned receive (job order no., lot, itemId/stockInLineId, etc.) */ | /** 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"; | public static final String LASER_PRINT_LAST_RECEIVE_SUCCESS = "LASER_PRINT.lastReceiveSuccess"; | ||||
| /** PO / stock-in item label: 1 = original, 2 = enlarged, 3 = smaller QR with a larger item name. */ | |||||
| /** 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"; | public static final String LABEL_PO_ITEM_VERSION = "LABEL.poItem.version"; | ||||
| } | } | ||||
| @@ -87,6 +87,8 @@ open class SchedulerService( | |||||
| const val M18_BOM_SHOP_DEFAULT_CRON: String = "0 0 23 * * *" | const val M18_BOM_SHOP_DEFAULT_CRON: String = "0 0 23 * * *" | ||||
| /** Daily 00:00:15 — process job orders whose planStart was yesterday. */ | /** Daily 00:00:15 — process job orders whose planStart was yesterday. */ | ||||
| const val JO_PLAN_START_DEFAULT_CRON: String = "15 0 0 * * *" | const val JO_PLAN_START_DEFAULT_CRON: String = "15 0 0 * * *" | ||||
| /** MySQL TEXT is 65535 bytes; 16000 chars stays under that for utf8mb4. */ | |||||
| const val SYNC_LOG_TEXT_MAX_CHARS: Int = 16_000 | |||||
| } | } | ||||
| /** Class logger (was incorrectly wired to JwtTokenUtil, so all scheduler lines showed under that category). */ | /** Class logger (was incorrectly wired to JwtTokenUtil, so all scheduler lines showed under that category). */ | ||||
| @@ -730,14 +732,21 @@ open class SchedulerService( | |||||
| recordsProcessed = result?.totalProcessed ?: 0, | recordsProcessed = result?.totalProcessed ?: 0, | ||||
| recordsSaved = result?.totalSuccess ?: 0, | recordsSaved = result?.totalSuccess ?: 0, | ||||
| recordsFailed = result?.totalFail ?: 0, | recordsFailed = result?.totalFail ?: 0, | ||||
| errorMessage = error, | |||||
| query = result?.query ?: "", | |||||
| errorMessage = fitSyncLogText(error), | |||||
| query = fitSyncLogText(result?.query ?: ""), | |||||
| startTime = start, | startTime = start, | ||||
| endTime = LocalDateTime.now() | endTime = LocalDateTime.now() | ||||
| ) | ) | ||||
| schedulerSyncLogRepository.save(log) | schedulerSyncLogRepository.save(log) | ||||
| } | } | ||||
| private fun fitSyncLogText(value: String?): String? { | |||||
| if (value == null || value.length <= SYNC_LOG_TEXT_MAX_CHARS) return value | |||||
| val marker = "...(truncated, length=${value.length})" | |||||
| logger.warn("scheduler_sync_log text truncated from {} to {} characters", value.length, SYNC_LOG_TEXT_MAX_CHARS) | |||||
| return value.take((SYNC_LOG_TEXT_MAX_CHARS - marker.length).coerceAtLeast(0)) + marker | |||||
| } | |||||
| open fun getM18Dos2() { | open fun getM18Dos2() { | ||||
| logger.info("DO Scheduler 2 - DO") | logger.info("DO Scheduler 2 - DO") | ||||
| val currentTime = LocalDateTime.now() | val currentTime = LocalDateTime.now() | ||||
| @@ -103,7 +103,7 @@ open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> { | |||||
| orderDate = it["orderDate"] as LocalDateTime?, | orderDate = it["orderDate"] as LocalDateTime?, | ||||
| estimatedArrivalDate = it["estimatedArrivalDate"] as LocalDateTime?, | estimatedArrivalDate = it["estimatedArrivalDate"] as LocalDateTime?, | ||||
| supplierName = it["supplierName"] as String?, | supplierName = it["supplierName"] as String?, | ||||
| escalated = (it["escalated"] as Boolean?) ?: false, | |||||
| escalated = it["escalated"] == 1L, | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @@ -9,6 +9,7 @@ object PoItemLabelTemplate { | |||||
| const val V1 = "qrCodeLabel/poItemPDF.jrxml" | const val V1 = "qrCodeLabel/poItemPDF.jrxml" | ||||
| const val V2 = "qrCodeLabel/poItemPDF_v2.jrxml" | const val V2 = "qrCodeLabel/poItemPDF_v2.jrxml" | ||||
| const val V3 = "qrCodeLabel/poItemPDF_v3.jrxml" | const val V3 = "qrCodeLabel/poItemPDF_v3.jrxml" | ||||
| const val V4 = "qrCodeLabel/poItemPDF_v4.jrxml" | |||||
| fun version(settingsService: SettingsService): Int { | fun version(settingsService: SettingsService): Int { | ||||
| val raw = settingsService.findByName(SettingNames.LABEL_PO_ITEM_VERSION) | val raw = settingsService.findByName(SettingNames.LABEL_PO_ITEM_VERSION) | ||||
| @@ -18,6 +19,7 @@ object PoItemLabelTemplate { | |||||
| return when (raw) { | return when (raw) { | ||||
| "2" -> 2 | "2" -> 2 | ||||
| "3" -> 3 | "3" -> 3 | ||||
| "4" -> 4 | |||||
| else -> 1 | else -> 1 | ||||
| } | } | ||||
| } | } | ||||
| @@ -25,6 +27,7 @@ object PoItemLabelTemplate { | |||||
| fun path(version: Int): String = when (version) { | fun path(version: Int): String = when (version) { | ||||
| 2 -> V2 | 2 -> V2 | ||||
| 3 -> V3 | 3 -> V3 | ||||
| 4 -> V4 | |||||
| else -> V1 | else -> V1 | ||||
| } | } | ||||
| @@ -650,13 +650,15 @@ open class StockInLineService( | |||||
| open fun updatePurchaseOrderStatus(po : PurchaseOrder) : PurchaseOrder { | open fun updatePurchaseOrderStatus(po : PurchaseOrder) : PurchaseOrder { | ||||
| val pols = polRepository.findAllByPurchaseOrderIdAndDeletedIsFalse(po.id!!) | val pols = polRepository.findAllByPurchaseOrderIdAndDeletedIsFalse(po.id!!) | ||||
| var newStatus = PurchaseOrderStatus.PENDING | |||||
| if (pols.isNotEmpty()) { | |||||
| newStatus = if (pols.any { pol-> pol.status != PurchaseOrderLineStatus.COMPLETED }) { | |||||
| PurchaseOrderStatus.RECEIVING | |||||
| } else { | |||||
| // A line that was never received, or whose stock-in rows were all removed before putaway, is pending. | |||||
| // Receiving only while some line is still receiving or already completed. | |||||
| val newStatus = when { | |||||
| pols.isEmpty() || pols.all { it.status == null || it.status == PurchaseOrderLineStatus.PENDING } -> | |||||
| PurchaseOrderStatus.PENDING | |||||
| pols.all { it.status == PurchaseOrderLineStatus.COMPLETED } -> | |||||
| PurchaseOrderStatus.COMPLETED | PurchaseOrderStatus.COMPLETED | ||||
| } | |||||
| else -> | |||||
| PurchaseOrderStatus.RECEIVING | |||||
| } | } | ||||
| po.apply { | po.apply { | ||||
| status = newStatus | status = newStatus | ||||
| @@ -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'); | |||||
| @@ -0,0 +1,8 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset fpsms:20261002-scheduler-sync-log-text | |||||
| -- PO sync stores the M18 conds plus skipped PO codes. VARCHAR(2000) / VARCHAR(1024) | |||||
| -- reject that insert (MySQL 1406) and the scheduled task fails after the sync. | |||||
| ALTER TABLE `fpsmsdb`.`scheduler_sync_log` | |||||
| MODIFY COLUMN `query` TEXT NULL, | |||||
| MODIFY COLUMN `errorMessage` TEXT NULL; | |||||
| @@ -0,0 +1,172 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <!-- Created with Jaspersoft Studio version 6.21.3.final using JasperReports Library version 6.21.3-4a3078d20785ebe464f18037d738d12fc98c13cf --> | |||||
| <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="poItemPDF_v4" printOrder="Horizontal" pageWidth="425" pageHeight="283" orientation="Landscape" columnWidth="425" leftMargin="0" rightMargin="0" topMargin="0" bottomMargin="0" uuid="6cb85eb9-3573-42f3-a293-099db250aec0"> | |||||
| <property name="com.jaspersoft.studio.unit." value="pixel"/> | |||||
| <parameter name="lotNo" class="java.lang.String"/> | |||||
| <parameter name="itemName" class="java.lang.String"/> | |||||
| <parameter name="itemNo" class="java.lang.String"/> | |||||
| <parameter name="poCode" class="java.lang.String"/> | |||||
| <parameter name="qrCode" class="java.awt.Image"/> | |||||
| <parameter name="acceptedQty" class="java.math.BigDecimal"/> | |||||
| <parameter name="uom" class="java.lang.String"/> | |||||
| <parameter name="isTransfer" class="java.lang.String"/> | |||||
| <queryString> | |||||
| <![CDATA[]]> | |||||
| </queryString> | |||||
| <field name="lotNo" class="java.lang.String"/> | |||||
| <field name="itemName" class="java.lang.String"/> | |||||
| <field name="itemNo" class="java.lang.String"/> | |||||
| <field name="acceptedQty" class="java.lang.String"/> | |||||
| <field name="uom" class="java.lang.String"/> | |||||
| <field name="qtyLabel" class="java.lang.String"/> | |||||
| <field name="qtyDetail" class="java.lang.String"/> | |||||
| <field name="qrCode" class="java.awt.Image"/> | |||||
| <field name="poCode" class="java.lang.String"/> | |||||
| <field name="supplier" class="java.lang.String"/> | |||||
| <field name="productionDate" class="java.lang.String"/> | |||||
| <field name="expiryDate" class="java.lang.String"/> | |||||
| <background> | |||||
| <band splitType="Stretch"/> | |||||
| </background> | |||||
| <detail> | |||||
| <band height="279" splitType="Prevent"> | |||||
| <staticText> | |||||
| <reportElement x="4" y="0" width="222" height="16" uuid="c62ac00a-c495-462c-93bc-97bdd3cdb395"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="11" isBold="true"/> | |||||
| </textElement> | |||||
| <text><![CDATA[收貨日期:]]></text> | |||||
| </staticText> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="4" y="16" width="222" height="52" uuid="74a5cfba-046d-42f4-92d7-113729514a6f"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="48" isBold="true"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{productionDate}]]></textFieldExpression> | |||||
| </textField> | |||||
| <staticText> | |||||
| <reportElement x="4" y="70" width="222" height="16" uuid="32edcad1-23c1-4d41-8c9a-352d9e6ef377"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="11" isBold="true"/> | |||||
| </textElement> | |||||
| <text><![CDATA[到期日:]]></text> | |||||
| </staticText> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="4" y="86" width="222" height="52" uuid="97e4a9bb-fc84-4074-b523-ddf9eb9ed2d2"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="48" isBold="true"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{expiryDate}]]></textFieldExpression> | |||||
| </textField> | |||||
| <staticText> | |||||
| <reportElement x="8" y="142" width="104" height="16" uuid="96d46fb1-939d-4f3e-b8f0-172b69b85e9f"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="10" isBold="true"/> | |||||
| </textElement> | |||||
| <text><![CDATA[物料編號:]]></text> | |||||
| </staticText> | |||||
| <staticText> | |||||
| <reportElement x="116" y="142" width="110" height="16" uuid="53a2d7cf-bd50-4264-abee-346823cd49dd"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="10" isBold="true"/> | |||||
| </textElement> | |||||
| <text><![CDATA[數量:]]></text> | |||||
| </staticText> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="8" y="158" width="104" height="22" uuid="1f289946-b945-4f79-8515-212368b8be3b"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="18" isBold="true"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{itemNo}]]></textFieldExpression> | |||||
| </textField> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="116" y="158" width="110" height="18" uuid="629d9af8-c73f-4614-ba9d-a582e2ed4a29"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="14" isBold="true"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{qtyLabel}]]></textFieldExpression> | |||||
| </textField> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="116" y="176" width="110" height="12" uuid="7c1a0e44-6b2d-4a91-9f10-2d8e5b6c4a11"> | |||||
| <printWhenExpression><![CDATA[$F{qtyDetail} != null && !$F{qtyDetail}.trim().isEmpty()]]></printWhenExpression> | |||||
| </reportElement> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="8"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{qtyDetail}]]></textFieldExpression> | |||||
| </textField> | |||||
| <staticText> | |||||
| <reportElement x="8" y="192" width="218" height="16" uuid="5c3f9b03-222c-4c94-ab58-6bc86d44c5ce"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="10" isBold="true"/> | |||||
| </textElement> | |||||
| <text><![CDATA[物料名稱:]]></text> | |||||
| </staticText> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="8" y="208" width="218" height="68" uuid="e04b094d-2d16-4060-b6d9-2e6033a673ab"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="48" isBold="true"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{itemName}]]></textFieldExpression> | |||||
| </textField> | |||||
| <image scaleImage="FillFrame" hAlign="Center" vAlign="Middle"> | |||||
| <reportElement x="230" y="4" width="190" height="190" uuid="5dd35c73-973b-4f0d-9cad-13305a29a9fd"/> | |||||
| <imageExpression><![CDATA[$F{qrCode}]]></imageExpression> | |||||
| </image> | |||||
| <staticText> | |||||
| <reportElement x="230" y="198" width="70" height="24" uuid="f5f145f1-123f-4fe5-90fa-8550b3d2eb63"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="12" isBold="true"/> | |||||
| </textElement> | |||||
| <text><![CDATA[批次編號:]]></text> | |||||
| </staticText> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="300" y="198" width="120" height="24" uuid="402f35fc-b11a-4bf1-982d-e12c622f38ea"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="16" isBold="true"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{lotNo}]]></textFieldExpression> | |||||
| </textField> | |||||
| <staticText> | |||||
| <reportElement x="230" y="224" width="42" height="16" uuid="c2041205-51f8-45bb-a1d1-e3de37eebed7"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="8"/> | |||||
| </textElement> | |||||
| <text><![CDATA[供應商:]]></text> | |||||
| </staticText> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="272" y="224" width="144" height="16" uuid="ab82ca39-8840-4471-b7c0-cdeec9cb5693"/> | |||||
| <textElement verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="8"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{supplier}]]></textFieldExpression> | |||||
| </textField> | |||||
| <staticText> | |||||
| <reportElement x="230" y="242" width="72" height="16" uuid="1b1efafd-049a-432e-93c4-9662273011f7"/> | |||||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="8"/> | |||||
| </textElement> | |||||
| <text><![CDATA[採購訂單編號:]]></text> | |||||
| </staticText> | |||||
| <textField textAdjust="ScaleFont" isBlankWhenNull="true"> | |||||
| <reportElement x="302" y="242" width="114" height="16" uuid="b805e20a-9a36-45c8-8255-23b6cc58f47c"/> | |||||
| <textElement verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="8"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$P{poCode}]]></textFieldExpression> | |||||
| </textField> | |||||
| <textField> | |||||
| <reportElement x="265" y="87" width="120" height="24" uuid="d66a64b0-3335-4846-bcb0-a67594520055"> | |||||
| <printWhenExpression><![CDATA[$P{isTransfer} != null && !$P{isTransfer}.toString().trim().isEmpty()]]></printWhenExpression> | |||||
| </reportElement> | |||||
| <box> | |||||
| <pen lineWidth="2.0"/> | |||||
| </box> | |||||
| <textElement textAlignment="Center" verticalAlignment="Middle"> | |||||
| <font fontName="微軟正黑體" size="11" isBold="true" isUnderline="true"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$P{isTransfer}]]></textFieldExpression> | |||||
| </textField> | |||||
| </band> | |||||
| </detail> | |||||
| </jasperReport> | |||||