@@ -3,6 +3,7 @@ package com.ffii.core.utils | |||||
import org.apache.pdfbox.pdmodel.PDDocument | import org.apache.pdfbox.pdmodel.PDDocument | ||||
import org.apache.pdfbox.rendering.PDFRenderer | import org.apache.pdfbox.rendering.PDFRenderer | ||||
import org.apache.pdfbox.rendering.ImageType | import org.apache.pdfbox.rendering.ImageType | ||||
import java.awt.Graphics2D | |||||
import java.awt.image.BufferedImage | import java.awt.image.BufferedImage | ||||
import java.io.File | import java.io.File | ||||
import java.io.OutputStream | import java.io.OutputStream | ||||
@@ -15,18 +16,27 @@ import java.util.* | |||||
*/ | */ | ||||
open class ZebraPrinterUtil { | 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. | * 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 pdfFile The PDF file to be printed. | ||||
* @param printerIp The IP address of the Zebra printer. | * @param printerIp The IP address of the Zebra printer. | ||||
* @param printerPort The port of the Zebra printer, typically 9100. | * @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. | * @throws Exception if there is an error during file processing or printing. | ||||
*/ | */ | ||||
@JvmStatic | @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 | // Check if the file exists and is readable | ||||
if (!pdfFile.exists() || !pdfFile.canRead()) { | if (!pdfFile.exists() || !pdfFile.canRead()) { | ||||
@@ -43,14 +53,16 @@ open class ZebraPrinterUtil { | |||||
val image = renderer.renderImage(0, 300 / 72f, ImageType.BINARY) | val image = renderer.renderImage(0, 300 / 72f, ImageType.BINARY) | ||||
// 3. Convert the image to a ZPL format string | // 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 | // 4. Send the ZPL command to the printer via a network socket | ||||
val printData = zplCommand.toByteArray() | 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) { | } 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. | * 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. | * This function handles the conversion of pixel data to a compressed hex string. | ||||
* | * | ||||
* @param image The BufferedImage to convert. | * @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. | * @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() | 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 | // ZPL format for a graphical image is ^GFA | ||||
val bytesPerRow = (width + 7) / 8 | val bytesPerRow = (width + 7) / 8 | ||||
val totalBytes = bytesPerRow * height | val totalBytes = bytesPerRow * height | ||||
zpl.append("^FO0,0^GFA,").append(totalBytes).append(",").append(totalBytes).append(",").append(bytesPerRow).append(",") | 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 | // Extract pixel data and convert to ZPL hex string | ||||
for (y in 0 until height) { | for (y in 0 until height) { | ||||
val rowBits = BitSet(width) | val rowBits = BitSet(width) | ||||
for (x in 0 until width) { | for (x in 0 until width) { | ||||
// Check for a black pixel (0xFF000000 in ARGB) | // 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) | rowBits.set(x) | ||||
} | } | ||||
} | } | ||||
@@ -5,7 +5,7 @@ import com.ffii.fpsms.m18.M18Config | |||||
import com.ffii.fpsms.m18.service.* | import com.ffii.fpsms.m18.service.* | ||||
import com.ffii.fpsms.m18.web.models.M18CommonRequest | import com.ffii.fpsms.m18.web.models.M18CommonRequest | ||||
import com.ffii.fpsms.modules.common.SettingNames | 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.ItemUom | ||||
import com.ffii.fpsms.modules.master.entity.Items | import com.ffii.fpsms.modules.master.entity.Items | ||||
import com.ffii.fpsms.modules.master.entity.ShopRepository | import com.ffii.fpsms.modules.master.entity.ShopRepository | ||||
@@ -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.core.utils.JwtTokenUtil | ||||
import com.ffii.fpsms.m18.service.M18DeliveryOrderService | import com.ffii.fpsms.m18.service.M18DeliveryOrderService | ||||
@@ -14,7 +14,6 @@ import org.slf4j.LoggerFactory | |||||
import org.springframework.scheduling.TaskScheduler | import org.springframework.scheduling.TaskScheduler | ||||
import org.springframework.scheduling.support.CronTrigger | import org.springframework.scheduling.support.CronTrigger | ||||
import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
import java.time.LocalDate | |||||
import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
import java.util.HashMap | import java.util.HashMap | ||||
@@ -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<Long>() { | |||||
@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 | |||||
} |
@@ -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<Printer, Long> { | |||||
fun findPrinterComboByDeletedFalse(): List<PrinterCombo>; | |||||
fun findByIdAndDeletedFalse(id: Serializable): Printer?; | |||||
} |
@@ -1,8 +1,10 @@ | |||||
package com.ffii.fpsms.modules.master.entity | package com.ffii.fpsms.modules.master.entity | ||||
import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo | |||||
import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
@Repository | @Repository | ||||
interface WarehouseRepository : AbstractRepository<Warehouse, Long> { | interface WarehouseRepository : AbstractRepository<Warehouse, Long> { | ||||
fun findWarehouseComboByDeletedFalse(): List<WarehouseCombo>; | |||||
} | } |
@@ -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?; | |||||
} |
@@ -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; | |||||
} |
@@ -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<PrinterCombo> { | |||||
return printerRepository.findPrinterComboByDeletedFalse(); | |||||
} | |||||
open fun findById(id: Long): Printer? { | |||||
return printerRepository.findByIdAndDeletedFalse(id) | |||||
} | |||||
} |
@@ -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.ItemsRepository | ||||
import com.ffii.fpsms.modules.master.entity.Warehouse | import com.ffii.fpsms.modules.master.entity.Warehouse | ||||
import com.ffii.fpsms.modules.master.entity.WarehouseRepository | 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.purchaseOrder.entity.PurchaseOrderLineRepository | ||||
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | ||||
import com.ffii.fpsms.modules.stock.entity.StockInLine | import com.ffii.fpsms.modules.stock.entity.StockInLine | ||||
@@ -23,4 +24,8 @@ open class WarehouseService( | |||||
open fun getWarehouses(): List<Warehouse> { | open fun getWarehouses(): List<Warehouse> { | ||||
return warehouseRepository.findAll().filter { it.deleted == false } | return warehouseRepository.findAll().filter { it.deleted == false } | ||||
} | } | ||||
open fun findCombo(): List<WarehouseCombo> { | |||||
return warehouseRepository.findWarehouseComboByDeletedFalse(); | |||||
} | |||||
} | } |
@@ -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<PrinterCombo> { | |||||
return printerService.findCombo(); | |||||
} | |||||
} |
@@ -2,7 +2,7 @@ package com.ffii.fpsms.modules.master.web | |||||
import com.ffii.core.response.RecordsRes | import com.ffii.core.response.RecordsRes | ||||
import com.ffii.core.utils.CriteriaArgsBuilder | 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.ProductionScheduleRepository | ||||
import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleWithLine | import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleWithLine | ||||
import com.ffii.fpsms.modules.master.entity.projections.ProdScheduleInfo | 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.servlet.http.HttpServletRequest | ||||
import jakarta.validation.Valid | import jakarta.validation.Valid | ||||
import org.springframework.web.bind.annotation.* | import org.springframework.web.bind.annotation.* | ||||
import java.time.Duration | |||||
import java.time.LocalDate | import java.time.LocalDate | ||||
import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
import java.util.HashMap | import java.util.HashMap | ||||
import kotlin.collections.component1 | import kotlin.collections.component1 | ||||
import kotlin.collections.component2 | import kotlin.collections.component2 | ||||
import kotlin.math.abs | |||||
@RestController | @RestController | ||||
@@ -1,6 +1,7 @@ | |||||
package com.ffii.fpsms.modules.master.web | package com.ffii.fpsms.modules.master.web | ||||
import com.ffii.fpsms.modules.master.entity.Warehouse | 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 com.ffii.fpsms.modules.master.service.WarehouseService | ||||
import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||
import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||
@@ -15,4 +16,9 @@ class WarehouseController( | |||||
fun getWarehouses(): List<Warehouse> { | fun getWarehouses(): List<Warehouse> { | ||||
return warehouseService.getWarehouses() | return warehouseService.getWarehouses() | ||||
} | } | ||||
@GetMapping("/combo") | |||||
fun findCombo(): List<WarehouseCombo> { | |||||
return warehouseService.findCombo() | |||||
} | |||||
} | } |
@@ -28,6 +28,9 @@ interface EscalationLogInfo { | |||||
@get:Value("#{target.stockInLine?.dnDate}") | @get:Value("#{target.stockInLine?.dnDate}") | ||||
val dnDate: LocalDateTime? | val dnDate: LocalDateTime? | ||||
@get:Value("#{target.stockInLine?.item?.code} - #{target.stockInLine?.item?.name}") | |||||
val item: String? | |||||
@get:Value("#{target.stockInLine?.item?.code}") | @get:Value("#{target.stockInLine?.item?.code}") | ||||
val itemCode: String? | val itemCode: String? | ||||
@@ -1,6 +1,8 @@ | |||||
package com.ffii.fpsms.modules.stock.entity | package com.ffii.fpsms.modules.stock.entity | ||||
import com.fasterxml.jackson.annotation.JsonBackReference | 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.core.entity.BaseEntity | ||||
import com.ffii.fpsms.modules.master.entity.Items | import com.ffii.fpsms.modules.master.entity.Items | ||||
import jakarta.persistence.* | import jakarta.persistence.* | ||||
@@ -11,6 +13,7 @@ import org.hibernate.type.SqlTypes | |||||
import java.time.LocalDate | import java.time.LocalDate | ||||
import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id") | |||||
@Entity | @Entity | ||||
@Table(name = "inventory_lot") | @Table(name = "inventory_lot") | ||||
open class InventoryLot : BaseEntity<Long>() { | open class InventoryLot : BaseEntity<Long>() { | ||||
@@ -38,6 +41,9 @@ open class InventoryLot : BaseEntity<Long>() { | |||||
@Column(name = "lotNo") | @Column(name = "lotNo") | ||||
open var lotNo: String? = null | open var lotNo: String? = null | ||||
@OneToMany(mappedBy = "inventoryLot", cascade = [CascadeType.ALL], orphanRemoval = true) | |||||
open var inventoryLotLines: MutableList<InventoryLotLine>? = null | |||||
// @JdbcTypeCode(SqlTypes.JSON) | // @JdbcTypeCode(SqlTypes.JSON) | ||||
// @Column(name = "qrCodeJson") | // @Column(name = "qrCodeJson") | ||||
// open var qrCodeJson: MutableMap<String, Any>? = null | // open var qrCodeJson: MutableMap<String, Any>? = null |
@@ -31,4 +31,6 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long | |||||
fun findCurrentInventoryByItems(items: List<Serializable>): List<CurrentInventoryItemInfo> | fun findCurrentInventoryByItems(items: List<Serializable>): List<CurrentInventoryItemInfo> | ||||
fun findAllByIdIn(ids: List<Serializable>): List<InventoryLotLine> | fun findAllByIdIn(ids: List<Serializable>): List<InventoryLotLine> | ||||
fun findAllByInventoryLotId(id: Serializable): List<InventoryLotLine> | |||||
} | } |
@@ -28,13 +28,13 @@ interface InventoryInfo{ | |||||
val availableQty: BigDecimal? | val availableQty: BigDecimal? | ||||
@get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom.code}") | @get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom.code}") | ||||
val uomCode: String? | 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? | val uomUdfudesc: String? | ||||
// @get:Value("#{target.qty * target.uom.gramPerSmallestUnit}") | // @get:Value("#{target.qty * target.uom.gramPerSmallestUnit}") | ||||
// val germPerSmallestUnit: BigDecimal? | // val germPerSmallestUnit: BigDecimal? | ||||
@get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty)}") | @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty)}") | ||||
val qtyPerSmallestUnit: BigDecimal? | 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? | val baseUom: String? | ||||
// @get:Value("#{target.qty * (target.uom.unit4 != '' ? target.uom.unit4Qty " + | // @get:Value("#{target.qty * (target.uom.unit4 != '' ? target.uom.unit4Qty " + | ||||
// ": target.uom.unit3 != '' ? target.uom.unit3Qty " + | // ": target.uom.unit3 != '' ? target.uom.unit3Qty " + | ||||
@@ -1,9 +1,7 @@ | |||||
package com.ffii.fpsms.modules.stock.entity.projection | 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.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 org.springframework.beans.factory.annotation.Value | ||||
import java.math.BigDecimal | import java.math.BigDecimal | ||||
import java.time.LocalDate | import java.time.LocalDate | ||||
@@ -48,4 +46,22 @@ interface StockInLineInfo { | |||||
val dnDate: LocalDateTime? | val dnDate: LocalDateTime? | ||||
@get:Value("#{target.escalationLog.^[status.value == 'pending']?.handler?.id}") | @get:Value("#{target.escalationLog.^[status.value == 'pending']?.handler?.id}") | ||||
val handlerId: Long? | val handlerId: Long? | ||||
@get:Value("#{target.inventoryLot?.inventoryLotLines ?: new java.util.ArrayList()}") | |||||
val putAwayLines: List<PutAwayLineForSil>; | |||||
} | |||||
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?; | |||||
} | } |
@@ -22,20 +22,25 @@ import java.math.BigDecimal | |||||
import java.time.LocalDate | import java.time.LocalDate | ||||
import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
import com.ffii.core.utils.PdfUtils; | 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.ItemUomRespository | ||||
import com.ffii.fpsms.modules.master.entity.WarehouseRepository | 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.entity.PurchaseOrderRepository | ||||
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | ||||
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus | import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus | ||||
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | ||||
import com.ffii.fpsms.modules.stock.entity.projection.StockInLineInfo | 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.web.model.* | ||||
import com.ffii.fpsms.modules.stock.enums.EscalationLogStatus | |||||
import java.io.FileNotFoundException | import java.io.FileNotFoundException | ||||
import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
import kotlinx.serialization.Serializable | import kotlinx.serialization.Serializable | ||||
import kotlinx.serialization.json.Json | import kotlinx.serialization.json.Json | ||||
import kotlinx.serialization.encodeToString | import kotlinx.serialization.encodeToString | ||||
import net.sf.jasperreports.engine.JasperExportManager | |||||
import net.sf.jasperreports.engine.JasperPrint | |||||
import java.io.File | |||||
import kotlin.math.max | import kotlin.math.max | ||||
@Serializable | @Serializable | ||||
@@ -58,6 +63,7 @@ open class StockInLineService( | |||||
private val itemRepository: ItemsRepository, | private val itemRepository: ItemsRepository, | ||||
private val warehouseRepository: WarehouseRepository, | private val warehouseRepository: WarehouseRepository, | ||||
private val itemUomRespository: ItemUomRespository, | private val itemUomRespository: ItemUomRespository, | ||||
private val printerService: PrinterService, | |||||
): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | ||||
open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | ||||
@@ -139,26 +145,53 @@ open class StockInLineService( | |||||
@Throws(IOException::class) | @Throws(IOException::class) | ||||
@Transactional | @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<InventoryLotLine> { | |||||
val response = request.inventoryLotLines?.let { lines -> | |||||
val saveLines = mutableListOf<InventoryLotLine>(); | |||||
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) | @Throws(IOException::class) | ||||
@@ -221,7 +254,7 @@ open class StockInLineService( | |||||
@Throws(IOException::class) | @Throws(IOException::class) | ||||
@Transactional | @Transactional | ||||
fun updatePurchaseOrderStatus(request: SaveStockInLineRequest) { | |||||
open fun updatePurchaseOrderStatus(request: SaveStockInLineRequest) { | |||||
if (request.status == StockInLineStatus.COMPLETE.status) { | if (request.status == StockInLineStatus.COMPLETE.status) { | ||||
val unfinishedLines = polRepository | val unfinishedLines = polRepository | ||||
.findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(purchaseOrderId = request.purchaseOrderId, status = PurchaseOrderLineStatus.COMPLETED) | .findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(purchaseOrderId = request.purchaseOrderId, status = PurchaseOrderLineStatus.COMPLETED) | ||||
@@ -320,10 +353,23 @@ open class StockInLineService( | |||||
// Putaway | // Putaway | ||||
var savedInventoryLotLine: InventoryLotLine? = null | 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) { | } else if (request.status == StockInLineStatus.PENDING.status || request.status == StockInLineStatus.ESCALATED.status) { | ||||
// QC | // QC | ||||
@@ -481,7 +527,7 @@ open class StockInLineService( | |||||
field["uom"] = info.uom.code.toString() | field["uom"] = info.uom.code.toString() | ||||
field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" | field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" | ||||
field["expiryDate"] = info.expiryDate?.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!! | field["supplier"] = info.supplier!! | ||||
val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) | val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) | ||||
field["qrCode"] = image | field["qrCode"] = image | ||||
@@ -495,4 +541,36 @@ open class StockInLineService( | |||||
"fileName" to qrCodeInfo[0].poCode | "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() | |||||
} | |||||
} | |||||
} | } |
@@ -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.entity.projection.StockInLineInfo | ||||
import com.ffii.fpsms.modules.stock.service.StockInLineService | 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.ExportQrCodeRequest | ||||
import com.ffii.fpsms.modules.stock.web.model.PrintQrCodeForSilRequest | |||||
import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest | import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest | ||||
import jakarta.servlet.http.HttpServletResponse | import jakarta.servlet.http.HttpServletResponse | ||||
import jakarta.validation.Valid | import jakarta.validation.Valid | ||||
import net.sf.jasperreports.engine.JasperExportManager | import net.sf.jasperreports.engine.JasperExportManager | ||||
import net.sf.jasperreports.engine.JasperPrint | import net.sf.jasperreports.engine.JasperPrint | ||||
import org.springframework.context.NoSuchMessageException | import org.springframework.context.NoSuchMessageException | ||||
import org.springframework.http.ResponseEntity | |||||
import org.springframework.web.bind.annotation.* | import org.springframework.web.bind.annotation.* | ||||
import java.io.OutputStream | import java.io.OutputStream | ||||
import java.io.UnsupportedEncodingException | import java.io.UnsupportedEncodingException | ||||
@@ -53,5 +55,8 @@ class StockInLineController( | |||||
out.write(JasperExportManager.exportReportToPdf(jasperPrint)); | out.write(JasperExportManager.exportReportToPdf(jasperPrint)); | ||||
} | } | ||||
@GetMapping("/printQrCode") | |||||
fun printQrCode(@ModelAttribute request: PrintQrCodeForSilRequest) { | |||||
stockInLineService.printQrCode(request) | |||||
} | |||||
} | } |
@@ -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?, | |||||
) |
@@ -56,5 +56,11 @@ data class SaveStockInLineRequest( | |||||
var qcResult: List<SaveQcResultRequest>?, | var qcResult: List<SaveQcResultRequest>?, | ||||
var escalationLog: SaveEscalationLogRequest?, | var escalationLog: SaveEscalationLogRequest?, | ||||
var warehouseId: Long?, | var warehouseId: Long?, | ||||
var rejectQty: BigDecimal? | |||||
var rejectQty: BigDecimal?, | |||||
var inventoryLotLines: List<SaveInventoryLotLineForSil>? | |||||
) | |||||
data class SaveInventoryLotLineForSil ( | |||||
val qty: BigDecimal, | |||||
val warehouseId: Long? | |||||
) | ) |
@@ -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`) | |||||
); |