| @@ -0,0 +1,138 @@ | |||
| package com.ffii.core.utils | |||
| import org.apache.pdfbox.pdmodel.PDDocument | |||
| import java.io.File | |||
| import java.io.OutputStream | |||
| import java.net.Socket | |||
| /** | |||
| * Utility class for generating and sending print jobs to a Canon printer using PDF. | |||
| * This class requires the 'org.apache.pdfbox:pdfbox' dependency to be included in your project. | |||
| */ | |||
| open class CanonPrinterUtil { | |||
| // Enum to represent valid duplex print modes | |||
| enum class DuplexMode { | |||
| SIMPLEX, // Single-sided printing | |||
| DUPLEX_LONG_EDGE, // Double-sided, flip on long edge (portrait binding) | |||
| DUPLEX_SHORT_EDGE // Double-sided, flip on short edge (landscape binding) | |||
| } | |||
| companion object { | |||
| /** | |||
| * Detects if a PDF document is in landscape (horizontal) orientation. | |||
| * | |||
| * @param pdfFile The PDF file to check. | |||
| * @return true if the document is landscape, false if portrait. | |||
| */ | |||
| fun isLandscape(pdfFile: File): Boolean { | |||
| if (!pdfFile.exists() || !pdfFile.canRead()) { | |||
| return false | |||
| } | |||
| try { | |||
| PDDocument.load(pdfFile).use { document -> | |||
| if (document.numberOfPages > 0) { | |||
| val page = document.getPage(0) | |||
| val mediaBox = page.mediaBox | |||
| val width = mediaBox.width | |||
| val height = mediaBox.height | |||
| // Landscape if width > height | |||
| return width > height | |||
| } | |||
| } | |||
| } catch (e: Exception) { | |||
| println("DEBUG: Error detecting PDF orientation: ${e.message}") | |||
| } | |||
| return false | |||
| } | |||
| /** | |||
| * Sends a PDF document directly to the specified Canon printer with duplex printing support. | |||
| * | |||
| * @param pdfFile The PDF file to be printed. | |||
| * @param printerIp The IP address of the Canon printer. | |||
| * @param printerPort The port of the Canon printer, typically 9100. | |||
| * @param printQty The qty of print, default 1 | |||
| * @param duplexMode Valid values: SIMPLEX (single-sided), DUPLEX_LONG_EDGE (portrait), DUPLEX_SHORT_EDGE (landscape) | |||
| * @throws Exception if there is an error during file processing or printing. | |||
| */ | |||
| fun printPdfToCanon(pdfFile: File, printerIp: String, printerPort: Int, printQty: Int? = 1, duplexMode: DuplexMode = DuplexMode.DUPLEX_LONG_EDGE) { | |||
| // Check if the file exists and is readable | |||
| if (!pdfFile.exists() || !pdfFile.canRead()) { | |||
| throw IllegalArgumentException("Error: File not found or not readable at path: ${pdfFile.absolutePath}") | |||
| } | |||
| try { | |||
| // 1. Build PJL (Printer Job Language) commands for Canon printers | |||
| val pjlCommands = StringBuilder() | |||
| // PJL job start | |||
| pjlCommands.append("\u001B%-12345X@PJL\n") | |||
| pjlCommands.append("@PJL JOB NAME=\"PDF Print\"\n") | |||
| // 2. Set duplex mode | |||
| when (duplexMode) { | |||
| DuplexMode.SIMPLEX -> { | |||
| pjlCommands.append("@PJL SET DUPLEX=OFF\n") | |||
| } | |||
| DuplexMode.DUPLEX_LONG_EDGE -> { | |||
| pjlCommands.append("@PJL SET DUPLEX=ON\n") | |||
| pjlCommands.append("@PJL SET BINDING=LONGEDGE\n") | |||
| } | |||
| DuplexMode.DUPLEX_SHORT_EDGE -> { | |||
| pjlCommands.append("@PJL SET DUPLEX=ON\n") | |||
| pjlCommands.append("@PJL SET BINDING=SHORTEDGE\n") | |||
| } | |||
| } | |||
| // 3. Set number of copies | |||
| if ((printQty ?: 1) > 1) { | |||
| pjlCommands.append("@PJL SET COPIES=${printQty ?: 1}\n") | |||
| } | |||
| // 4. Set paper size to A4 | |||
| pjlCommands.append("@PJL SET PAPER=A4\n") | |||
| // 5. Enter PDF language mode | |||
| pjlCommands.append("@PJL ENTER LANGUAGE=PDF\n") | |||
| // PJL job end marker (before PDF content) | |||
| pjlCommands.append("\u001B%-12345X\n") | |||
| // 6. Read PDF file content | |||
| val pdfContent = pdfFile.readBytes() | |||
| println("DEBUG: PDF file size: ${pdfContent.size} bytes") | |||
| // Print each copy for the specified quantity | |||
| repeat(printQty ?: 1) { copyIndex -> | |||
| println("DEBUG: Printing copy ${copyIndex + 1} of ${printQty ?: 1}") | |||
| // 7. Send to printer via TCP/IP socket | |||
| Socket(printerIp, printerPort).use { socket -> | |||
| val os: OutputStream = socket.getOutputStream() | |||
| // Send PJL commands | |||
| val printData = pjlCommands.toString().toByteArray(Charsets.US_ASCII) | |||
| os.write(printData) | |||
| // Send PDF content | |||
| os.write(pdfContent) | |||
| // Send job end marker | |||
| os.write("\u001B%-12345X".toByteArray(Charsets.US_ASCII)) | |||
| os.flush() | |||
| println("DEBUG: Copy ${copyIndex + 1} sent to printer") | |||
| } | |||
| } | |||
| } catch (e: Exception) { | |||
| // Re-throw the exception with a more descriptive message | |||
| throw Exception("Error processing print job for PDF: ${e.message}", e) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -71,6 +71,7 @@ import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository | |||
| import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService // 添加这行 | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.* | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository // 添加 | |||
| import com.ffii.core.utils.CanonPrinterUtil | |||
| @Service | |||
| open class DeliveryOrderService( | |||
| @@ -928,36 +929,53 @@ open class DeliveryOrderService( | |||
| ) | |||
| } | |||
| //Print Delivery Note | |||
| @Transactional | |||
| open fun printDeliveryNote(request: PrintDeliveryNoteRequest) { | |||
| //val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val pdf = exportDeliveryNote( | |||
| ExportDeliveryNoteRequest( | |||
| doPickOrderId = request.doPickOrderId, | |||
| numOfCarton = request.numOfCarton, | |||
| isDraft = request.isDraft | |||
| ) | |||
| //Print Delivery Note | |||
| @Transactional | |||
| open fun printDeliveryNote(request: PrintDeliveryNoteRequest) { | |||
| val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val pdf = exportDeliveryNote( | |||
| ExportDeliveryNoteRequest( | |||
| doPickOrderId = request.doPickOrderId, | |||
| numOfCarton = request.numOfCarton, | |||
| isDraft = request.isDraft | |||
| ) | |||
| ) | |||
| val jasperPrint = pdf["report"] as JasperPrint | |||
| val jasperPrint = pdf["report"] as JasperPrint | |||
| val tempPdfFile = File.createTempFile("print_job_", ".pdf") | |||
| val tempPdfFile = File.createTempFile("print_job_", ".pdf") | |||
| try { | |||
| JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath) | |||
| try { | |||
| JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath) | |||
| //val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty | |||
| //printer.ip?.let { ip -> printer.port?.let { port -> | |||
| // ZebraPrinterUtil.printPdfToZebra(tempPdfFile, ip, port, printQty, ZebraPrinterUtil.PrintDirection.ROTATED) | |||
| //}} | |||
| } finally { | |||
| //tempPdfFile.delete() | |||
| val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty | |||
| val duplexMode = if (CanonPrinterUtil.isLandscape(tempPdfFile)) { | |||
| CanonPrinterUtil.DuplexMode.DUPLEX_SHORT_EDGE // Landscape: flip on short edge | |||
| } else { | |||
| CanonPrinterUtil.DuplexMode.DUPLEX_LONG_EDGE // Portrait: flip on long edge | |||
| } | |||
| println("DEBUG: PDF orientation detected - Landscape: ${CanonPrinterUtil.isLandscape(tempPdfFile)}, Duplex mode: $duplexMode") | |||
| printer.ip?.let { ip -> | |||
| printer.port?.let { port -> | |||
| CanonPrinterUtil.printPdfToCanon( | |||
| tempPdfFile, | |||
| ip, | |||
| port, | |||
| printQty, | |||
| duplexMode | |||
| ) | |||
| } | |||
| } | |||
| } finally { | |||
| //tempPdfFile.delete() | |||
| } | |||
| } | |||
| //Carton Labels | |||
| open fun exportDNLabels(request: ExportDNLabelsRequest): Map<String, Any> { | |||
| val DNLABELS_PDF = "DeliveryNote/DeliveryNoteCartonLabelsPDF.jrxml" | |||
| @@ -2,6 +2,7 @@ package com.ffii.fpsms.modules.jobOrder.service | |||
| import com.ffii.core.exception.BadRequestException | |||
| import com.ffii.core.response.RecordsRes | |||
| import com.ffii.core.utils.CanonPrinterUtil | |||
| import com.ffii.core.utils.GsonUtils | |||
| import com.ffii.core.utils.PdfUtils | |||
| import com.ffii.core.utils.QrCodeUtil | |||
| @@ -395,7 +396,7 @@ open class JobOrderService( | |||
| params["FGCode"] = pickRecordInfo.firstOrNull()?.get("fgCode") as? String ?: "N/A" | |||
| params["FGName"] = pickRecordInfo.firstOrNull()?.get("fgName") as? String ?: "N/A" | |||
| /*// Debug UOM information | |||
| /* Debug UOM information | |||
| val bomItemUomIdRaw = pickRecordInfo.firstOrNull()?.get("bomItemUomId") | |||
| val bomItemId = pickRecordInfo.firstOrNull()?.get("bomItemId") | |||
| val uomCode = pickRecordInfo.firstOrNull()?.get("uomCode") as? String | |||
| @@ -420,9 +421,10 @@ open class JobOrderService( | |||
| ) | |||
| } | |||
| //Print Pick Record | |||
| @Transactional | |||
| open fun printPickRecord(request: PrintPickRecordRequest){ | |||
| //val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val pdf = exportPickRecord( | |||
| ExportPickRecordRequest( | |||
| @@ -437,8 +439,30 @@ open class JobOrderService( | |||
| try{ | |||
| JasperExportManager.exportReportToPdfFile(jasperPrint,tempPdfFile.absolutePath) | |||
| val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty | |||
| // Auto-detect orientation and set duplex mode accordingly | |||
| val duplexMode = if (CanonPrinterUtil.isLandscape(tempPdfFile)) { | |||
| CanonPrinterUtil.DuplexMode.DUPLEX_SHORT_EDGE // Landscape: flip on short edge | |||
| } else { | |||
| CanonPrinterUtil.DuplexMode.DUPLEX_LONG_EDGE // Portrait: flip on long edge | |||
| } | |||
| println("DEBUG: PDF orientation detected - Landscape: ${CanonPrinterUtil.isLandscape(tempPdfFile)}, Duplex mode: $duplexMode") | |||
| printer.ip?.let { ip -> | |||
| printer.port?.let { port -> | |||
| CanonPrinterUtil.printPdfToCanon( | |||
| tempPdfFile, | |||
| ip, | |||
| port, | |||
| printQty, | |||
| duplexMode | |||
| ) | |||
| } | |||
| } | |||
| } finally { | |||
| //tempPdfFile.delete | |||
| //tempPdfFile.delete() | |||
| } | |||
| } | |||
| @@ -492,7 +492,7 @@ open class BomService( | |||
| // val folder = File(folderPath) | |||
| val resolver = PathMatchingResourcePatternResolver() | |||
| // val excels = resolver.getResources("bomImport/*.xlsx") | |||
| val excels = resolver.getResources("file:C:/Users/ffii_/Downloads/bom/new/*.xlsx") | |||
| val excels = resolver.getResources("file:C:/Users/Kelvin YAU/Downloads/bom/*.xlsx") | |||
| // val excels = resolver.getResources("file:C:/Users/2Fi/Desktop/Third Wave of BOM Excel/*.xlsx") | |||
| println("size: ${excels.size}") | |||
| val logExcel = ClassPathResource("excelTemplate/bom_excel_issue_log.xlsx") | |||