@@ -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) | |||
} | |||
} | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
import com.ffii.core.support.AbstractRepository | |||
import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo | |||
import org.springframework.stereotype.Repository | |||
@Repository | |||
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.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<Warehouse> { | |||
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.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 | |||
@@ -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<Warehouse> { | |||
return warehouseService.getWarehouses() | |||
} | |||
@GetMapping("/combo") | |||
fun findCombo(): List<WarehouseCombo> { | |||
return warehouseService.findCombo() | |||
} | |||
} |
@@ -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? | |||
@@ -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<Long>() { | |||
@@ -38,6 +41,9 @@ open class InventoryLot : BaseEntity<Long>() { | |||
@Column(name = "lotNo") | |||
open var lotNo: String? = null | |||
@OneToMany(mappedBy = "inventoryLot", cascade = [CascadeType.ALL], orphanRemoval = true) | |||
open var inventoryLotLines: MutableList<InventoryLotLine>? = null | |||
// @JdbcTypeCode(SqlTypes.JSON) | |||
// @Column(name = "qrCodeJson") | |||
// open var qrCodeJson: MutableMap<String, Any>? = null |
@@ -31,4 +31,6 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long | |||
fun findCurrentInventoryByItems(items: List<Serializable>): List<CurrentInventoryItemInfo> | |||
fun findAllByIdIn(ids: List<Serializable>): List<InventoryLotLine> | |||
fun findAllByInventoryLotId(id: Serializable): List<InventoryLotLine> | |||
} |
@@ -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 " + | |||
@@ -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<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.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<StockInLine, Long, StockInLineRepository>(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<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) | |||
@@ -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() | |||
} | |||
} | |||
} |
@@ -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) | |||
} | |||
} |
@@ -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 escalationLog: SaveEscalationLogRequest?, | |||
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`) | |||
); |