diff --git a/src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt b/src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt index 12c21ca..f92b61f 100644 --- a/src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt +++ b/src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt @@ -3,6 +3,7 @@ package com.ffii.core.utils import org.apache.pdfbox.pdmodel.PDDocument import org.apache.pdfbox.rendering.PDFRenderer import org.apache.pdfbox.rendering.ImageType +import java.awt.Graphics2D import java.awt.image.BufferedImage import java.io.File import java.io.OutputStream @@ -15,18 +16,27 @@ import java.util.* */ open class ZebraPrinterUtil { - companion object { + // Enum to represent valid ZPL print directions + enum class PrintDirection(val zplCode: String, val degree: Double) { + NORMAL("N", 0.0), // Normal (0 degrees) + ROTATED("R", 90.0), // Rotated 90 degrees clockwise + INVERTED("I", 180.0), // Inverted 180 degrees + BOTTOM_UP("B", 270.0) // Bottom-up, 270 degrees clockwise + } + companion object { /** * Converts the first page of a PDF document into a ZPL command and sends it to the specified printer. * * @param pdfFile The PDF file to be printed. * @param printerIp The IP address of the Zebra printer. * @param printerPort The port of the Zebra printer, typically 9100. + * @param printQty The qty of print, default 1 + * @param printDirection Valid values: N (Normal), R (Rotated 90), I (Inverted 180), B (Bottom-up 270) * @throws Exception if there is an error during file processing or printing. */ @JvmStatic - fun printPdfToZebra(pdfFile: File, printerIp: String, printerPort: Int) { + fun printPdfToZebra(pdfFile: File, printerIp: String, printerPort: Int, printQty: Int? = 1, printDirection: PrintDirection = PrintDirection.NORMAL) { // Check if the file exists and is readable if (!pdfFile.exists() || !pdfFile.canRead()) { @@ -43,14 +53,16 @@ open class ZebraPrinterUtil { val image = renderer.renderImage(0, 300 / 72f, ImageType.BINARY) // 3. Convert the image to a ZPL format string - val zplCommand = convertImageToZpl(image) + val zplCommand = convertImageToZpl(image, printDirection) // 4. Send the ZPL command to the printer via a network socket val printData = zplCommand.toByteArray() - Socket(printerIp, printerPort).use { socket -> - val os: OutputStream = socket.getOutputStream() - os.write(printData) - os.flush() + repeat(printQty ?: 1) { index -> + Socket(printerIp, printerPort).use { socket -> + val os: OutputStream = socket.getOutputStream() + os.write(printData) + os.flush() + } } } } catch (e: Exception) { @@ -59,32 +71,60 @@ open class ZebraPrinterUtil { } } + /** + * Rotates a BufferedImage by the specified angle. + * + * @param image The BufferedImage to rotate. + * @param angleDegrees The rotation angle in degrees (clockwise). + * @return The rotated BufferedImage. + */ + private fun rotateImage(image: BufferedImage, angleDegrees: Double): BufferedImage { + val rads = Math.toRadians(angleDegrees) + val sin = Math.abs(Math.sin(rads)) + val cos = Math.abs(Math.cos(rads)) + val w = image.width + val h = image.height + val newWidth = (w * cos + h * sin).toInt() + val newHeight = (w * sin + h * cos).toInt() + + val rotated = BufferedImage(newWidth, newHeight, BufferedImage.TYPE_BYTE_BINARY) + val g2d: Graphics2D = rotated.createGraphics() + g2d.translate((newWidth - w) / 2, (newHeight - h) / 2) + g2d.rotate(rads, w / 2.0, h / 2.0) + g2d.drawImage(image, 0, 0, null) + g2d.dispose() + return rotated + } + /** * Converts a BufferedImage (monochrome) to a ZPL string using the ^GFA command. * This function handles the conversion of pixel data to a compressed hex string. * * @param image The BufferedImage to convert. + * @param printDirection Valid values: N (Normal), R (Rotated 90), I (Inverted 180), B (Bottom-up 270) * @return A ZPL-formatted string ready to be sent to the printer. */ - private fun convertImageToZpl(image: BufferedImage): String { + private fun convertImageToZpl(image: BufferedImage, printDirection: PrintDirection = PrintDirection.NORMAL): String { + val rotatedImage = rotateImage(image, printDirection.degree) val zpl = StringBuilder() - zpl.append("^XA\n") + zpl.append("^XA") - val width = image.width - val height = image.height + val width = rotatedImage.width + val height = rotatedImage.height // ZPL format for a graphical image is ^GFA val bytesPerRow = (width + 7) / 8 val totalBytes = bytesPerRow * height zpl.append("^FO0,0^GFA,").append(totalBytes).append(",").append(totalBytes).append(",").append(bytesPerRow).append(",") + println(zpl) // Extract pixel data and convert to ZPL hex string for (y in 0 until height) { val rowBits = BitSet(width) for (x in 0 until width) { // Check for a black pixel (0xFF000000 in ARGB) - if (image.getRGB(x, y) == 0xFF000000.toInt()) { + if (rotatedImage.getRGB(x, y) == 0xFF000000.toInt()) { rowBits.set(x) } } diff --git a/src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt b/src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt index 23412ae..5da2114 100644 --- a/src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt +++ b/src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt @@ -5,7 +5,7 @@ import com.ffii.fpsms.m18.M18Config import com.ffii.fpsms.m18.service.* import com.ffii.fpsms.m18.web.models.M18CommonRequest import com.ffii.fpsms.modules.common.SettingNames -import com.ffii.fpsms.modules.common.scheduler.SchedulerService +import com.ffii.fpsms.modules.common.scheduler.service.SchedulerService import com.ffii.fpsms.modules.master.entity.ItemUom import com.ffii.fpsms.modules.master.entity.Items import com.ffii.fpsms.modules.master.entity.ShopRepository diff --git a/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt b/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt index a08082c..8825219 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt @@ -1,4 +1,4 @@ -package com.ffii.fpsms.modules.common.scheduler +package com.ffii.fpsms.modules.common.scheduler.service import com.ffii.core.utils.JwtTokenUtil import com.ffii.fpsms.m18.service.M18DeliveryOrderService @@ -14,7 +14,6 @@ import org.slf4j.LoggerFactory import org.springframework.scheduling.TaskScheduler import org.springframework.scheduling.support.CronTrigger import org.springframework.stereotype.Service -import java.time.LocalDate import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.HashMap diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/Printer.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/Printer.kt new file mode 100644 index 0000000..82b5d4b --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/Printer.kt @@ -0,0 +1,32 @@ +package com.ffii.fpsms.modules.master.entity + +import com.ffii.core.entity.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.validation.constraints.Size + +@Entity +@Table(name = "printer") +open class Printer : BaseEntity() { + @Size(max = 500) + @Column(name = "code", length = 500) + open var code: String? = null + + @Size(max = 500) + @Column(name = "name", length = 500) + open var name: String? = null + + @Size(max = 500) + @Column(name = "description", length = 500) + open var description: String? = null + + @Size(max = 30) + @Column(name = "ip", length = 30) + open var ip: String? = null + + @Size(max = 10) + @Column(name = "port", length = 10) + open var port: Int? = null +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/PrinterRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/PrinterRepository.kt new file mode 100644 index 0000000..b1457f7 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/PrinterRepository.kt @@ -0,0 +1,13 @@ +package com.ffii.fpsms.modules.master.entity + +import com.ffii.core.support.AbstractRepository +import com.ffii.fpsms.modules.master.entity.projections.PrinterCombo +import org.springframework.stereotype.Repository +import java.io.Serializable + +@Repository +interface PrinterRepository : AbstractRepository { + fun findPrinterComboByDeletedFalse(): List; + + fun findByIdAndDeletedFalse(id: Serializable): Printer?; +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt index e020040..94bf93d 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt @@ -1,8 +1,10 @@ package com.ffii.fpsms.modules.master.entity import com.ffii.core.support.AbstractRepository +import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo import org.springframework.stereotype.Repository @Repository interface WarehouseRepository : AbstractRepository { + fun findWarehouseComboByDeletedFalse(): List; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/projections/PrinterCombo.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/projections/PrinterCombo.kt new file mode 100644 index 0000000..30540f7 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/projections/PrinterCombo.kt @@ -0,0 +1,16 @@ +package com.ffii.fpsms.modules.master.entity.projections + +import org.springframework.beans.factory.annotation.Value + +interface PrinterCombo { + val id: Long; + @get:Value("#{target.name}") + val label: String?; + @get:Value("#{target.id}") + val value: String; + val code: String?; + val name: String?; + val description: String?; + val ip: String?; + val port: Int?; +} diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/projections/WarehouseCombo.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/projections/WarehouseCombo.kt new file mode 100644 index 0000000..39c1983 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/projections/WarehouseCombo.kt @@ -0,0 +1,11 @@ +package com.ffii.fpsms.modules.master.entity.projections + +import org.springframework.beans.factory.annotation.Value + +interface WarehouseCombo { + val id: Long; + @get:Value("#{target.id}") + val value: Long; + @get:Value("#{target.code} - #{target.name}") + val label: String; +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/PrinterService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/PrinterService.kt new file mode 100644 index 0000000..c43fd7a --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/service/PrinterService.kt @@ -0,0 +1,19 @@ +package com.ffii.fpsms.modules.master.service + +import com.ffii.fpsms.modules.master.entity.Printer +import com.ffii.fpsms.modules.master.entity.PrinterRepository +import com.ffii.fpsms.modules.master.entity.projections.PrinterCombo +import org.springframework.stereotype.Service + +@Service +open class PrinterService( + val printerRepository: PrinterRepository +) { + open fun findCombo(): List { + return printerRepository.findPrinterComboByDeletedFalse(); + } + + open fun findById(id: Long): Printer? { + return printerRepository.findByIdAndDeletedFalse(id) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt index 349739b..af3bfab 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt @@ -5,6 +5,7 @@ import com.ffii.core.support.JdbcDao import com.ffii.fpsms.modules.master.entity.ItemsRepository import com.ffii.fpsms.modules.master.entity.Warehouse import com.ffii.fpsms.modules.master.entity.WarehouseRepository +import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository import com.ffii.fpsms.modules.stock.entity.StockInLine @@ -23,4 +24,8 @@ open class WarehouseService( open fun getWarehouses(): List { return warehouseRepository.findAll().filter { it.deleted == false } } + + open fun findCombo(): List { + return warehouseRepository.findWarehouseComboByDeletedFalse(); + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/PrinterController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/PrinterController.kt new file mode 100644 index 0000000..4dc2f17 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/web/PrinterController.kt @@ -0,0 +1,18 @@ +package com.ffii.fpsms.modules.master.web + +import com.ffii.fpsms.modules.master.entity.projections.PrinterCombo +import com.ffii.fpsms.modules.master.service.PrinterService +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("printers") +@RestController +class PrinterController( + private val printerService: PrinterService +) { + @GetMapping("/combo") + fun findCombo(): List { + return printerService.findCombo(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt index a2fa062..cebd7ad 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt @@ -2,7 +2,7 @@ package com.ffii.fpsms.modules.master.web import com.ffii.core.response.RecordsRes import com.ffii.core.utils.CriteriaArgsBuilder -import com.ffii.fpsms.modules.common.scheduler.SchedulerService +import com.ffii.fpsms.modules.common.scheduler.service.SchedulerService import com.ffii.fpsms.modules.master.entity.ProductionScheduleRepository import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleWithLine import com.ffii.fpsms.modules.master.entity.projections.ProdScheduleInfo @@ -16,14 +16,12 @@ import com.ffii.fpsms.modules.master.web.models.SearchProdScheduleRequest import jakarta.servlet.http.HttpServletRequest import jakarta.validation.Valid import org.springframework.web.bind.annotation.* -import java.time.Duration import java.time.LocalDate import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.HashMap import kotlin.collections.component1 import kotlin.collections.component2 -import kotlin.math.abs @RestController 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 51106fc..cafb7ff 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 @@ -1,6 +1,7 @@ package com.ffii.fpsms.modules.master.web import com.ffii.fpsms.modules.master.entity.Warehouse +import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo import com.ffii.fpsms.modules.master.service.WarehouseService import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -15,4 +16,9 @@ class WarehouseController( fun getWarehouses(): List { return warehouseService.getWarehouses() } + + @GetMapping("/combo") + fun findCombo(): List { + return warehouseService.findCombo() + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/EscalationLogInfo.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/EscalationLogInfo.kt index 3a28e09..2d96c2c 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/EscalationLogInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/EscalationLogInfo.kt @@ -28,6 +28,9 @@ interface EscalationLogInfo { @get:Value("#{target.stockInLine?.dnDate}") val dnDate: LocalDateTime? + @get:Value("#{target.stockInLine?.item?.code} - #{target.stockInLine?.item?.name}") + val item: String? + @get:Value("#{target.stockInLine?.item?.code}") val itemCode: String? diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLot.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLot.kt index 151f359..e79a807 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLot.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLot.kt @@ -1,6 +1,8 @@ package com.ffii.fpsms.modules.stock.entity import com.fasterxml.jackson.annotation.JsonBackReference +import com.fasterxml.jackson.annotation.JsonIdentityInfo +import com.fasterxml.jackson.annotation.ObjectIdGenerators import com.ffii.core.entity.BaseEntity import com.ffii.fpsms.modules.master.entity.Items import jakarta.persistence.* @@ -11,6 +13,7 @@ import org.hibernate.type.SqlTypes import java.time.LocalDate import java.time.LocalDateTime +@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id") @Entity @Table(name = "inventory_lot") open class InventoryLot : BaseEntity() { @@ -38,6 +41,9 @@ open class InventoryLot : BaseEntity() { @Column(name = "lotNo") open var lotNo: String? = null + @OneToMany(mappedBy = "inventoryLot", cascade = [CascadeType.ALL], orphanRemoval = true) + open var inventoryLotLines: MutableList? = null + // @JdbcTypeCode(SqlTypes.JSON) // @Column(name = "qrCodeJson") // open var qrCodeJson: MutableMap? = null diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt index e5486bb..9c07cbf 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt @@ -31,4 +31,6 @@ interface InventoryLotLineRepository : AbstractRepository): List fun findAllByIdIn(ids: List): List + + fun findAllByInventoryLotId(id: Serializable): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfo.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfo.kt index 523c7a4..ceca671 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfo.kt @@ -28,13 +28,13 @@ interface InventoryInfo{ val availableQty: BigDecimal? @get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom.code}") val uomCode: String? - @get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom.udfudesc}") + @get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom?.udfudesc}") val uomUdfudesc: String? // @get:Value("#{target.qty * target.uom.gramPerSmallestUnit}") // val germPerSmallestUnit: BigDecimal? @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty)}") val qtyPerSmallestUnit: BigDecimal? - @get:Value("#{target.item.itemUoms.^[baseUnit == true && deleted == false]?.uom.udfudesc}") + @get:Value("#{target.item.itemUoms.^[baseUnit == true && deleted == false]?.uom?.udfudesc}") val baseUom: String? // @get:Value("#{target.qty * (target.uom.unit4 != '' ? target.uom.unit4Qty " + // ": target.uom.unit3 != '' ? target.uom.unit3Qty " + diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt index 478ce55..a7bd82a 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt @@ -1,9 +1,7 @@ package com.ffii.fpsms.modules.stock.entity.projection -import com.ffii.fpsms.modules.master.entity.Items -import com.ffii.fpsms.modules.master.entity.ItemsRepository import com.ffii.fpsms.modules.master.entity.UomConversion -import com.ffii.fpsms.modules.stock.enums.EscalationLogStatus +import com.ffii.fpsms.modules.stock.entity.InventoryLot import org.springframework.beans.factory.annotation.Value import java.math.BigDecimal import java.time.LocalDate @@ -48,4 +46,22 @@ interface StockInLineInfo { val dnDate: LocalDateTime? @get:Value("#{target.escalationLog.^[status.value == 'pending']?.handler?.id}") val handlerId: Long? + @get:Value("#{target.inventoryLot?.inventoryLotLines ?: new java.util.ArrayList()}") + val putAwayLines: List; +} + +interface PutAwayLineForSil { + val id: Long?; + @get:Value("#{target.inQty " + + "* ((target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD))" + + "/ ((target.inventoryLot.item.itemUoms.^[purchaseUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[purchaseUnit == true && deleted == false]?.ratioD))}") + val qty: BigDecimal?; + @get:Value("#{target.warehouse?.code} - #{target.warehouse?.name}") + val warehouse: String?; + @get:Value("#{target.warehouse?.id}") + val warehouseId: Long?; + @get:Value("#{target.warehouse?.code}") + val warehouseCode: String?; + @get:Value("#{target.warehouse?.name}") + val warehouseName: String?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index 96b0801..fe62b0c 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -22,20 +22,25 @@ import java.math.BigDecimal import java.time.LocalDate import java.time.LocalDateTime import com.ffii.core.utils.PdfUtils; +import com.ffii.core.utils.ZebraPrinterUtil import com.ffii.fpsms.modules.master.entity.ItemUomRespository import com.ffii.fpsms.modules.master.entity.WarehouseRepository +import com.ffii.fpsms.modules.master.service.PrinterService import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus import com.ffii.fpsms.modules.stock.entity.projection.StockInLineInfo -import com.ffii.fpsms.modules.stock.enums.EscalationLogStatus import com.ffii.fpsms.modules.stock.web.model.* +import com.ffii.fpsms.modules.stock.enums.EscalationLogStatus import java.io.FileNotFoundException import java.time.format.DateTimeFormatter import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.encodeToString +import net.sf.jasperreports.engine.JasperExportManager +import net.sf.jasperreports.engine.JasperPrint +import java.io.File import kotlin.math.max @Serializable @@ -58,6 +63,7 @@ open class StockInLineService( private val itemRepository: ItemsRepository, private val warehouseRepository: WarehouseRepository, private val itemUomRespository: ItemUomRespository, + private val printerService: PrinterService, ): AbstractBaseEntityService(jdbcDao, stockInLineRepository) { open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { @@ -139,26 +145,53 @@ open class StockInLineService( @Throws(IOException::class) @Transactional - fun saveInventoryLotLineWhenStockIn(request: SaveStockInLineRequest, stockInLine: StockInLine): InventoryLotLine { - val inventoryLotLine = InventoryLotLine() - val warehouse = warehouseRepository.findById(request.warehouseId!!).orElseThrow() - val stockItemUom = itemUomRespository.findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse( - itemId = request.itemId - ) - val convertedBaseQty = if (stockItemUom != null) { - (request.acceptQty?:request.acceptedQty) * stockItemUom.ratioN!! / stockItemUom.ratioD!! - } else { - (request.acceptQty?:request.acceptedQty) - } - inventoryLotLine.apply { - this.inventoryLot = stockInLine.inventoryLot - this.warehouse = warehouse - this.inQty = convertedBaseQty - this.status = InventoryLotLineStatus.AVAILABLE - this.stockUom = stockItemUom + fun saveInventoryLotLineWhenStockIn(request: SaveStockInLineRequest, stockInLine: StockInLine): List { + val response = request.inventoryLotLines?.let { lines -> + val saveLines = mutableListOf(); + lines.forEach { line -> + val inventoryLotLine = InventoryLotLine() + val warehouse = warehouseRepository.findById(line.warehouseId!!).orElseThrow() + val stockItemUom = itemUomRespository.findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse( + itemId = request.itemId + ) + val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) + val convertedBaseQty = if (stockItemUom != null && purchaseItemUom != null) { + (line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) + } else { + (line.qty) + } + inventoryLotLine.apply { + this.inventoryLot = stockInLine.inventoryLot + this.warehouse = warehouse + this.inQty = convertedBaseQty + this.status = InventoryLotLineStatus.AVAILABLE + this.stockUom = stockItemUom + } + saveLines.add(inventoryLotLine) + } + inventoryLotLineRepository.saveAll(saveLines) } - val savedInventoryLotLine = inventoryLotLineRepository.saveAndFlush(inventoryLotLine) - return savedInventoryLotLine + + return response ?: emptyList(); +// val inventoryLotLine = InventoryLotLine() +// val warehouse = warehouseRepository.findById(request.warehouseId!!).orElseThrow() +// val stockItemUom = itemUomRespository.findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse( +// itemId = request.itemId +// ) +// val convertedBaseQty = if (stockItemUom != null) { +// (request.acceptQty?:request.acceptedQty) * stockItemUom.ratioN!! / stockItemUom.ratioD!! +// } else { +// (request.acceptQty?:request.acceptedQty) +// } +// inventoryLotLine.apply { +// this.inventoryLot = stockInLine.inventoryLot +// this.warehouse = warehouse +// this.inQty = convertedBaseQty +// this.status = InventoryLotLineStatus.AVAILABLE +// this.stockUom = stockItemUom +// } +// val savedInventoryLotLine = inventoryLotLineRepository.saveAndFlush(inventoryLotLine) +// return savedInventoryLotLine } @Throws(IOException::class) @@ -221,7 +254,7 @@ open class StockInLineService( @Throws(IOException::class) @Transactional - fun updatePurchaseOrderStatus(request: SaveStockInLineRequest) { + open fun updatePurchaseOrderStatus(request: SaveStockInLineRequest) { if (request.status == StockInLineStatus.COMPLETE.status) { val unfinishedLines = polRepository .findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(purchaseOrderId = request.purchaseOrderId, status = PurchaseOrderLineStatus.COMPLETED) @@ -320,10 +353,23 @@ open class StockInLineService( // Putaway var savedInventoryLotLine: InventoryLotLine? = null - savedInventoryLotLine = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine) - stockInLine.apply { - this.status = StockInLineStatus.COMPLETE.status - this.inventoryLotLine = savedInventoryLotLine +// savedInventoryLotLine = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine) + val savedInventoryLotLines = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine) + val inventoryLotLines = stockInLine.inventoryLot?.let { it.id?.let { _id -> inventoryLotLineRepository.findAllByInventoryLotId(_id) } } ?: listOf() + + val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) + val stockItemUom = itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(request.itemId) + val ratio = if (stockItemUom != null && purchaseItemUom != null) { + (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) + } else { + BigDecimal.ONE + } + + if (inventoryLotLines.sumOf { it.inQty ?: BigDecimal.ZERO } >= request.acceptQty?.times(ratio)) { + stockInLine.apply { + this.status = StockInLineStatus.COMPLETE.status +// this.inventoryLotLine = savedInventoryLotLine + } } } else if (request.status == StockInLineStatus.PENDING.status || request.status == StockInLineStatus.ESCALATED.status) { // QC @@ -481,7 +527,7 @@ open class StockInLineService( field["uom"] = info.uom.code.toString() field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" field["expiryDate"] = info.expiryDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" - field["lotNo"] = info.lotNo!! + field["lotNo"] = info.lotNo ?: "" field["supplier"] = info.supplier!! val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) field["qrCode"] = image @@ -495,4 +541,36 @@ open class StockInLineService( "fileName" to qrCodeInfo[0].poCode ); } + + @Transactional + open fun printQrCode(request: PrintQrCodeForSilRequest) { + val printer = printerService.findById(request.printerId) ?: throw NoSuchElementException("No such printer"); + + val pdf = exportStockInLineQrcode( + ExportQrCodeRequest( + stockInLineIds = listOf(request.stockInLineId) + ) + ) + + val jasperPrint = pdf["report"] as JasperPrint + + // 1. Create a temporary file to save the PDF. + val tempPdfFile = File.createTempFile("print_job_", ".pdf") + + try { + // 2. Export the JasperPrint to the temporary PDF file. + JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath) + + // 3. Call the utility function with the temporary file. + 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 { + // 4. Ensure the temporary file is deleted after the print job is sent. + tempPdfFile.delete() + } + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/StockInLineController.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/StockInLineController.kt index b2132cc..e793b2b 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/StockInLineController.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/StockInLineController.kt @@ -6,12 +6,14 @@ import com.ffii.fpsms.modules.stock.entity.StockInLine import com.ffii.fpsms.modules.stock.entity.projection.StockInLineInfo import com.ffii.fpsms.modules.stock.service.StockInLineService import com.ffii.fpsms.modules.stock.web.model.ExportQrCodeRequest +import com.ffii.fpsms.modules.stock.web.model.PrintQrCodeForSilRequest import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest import jakarta.servlet.http.HttpServletResponse import jakarta.validation.Valid import net.sf.jasperreports.engine.JasperExportManager import net.sf.jasperreports.engine.JasperPrint import org.springframework.context.NoSuchMessageException +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import java.io.OutputStream import java.io.UnsupportedEncodingException @@ -53,5 +55,8 @@ class StockInLineController( out.write(JasperExportManager.exportReportToPdf(jasperPrint)); } - + @GetMapping("/printQrCode") + fun printQrCode(@ModelAttribute request: PrintQrCodeForSilRequest) { + stockInLineService.printQrCode(request) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/PrintQrCodeRequest.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/PrintQrCodeRequest.kt new file mode 100644 index 0000000..2ecb6e8 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/PrintQrCodeRequest.kt @@ -0,0 +1,8 @@ +package com.ffii.fpsms.modules.stock.web.model + +// Stock in line +data class PrintQrCodeForSilRequest( + val stockInLineId: Long, + val printerId: Long, + val printQty: Int?, +) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt index 64e6ceb..32cf3c1 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt @@ -56,5 +56,11 @@ data class SaveStockInLineRequest( var qcResult: List?, var escalationLog: SaveEscalationLogRequest?, var warehouseId: Long?, - var rejectQty: BigDecimal? + var rejectQty: BigDecimal?, + var inventoryLotLines: List? +) + +data class SaveInventoryLotLineForSil ( + val qty: BigDecimal, + val warehouseId: Long? ) diff --git a/src/main/resources/db/changelog/changes/20250825_01_cyril/02_create_printer.sql b/src/main/resources/db/changelog/changes/20250825_01_cyril/02_create_printer.sql new file mode 100644 index 0000000..c3646cc --- /dev/null +++ b/src/main/resources/db/changelog/changes/20250825_01_cyril/02_create_printer.sql @@ -0,0 +1,19 @@ +-- liquibase formatted sql +-- changeset cyril:create_printer + +CREATE TABLE `printer` +( + `id` INT NOT NULL AUTO_INCREMENT, + `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` VARCHAR(30) NULL DEFAULT NULL, + `version` INT NOT NULL DEFAULT '0', + `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, + `deleted` TINYINT(1) NOT NULL DEFAULT '0', + `code` VARCHAR(500) NULL, + `name` VARCHAR(500) NULL, + `description` VARCHAR(500) NULL, + `ip` VARCHAR(30) NULL, + `port` INT(10) NULL, + PRIMARY KEY (`id`) +);