diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18DeliveryOrderService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18DeliveryOrderService.kt index dc95196..d6cf5b9 100644 --- a/src/main/java/com/ffii/fpsms/m18/service/M18DeliveryOrderService.kt +++ b/src/main/java/com/ffii/fpsms/m18/service/M18DeliveryOrderService.kt @@ -57,16 +57,33 @@ open class M18DeliveryOrderService( open fun getDeliveryOrdersWithType(request: M18CommonRequest): M18PurchaseOrderListResponseWithType? { val deliveryOrders = M18PurchaseOrderListResponseWithType(mutableListOf()) val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") - val dateFrom = request.modifiedDateFrom?.let { - LocalDateTime.parse(it, formatter).toLocalDate().toString() + + val lastDateConds = if (request.modifiedDateFrom != null && request.modifiedDateTo != null) { + val dateFrom = LocalDateTime.parse(request.modifiedDateFrom, formatter).toString() + val dateTo = LocalDateTime.parse(request.modifiedDateTo, formatter).toString() + + "lastModifyDate=largerOrEqual=$dateFrom=and=lastModifyDate=lessOrEqual=$dateTo" + } else { + "" } - val dateTo = request.modifiedDateTo?.let { - LocalDateTime.parse(it, formatter).toLocalDate().toString() + + val dDateConds = if (request.dDateFrom != null && request.dDateTo != null) { + val dateFrom = LocalDateTime.parse(request.dDateFrom, formatter).toLocalDate().toString() + val dateTo = LocalDateTime.parse(request.dDateTo, formatter).toLocalDate().toString() + + "dDate=largerOrEqual=$dateFrom=and=dDate=lessOrEqual=$dateTo" + } else { + "" } - val lastDateConds = - //"lastModifyDate=largerOrEqual=${request.modifiedDateFrom ?: lastModifyDateStart}=and=lastModifyDate=lessOrEqual=${request.modifiedDateTo ?: lastModifyDateEnd}" - "dDate=largerOrEqual=${dateFrom ?: lastModifyDateStart}=and=dDate=lessOrEqual=${dateTo ?: lastModifyDateEnd}" + val dDateEqualConds = if (request.dDateEqual != null) { + val dDateEqual = LocalDateTime.parse(request.dDateEqual, formatter).toLocalDate().toString() + + "dDate=equal=$dDateEqual" + } else { + "" + } + // Shop PO val shopPoBuyers = commonUtils.listToString(listOf(m18Config.BEID_TOA), "beId=equal=", "=or=") val shopPoSupplier = commonUtils.listToString( @@ -74,8 +91,20 @@ open class M18DeliveryOrderService( "venId=equal=", "=or=" ) - val shopPoConds = "(${shopPoBuyers})=and=(${shopPoSupplier})=and=(${lastDateConds})" + var shopPoConds = "(${shopPoBuyers})=and=(${shopPoSupplier})" + + if (request.modifiedDateFrom != null && request.modifiedDateTo != null) { + shopPoConds += "=and=(${lastDateConds})" + } + if (request.dDateFrom != null && request.dDateTo != null) { + shopPoConds += "=and=(${dDateConds})" + } + if (request.dDateEqual != null) { + shopPoConds += "=and=(${dDateEqualConds})" + } + println("shopPoConds: ${shopPoConds}") + val shopPoParams = M18PurchaseOrderListRequest( params = null, conds = shopPoConds diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt index 5a9367e..7374c5c 100644 --- a/src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt +++ b/src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt @@ -59,15 +59,32 @@ open class M18PurchaseOrderService( open fun getPurchaseOrdersWithType(request: M18CommonRequest): M18PurchaseOrderListResponseWithType? { val purchaseOrders = M18PurchaseOrderListResponseWithType(mutableListOf()) val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") - val dateFrom = request.modifiedDateFrom?.let { - LocalDateTime.parse(it, formatter).toLocalDate().toString() + val lastDateConds = if (request.modifiedDateFrom != null && request.modifiedDateTo != null) { + val dateFrom = LocalDateTime.parse(request.modifiedDateFrom, formatter).toLocalDate().toString() + val dateTo = LocalDateTime.parse(request.modifiedDateTo, formatter).toLocalDate().toString() + + "lastModifyDate=largerOrEqual=$dateFrom=and=lastModifyDate=lessOrEqual=$dateTo" + } else { + "" } - val dateTo = request.modifiedDateTo?.let { - LocalDateTime.parse(it, formatter).toLocalDate().toString() + + val dDateConds = if (request.dDateFrom != null && request.dDateTo != null) { + val dateFrom = LocalDateTime.parse(request.dDateFrom, formatter).toLocalDate().toString() + val dateTo = LocalDateTime.parse(request.dDateTo, formatter).toLocalDate().toString() + + "dDate=largerOrEqual=$dateFrom=and=dDate=lessOrEqual=$dateTo" + } else { + "" } - val lastDateConds = - //"lastModifyDate=largerOrEqual=${request.modifiedDateFrom ?: lastModifyDateStart}=and=lastModifyDate=lessOrEqual=${request.modifiedDateTo ?: lastModifyDateEnd}" - "dDate=largerOrEqual=${dateFrom ?: lastModifyDateStart}=and=dDate=lessOrEqual=${dateTo ?: lastModifyDateEnd}" + + val dDateEqualConds = if (request.dDateEqual != null) { + val dDateEqual = LocalDateTime.parse(request.dDateEqual, formatter).toLocalDate().toString() + + "dDate=equal=$dDateEqual" + } else { + "" + } + // Material PO val materialPoBuyers = commonUtils.listToString(listOf(m18Config.BEID_PP, m18Config.BEID_PF), "beId=equal=", "=or=") @@ -76,8 +93,21 @@ open class M18PurchaseOrderService( "venId=unequal=", "=or=" ) - val materialPoConds = "(${materialPoBuyers})=and=(${materialPoSupplierNot})=and=(${lastDateConds})" + var materialPoConds = "(${materialPoBuyers})=and=(${materialPoSupplierNot}))" + + if (request.modifiedDateFrom != null && request.modifiedDateTo != null) { + materialPoConds += "=and=(${lastDateConds})" + } + if (request.dDateFrom != null && request.dDateTo != null) { + materialPoConds += "=and=(${dDateConds})" + } + if (request.dDateEqual != null) { + materialPoConds += "=and=(${dDateEqualConds})" + } + println("materialPoConds: ${materialPoConds}") + + val materialPoParams = M18PurchaseOrderListRequest( params = null, conds = materialPoConds diff --git a/src/main/java/com/ffii/fpsms/m18/web/models/M18TestRequest.kt b/src/main/java/com/ffii/fpsms/m18/web/models/M18TestRequest.kt index e682762..06c358d 100644 --- a/src/main/java/com/ffii/fpsms/m18/web/models/M18TestRequest.kt +++ b/src/main/java/com/ffii/fpsms/m18/web/models/M18TestRequest.kt @@ -3,4 +3,7 @@ package com.ffii.fpsms.m18.web.models data class M18CommonRequest( val modifiedDateFrom: String? = null, val modifiedDateTo: String? = null, + val dDateFrom: String? = null, + val dDateTo: String? = null, + val dDateEqual: String? = null, ) 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 69620b4..a8aa84e 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java +++ b/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java @@ -25,6 +25,9 @@ public abstract class SettingNames { */ public static final String SCHEDULE_M18_PO = "SCHEDULE.m18.po"; + public static final String SCHEDULE_M18_DO1 = "SCHEDULE.m18.do1"; + public static final String SCHEDULE_M18_DO2 = "SCHEDULE.m18.do2"; + public static final String SCHEDULE_M18_MASTER = "SCHEDULE.m18.master"; public static final String SCHEDULE_PROD_ROUGH = "SCHEDULE.prod.rough"; diff --git a/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt b/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt index 16c8837..6684bc1 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt @@ -31,19 +31,23 @@ open class SchedulerService( ) { var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd") + val dateTimeStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val defaultCronExpression = "0 0 2 * * *"; - @Volatile + //@Volatile var scheduledM18Po: ScheduledFuture<*>? = null + var scheduledM18Do1: ScheduledFuture<*>? = null + var scheduledM18Do2: ScheduledFuture<*>? = null + @Volatile var scheduledM18Master: ScheduledFuture<*>? = null - @Volatile - var scheduledRoughProd: ScheduledFuture<*>? = null + //@Volatile + //var scheduledRoughProd: ScheduledFuture<*>? = null - @Volatile - var scheduledDetailedProd: ScheduledFuture<*>? = null + //@Volatile + //var scheduledDetailedProd: ScheduledFuture<*>? = null // Common Function fun isValidCronExpression(cronExpression: String): Boolean { @@ -55,52 +59,63 @@ open class SchedulerService( } } - fun commonSchedule(scheduled: ScheduledFuture<*>?, settingName: String, scheduleFunc: () -> Unit) { + fun commonSchedule(scheduled: ScheduledFuture<*>?, settingName: String, scheduleFunc: () -> Unit): ScheduledFuture<*>? { scheduled?.cancel(false) - var cron = settingsService.findByName(settingName).getOrNull()?.value ?: defaultCronExpression; + var cron = settingsService.findByName(settingName).getOrNull()?.value ?: defaultCronExpression if (!isValidCronExpression(cron)) { cron = defaultCronExpression } - scheduledM18Po = taskScheduler.schedule( - { - scheduleFunc() - }, + + // Now Kotlin is happy because both the method and the function allow nulls + return taskScheduler.schedule( + { scheduleFunc() }, CronTrigger(cron) ) } // Init Scheduler -// @PostConstruct + @PostConstruct fun init() { -// scheduleM18PoTask(); + //scheduleM18PoTask(); + scheduleM18Do1(); + scheduleM18Do2(); scheduleM18MasterData(); - scheduleRoughProd(); - scheduleDetailedProd(); + //scheduleRoughProd(); + //scheduleDetailedProd(); } // Scheduler // --------------------------- FP-MTMS --------------------------- // - fun scheduleRoughProd() { - commonSchedule(scheduledRoughProd, SettingNames.SCHEDULE_PROD_ROUGH, ::getRoughProdSchedule) - } + //fun scheduleRoughProd() { + // commonSchedule(scheduledRoughProd, SettingNames.SCHEDULE_PROD_ROUGH, ::getRoughProdSchedule) + //} - fun scheduleDetailedProd() { - commonSchedule(scheduledDetailedProd, SettingNames.SCHEDULE_PROD_DETAILED, ::getDetailedProdSchedule) - } + //fun scheduleDetailedProd() { + // commonSchedule(scheduledDetailedProd, SettingNames.SCHEDULE_PROD_DETAILED, ::getDetailedProdSchedule) + //} // --------------------------- M18 --------------------------- // fun scheduleM18PoTask() { commonSchedule(scheduledM18Po, SettingNames.SCHEDULE_M18_PO, ::getM18Pos) } + fun scheduleM18Do1() { + commonSchedule(scheduledM18Do1, SettingNames.SCHEDULE_M18_DO1, ::getM18Dos1) + } + + fun scheduleM18Do2() { + commonSchedule(scheduledM18Do2, SettingNames.SCHEDULE_M18_DO2, ::getM18Dos2) + } + fun scheduleM18MasterData() { commonSchedule(scheduledM18Master, SettingNames.SCHEDULE_M18_MASTER, ::getM18MasterData) } // Function for schedule // --------------------------- FP-MTMS --------------------------- // + open fun getRoughProdSchedule() { try { logger.info("Daily Scheduler - Rough Prod") @@ -123,7 +138,7 @@ open class SchedulerService( throw RuntimeException("Error generate schedule: ${e.message}", e) } } - + open fun getDetailedProdSchedule() { try { logger.info("Daily Scheduler - Detailed Prod") @@ -145,7 +160,6 @@ open class SchedulerService( throw RuntimeException("Error generate schedule: ${e.message}", e) } } - // --------------------------- M18 --------------------------- // // @Async // @Scheduled(cron = "0 0 2 * * *") // (SS/MM/HH/DD/MM/YY) @@ -175,6 +189,46 @@ open class SchedulerService( // logger.info("yesterday: ${yesterday.format(dataStringFormat)}") } + open fun getM18Dos1() { + logger.info("DO Scheduler 1 - DO") + val currentTime = LocalDateTime.now() + val today = currentTime.toLocalDate().atStartOfDay() + val twoDaysLater = today.plusDays(2L) + + var requestDO = M18CommonRequest( + dDateTo = twoDaysLater.format(dateTimeStringFormat), + dDateFrom = twoDaysLater.format(dateTimeStringFormat) + ) + + m18DeliveryOrderService.saveDeliveryOrders(requestDO); + } + + open fun getM18Dos2() { + logger.info("DO Scheduler 2 - DO") + val currentTime = LocalDateTime.now() + + // .atStartOfDay() results in 00:00:00 + val today = currentTime.toLocalDate().atStartOfDay() + val ysd = today.minusDays(1L) + val tmr = today.plusDays(1L) + + // Set to 19:00:00 of yesterday + val ysdNight = ysd.withHour(19).withMinute(0).withSecond(0) + + // Set to 11:00:00 of today + val todayEleven = today.withHour(11).withMinute(0).withSecond(0) + + val requestDO = M18CommonRequest( + // These will now produce "yyyy-MM-dd HH:mm:ss" + dDateTo = tmr.format(dateTimeStringFormat), // e.g. 2026-01-19 00:00:00 + dDateFrom = tmr.format(dateTimeStringFormat), // e.g. 2026-01-19 00:00:00 + modifiedDateTo = todayEleven.format(dateTimeStringFormat), // 2026-01-18 11:00:00 + modifiedDateFrom = ysdNight.format(dateTimeStringFormat) // 2026-01-17 19:00:00 + ) + + m18DeliveryOrderService.saveDeliveryOrders(requestDO) + } + open fun getM18MasterData() { logger.info("Daily Scheduler - Master Data") val currentTime = LocalDateTime.now() 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 0ae1cdc..74ac5e7 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 @@ -4,7 +4,6 @@ import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository import com.ffii.fpsms.modules.jobOrder.web.model.PrintRequest import com.ffii.fpsms.modules.jobOrder.web.model.LaserRequest import org.springframework.stereotype.Service - import java.awt.Color import java.awt.Font import java.awt.Graphics2D @@ -24,112 +23,117 @@ import java.nio.charset.Charset import java.io.BufferedReader import java.io.InputStreamReader +import java.net.ConnectException +import java.net.SocketTimeoutException -// 1. Data class to store both the raw bytes and the width for XML injection +// Data class to store bitmap bytes + width (for XML) data class BitmapResult(val bytes: ByteArray, val width: Int) @Service open class PlasticBagPrinterService( val jobOrderRepository: JobOrderRepository, ) { - fun generatePrintJobBundle(itemCode: String, lotNo: String, expiryDate: String, productName: String): ByteArray { + + fun generatePrintJobBundle( + itemCode: String, + lotNo: String, + expiryDate: String, + productName: String + ): ByteArray { val baos = ByteArrayOutputStream() ZipOutputStream(baos).use { zos -> - - // Generate Bitmaps and capture their widths - val productBmp = createMonochromeBitmap(productName, 704) - val codeBmp = createMonochromeBitmap(itemCode, 1173) - val expBmp = createMonochromeBitmap(expiryDate, 1173) - val qrBmp = createQrCodeBitmap("$itemCode|$lotNo|$expiryDate", 1000) + + // Reduced sizes to make files smaller while keeping readability/scanability + val productBmp = createMonochromeBitmap(productName, 520) // was 704 + val codeBmp = createMonochromeBitmap(itemCode, 900) // was 1173 + val expBmp = createMonochromeBitmap(expiryDate, 900) // was 1173 + val qrBmp = createQrCodeBitmap("$itemCode|$lotNo|$expiryDate", 450) // was 1000 val nameFile = "${lotNo}_product.bmp" val codeFile = "${lotNo}_code.bmp" - val expFile = "${lotNo}_expiry.bmp" - val qrFile = "${lotNo}_qr.bmp" + val expFile = "${lotNo}_expiry.bmp" + val qrFile = "${lotNo}_qr.bmp" - // Now "bytes" property is resolved addToZip(zos, nameFile, productBmp.bytes) addToZip(zos, codeFile, codeBmp.bytes) - addToZip(zos, expFile, expBmp.bytes) - addToZip(zos, qrFile, qrBmp.bytes) - - // 2. Generate the .image file with XML headers and mandatory X40 tags - val imageXml = """ - - - - false - DEG_0 - 53007100 - - - LOGO - 0250 - BLACK - $nameFile - ${productBmp.width} - 704 - - - LOGO_2 - 10001250 - BLACK - $codeFile - ${codeBmp.width} - 1173 - - - LOGO_3 - 5002500 - BLACK - $expFile - ${expBmp.width} - 1173 - - - LOGO_4 - 7503750 - BLACK - $qrFile - ${qrBmp.width} - 1000 - - - - """.trimIndent() - + addToZip(zos, expFile, expBmp.bytes) + addToZip(zos, qrFile, qrBmp.bytes) + + // Minified XML - no indentation/newlines between tags + val imageXml = """falseDEG_053007100LOGO0250BLACK$nameFile${productBmp.width}520LOGO_210001250BLACK$codeFile${codeBmp.width}900LOGO_35002500BLACK$expFile${expBmp.width}900LOGO_47503750BLACK$qrFile${qrBmp.width}450""".trimIndent() + addToZip(zos, "$lotNo.image", imageXml.toByteArray(Charsets.UTF_8)) - + val jobXml = """$lotNo.image""" addToZip(zos, "$lotNo.job", jobXml.toByteArray(Charsets.UTF_8)) } return baos.toByteArray() } - private fun createMonochromeBitmap(text: String, maxHeight: Int): BitmapResult { + private fun createMonochromeBitmap(text: String, targetHeight: Int): BitmapResult { + // Step 1: Measure text width with temporary image val tempImg = BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY) val g2dTemp = tempImg.createGraphics() - val font = Font("SimSun", Font.BOLD, (maxHeight * 0.8).toInt()) + val font = Font("SimSun", Font.BOLD, (targetHeight * 0.92).toInt()) g2dTemp.font = font val metrics = g2dTemp.getFontMetrics(font) - - val width = metrics.stringWidth(text).let { if (it < 1) 1 else it } - val height = maxHeight + val textWidth = metrics.stringWidth(text).coerceAtLeast(1) g2dTemp.dispose() - val finalImg = BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY) - val g2d = finalImg.createGraphics() - g2d.color = Color.WHITE - g2d.fillRect(0, 0, width, height) - g2d.color = Color.BLACK - g2d.font = font - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF) - g2d.drawString(text, 0, metrics.ascent) - g2d.dispose() + // Step 2: Create actual image + // CHANGE 1: Changed 'val' to 'var' so we can reassign it on line 84 + var img = BufferedImage(textWidth, targetHeight, BufferedImage.TYPE_BYTE_BINARY) + + // CHANGE 2: Removed 'val g2d =' because it was unused + img.createGraphics().apply { + color = Color.WHITE + fillRect(0, 0, textWidth, targetHeight) + color = Color.BLACK + this.font = font // use 'this.font' to be explicit inside apply + setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF) + drawString(text, 0, metrics.ascent) + dispose() + } + + // Step 3: Crop excess white space + // This line (84) will now work because 'img' is a 'var' + img = cropToContent(img) val baos = ByteArrayOutputStream() - ImageIO.write(finalImg, "bmp", baos) - return BitmapResult(baos.toByteArray(), width) + ImageIO.write(img, "bmp", baos) + return BitmapResult(baos.toByteArray(), img.width) + } + + fun testSmartDateX40Tcp(printerIp: String, port: Int = 3001, timeoutMs: Int = 3000): Pair { + return try { + Socket().use { socket -> + socket.connect(InetSocketAddress(printerIp, port), timeoutMs) + socket.soTimeout = 2000 // read timeout + + val output = PrintWriter(socket.getOutputStream(), true) + output.println("~VR") // or any simple command your printer understands + output.flush() + + val input = BufferedReader(InputStreamReader(socket.getInputStream())) + val response = try { + input.readLine() ?: "No response line" + } catch (e: java.net.SocketTimeoutException) { + "No response within timeout" + } + + if (response.isNotBlank() && response != "No response within timeout") { + true to "Connected successfully. Response: $response" + } else { + true to "Connected (port open), but no meaningful response" + } + } + } catch (e: SocketTimeoutException) { + false to "Connection timed out (port may be closed or printer unreachable)" + } catch (e: ConnectException) { + false to "Connection refused (port closed or wrong IP)" + } catch (e: Exception) { + false to "Test failed: ${e.javaClass.simpleName} - ${e.message}" + } } private fun createQrCodeBitmap(content: String, size: Int): BitmapResult { @@ -141,10 +145,43 @@ open class PlasticBagPrinterService( image.setRGB(x, y, if (bitMatrix.get(x, y)) Color.BLACK.rgb else Color.WHITE.rgb) } } + + // Crop QR code (removes quiet zone excess if any) + val cropped = cropToContent(image) + val baos = ByteArrayOutputStream() - ImageIO.write(image, "bmp", baos) - // For QR code, width and height are the same (size) - return BitmapResult(baos.toByteArray(), size) + ImageIO.write(cropped, "bmp", baos) + return BitmapResult(baos.toByteArray(), cropped.width) + } + + private fun cropToContent(img: BufferedImage): BufferedImage { + val width = img.width + val height = img.height + var minX = width + var minY = height + var maxX = 0 + var maxY = 0 + + for (x in 0 until width) { + for (y in 0 until height) { + if (img.getRGB(x, y) == Color.BLACK.rgb) { + minX = minOf(minX, x) + minY = minOf(minY, y) + maxX = maxOf(maxX, x) + maxY = maxOf(maxY, y) + } + } + } + + if (minX > maxX || minY > maxY) return img // empty image fallback + + val padding = 4 + val cropX = (minX - padding).coerceAtLeast(0) + val cropY = (minY - padding).coerceAtLeast(0) + val cropW = (maxX - minX + padding * 2).coerceAtMost(width - cropX) + val cropH = (maxY - minY + padding * 2).coerceAtMost(height - cropY) + + return img.getSubimage(cropX, cropY, cropW, cropH) } private fun addToZip(zos: ZipOutputStream, entryName: String, content: ByteArray) { @@ -153,30 +190,35 @@ open class PlasticBagPrinterService( zos.write(content) zos.closeEntry() } - + + // ──────────────────────────────────────────────── + // The rest of your printer communication methods remain unchanged + // ──────────────────────────────────────────────── + fun sendDataFlexJob(request: PrintRequest) { Socket().use { socket -> try { - socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 3000) - val writer = PrintWriter(socket.getOutputStream(), true) - - // Videojet/DataFlex ASCII protocol - // We assign the Lot number to both the text variable and the barcode variable - val command = """ - ^SVAR1|${request.itemCode} - ^SVAR2|${request.itemName} - ^SVAR3|${request.lotNo} - ^SVAR4|${request.expiryDate} - ^SVAR_QR|${request.lotNo} - ^PRNT1 - """.trimIndent() - - writer.println(command) + socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 5000) + val output = socket.getOutputStream() + val writer = PrintWriter(output, true) + + val command = buildString { + append("\u0002") // + append("JI;YOUR_JOB_NAME\r\n") + append("VB;ItemCode;${request.itemCode}\r\n") + append("VB;ItemName;${request.itemName}\r\n") + append("VB;LotNo;${request.lotNo}\r\n") + append("VB;ExpiryDate;${request.expiryDate}\r\n") + append("VB;QRContent;lot=${request.lotNo}&item=${request.itemCode}\r\n") + append("P;0\r\n") + append("\u0003") // + } + + writer.print(command) writer.flush() - - println("DataFlex: Print job with QR data sent to ${request.printerIp}") + println("DataFlex: Print job sent to ${request.printerIp}:${request.printerPort}") } catch (e: Exception) { - throw RuntimeException("DataFlex Error: ${e.message}") + throw RuntimeException("DataFlex communication failed: ${e.message}", e) } } } @@ -185,46 +227,20 @@ open class PlasticBagPrinterService( Socket().use { socket -> socket.connect(InetSocketAddress(request.printerIp, request.printerPort.toInt()), 3000) val writer = PrintWriter(socket.getOutputStream(), true) - - // Standard HANS/General Laser Command Format: - // [STX]Command|Variable1|Variable2[ETX] - // Note: Exact protocol depends on your HANS controller software (usually JCZ or similar) - val command = "STRSET|${request.templateId}|LOT=${request.lotNo}|EXP=${request.expiryDate}\n" writer.print(command) writer.flush() - - // Optional: Trigger the marking immediately - // writer.print("STRMARK\n") - // writer.flush() } } - /* - fun previewLaser(request: LaserRequest) { - Socket().use { socket -> - socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 2000) - val writer = PrintWriter(socket.getOutputStream(), true) - - // Typical HANS command for Red Light Preview - writer.println("JOBLOAD|${request.templateId}") - writer.println("REDLIGHT|1") // 1 to turn on, 0 to turn off - writer.flush() - } - } */ - fun sendLaserPreview(request: LaserRequest) { Socket().use { socket -> socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 3000) val writer = PrintWriter(socket.getOutputStream(), true) - - // HANS Protocol for Red Light - // Often requires loading the job first so it knows the boundary val command = """ JOBLOAD|${request.templateId} REDLIGHT|1 """.trimIndent() - writer.println(command) writer.flush() } @@ -236,38 +252,25 @@ open class PlasticBagPrinterService( socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 3000) val out = DataOutputStream(socket.getOutputStream()) - // 203 DPI: 8 dots = 1mm val tspl = StringBuilder() .append("SIZE 100 mm, 50 mm\n") .append("GAP 3 mm, 0 mm\n") .append("DIRECTION 1\n") .append("CLS\n") - - // --- Text Content --- .append("TEXT 50,40,\"ROMAN.TTF\",0,1,1,\"ITEM: ${request.itemCode}\"\n") .append("TEXT 50,80,\"ROMAN.TTF\",0,1,1,\"NAME: ${request.itemName}\"\n") .append("TEXT 50,120,\"ROMAN.TTF\",0,1,1,\"EXP: ${request.expiryDate}\"\n") .append("TEXT 50,160,\"ROMAN.TTF\",0,1,1,\"LOT: ${request.lotNo}\"\n") - - // --- QR Code for Lot Number --- - // Syntax: QRCODE x, y, ECC, cell_width, mode, rotation, "content" - // ECC Level M (Standard), Cell width 4 (size), Auto mode, 0 rotation .append("QRCODE 450,40,M,4,A,0,\"${request.lotNo}\"\n") - - // Optional: Label for the QR Code .append("TEXT 450,180,\"ROMAN.TTF\",0,1,1,\"SCAN LOT\"\n") - .append("PRINT 1,1\n") .toString() - // Use TIS-620 for Thai support or Windows-1252 for English val bytes = tspl.toByteArray(Charset.forName("TIS-620")) out.write(bytes) out.flush() - - println("TSC: Print job with QR code sent to ${request.printerIp}") + println("TSC: Print job sent to ${request.printerIp}") } catch (e: Exception) { - println("TSC Error: ${e.message}") throw RuntimeException("TSC Printer Error: ${e.message}") } } @@ -335,5 +338,4 @@ open class PlasticBagPrinterService( println("Han's Laser TCP 連接已關閉") } } - } \ No newline at end of file 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 c6fe1ed..7b16662 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 @@ -88,6 +88,19 @@ class PlasticBagPrinterController( } } + @GetMapping("/test-x40") + fun testX40( + @RequestParam printerIp: String, + @RequestParam(defaultValue = "3001") port: Int + ): ResponseEntity { + val (success, message) = plasticBagPrinterService.testSmartDateX40Tcp(printerIp, port) + return if (success) { + ResponseEntity.ok("OK - $message") + } else { + ResponseEntity.status(503).body("FAIL - $message") + } + } + @PostMapping("/print-laser-tcp") fun printLaserTcp(@RequestBody request: Laser2Request): ResponseEntity { return try { diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt index e3cc85b..cdb27f4 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt @@ -628,19 +628,20 @@ open class ProductionScheduleService( i.outputQty, i.avgQtyLastMonth, (i.onHandQty -500), - (i.onHandQty -500) * 1.0 / i.avgQtyLastMonth as daysLeft, + -- (i.onHandQty -500) * 1.0 / i.avgQtyLastMonth as daysLeft, i.avgQtyLastMonth * 2.6 - stockQty as needQty, i.stockQty, - CASE - WHEN stockQty * 1.0 / i.avgQtyLastMonth <= 1.9 THEN - CEIL((i.avgQtyLastMonth * 2.6 - stockQty) / i.outputQty) - ELSE 0 - END AS needNoOfJobOrder, - CASE - WHEN stockQty * 1.0 / i.avgQtyLastMonth <= 1.9 THEN - CEIL((i.avgQtyLastMonth * 2.6 - stockQty) / i.outputQty) - ELSE 0 - END AS batchNeed, + -- CASE + -- WHEN stockQty * 1.0 / i.avgQtyLastMonth <= 1.9 THEN + -- CEIL((i.avgQtyLastMonth * 2.6 - stockQty) / i.outputQty) + -- ELSE 0 + -- END AS needNoOfJobOrder, + -- The first date only consider how many batch pending for job order + 0 AS needNoOfJobOrder, + 0 AS batchNeed, + i.pendingJobQty, + ((i.stockQty * 1.0) + (i.needNoOfJobOrder * i.outputQty) ) / i.avgQtyLastMonth as daysLeft, + 25 + 25 + markDark + markFloat + markDense + markAS + markTimeSequence + markComplexity as priority, i.* FROM @@ -658,6 +659,9 @@ open class ProductionScheduleService( -- AND MONTH(do.estimatedArrivalDate) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND do.estimatedArrivalDate >= '2025-12-01' AND do.estimatedArrivalDate < '2025-12-11' GROUP BY do.estimatedArrivalDate) AS d) AS avgQtyLastMonth, + + (select sum(reqQty) from job_order where bomId = bom.id and status != 'completed') AS pendingJobQty, + inventory.onHandQty - 500 AS stockQty, bom.outputQty, bom.outputQtyUom, diff --git a/src/main/resources/db/changelog/changes/20260118_fai/01_insert_scheduler.sql b/src/main/resources/db/changelog/changes/20260118_fai/01_insert_scheduler.sql new file mode 100644 index 0000000..0b3cee9 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20260118_fai/01_insert_scheduler.sql @@ -0,0 +1,16 @@ +--liquibase formatted sql +--changeset author:add_scheduler + +INSERT INTO `fpsmsdb`.`settings` (`name`, `value`, `category`, `type`) +SELECT 'SCHEDULE.m18.do1', '0 0 19 * * *', 'SCHEDULE', 'string' +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `fpsmsdb`.`settings` WHERE `name` = 'SCHEDULE.m18.do1' +); + +INSERT INTO `fpsmsdb`.`settings` (`name`, `value`, `category`, `type`) +SELECT 'SCHEDULE.m18.do2', '0 0 11 * * *', 'SCHEDULE', 'string' +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `fpsmsdb`.`settings` WHERE `name` = 'SCHEDULE.m18.do2' +); \ No newline at end of file diff --git a/src/main/resources/jasper/FGDeliveryReport.jrxml b/src/main/resources/jasper/FGDeliveryReport.jrxml index 87d4101..aba30ec 100644 --- a/src/main/resources/jasper/FGDeliveryReport.jrxml +++ b/src/main/resources/jasper/FGDeliveryReport.jrxml @@ -1,6 +1,6 @@ - - + + @@ -310,7 +310,7 @@ - +