|
|
|
@@ -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 = """ |
|
|
|
<Legend type="Image.V1" PaperColor="White" BackgroundColor="DarkGray"> |
|
|
|
<FieldList> |
|
|
|
<Logo> |
|
|
|
<Name>PRODUCT_NAME</Name> |
|
|
|
<Geometry><X>0</X><Y>250</Y></Geometry> |
|
|
|
<FileName>$nameFile</FileName> |
|
|
|
</Logo> |
|
|
|
<Logo> |
|
|
|
<Name>EXPIRY_DATE</Name> |
|
|
|
<Geometry><X>500</X><Y>2500</Y></Geometry> |
|
|
|
<FileName>$expFile</FileName> |
|
|
|
</Logo> |
|
|
|
<Logo> |
|
|
|
<Name>QR_CODE</Name> |
|
|
|
<Geometry><X>750</X><Y>3750</Y></Geometry> |
|
|
|
<FileName>$qrFile</FileName> |
|
|
|
</Logo> |
|
|
|
</FieldList> |
|
|
|
</Legend> |
|
|
|
""".trimIndent() |
|
|
|
|
|
|
|
addToZip(zos, "$lotNo.image", imageXml.toByteArray()) |
|
|
|
|
|
|
|
// 3. Generate the .job file pointing to the new .image [cite: 2] |
|
|
|
val jobXml = "<Job><ImageFileName>$lotNo.image</ImageFileName></Job>" |
|
|
|
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}") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |