浏览代码

update put away

master
cyril.tsui 9 小时前
父节点
当前提交
be506de370
共有 23 个文件被更改,包括 353 次插入51 次删除
  1. +52
    -12
      src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt
  2. +1
    -1
      src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt
  3. +1
    -2
      src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt
  4. +32
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/Printer.kt
  5. +13
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/PrinterRepository.kt
  6. +2
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt
  7. +16
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/projections/PrinterCombo.kt
  8. +11
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/projections/WarehouseCombo.kt
  9. +19
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/PrinterService.kt
  10. +5
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt
  11. +18
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/PrinterController.kt
  12. +1
    -3
      src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt
  13. +6
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/WarehouseController.kt
  14. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/EscalationLogInfo.kt
  15. +6
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLot.kt
  16. +2
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  17. +2
    -2
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfo.kt
  18. +19
    -3
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt
  19. +104
    -26
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  20. +6
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/StockInLineController.kt
  21. +8
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/PrintQrCodeRequest.kt
  22. +7
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt
  23. +19
    -0
      src/main/resources/db/changelog/changes/20250825_01_cyril/02_create_printer.sql

+ 52
- 12
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.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)
} }
} }


+ 1
- 1
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.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
- 2
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.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


+ 32
- 0
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<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
}

+ 13
- 0
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<Printer, Long> {
fun findPrinterComboByDeletedFalse(): List<PrinterCombo>;

fun findByIdAndDeletedFalse(id: Serializable): Printer?;
}

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt 查看文件

@@ -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>;
} }

+ 16
- 0
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?;
}

+ 11
- 0
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;
}

+ 19
- 0
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<PrinterCombo> {
return printerRepository.findPrinterComboByDeletedFalse();
}

open fun findById(id: Long): Printer? {
return printerRepository.findByIdAndDeletedFalse(id)
}
}

+ 5
- 0
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.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();
}
} }

+ 18
- 0
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<PrinterCombo> {
return printerService.findCombo();
}
}

+ 1
- 3
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.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


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/master/web/WarehouseController.kt 查看文件

@@ -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()
}
} }

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/EscalationLogInfo.kt 查看文件

@@ -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?




+ 6
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLot.kt 查看文件

@@ -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

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt 查看文件

@@ -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>
} }

+ 2
- 2
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfo.kt 查看文件

@@ -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 " +


+ 19
- 3
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt 查看文件

@@ -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?;
} }

+ 104
- 26
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.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
- 1
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.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)
}
} }

+ 8
- 0
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?,
)

+ 7
- 1
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt 查看文件

@@ -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?
) )

+ 19
- 0
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`)
);

正在加载...
取消
保存