| @@ -35,7 +35,6 @@ open class ZebraPrinterUtil { | |||
| * @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, printQty: Int? = 1, printDirection: PrintDirection = PrintDirection.NORMAL) { | |||
| // Check if the file exists and is readable | |||
| @@ -47,22 +46,36 @@ open class ZebraPrinterUtil { | |||
| // 1. Load the PDF document | |||
| PDDocument.load(pdfFile).use { document -> | |||
| val renderer = PDFRenderer(document) | |||
| val totalPages = document.numberOfPages | |||
| // 2. Render the first page of the PDF as a monochrome image | |||
| // The '300 / 72f' scales the image to 300 DPI. | |||
| val image = renderer.renderImage(0, 300 / 72f, ImageType.BINARY) | |||
| println("DEBUG: PDF has $totalPages pages") | |||
| // 3. Convert the image to a ZPL format string | |||
| val zplCommand = convertImageToZpl(image, printDirection) | |||
| // Print each page for the specified quantity | |||
| repeat(printQty ?: 1) { copyIndex -> | |||
| println("DEBUG: Printing copy ${copyIndex + 1} of ${printQty ?: 1}") | |||
| // 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() | |||
| repeat(printQty ?: 1) { index -> | |||
| // Iterate through all pages in the PDF | |||
| for (pageIndex in 0 until totalPages) { | |||
| println("DEBUG: Processing page ${pageIndex + 1} of $totalPages") | |||
| // 2. Render each page of the PDF as a monochrome image | |||
| val image = renderer.renderImage(pageIndex, 300 / 72f, ImageType.BINARY) | |||
| // 3. Convert the image to a ZPL format string | |||
| val zplCommand = convertImageToZpl(image, printDirection) | |||
| // 4. Send each page as a separate print job | |||
| Socket(printerIp, printerPort).use { socket -> | |||
| val os: OutputStream = socket.getOutputStream() | |||
| val printData = zplCommand.toByteArray() | |||
| os.write(printData) | |||
| os.flush() | |||
| println("DEBUG: Page ${pageIndex + 1} sent to printer") | |||
| } | |||
| // Small delay between pages to ensure printer can process each page | |||
| Thread.sleep(100) | |||
| } | |||
| } | |||
| } | |||
| } catch (e: Exception) { | |||
| @@ -695,7 +695,7 @@ open class DeliveryOrderService( | |||
| //Print Delivery Note | |||
| @Transactional | |||
| open fun printDeliveryNote(request: PrintDeliveryNoteRequest){ | |||
| //val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val pdf = exportDeliveryNote( | |||
| ExportDeliveryNoteRequest( | |||
| @@ -713,12 +713,12 @@ open class DeliveryOrderService( | |||
| try{ | |||
| JasperExportManager.exportReportToPdfFile(jasperPrint,tempPdfFile.absolutePath) | |||
| // 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) | |||
| // }} | |||
| 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 { | |||
| // tempPdfFile.delete() | |||
| //tempPdfFile.delete() | |||
| } | |||
| } | |||
| @@ -754,14 +754,15 @@ open class DeliveryOrderService( | |||
| "filename" to "${cartonLabelInfo[0].code}_carton_labels" | |||
| ) | |||
| } | |||
| //Print Carton Labels | |||
| @Transactional | |||
| open fun printDNLabels(request: PrintDNLabelsRequest){ | |||
| //val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val pdf = exportDNLabels( | |||
| ExportDNLabelsRequest( | |||
| @@ -778,10 +779,10 @@ open class DeliveryOrderService( | |||
| JasperExportManager.exportReportToPdfFile(jasperPrint,tempPdfFile.absolutePath) | |||
| //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) | |||
| //}} | |||
| 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) | |||
| }} | |||
| println("Test PDF saved to: ${tempPdfFile.absolutePath}") | |||
| @@ -21,6 +21,7 @@ import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDNLabelsRequest | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDeliveryNoteRequest | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.ReleaseDoRequest | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.stock.web.model.PrintQrCodeForSilRequest | |||
| import jakarta.servlet.http.HttpServletResponse | |||
| import jakarta.validation.Valid | |||
| import net.sf.jasperreports.engine.JasperExportManager | |||
| @@ -195,8 +196,8 @@ class DeliveryOrderController( | |||
| out.write(JasperExportManager.exportReportToPdf(jasperPrint)) | |||
| } | |||
| @GetMapping("print-DN") | |||
| fun printDN(@RequestBody request: PrintDeliveryNoteRequest) { | |||
| @GetMapping("/print-DN") | |||
| fun printDN(@ModelAttribute request: PrintDeliveryNoteRequest) { | |||
| deliveryOrderService.printDeliveryNote(request) | |||
| } | |||
| @@ -213,7 +214,7 @@ class DeliveryOrderController( | |||
| } | |||
| @GetMapping("print-DNLabels") | |||
| fun printDN(@RequestBody request: PrintDNLabelsRequest) { | |||
| fun printDN(@ModelAttribute request: PrintDNLabelsRequest) { | |||
| deliveryOrderService.printDNLabels(request) | |||
| } | |||
| } | |||
| @@ -2,6 +2,8 @@ package com.ffii.fpsms.modules.jobOrder.entity | |||
| import com.fasterxml.jackson.annotation.JsonBackReference | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderBomMaterialStatus | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderBomMaterialStatusConverter | |||
| import com.ffii.fpsms.modules.master.entity.Items | |||
| import com.ffii.fpsms.modules.master.entity.UomConversion | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLine | |||
| @@ -35,10 +37,11 @@ open class JobOrderBomMaterial : BaseEntity<Long>() { | |||
| @JoinColumn(name = "uomId", nullable = false) | |||
| open var uom: UomConversion? = null | |||
| @Size(max = 255) | |||
| // @Size(max = 255) | |||
| @NotNull | |||
| @Column(name = "status", nullable = false) | |||
| open var status: String? = null | |||
| @Convert(converter = JobOrderBomMaterialStatusConverter::class) | |||
| open var status: JobOrderBomMaterialStatus? = null | |||
| @ManyToOne | |||
| @JoinColumn(name = "stockOutLineId") | |||
| @@ -6,4 +6,6 @@ import org.springframework.stereotype.Repository | |||
| @Repository | |||
| interface JobOrderBomMaterialRepository : AbstractRepository<JobOrderBomMaterial, Long> { | |||
| fun findByJobOrderIdAndItemId(jobOrderId: Long, itemId: Long): JobOrderBomMaterial?; | |||
| fun findAllByJobOrderId(jobOrderId: Long): List<JobOrderBomMaterial>; | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.enums | |||
| enum class JobOrderBomMaterialStatus(val value: String) { | |||
| PENDING("pending"), | |||
| COMPLETED("completed") | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.enums | |||
| import jakarta.persistence.AttributeConverter | |||
| import jakarta.persistence.Converter | |||
| // Job Order Bom Material Status | |||
| @Converter(autoApply = true) | |||
| class JobOrderBomMaterialStatusConverter : AttributeConverter<JobOrderBomMaterialStatus, String> { | |||
| override fun convertToDatabaseColumn(status: JobOrderBomMaterialStatus?): String? { | |||
| return status?.value | |||
| } | |||
| override fun convertToEntityAttribute(value: String?): JobOrderBomMaterialStatus? { | |||
| return value?.let { v -> | |||
| JobOrderBomMaterialStatus.entries.find { it.value == v } | |||
| } | |||
| } | |||
| } | |||
| @@ -3,6 +3,7 @@ package com.ffii.fpsms.modules.jobOrder.service | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterial | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterialRepository | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderBomMaterialStatus | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderBomMaterialRequest | |||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||
| import com.ffii.fpsms.modules.master.entity.UomConversionRepository | |||
| @@ -45,13 +46,14 @@ open class JobOrderBomMaterialService( | |||
| val jo = req.joId?.let { jobOrderRepository.findById(it).getOrNull() } | |||
| val item = req.itemId?.let { itemsRepository.findById(it).getOrNull() } | |||
| val uom = req.uomId?.let { uomConversionRepository.findById(it).getOrNull() } | |||
| val status = JobOrderBomMaterialStatus.entries.find { it.value === req.status } | |||
| JobOrderBomMaterial().apply { | |||
| jobOrder = jo | |||
| this.item = item | |||
| reqQty = req.reqQty | |||
| this.uom = uom | |||
| status = req.status | |||
| this.status = status | |||
| } | |||
| } | |||
| @@ -70,4 +72,29 @@ open class JobOrderBomMaterialService( | |||
| fun createJobOrderBomMaterialsByJoId(joId: Long): MessageResponse { | |||
| return createJobOrderBomMaterials(createJobOrderBomMaterialRequests(joId)) | |||
| } | |||
| // After Picked Job Order Bom Material | |||
| fun completeJobOrderBomMaterials(joId: Long): MessageResponse { | |||
| val jobms = jobOrderBomMaterialRepository.findAllByJobOrderId(joId) | |||
| val updatedJobms = jobms.map { | |||
| it.apply { | |||
| status = JobOrderBomMaterialStatus.COMPLETED | |||
| } | |||
| } | |||
| jobOrderBomMaterialRepository.saveAll(updatedJobms) | |||
| return MessageResponse( | |||
| id = null, | |||
| name = null, | |||
| code = null, | |||
| type = null, | |||
| message = "Success", | |||
| errorPosition = null | |||
| ) | |||
| } | |||
| fun completeJobOrderBomMaterialsByJoId(joId: Long): MessageResponse { | |||
| return completeJobOrderBomMaterials(joId) | |||
| } | |||
| } | |||
| @@ -10,7 +10,7 @@ import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderReleaseRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderCommonActionRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest | |||
| import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository | |||
| import com.ffii.fpsms.modules.master.service.BomService | |||
| @@ -158,7 +158,7 @@ open class JobOrderService( | |||
| } | |||
| @Transactional(rollbackFor = [Exception::class]) | |||
| open fun releaseJobOrder(request: JobOrderReleaseRequest): MessageResponse { | |||
| open fun releaseJobOrder(request: JobOrderCommonActionRequest): MessageResponse { | |||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | |||
| jo.apply { | |||
| status = JobOrderStatus.PENDING | |||
| @@ -255,6 +255,26 @@ open class JobOrderService( | |||
| } | |||
| return MessageResponse( | |||
| id = jo.id, | |||
| code = jo.code, | |||
| name = jo.bom?.name, | |||
| type = null, | |||
| message = null, | |||
| errorPosition = null, | |||
| entity = mapOf("status" to jo.status?.value) | |||
| ) | |||
| } | |||
| @Transactional(rollbackFor = [Exception::class]) | |||
| open fun startJobOrder(request: JobOrderCommonActionRequest): MessageResponse { | |||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | |||
| jo.apply { | |||
| status = JobOrderStatus.PROCESSING | |||
| } | |||
| jobOrderRepository.save(jo) | |||
| return MessageResponse( | |||
| id = jo.id, | |||
| code = jo.code, | |||
| @@ -7,7 +7,7 @@ import com.ffii.fpsms.modules.jobOrder.service.JobOrderBomMaterialService | |||
| import com.ffii.fpsms.modules.jobOrder.service.JobOrderProcessService | |||
| import com.ffii.fpsms.modules.jobOrder.service.JobOrderService | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderReleaseRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderCommonActionRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import jakarta.validation.Valid | |||
| @@ -46,10 +46,21 @@ class JobOrderController( | |||
| } | |||
| @PostMapping("/release") | |||
| fun releaseJobOrder(@Valid @RequestBody request: JobOrderReleaseRequest): MessageResponse { | |||
| fun releaseJobOrder(@Valid @RequestBody request: JobOrderCommonActionRequest): MessageResponse { | |||
| return jobOrderService.releaseJobOrder(request) | |||
| } | |||
| @PostMapping("/start") | |||
| fun startJobOrder(@Valid @RequestBody request: JobOrderCommonActionRequest): MessageResponse { | |||
| val jo = jobOrderService.startJobOrder(request) | |||
| if (jo.id == null) { | |||
| throw NoSuchElementException() | |||
| } | |||
| jobOrderBomMaterialService.completeJobOrderBomMaterialsByJoId(jo.id) | |||
| return jo | |||
| } | |||
| @PostMapping("/manualCreate") | |||
| fun manualCreateJobOrder(@Valid @RequestBody request: CreateJobOrderRequest): MessageResponse { | |||
| val jo = jobOrderService.createJobOrder(request) | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.web.model | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderBomMaterialStatus | |||
| import java.math.BigDecimal | |||
| import java.time.LocalDateTime | |||
| @@ -19,7 +20,7 @@ data class CreateJobOrderBomMaterialRequest ( | |||
| val itemId: Long?, | |||
| val reqQty: BigDecimal?, | |||
| val uomId: Long?, | |||
| val status: String = "pending", | |||
| val status: String = JobOrderBomMaterialStatus.PENDING.value, | |||
| ) | |||
| data class CreateJobOrderProcessRequest ( | |||
| @@ -1,5 +1,5 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.web.model | |||
| data class JobOrderReleaseRequest( | |||
| data class JobOrderCommonActionRequest( | |||
| val id: Long, | |||
| ) | |||
| ) | |||
| @@ -13,30 +13,32 @@ interface InventoryInfo{ | |||
| val itemName: String? | |||
| @get:Value("#{target.item.type}") | |||
| val itemType: String? | |||
| // @get:Value("#{target.qty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{target.qty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{target.onHandQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{target.onHandQty}") | |||
| val onHandQty: BigDecimal? | |||
| // @get:Value("#{target.onHoldQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{target.onHoldQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{target.onHoldQty}") | |||
| val onHoldQty: BigDecimal? | |||
| // @get:Value("#{target.unavailableQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{target.unavailableQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{target.unavailableQty}") | |||
| val unavailableQty: BigDecimal? | |||
| // @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty) / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty) / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty)}") | |||
| 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}") | |||
| val uomUdfudesc: String? | |||
| @get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom?.udfShortDesc}") | |||
| val uomShortDesc: 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}") | |||
| 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.unit2 != '' ? target.uom.unit2Qty " + | |||
| // ": target.uom.unit1Qty)}") | |||
| @@ -83,8 +83,8 @@ open class StockInLineService( | |||
| val pol = if (request.purchaseOrderLineId != null) | |||
| request.purchaseOrderLineId?.let { polRepository.findById(it).orElseThrow() } | |||
| else null | |||
| val stl = if (request.stockTakeId != null) | |||
| request.stockTakeId?.let { stockTakeLineRepository.findById(it).getOrNull() } | |||
| val stl = if (request.stockTakeLineId != null) | |||
| request.stockTakeLineId?.let { stockTakeLineRepository.findById(it).getOrNull() } | |||
| else null | |||
| var stockIn = if (request.stockInId != null) request.stockInId?.let { stockInRepository.findByIdAndDeletedFalse(it) } | |||
| @@ -1,5 +1,5 @@ | |||
| spring: | |||
| datasource: | |||
| jdbc-url: jdbc:mysql://127.0.0.1:3308/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 | |||
| jdbc-url: jdbc:mysql://127.0.0.1:3306/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 | |||
| username: root | |||
| password: secret | |||
| @@ -0,0 +1,16 @@ | |||
| --liquibase formatted sql | |||
| --changeset enson:update | |||
| ALTER TABLE pick_execution_issue | |||
| CHANGE COLUMN handle_status handle_status ENUM('pending', 'handled', 'resolved', "Jopending", "Johandled", "Joresloved") NULL DEFAULT 'pending' ; | |||
| ALTER TABLE do_pick_order | |||
| ADD COLUMN do_order_id INT NULL AFTER pick_order_id, | |||
| ADD COLUMN ticket_release_time DATETIME NULL DEFAULT NULL AFTER handled_by, | |||
| CHANGE COLUMN handled_by handled_by INT NULL ; | |||
| ALTER TABLE do_pick_order_record | |||
| ADD COLUMN do_order_id INT NULL AFTER pick_order_id, | |||
| ADD COLUMN ticket_release_time DATETIME NULL DEFAULT NULL AFTER handled_by, | |||
| CHANGE COLUMN handled_by handled_by INT NULL ; | |||