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
new file mode 100644
index 0000000..8c6a4d8
--- /dev/null
+++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt
@@ -0,0 +1,221 @@
+package com.ffii.fpsms.modules.jobOrder.service
+
+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.image.BufferedImage
+import java.io.ByteArrayOutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+import javax.imageio.ImageIO
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.qrcode.QRCodeWriter
+import java.net.Socket
+import java.net.InetSocketAddress
+import java.io.PrintWriter
+import java.io.DataOutputStream
+import java.nio.charset.Charset
+
+@Service
+open class PlasticBagPrinterService(
+ val jobOrderRepository: JobOrderRepository,
+) {
+ fun generatePrintJobBundle(itemCode: String, lotNo: String, expiryDate: String, productName: String): ByteArray {
+ val baos = ByteArrayOutputStream()
+ ZipOutputStream(baos).use { zos ->
+
+ // Use unique names based on the Lot Number
+ val nameFile = "${lotNo}_product.bmp"
+ val expFile = "${lotNo}_expiry.bmp"
+ val qrFile = "${lotNo}_qr.bmp"
+
+ // 1. Generate Bitmaps
+ addToZip(zos, nameFile, createMonochromeBitmap(productName, 5365, 704))
+ addToZip(zos, expFile, createMonochromeBitmap(expiryDate, 4203, 1173))
+ addToZip(zos, qrFile, createQrCodeBitmap("$itemCode|$lotNo|$expiryDate", 1000))
+
+ // 2. Generate the .image file with dynamic references
+ val imageXml = """
+
+ """.trimIndent()
+
+ addToZip(zos, "$lotNo.image", imageXml.toByteArray())
+
+ // 3. Generate the .job file pointing to the new .image [cite: 2]
+ val jobXml = "$lotNo.image"
+ addToZip(zos, "$lotNo.job", jobXml.toByteArray())
+ }
+ return baos.toByteArray()
+ }
+
+ private fun createMonochromeBitmap(text: String, width: Int, height: Int): ByteArray {
+ val image = BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY) // Essential for printers
+ val g = image.createGraphics()
+ g.color = Color.WHITE
+ g.fillRect(0, 0, width, height)
+ g.color = Color.BLACK
+ g.font = Font("Arial", Font.BOLD, 400)
+ g.drawString(text, 50, height - 200)
+ g.dispose()
+
+ val baos = ByteArrayOutputStream()
+ ImageIO.write(image, "bmp", baos)
+ return baos.toByteArray()
+ }
+
+ private fun createQrCodeBitmap(content: String, size: Int): ByteArray {
+ val bitMatrix = QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, size, size)
+ val image = BufferedImage(size, size, BufferedImage.TYPE_BYTE_BINARY)
+ for (x in 0 until size) {
+ for (y in 0 until size) {
+ image.setRGB(x, y, if (bitMatrix.get(x, y)) Color.BLACK.rgb else Color.WHITE.rgb)
+ }
+ }
+ val baos = ByteArrayOutputStream()
+ ImageIO.write(image, "bmp", baos)
+ return baos.toByteArray()
+ }
+
+ private fun addToZip(zos: ZipOutputStream, fileName: String, content: ByteArray) {
+ zos.putNextEntry(ZipEntry(fileName))
+ zos.write(content)
+ zos.closeEntry()
+ }
+
+ fun sendDataFlexJob(request: PrintRequest) {
+ try {
+ // 1. Establish Socket Connection
+ val socket = Socket(request.printerIp, request.printerPort)
+ socket.soTimeout = 5000 // 5 seconds timeout
+
+ val writer = PrintWriter(socket.getOutputStream(), true)
+
+ // 2. Format the command for DataFlex 6330
+ // Note: This format depends on your specific label design in CLARiSOFT.
+ // Many DataFlex printers use the "Job Select" or "Set Variable" protocol.
+
+ val command = """
+ ^SVAR1|${request.itemCode}
+ ^SVAR2|${request.itemName}
+ ^SVAR3|${request.lotNo}
+ ^SVAR4|${request.expiryDate}
+ ^PRNT1
+ """.trimIndent()
+
+ // 3. Send and Close
+ writer.print(command)
+ writer.flush()
+
+ socket.close()
+ println("Successfully sent command to DataFlex at ${request.printerIp}")
+
+ } catch (e: Exception) {
+ throw RuntimeException("Failed to communicate with DataFlex printer: ${e.message}")
+ }
+ }
+
+ fun sendLaserMark(request: LaserRequest) {
+ 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()
+ }
+ }
+
+ fun sendTscPrintJob(request: PrintRequest) {
+ Socket().use { socket ->
+ try {
+ socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 3000)
+ val out = DataOutputStream(socket.getOutputStream())
+
+ // Construct TSPL commands
+ // Note: Coordinates (x,y) are in dots.
+ // For 203 DPI: 8 dots = 1mm.
+ val tspl = StringBuilder()
+ .append("SIZE 100 mm, 50 mm\n") // Adjust to your label size
+ .append("GAP 3 mm, 0 mm\n")
+ .append("DIRECTION 1\n")
+ .append("CLS\n") // Clear buffer
+ // Text commands: TEXT x, y, "font", rotation, x-multi, y-multi, "content"
+ .append("TEXT 50,50,\"ROMAN.TTF\",0,1,1,\"ITEM: ${request.itemCode}\"\n")
+ .append("TEXT 50,100,\"ROMAN.TTF\",0,1,1,\"NAME: ${request.itemName}\"\n")
+ .append("TEXT 50,150,\"ROMAN.TTF\",0,1,1,\"LOT: ${request.lotNo}\"\n")
+ .append("TEXT 50,200,\"ROMAN.TTF\",0,1,1,\"EXP: ${request.expiryDate}\"\n")
+ .append("PRINT 1,1\n")
+ .toString()
+
+ // TSC printers usually expect encoding in Windows-1252 or Thai (TIS-620)
+ val bytes = tspl.toByteArray(Charset.forName("TIS-620"))
+ out.write(bytes)
+ out.flush()
+
+ } catch (e: Exception) {
+ throw RuntimeException("TSC Printer Error: ${e.message}")
+ }
+ }
+ }
+
+}
\ 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
new file mode 100644
index 0000000..29cb031
--- /dev/null
+++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt
@@ -0,0 +1,90 @@
+package com.ffii.fpsms.modules.jobOrder.web
+
+import com.ffii.fpsms.modules.jobOrder.service.PlasticBagPrinterService
+import com.ffii.fpsms.modules.jobOrder.web.model.PrintRequest
+import com.ffii.fpsms.modules.jobOrder.web.model.LaserRequest
+import jakarta.servlet.http.HttpServletResponse
+import org.springframework.http.HttpHeaders
+import org.springframework.web.bind.annotation.*
+import java.time.LocalDate
+import org.springframework.http.ResponseEntity
+
+@RestController
+@RequestMapping("/plastic")
+class PlasticBagPrinterController(
+ private val plasticBagPrinterService: PlasticBagPrinterService,
+) {
+
+ /**
+ * Test API to generate and download the printer job files as a ZIP.
+ * ONPACK2030
+ */
+ @GetMapping("/get-printer6")
+ fun testPrintJob(
+ @RequestParam itemCode: String,
+ @RequestParam lotNo: String,
+ @RequestParam expiryDate: String,
+ @RequestParam productName: String,
+ response: HttpServletResponse
+ ) {
+ // Generate the ZIP bundle via the service
+ val zipBytes = plasticBagPrinterService.generatePrintJobBundle(
+ itemCode,
+ lotNo,
+ expiryDate,
+ productName
+ )
+
+ // Set headers for file download
+ response.contentType = "application/zip"
+ response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"Job_$lotNo.zip\"")
+ response.setContentLength(zipBytes.size)
+
+ // Write to response stream
+ response.outputStream.write(zipBytes)
+ response.outputStream.flush()
+ }
+
+ @PostMapping("/print-dataflex")
+ fun printDataFlex(@RequestBody request: PrintRequest): ResponseEntity {
+ return try {
+ plasticBagPrinterService.sendDataFlexJob(request)
+ ResponseEntity.ok("Print command sent successfully")
+ } catch (e: Exception) {
+ ResponseEntity.status(500).body("Error: ${e.message}")
+ }
+ }
+
+ @PostMapping("/print-laser")
+ fun printLaser(@RequestBody request: LaserRequest): ResponseEntity {
+ return try {
+ // Call the laser marking service
+ plasticBagPrinterService.sendLaserMark(request)
+ ResponseEntity.ok("Laser marking command sent successfully to ${request.printerIp}")
+ } catch (e: Exception) {
+ // Log the error and return 500 status
+ ResponseEntity.status(500).body("Laser Error: ${e.message}")
+ }
+ }
+
+ @PostMapping("/preview-laser")
+ fun previewLaser(@RequestBody request: LaserRequest): ResponseEntity {
+ return try {
+ plasticBagPrinterService.sendLaserPreview(request)
+ ResponseEntity.ok("Red light activated")
+ } catch (e: Exception) {
+ ResponseEntity.status(500).body("Preview Error: ${e.message}")
+ }
+ }
+
+ @PostMapping("/print-tsc")
+ fun printTsc(@RequestBody request: PrintRequest): ResponseEntity {
+ return try {
+ plasticBagPrinterService.sendTscPrintJob(request)
+ ResponseEntity.ok("TSC Print command sent to ${request.printerIp}")
+ } catch (e: Exception) {
+ ResponseEntity.status(500).body("Error: ${e.message}")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/PlasticPrintRequest.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/PlasticPrintRequest.kt
new file mode 100644
index 0000000..9aa593c
--- /dev/null
+++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/PlasticPrintRequest.kt
@@ -0,0 +1,22 @@
+package com.ffii.fpsms.modules.jobOrder.web.model
+
+data class PrintRequest(
+ val itemCode: String,
+ val itemName: String,
+ val lotNo: String,
+ val expiryDate: String,
+ val printerIp: String,
+ val printerPort: Int
+)
+
+data class LaserRequest(
+ val templateId: String, // The name of the file on the laser controller
+ val itemCode: String,
+ val itemName: String,
+ val lotNo: String,
+ val expiryDate: String,
+ val power: String, // Laser intensity (0-100)
+ val speed: String, // Marking speed
+ val printerIp: String,
+ val printerPort: Int
+)
\ No newline at end of file