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 = """
-
-
- """.trimIndent()
-
+ addToZip(zos, expFile, expBmp.bytes)
+ addToZip(zos, qrFile, qrBmp.bytes)
+
+ // Minified XML - no indentation/newlines between tags
+ val imageXml = """""".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 @@
-
+