Tommy\2Fi-Staff пре 6 дана
родитељ
комит
c0c50a6832
5 измењених фајлова са 54 додато и 42 уклоњено
  1. +33
    -39
      src/main/java/com/ffii/core/utils/BrotherPrinterUtil.kt
  2. +1
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
  3. +2
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt
  4. +4
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  5. +14
    -3
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt

+ 33
- 39
src/main/java/com/ffii/core/utils/BrotherPrinterUtil.kt Прегледај датотеку

@@ -5,58 +5,51 @@ import org.apache.pdfbox.rendering.PDFRenderer
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.OutputStream
import java.net.Socket import java.net.Socket
import javax.imageio.ImageIO
import org.slf4j.LoggerFactory


class BrotherPrinterUtil { class BrotherPrinterUtil {


companion object { companion object {
private const val SOCKET_TIMEOUT_MS = 120_000 // 2 min for large jobs; Brother may be slow to accept
private const val BROTHER_DPI = 200 // Lower DPI = smaller job; some Brother models buffer better at 200

private val log = LoggerFactory.getLogger(BrotherPrinterUtil::class.java)

/** /**
* //Usage
* BrotherPrinterUtil.printToBrother(
pdfFile = File("document.pdf"),
printerIp = "192.168.1.50",
printQty = 1
)

* Sends a PDF to a Brother DCP-1610W by rendering it to a PCL-compatible format.
* Sends a PDF to a Brother (e.g. DCP-1610W) by rendering to PCL raster over port 9100.
* Uses socket timeout and flush-per-page so the printer has time to accept data.
*/ */
fun printToBrother(pdfFile: File, printerIp: String, printerPort: Int = 9100, printQty: Int = 1) { fun printToBrother(pdfFile: File, printerIp: String, printerPort: Int = 9100, printQty: Int = 1) {
if (!pdfFile.exists()) throw IllegalArgumentException("File not found.")
if (!pdfFile.exists()) throw IllegalArgumentException("File not found: ${pdfFile.absolutePath}")


println("DEBUG: PDF file size: ${pdfFile.length()} bytes")
log.info("Brother print: file=${pdfFile.name} size=${pdfFile.length()} ip=$printerIp port=$printerPort copies=$printQty")


PDDocument.load(pdfFile).use { document -> PDDocument.load(pdfFile).use { document ->
val renderer = PDFRenderer(document) val renderer = PDFRenderer(document)

val totalPages = document.numberOfPages val totalPages = document.numberOfPages
repeat(printQty) { copyIndex ->
println("DEBUG: Printing copy ${copyIndex + 1} of $printQty")
}


Socket(printerIp, printerPort).use { socket -> Socket(printerIp, printerPort).use { socket ->
socket.soTimeout = SOCKET_TIMEOUT_MS
val os = socket.getOutputStream() val os = socket.getOutputStream()
// 1. Start PJL Job
// 1. PJL header (optional; some Brother prefer minimal PJL)
os.write(getPjlHeader(printQty)) os.write(getPjlHeader(printQty))
os.flush()


// 2. Render each page as a 300 DPI image
for (pageIndex in 0 until document.numberOfPages) {
val image = renderer.renderImageWithDPI(pageIndex, 300f, org.apache.pdfbox.rendering.ImageType.BINARY)
// 3. Convert Image to PCL Raster Data
val pclData = convertImageToPcl(image)
os.write(pclData)
// Form Feed (Move to next page)
os.write("\u000C".toByteArray())
repeat(printQty) { copyIndex ->
for (pageIndex in 0 until totalPages) {
val image = renderer.renderImageWithDPI(pageIndex, BROTHER_DPI.toFloat(), org.apache.pdfbox.rendering.ImageType.BINARY)
val pclData = convertImageToPcl(image, BROTHER_DPI)
os.write(pclData)
os.write("\u000C".toByteArray(Charsets.US_ASCII)) // Form feed
os.flush() // Flush per page so Brother receives incrementally
}
} }


// 4. End Job
os.write("\u001B%-12345X".toByteArray(Charsets.US_ASCII)) os.write("\u001B%-12345X".toByteArray(Charsets.US_ASCII))
os.flush() os.flush()
println("DEBUG: Print job sent to printer")
log.info("Brother print job sent successfully")
} }
} }
} }
@@ -64,24 +57,25 @@ class BrotherPrinterUtil {
private fun getPjlHeader(qty: Int): ByteArray { private fun getPjlHeader(qty: Int): ByteArray {
val pjl = StringBuilder() val pjl = StringBuilder()
pjl.append("\u001B%-12345X@PJL\r\n") pjl.append("\u001B%-12345X@PJL\r\n")
pjl.append("@PJL SET COPIES=$qty\r\n")
pjl.append("@PJL ENTER LANGUAGE=PCL\r\n") // Tell printer to expect PCL graphics, not PDF
pjl.append("@PJL SET COPIES=1\r\n") // We send copies in app; some Brother ignore COPIES or mis-handle it
pjl.append("@PJL ENTER LANGUAGE=PCL\r\n")
return pjl.toString().toByteArray(Charsets.US_ASCII) return pjl.toString().toByteArray(Charsets.US_ASCII)
} }


/** /**
* Converts a BufferedImage into PCL Level 3/5 Raster Graphics.
* This is the "magic" that allows a budget printer to print a PDF.
* Converts a BufferedImage into PCL raster graphics for Brother.
* Uses given DPI (e.g. 200) to keep job size smaller for printers with limited buffer.
*/ */
private fun convertImageToPcl(image: BufferedImage): ByteArray {
private fun convertImageToPcl(image: BufferedImage, dpi: Int): ByteArray {
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
val width = image.width val width = image.width
val height = image.height val height = image.height


// PCL: Start Graphics at 300 DPI
out.write("\u001B*t300R".toByteArray())
// PCL: Start Raster Graphics
out.write("\u001B*r1A".toByteArray())
// PCL reset (Brother often needs clean state)
out.write("\u001BE".toByteArray(Charsets.US_ASCII)) // Esc E = reset
// PCL: Set resolution and start raster
out.write("\u001B*t${dpi}R".toByteArray(Charsets.US_ASCII))
out.write("\u001B*r1A".toByteArray(Charsets.US_ASCII))


for (y in 0 until height) { for (y in 0 until height) {
val rowBytes = (width + 7) / 8 val rowBytes = (width + 7) / 8


+ 1
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt Прегледај датотеку

@@ -46,6 +46,7 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> {
b.code as itemCode, b.code as itemCode,
b.name, b.name,
jo.reqQty, jo.reqQty,
jo.bomId,
b.outputQtyUom as unit, b.outputQtyUom as unit,
uc2.udfudesc as uom, uc2.udfudesc as uom,
COALESCE(uc2.udfShortDesc, uc2.udfudesc) as shortUom, COALESCE(uc2.udfShortDesc, uc2.udfudesc) as shortUom,


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt Прегледај датотеку

@@ -76,6 +76,7 @@ interface JobOrderDetailWithJsonString {
val code: String?; val code: String?;
val itemCode: String?; val itemCode: String?;
val name: String?; val name: String?;
val bomId: Long?;
val itemId: Long?; val itemId: Long?;
val reqQty: BigDecimal?; val reqQty: BigDecimal?;
val uom: String?; val uom: String?;
@@ -115,6 +116,7 @@ data class JobOrderDetail(
val code: String?, val code: String?,
val itemCode: String?, val itemCode: String?,
val name: String?, val name: String?,
val bomId: Long?,
val reqQty: BigDecimal?, val reqQty: BigDecimal?,
val uom: String?, val uom: String?,
val shortUom: String?, val shortUom: String?,


+ 4
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt Прегледај датотеку

@@ -334,6 +334,7 @@ open class JobOrderService(
name = sqlResult.name, name = sqlResult.name,
reqQty = sqlResult.reqQty, reqQty = sqlResult.reqQty,
uom = sqlResult.uom, uom = sqlResult.uom,
bomId = sqlResult.bomId,
pickLines = jsonResult, pickLines = jsonResult,
status = sqlResult.status, status = sqlResult.status,
shortUom = sqlResult.shortUom shortUom = sqlResult.shortUom
@@ -352,6 +353,7 @@ open class JobOrderService(
name = sqlResult.name, name = sqlResult.name,
reqQty = sqlResult.reqQty, reqQty = sqlResult.reqQty,
uom = sqlResult.uom, uom = sqlResult.uom,
bomId = sqlResult.bomId,
pickLines = jsonResult, pickLines = jsonResult,
status = sqlResult.status, status = sqlResult.status,
shortUom = sqlResult.shortUom shortUom = sqlResult.shortUom
@@ -371,6 +373,7 @@ open class JobOrderService(
code = sqlResult.code, code = sqlResult.code,
itemCode = sqlResult.itemCode, itemCode = sqlResult.itemCode,
name = sqlResult.name, name = sqlResult.name,
bomId = sqlResult.bomId,
reqQty = sqlResult.reqQty, reqQty = sqlResult.reqQty,
uom = sqlResult.uom, uom = sqlResult.uom,
pickLines = jsonResult, pickLines = jsonResult,
@@ -390,6 +393,7 @@ open class JobOrderService(
code = sqlResult.code, code = sqlResult.code,
itemCode = sqlResult.itemCode, itemCode = sqlResult.itemCode,
name = sqlResult.name, name = sqlResult.name,
bomId = sqlResult.bomId,
reqQty = sqlResult.reqQty, reqQty = sqlResult.reqQty,
uom = sqlResult.uom, uom = sqlResult.uom,
pickLines = jsonResult, pickLines = jsonResult,


+ 14
- 3
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt Прегледај датотеку

@@ -298,7 +298,7 @@ open class StockInLineService(
) )
val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId)
// PO-origin: frontend sends qty in stock; non-PO: treat as purchase and convert to stock // PO-origin: frontend sends qty in stock; non-PO: treat as purchase and convert to stock
val convertedBaseQty = if (stockInLine.purchaseOrderLine != null) {
val convertedBaseQty = if (stockInLine.purchaseOrderLine != null || stockInLine.jobOrder != null) {
line.qty line.qty
} else if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) { } else if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
(line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) (line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!)
@@ -680,6 +680,17 @@ open class StockInLineService(
} else { } else {
requestQty ?: this.acceptedQty requestQty ?: this.acceptedQty
} }
} else if (request.qcAccept == true && this.status == StockInLineStatus.ESCALATED.status) {
// Case: line was escalated (QC decision 3), handler resolves with decision 1 (accept).
// Use 揀收數量 (acceptQty) for put away instead of keeping original received qty.
val requestQty = request.acceptQty ?: request.acceptedQty
if (requestQty != null) {
this.acceptedQty = if (this.purchaseOrderLine != null && this.item?.id != null) {
itemUomService.convertPurchaseQtyToStockQty(this.item!!.id!!, requestQty)
} else {
requestQty
}
}
} }
// Set demandQty based on source // Set demandQty based on source
if (this.jobOrder != null && this.jobOrder?.bom != null) { if (this.jobOrder != null && this.jobOrder?.bom != null) {
@@ -710,8 +721,8 @@ open class StockInLineService(
) )
val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId)


val convertedBaseQty = if (stockInLine.purchaseOrderLine != null) {
// PO-origin: qty is already stock qty
val convertedBaseQty = if (stockInLine.purchaseOrderLine != null || stockInLine.jobOrder != null) {
// PO and Job Order: qty is already stock qty
line.qty line.qty
} else if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) { } else if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
// Legacy: treat as purchase qty, convert to stock qty // Legacy: treat as purchase qty, convert to stock qty


Loading…
Откажи
Сачувај