diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/LaserBag2AutoSendService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/LaserBag2AutoSendService.kt index 7be6d41..e536975 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/LaserBag2AutoSendService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/LaserBag2AutoSendService.kt @@ -13,6 +13,11 @@ import java.time.LocalDate * ([LASER_PRINT.itemCodes]), then sends Bag2-style laser TCP payloads via [PlasticBagPrinterService.sendLaserBag2Job], * which uses [com.ffii.fpsms.modules.common.SettingNames.LASER_PRINT_HOST] / [LASER_PRINT_PORT] from the database. * + * Safety rules: + * 1) Probe laser TCP first; offline = skip. + * 2) If [LASER_PRINT.lastReceiveSuccess] is already on [planStart] (AUTO or MANUAL), skip for the day. + * 3) Send only the first matching job order. + * * [sendsPerJob] TCP rounds per job order (default from `laser.bag2.auto-send.sends-per-job`, typically 1). * [sendLaserBag2Job] may still retry once internally on failure. */ @@ -41,8 +46,19 @@ class LaserBag2AutoSendService( ) } + if (plasticBagPrinterService.hasLaserBag2ReceiveOnDate(planStart)) { + logger.info("Laser Bag2 auto-send skipped: already sent (AUTO/MANUAL) on {}", planStart) + return LaserBag2AutoSendReport( + planStart = planStart, + jobOrdersFound = 0, + jobOrdersProcessed = 0, + results = emptyList(), + ) + } + val orders = plasticBagPrinterService.listLaserPrintJobOrders(planStart) - val toProcess = if (limitPerRun > 0) orders.take(limitPerRun) else orders + val effectiveLimit = if (limitPerRun > 0) limitPerRun.coerceAtMost(1) else 1 + val toProcess = orders.take(effectiveLimit) val results = mutableListOf() logger.info( 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 6a7381d..fcad435 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 @@ -55,6 +55,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import java.time.Duration import java.time.Instant import java.time.LocalDate +import java.time.ZoneId // Data class to store bitmap bytes + width (for XML) data class BitmapResult(val bytes: ByteArray, val width: Int) @@ -80,6 +81,7 @@ class PlasticBagPrinterService( private val objectMapper: ObjectMapper, ) { private val logger = LoggerFactory.getLogger(javaClass) + private val hongKongZoneId = ZoneId.of("Asia/Hong_Kong") companion object { private const val DEFAULT_LASER_BAG2_HOST = "192.168.18.77" @@ -266,6 +268,23 @@ class PlasticBagPrinterService( return Triple(ok, host, port) } + /** + * Returns true when [SettingNames.LASER_PRINT_LAST_RECEIVE_SUCCESS] has a [sentAt] on [date] + * (interpreted in Asia/Hong_Kong local date). + */ + fun hasLaserBag2ReceiveOnDate(date: LocalDate): Boolean { + val last = readLaserLastReceiveSuccessFromSettings() ?: return false + val sentAtRaw = last.sentAt?.trim().orEmpty() + if (sentAtRaw.isEmpty()) return false + return try { + val sentDateHk = Instant.parse(sentAtRaw).atZone(hongKongZoneId).toLocalDate() + sentDateHk == date + } catch (e: Exception) { + logger.warn("Could not parse LASER_PRINT.lastReceiveSuccess.sentAt='{}': {}", sentAtRaw, e.message) + false + } + } + private fun sendLaserBag2TcpOnce( ip: String, port: Int, diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt index a606555..f752803 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt @@ -74,7 +74,8 @@ class PlasticBagPrinterController( /** * Same as /laserPrint row workflow: list job orders for [planStart] filtered by LASER_PRINT.itemCodes, - * then for each (up to [limitPerRun], 0 = all) send laser TCP using LASER_PRINT.host/port (`laser.bag2.auto-send.sends-per-job` rounds per job). + * then auto-send at most the first matching job order via LASER_PRINT.host/port + * (`laser.bag2.auto-send.sends-per-job` rounds for that one job). * For manual runs from /testing; scheduler uses [LaserBag2AutoSendScheduler]. */ @PostMapping("/laser-bag2-auto-send") diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index d9d9024..83b725d 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -36,11 +36,11 @@ scheduler: inventoryLotExpiry: enabled: true -# Laser Bag2 (/laserPrint) TCP auto-send; uses LASER_PRINT host/port/itemCodes from DB (see application.yml for interval-ms, limit-per-run). +# Laser Bag2 (/laserPrint) TCP auto-send; uses LASER_PRINT host/port/itemCodes from DB and sends first matching job only. laser: bag2: auto-send: - enabled: false + enabled: true sends-per-job: 1 m18: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f01703f..9d4cb97 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -64,7 +64,7 @@ ngpcl: push-url: ${NGPCL_PUSH_URL:} # Laser Bag2 (/laserPrint) auto-send: same as listing + TCP send using DB LASER_PRINT.host / port / itemCodes. -# Scheduler is off by default. limit-per-run: max job orders per tick (1 = first matching only); 0 = all matches (heavy). +# Scheduler is off by default. Service enforces first matching job order only (limit-per-run values >1 are ignored). # sends-per-job: TCP payloads per job order per tick (1 = single send; each send may retry once on failure inside sendLaserBag2Job). laser: bag2: