| @@ -13,6 +13,11 @@ import java.time.LocalDate | |||||
| * ([LASER_PRINT.itemCodes]), then sends Bag2-style laser TCP payloads via [PlasticBagPrinterService.sendLaserBag2Job], | * ([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. | * 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). | * [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. | * [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 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<LaserBag2JobSendResult>() | val results = mutableListOf<LaserBag2JobSendResult>() | ||||
| logger.info( | logger.info( | ||||
| @@ -55,6 +55,7 @@ import com.fasterxml.jackson.databind.ObjectMapper | |||||
| import java.time.Duration | import java.time.Duration | ||||
| import java.time.Instant | import java.time.Instant | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.ZoneId | |||||
| // Data class to store bitmap bytes + width (for XML) | // Data class to store bitmap bytes + width (for XML) | ||||
| data class BitmapResult(val bytes: ByteArray, val width: Int) | data class BitmapResult(val bytes: ByteArray, val width: Int) | ||||
| @@ -80,6 +81,7 @@ class PlasticBagPrinterService( | |||||
| private val objectMapper: ObjectMapper, | private val objectMapper: ObjectMapper, | ||||
| ) { | ) { | ||||
| private val logger = LoggerFactory.getLogger(javaClass) | private val logger = LoggerFactory.getLogger(javaClass) | ||||
| private val hongKongZoneId = ZoneId.of("Asia/Hong_Kong") | |||||
| companion object { | companion object { | ||||
| private const val DEFAULT_LASER_BAG2_HOST = "192.168.18.77" | private const val DEFAULT_LASER_BAG2_HOST = "192.168.18.77" | ||||
| @@ -266,6 +268,23 @@ class PlasticBagPrinterService( | |||||
| return Triple(ok, host, port) | 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( | private fun sendLaserBag2TcpOnce( | ||||
| ip: String, | ip: String, | ||||
| port: Int, | port: Int, | ||||
| @@ -74,7 +74,8 @@ class PlasticBagPrinterController( | |||||
| /** | /** | ||||
| * Same as /laserPrint row workflow: list job orders for [planStart] filtered by LASER_PRINT.itemCodes, | * 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]. | * For manual runs from /testing; scheduler uses [LaserBag2AutoSendScheduler]. | ||||
| */ | */ | ||||
| @PostMapping("/laser-bag2-auto-send") | @PostMapping("/laser-bag2-auto-send") | ||||
| @@ -36,11 +36,11 @@ scheduler: | |||||
| inventoryLotExpiry: | inventoryLotExpiry: | ||||
| enabled: true | 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: | laser: | ||||
| bag2: | bag2: | ||||
| auto-send: | auto-send: | ||||
| enabled: false | |||||
| enabled: true | |||||
| sends-per-job: 1 | sends-per-job: 1 | ||||
| m18: | m18: | ||||
| @@ -64,7 +64,7 @@ ngpcl: | |||||
| push-url: ${NGPCL_PUSH_URL:} | push-url: ${NGPCL_PUSH_URL:} | ||||
| # Laser Bag2 (/laserPrint) auto-send: same as listing + TCP send using DB LASER_PRINT.host / port / itemCodes. | # 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). | # sends-per-job: TCP payloads per job order per tick (1 = single send; each send may retry once on failure inside sendLaserBag2Job). | ||||
| laser: | laser: | ||||
| bag2: | bag2: | ||||