From 8002b6d621d27d758dac52549812c88340e0f2ca Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Thu, 30 Apr 2026 14:39:36 +0800 Subject: [PATCH] QR Code Printing Update --- .../master/service/EquipmentQrCodeService.kt | 71 +++++++++++++----- .../master/service/WarehouseQrCodeService.kt | 67 ++++++++++++----- .../modules/master/web/EquipmentController.kt | 5 ++ .../master/web/PrintEquipmentQrCodeRequest.kt | 7 ++ .../master/web/PrintWarehouseQrCodeRequest.kt | 7 ++ .../modules/master/web/WarehouseController.kt | 4 + .../modules/user/service/UserQrCodeService.kt | 75 +++++++++++++------ .../user/web/PrintUserQrCodeRequest.kt | 7 ++ .../modules/user/web/UserController.java | 7 +- 9 files changed, 186 insertions(+), 64 deletions(-) create mode 100644 src/main/java/com/ffii/fpsms/modules/master/web/PrintEquipmentQrCodeRequest.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/master/web/PrintWarehouseQrCodeRequest.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/user/web/PrintUserQrCodeRequest.kt diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/EquipmentQrCodeService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/EquipmentQrCodeService.kt index 2ee1ac5..5e700fa 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/EquipmentQrCodeService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/EquipmentQrCodeService.kt @@ -4,30 +4,52 @@ import com.ffii.core.utils.PdfUtils import com.ffii.core.utils.QrCodeUtil import com.ffii.fpsms.modules.master.entity.EquipmentDetailRepository import com.ffii.fpsms.modules.master.web.ExportEquipmentQrCodeRequest +import com.ffii.fpsms.modules.master.web.PrintEquipmentQrCodeRequest +import com.ffii.fpsms.modules.master.print.A4PrintDriverRegistry import net.sf.jasperreports.engine.JasperCompileManager +import net.sf.jasperreports.engine.JasperExportManager +import net.sf.jasperreports.engine.JasperReport import net.sf.jasperreports.engine.JasperPrint import org.springframework.core.io.ClassPathResource import org.springframework.stereotype.Service import java.io.FileNotFoundException +import java.io.File import java.awt.GraphicsEnvironment import kotlinx.serialization.json.Json import kotlinx.serialization.encodeToString @Service class EquipmentQrCodeService( - private val equipmentDetailRepository: EquipmentDetailRepository + private val equipmentDetailRepository: EquipmentDetailRepository, + private val printerService: PrinterService, ) { + private val qrCodeHandleJrxmlPath = "qrCodeHandle/equipment_QrHandle.jrxml" - fun exportEquipmentQrCode(request: ExportEquipmentQrCodeRequest): Map { - val QRCODE_HANDLE_PDF = "qrCodeHandle/equipment_QrHandle.jrxml" - val resource = ClassPathResource(QRCODE_HANDLE_PDF) + /** + * Compile the Jasper template once; compiling per request is expensive. + */ + private val qrCodeHandleReport: JasperReport by lazy { + val resource = ClassPathResource(qrCodeHandleJrxmlPath) if (!resource.exists()) { - throw FileNotFoundException("Report file not found: $QRCODE_HANDLE_PDF") + throw FileNotFoundException("Report file not found: $qrCodeHandleJrxmlPath") } - - val inputStream = resource.inputStream - val qrCodeHandleReport = JasperCompileManager.compileReport(inputStream) - + resource.inputStream.use { JasperCompileManager.compileReport(it) } + } + + /** + * Cache the chosen Chinese font family name (font scanning is expensive). + */ + private val chineseFontFamily: String by lazy { + val availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames + availableFonts.find { family -> + family.contains("SimSun", ignoreCase = true) || + family.contains("Microsoft YaHei", ignoreCase = true) || + family.contains("STSong", ignoreCase = true) || + family.contains("SimHei", ignoreCase = true) + } ?: "Arial Unicode MS" + } + + fun exportEquipmentQrCode(request: ExportEquipmentQrCodeRequest): Map { val equipmentDetails = equipmentDetailRepository.findAllById(request.equipmentDetailIds) if (equipmentDetails.isEmpty()) { throw IllegalArgumentException("No equipment details found for the provided equipment detail IDs: ${request.equipmentDetailIds}") @@ -63,18 +85,10 @@ class EquipmentQrCodeService( } val params: MutableMap = mutableMapOf() - - val availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames - val chineseFont = availableFonts.find { - it.contains("SimSun", ignoreCase = true) || - it.contains("Microsoft YaHei", ignoreCase = true) || - it.contains("STSong", ignoreCase = true) || - it.contains("SimHei", ignoreCase = true) - } ?: "Arial Unicode MS" - + params["net.sf.jasperreports.default.pdf.encoding"] = "Identity-H" params["net.sf.jasperreports.default.pdf.embedded"] = true - params["net.sf.jasperreports.default.pdf.font.name"] = chineseFont + params["net.sf.jasperreports.default.pdf.font.name"] = chineseFontFamily val firstEquipmentDetail = equipmentDetails.firstOrNull() @@ -83,4 +97,23 @@ class EquipmentQrCodeService( "fileName" to (firstEquipmentDetail?.code ?: "equipment_qrcode") ) } + + fun printEquipmentQrCode(request: PrintEquipmentQrCodeRequest) { + val printer = printerService.findById(request.printerId) ?: throw NoSuchElementException("No such printer") + val pdf = exportEquipmentQrCode(ExportEquipmentQrCodeRequest(request.equipmentDetailIds)) + val jasperPrint = pdf["report"] as JasperPrint + val tempPdfFile = File.createTempFile("print_job_", ".pdf") + + try { + JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath) + val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty + printer.ip?.let { ip -> + val port = printer.port ?: 9100 + val driver = A4PrintDriverRegistry.getDriver(printer.brand) + driver.print(tempPdfFile, ip, port, printQty) + } + } finally { + tempPdfFile.delete() + } + } } diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseQrCodeService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseQrCodeService.kt index 61d932b..5b471d8 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseQrCodeService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseQrCodeService.kt @@ -2,12 +2,17 @@ package com.ffii.fpsms.modules.master.service import com.ffii.core.utils.PdfUtils import com.ffii.core.utils.QrCodeUtil +import com.ffii.fpsms.modules.master.print.A4PrintDriverRegistry import com.ffii.fpsms.modules.master.entity.WarehouseRepository import com.ffii.fpsms.modules.master.web.ExportWarehouseQrCodeRequest +import com.ffii.fpsms.modules.master.web.PrintWarehouseQrCodeRequest import net.sf.jasperreports.engine.JasperCompileManager +import net.sf.jasperreports.engine.JasperExportManager +import net.sf.jasperreports.engine.JasperReport import net.sf.jasperreports.engine.JasperPrint import org.springframework.core.io.ClassPathResource import org.springframework.stereotype.Service +import java.io.File import java.io.FileNotFoundException import java.awt.GraphicsEnvironment import kotlinx.serialization.json.Json @@ -15,19 +20,32 @@ import kotlinx.serialization.encodeToString @Service class WarehouseQrCodeService( - private val warehouseRepository: WarehouseRepository + private val warehouseRepository: WarehouseRepository, + private val printerService: PrinterService, ) { + private val qrCodeHandleJrxmlPath = "qrCodeHandle/warehouse_QrHandle.jrxml" - fun exportWarehouseQrCode(request: ExportWarehouseQrCodeRequest): Map { - val QRCODE_HANDLE_PDF = "qrCodeHandle/warehouse_QrHandle.jrxml" - val resource = ClassPathResource(QRCODE_HANDLE_PDF) + /** Compile the Jasper template once; compiling per request is expensive. */ + private val qrCodeHandleReport: JasperReport by lazy { + val resource = ClassPathResource(qrCodeHandleJrxmlPath) if (!resource.exists()) { - throw FileNotFoundException("Report file not found: $QRCODE_HANDLE_PDF") + throw FileNotFoundException("Report file not found: $qrCodeHandleJrxmlPath") } - - val inputStream = resource.inputStream - val qrCodeHandleReport = JasperCompileManager.compileReport(inputStream) - + resource.inputStream.use { JasperCompileManager.compileReport(it) } + } + + /** Cache the chosen Chinese font family name (font scanning is expensive). */ + private val chineseFontFamily: String by lazy { + val availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames + availableFonts.find { family -> + family.contains("SimSun", ignoreCase = true) || + family.contains("Microsoft YaHei", ignoreCase = true) || + family.contains("STSong", ignoreCase = true) || + family.contains("SimHei", ignoreCase = true) + } ?: "Arial Unicode MS" + } + + fun exportWarehouseQrCode(request: ExportWarehouseQrCodeRequest): Map { val warehouses = warehouseRepository.findAllById(request.warehouseIds) if (warehouses.isEmpty()) { throw IllegalArgumentException("No warehouses found for the provided warehouse IDs: ${request.warehouseIds}") @@ -68,18 +86,10 @@ class WarehouseQrCodeService( } val params: MutableMap = mutableMapOf() - - val availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames - val chineseFont = availableFonts.find { - it.contains("SimSun", ignoreCase = true) || - it.contains("Microsoft YaHei", ignoreCase = true) || - it.contains("STSong", ignoreCase = true) || - it.contains("SimHei", ignoreCase = true) - } ?: "Arial Unicode MS" - + params["net.sf.jasperreports.default.pdf.encoding"] = "Identity-H" params["net.sf.jasperreports.default.pdf.embedded"] = true - params["net.sf.jasperreports.default.pdf.font.name"] = chineseFont + params["net.sf.jasperreports.default.pdf.font.name"] = chineseFontFamily val firstWarehouse = warehouses.firstOrNull() @@ -88,4 +98,23 @@ class WarehouseQrCodeService( "fileName" to (firstWarehouse?.code ?: "warehouse_qrcode") ) } + + fun printWarehouseQrCode(request: PrintWarehouseQrCodeRequest) { + val printer = printerService.findById(request.printerId) ?: throw NoSuchElementException("No such printer") + val pdf = exportWarehouseQrCode(ExportWarehouseQrCodeRequest(request.warehouseIds)) + val jasperPrint = pdf["report"] as JasperPrint + val tempPdfFile = File.createTempFile("print_job_", ".pdf") + + try { + JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath) + val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty + printer.ip?.let { ip -> + val port = printer.port ?: 9100 + val driver = A4PrintDriverRegistry.getDriver(printer.brand) + driver.print(tempPdfFile, ip, port, printQty) + } + } finally { + tempPdfFile.delete() + } + } } diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/EquipmentController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/EquipmentController.kt index 6251503..986b2ce 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/EquipmentController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/EquipmentController.kt @@ -93,4 +93,9 @@ fun getAllEquipmentByPage( out.flush() } + @PostMapping("/print-qrcode") + fun printQrCode(@Valid @RequestBody request: PrintEquipmentQrCodeRequest) { + equipmentQrCodeService.printEquipmentQrCode(request) + } + } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/PrintEquipmentQrCodeRequest.kt b/src/main/java/com/ffii/fpsms/modules/master/web/PrintEquipmentQrCodeRequest.kt new file mode 100644 index 0000000..574742d --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/web/PrintEquipmentQrCodeRequest.kt @@ -0,0 +1,7 @@ +package com.ffii.fpsms.modules.master.web + +data class PrintEquipmentQrCodeRequest( + val equipmentDetailIds: List, + val printerId: Long, + val printQty: Int? = 1, +) diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/PrintWarehouseQrCodeRequest.kt b/src/main/java/com/ffii/fpsms/modules/master/web/PrintWarehouseQrCodeRequest.kt new file mode 100644 index 0000000..6f4f1cc --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/web/PrintWarehouseQrCodeRequest.kt @@ -0,0 +1,7 @@ +package com.ffii.fpsms.modules.master.web + +data class PrintWarehouseQrCodeRequest( + val warehouseIds: List, + val printerId: Long, + val printQty: Int? = 1, +) diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/WarehouseController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/WarehouseController.kt index 3e7658c..94f9fcb 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/WarehouseController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/WarehouseController.kt @@ -98,6 +98,10 @@ class WarehouseController( out.write(JasperExportManager.exportReportToPdf(jasperPrint)) out.flush() } + @PostMapping("/print-qrcode") + fun printQrCode(@Valid @RequestBody request: PrintWarehouseQrCodeRequest) { + warehouseQrCodeService.printWarehouseQrCode(request) + } @GetMapping("/stockTakeSections") fun getStockTakeSections(): List { return warehouseService.getStockTakeSections() diff --git a/src/main/java/com/ffii/fpsms/modules/user/service/UserQrCodeService.kt b/src/main/java/com/ffii/fpsms/modules/user/service/UserQrCodeService.kt index 9b646cb..83bdda3 100644 --- a/src/main/java/com/ffii/fpsms/modules/user/service/UserQrCodeService.kt +++ b/src/main/java/com/ffii/fpsms/modules/user/service/UserQrCodeService.kt @@ -2,15 +2,18 @@ package com.ffii.fpsms.modules.user.service import com.ffii.core.utils.PdfUtils import com.ffii.core.utils.QrCodeUtil +import com.ffii.fpsms.modules.master.print.A4PrintDriverRegistry +import com.ffii.fpsms.modules.master.service.PrinterService import com.ffii.fpsms.modules.user.entity.UserRepository import com.ffii.fpsms.modules.user.web.ExportUserQrCodeRequest +import com.ffii.fpsms.modules.user.web.PrintUserQrCodeRequest import net.sf.jasperreports.engine.JasperCompileManager +import net.sf.jasperreports.engine.JasperExportManager +import net.sf.jasperreports.engine.JasperReport import net.sf.jasperreports.engine.JasperPrint -import net.sf.jasperreports.engine.export.JRPdfExporter -import net.sf.jasperreports.export.SimpleExporterInput -import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput import org.springframework.core.io.ClassPathResource import org.springframework.stereotype.Service +import java.io.File import java.io.FileNotFoundException import java.awt.GraphicsEnvironment import kotlinx.serialization.json.Json @@ -18,19 +21,34 @@ import kotlinx.serialization.encodeToString @Service class UserQrCodeService( - private val userRepository: UserRepository + private val userRepository: UserRepository, + private val printerService: PrinterService, ) { + private val qrCodeHandleJrxmlPath = "qrCodeHandle/qrCodeHandle.jrxml" - fun exportUserQrCode(request: ExportUserQrCodeRequest): Map { - val QRCODE_HANDLE_PDF = "qrCodeHandle/qrCodeHandle.jrxml" - val resource = ClassPathResource(QRCODE_HANDLE_PDF) + /** + * Compile the Jasper template once; compiling per request is expensive. + */ + private val qrCodeHandleReport: JasperReport by lazy { + val resource = ClassPathResource(qrCodeHandleJrxmlPath) if (!resource.exists()) { - throw FileNotFoundException("Report file not found: $QRCODE_HANDLE_PDF") + throw FileNotFoundException("Report file not found: $qrCodeHandleJrxmlPath") } - - val inputStream = resource.inputStream - val qrCodeHandleReport = JasperCompileManager.compileReport(inputStream) - + resource.inputStream.use { JasperCompileManager.compileReport(it) } + } + + /** Cache the chosen Chinese font family name (font scanning is expensive). */ + private val chineseFontFamily: String by lazy { + val availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames + availableFonts.find { family -> + family.contains("SimSun", ignoreCase = true) || + family.contains("Microsoft YaHei", ignoreCase = true) || + family.contains("STSong", ignoreCase = true) || + family.contains("SimHei", ignoreCase = true) + } ?: "Arial Unicode MS" + } + + fun exportUserQrCode(request: ExportUserQrCodeRequest): Map { val users = userRepository.findAllById(request.userIds) val fields = mutableListOf>() @@ -53,24 +71,33 @@ class UserQrCodeService( } val params: MutableMap = mutableMapOf() - - // Configure for Chinese character support - // Try to find a Chinese-supporting font - val availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames - val chineseFont = availableFonts.find { - it.contains("SimSun", ignoreCase = true) || - it.contains("Microsoft YaHei", ignoreCase = true) || - it.contains("STSong", ignoreCase = true) || - it.contains("SimHei", ignoreCase = true) - } ?: "Arial Unicode MS" // Fallback - + params["net.sf.jasperreports.default.pdf.encoding"] = "Identity-H" params["net.sf.jasperreports.default.pdf.embedded"] = true - params["net.sf.jasperreports.default.pdf.font.name"] = chineseFont + params["net.sf.jasperreports.default.pdf.font.name"] = chineseFontFamily return mapOf( "report" to PdfUtils.fillReport(qrCodeHandleReport, fields, params), "fileName" to (users.firstOrNull()?.username ?: "user_qrcode") ) } + + fun printUserQrCode(request: PrintUserQrCodeRequest) { + val printer = printerService.findById(request.printerId) ?: throw NoSuchElementException("No such printer") + val pdf = exportUserQrCode(ExportUserQrCodeRequest(request.userIds)) + val jasperPrint = pdf["report"] as JasperPrint + val tempPdfFile = File.createTempFile("print_job_", ".pdf") + + try { + JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath) + val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty + printer.ip?.let { ip -> + val port = printer.port ?: 9100 + val driver = A4PrintDriverRegistry.getDriver(printer.brand) + driver.print(tempPdfFile, ip, port, printQty) + } + } finally { + tempPdfFile.delete() + } + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/user/web/PrintUserQrCodeRequest.kt b/src/main/java/com/ffii/fpsms/modules/user/web/PrintUserQrCodeRequest.kt new file mode 100644 index 0000000..a50468c --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/user/web/PrintUserQrCodeRequest.kt @@ -0,0 +1,7 @@ +package com.ffii.fpsms.modules.user.web + +data class PrintUserQrCodeRequest( + val userIds: List, + val printerId: Long, + val printQty: Int? = 1, +) diff --git a/src/main/java/com/ffii/fpsms/modules/user/web/UserController.java b/src/main/java/com/ffii/fpsms/modules/user/web/UserController.java index ed08b73..af4abda 100644 --- a/src/main/java/com/ffii/fpsms/modules/user/web/UserController.java +++ b/src/main/java/com/ffii/fpsms/modules/user/web/UserController.java @@ -42,13 +42,11 @@ import com.ffii.fpsms.modules.user.req.UpdateUserReq; import com.ffii.fpsms.modules.user.service.UserService; import com.ffii.fpsms.modules.user.service.res.LoadUserRes; -import com.ffii.fpsms.modules.user.web.ExportUserQrCodeRequest; import com.ffii.fpsms.modules.user.service.UserQrCodeService; import jakarta.servlet.http.HttpServletResponse; import net.sf.jasperreports.engine.JasperExportManager; import net.sf.jasperreports.engine.JasperPrint; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -255,6 +253,11 @@ public class UserController{ out.flush(); } + @PostMapping("/print-qrcode") + public void printQrCode(@Valid @RequestBody PrintUserQrCodeRequest request) { + userQrCodeService.printUserQrCode(request); + } + public static class AdminChangePwdReq { private Long id; @NotBlank