| @@ -6,6 +6,7 @@ import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatusConverter | import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatusConverter | ||||
| import com.ffii.fpsms.modules.master.entity.Bom | import com.ffii.fpsms.modules.master.entity.Bom | ||||
| import com.ffii.fpsms.modules.master.entity.ProductionScheduleLine | import com.ffii.fpsms.modules.master.entity.ProductionScheduleLine | ||||
| import com.ffii.fpsms.modules.stock.entity.StockInLine | |||||
| import com.ffii.fpsms.modules.user.entity.User | import com.ffii.fpsms.modules.user.entity.User | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| @@ -46,8 +47,8 @@ open class JobOrder : BaseEntity<Long>() { | |||||
| // @Size(max = 100) | // @Size(max = 100) | ||||
| @NotNull | @NotNull | ||||
| @Column(name = "status", nullable = false, length = 100) | |||||
| @Convert(converter = JobOrderStatusConverter::class) | @Convert(converter = JobOrderStatusConverter::class) | ||||
| @Column(name = "status", nullable = false, length = 100) | |||||
| open var status: JobOrderStatus? = null | open var status: JobOrderStatus? = null | ||||
| @Size(max = 500) | @Size(max = 500) | ||||
| @@ -71,4 +72,8 @@ open class JobOrder : BaseEntity<Long>() { | |||||
| @JsonManagedReference | @JsonManagedReference | ||||
| @OneToMany(mappedBy = "jobOrder", cascade = [CascadeType.ALL], orphanRemoval = true) | @OneToMany(mappedBy = "jobOrder", cascade = [CascadeType.ALL], orphanRemoval = true) | ||||
| open var jobms: MutableList<JobOrderBomMaterial> = mutableListOf() | open var jobms: MutableList<JobOrderBomMaterial> = mutableListOf() | ||||
| @JsonManagedReference | |||||
| @OneToMany(mappedBy = "jobOrder", cascade = [CascadeType.ALL], orphanRemoval = true) | |||||
| open var stockInLines: MutableList<StockInLine> = mutableListOf() | |||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity.projections | package com.ffii.fpsms.modules.jobOrder.entity.projections | ||||
| import com.ffii.fpsms.modules.master.entity.* | |||||
| import org.springframework.beans.factory.annotation.Value | import org.springframework.beans.factory.annotation.Value | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| @@ -10,14 +11,30 @@ interface JobOrderInfo { | |||||
| @get:Value("#{target.bom.item.code}") | @get:Value("#{target.bom.item.code}") | ||||
| val itemCode: String; | val itemCode: String; | ||||
| @get:Value("#{target.bom.item.name}") | |||||
| val itemName: String; | |||||
| @get:Value("#{target.bom.name}") | @get:Value("#{target.bom.name}") | ||||
| val name: String; | val name: String; | ||||
| val reqQty: BigDecimal; | val reqQty: BigDecimal; | ||||
| @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}") | |||||
| val uom: String; | |||||
| @get:Value("#{target.bom.item}") | |||||
| val item: JobOrderItemInfo; | |||||
| @get:Value("#{target.status.value}") | |||||
| // TODO pack below as StockInLineInfo | |||||
| @get:Value("#{target.stockInLines?.size() > 0 ? target.stockInLines[0].id : null}") | |||||
| val stockInLineId: Long?; | |||||
| @get:Value("#{target.stockInLines?.size() > 0 ? target.stockInLines[0].status : null}") | |||||
| val stockInLineStatus: String?; | |||||
| @get:Value("#{target.stockInLines?.size() > 0 ? target.stockInLines[0].escalationLog.^[status.value == 'pending']?.handler?.id : null}") | |||||
| val silHandlerId: Long?; | |||||
| // @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom}") | |||||
| //// @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}") | |||||
| // val uom: UomConversion; | |||||
| @get:Value("#{target.status.value}") | |||||
| val status: String; | val status: String; | ||||
| } | } | ||||
| @@ -27,6 +44,7 @@ interface JobOrderDetailWithJsonString { | |||||
| val code: String?; | val code: String?; | ||||
| val itemCode: String?; | val itemCode: String?; | ||||
| val name: String?; | val name: String?; | ||||
| val itemId: Long?; | |||||
| val reqQty: BigDecimal?; | val reqQty: BigDecimal?; | ||||
| val uom: String?; | val uom: String?; | ||||
| val shortUom: String?; | val shortUom: String?; | ||||
| @@ -34,6 +52,32 @@ interface JobOrderDetailWithJsonString { | |||||
| val status: String?; | val status: String?; | ||||
| } | } | ||||
| //interface JobOrderResult { | |||||
| // val id: Long; | |||||
| // val code: String?; | |||||
| // @get:Value("#{target.status.value}") | |||||
| // val status: String; | |||||
| // @get:Value("#{target.bom.item}") | |||||
| // val item: Items; | |||||
| // val reqQty: BigDecimal; | |||||
| // @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom}") | |||||
| // val uom: UomConversion?; | |||||
| //} | |||||
| interface JobOrderItemInfo{ | |||||
| val id: Long | |||||
| val name: String | |||||
| val code: String | |||||
| val description: String? | |||||
| val remarks: String? | |||||
| val type: String? | |||||
| val shelfLife: Double? | |||||
| val countryOfOrigin: String? | |||||
| val maxQty: Double? | |||||
| @get:Value("#{target.itemUoms.^[salesUnit == true && deleted == false]?.uom}") | |||||
| val uom: UomConversion? | |||||
| } | |||||
| data class JobOrderDetail( | data class JobOrderDetail( | ||||
| val id: Long?, | val id: Long?, | ||||
| val code: String?, | val code: String?, | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.service | package com.ffii.fpsms.modules.jobOrder.service | ||||
| import com.ffii.core.exception.BadRequestException | |||||
| import com.ffii.core.response.RecordsRes | import com.ffii.core.response.RecordsRes | ||||
| import com.ffii.core.utils.GsonUtils | import com.ffii.core.utils.GsonUtils | ||||
| import com.ffii.core.utils.PdfUtils | import com.ffii.core.utils.PdfUtils | ||||
| @@ -10,9 +11,6 @@ import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine | import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | ||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | 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.JobOrderCommonActionRequest | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest | |||||
| import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository | import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository | ||||
| import com.ffii.fpsms.modules.master.service.BomService | import com.ffii.fpsms.modules.master.service.BomService | ||||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | import com.ffii.fpsms.modules.master.web.models.MessageResponse | ||||
| @@ -32,6 +30,7 @@ import java.time.format.DateTimeFormatter | |||||
| import kotlin.jvm.optionals.getOrNull | import kotlin.jvm.optionals.getOrNull | ||||
| import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService | import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.* | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest | import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest | import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest | ||||
| import com.ffii.fpsms.modules.master.service.PrinterService | import com.ffii.fpsms.modules.master.service.PrinterService | ||||
| @@ -78,7 +77,7 @@ open class JobOrderService( | |||||
| val response = jobOrderRepository.findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc( | val response = jobOrderRepository.findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc( | ||||
| code = request.code ?: "", | code = request.code ?: "", | ||||
| bomName = request.name ?: "", | |||||
| bomName = request.itemName ?: "", | |||||
| pageable = pageable | pageable = pageable | ||||
| ) | ) | ||||
| @@ -177,6 +176,35 @@ open class JobOrderService( | |||||
| ) | ) | ||||
| } | } | ||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun updateJobOrder(request: JobOrderUpdateRequest): MessageResponse { | |||||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | |||||
| val newStatus: JobOrderStatus = when (request.status) { //TODO: improve if can | |||||
| "pending" -> { JobOrderStatus.PENDING } | |||||
| "processing" -> { JobOrderStatus.PROCESSING } | |||||
| "planning" -> { JobOrderStatus.PLANNING } | |||||
| "storing" -> { JobOrderStatus.STORING } | |||||
| "completed" -> { JobOrderStatus.COMPLETED } | |||||
| "packaging" -> { JobOrderStatus.PACKAGING } | |||||
| else -> { throw BadRequestException() } | |||||
| } | |||||
| jo.apply { | |||||
| status = newStatus | |||||
| } | |||||
| val savedJo = jobOrderRepository.save(jo) | |||||
| return MessageResponse( | |||||
| id = savedJo.id, | |||||
| code = savedJo.code, | |||||
| name = savedJo.bom?.name, | |||||
| type = null, | |||||
| message = "Job Order status updated to " + savedJo.status, | |||||
| errorPosition = null, | |||||
| entity = mapOf("status" to jo.status?.value) | |||||
| ) | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| open fun releaseJobOrder(request: JobOrderCommonActionRequest): MessageResponse { | open fun releaseJobOrder(request: JobOrderCommonActionRequest): MessageResponse { | ||||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | ||||
| @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestBody | |||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||
| import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService | import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.* | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest | import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest | import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest | import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest | ||||
| @@ -56,6 +57,11 @@ class JobOrderController( | |||||
| return jobOrderService.jobOrderDetailByCode(code); | return jobOrderService.jobOrderDetailByCode(code); | ||||
| } | } | ||||
| @PostMapping("/update") | |||||
| fun updateJobOrder(@Valid @RequestBody request: JobOrderUpdateRequest): MessageResponse { | |||||
| return jobOrderService.updateJobOrder(request) | |||||
| } | |||||
| @PostMapping("/release") | @PostMapping("/release") | ||||
| fun releaseJobOrder(@Valid @RequestBody request: JobOrderCommonActionRequest): MessageResponse { | fun releaseJobOrder(@Valid @RequestBody request: JobOrderCommonActionRequest): MessageResponse { | ||||
| return jobOrderService.releaseJobOrder(request) | return jobOrderService.releaseJobOrder(request) | ||||
| @@ -2,4 +2,9 @@ package com.ffii.fpsms.modules.jobOrder.web.model | |||||
| data class JobOrderCommonActionRequest( | data class JobOrderCommonActionRequest( | ||||
| val id: Long, | val id: Long, | ||||
| ) | |||||
| data class JobOrderUpdateRequest( | |||||
| val id: Long, | |||||
| val status: String, | |||||
| ) | ) | ||||
| @@ -2,7 +2,7 @@ package com.ffii.fpsms.modules.jobOrder.web.model | |||||
| data class SearchJobOrderInfoRequest( | data class SearchJobOrderInfoRequest( | ||||
| val code: String?, | val code: String?, | ||||
| val name: String?, | |||||
| val itemName: String?, | |||||
| val pageSize: Int?, | val pageSize: Int?, | ||||
| val pageNum: Int?, | val pageNum: Int?, | ||||
| ) | ) | ||||
| @@ -60,6 +60,7 @@ open class Items : BaseEntity<Long>() { | |||||
| open var category: ItemCategory? = null | open var category: ItemCategory? = null | ||||
| @ManyToOne | @ManyToOne | ||||
| @JsonManagedReference | |||||
| @JoinColumn(name = "qcCategoryId") | @JoinColumn(name = "qcCategoryId") | ||||
| open var qcCategory: QcCategory? = null | open var qcCategory: QcCategory? = null | ||||
| } | } | ||||
| @@ -241,8 +241,8 @@ open class PurchaseOrderService( | |||||
| qty = thisPol.qty!!, | qty = thisPol.qty!!, | ||||
| // processed = inLine.filter{ it.status == StockInLineStatus.COMPLETE.status}.sumOf { it.acceptedQty }, | // processed = inLine.filter{ it.status == StockInLineStatus.COMPLETE.status}.sumOf { it.acceptedQty }, | ||||
| processed = inLine | processed = inLine | ||||
| .filter { line -> line.putAwayLines.any { it.qty?.let { qty -> qty > BigDecimal.ZERO } == true } } | |||||
| .sumOf { line -> line.putAwayLines.sumOf { it.qty?.takeIf { qty -> qty > BigDecimal.ZERO } ?: BigDecimal.ZERO } }, | |||||
| .filter { line -> line.putAwayLines?.any { it.qty?.let { qty -> qty > BigDecimal.ZERO } == true } ?: false } | |||||
| .sumOf { line -> line.putAwayLines?.sumOf { it.qty?.takeIf { qty -> qty > BigDecimal.ZERO } ?: BigDecimal.ZERO } ?: BigDecimal.ZERO}, | |||||
| receivedQty = thisPol.stockInLines.sumOf { it.acceptedQty ?: BigDecimal.ZERO }, | receivedQty = thisPol.stockInLines.sumOf { it.acceptedQty ?: BigDecimal.ZERO }, | ||||
| uom = thisPol.uom!!, | uom = thisPol.uom!!, | ||||
| price = thisPol.price!!, | price = thisPol.price!!, | ||||
| @@ -3,6 +3,7 @@ package com.ffii.fpsms.modules.stock.entity | |||||
| import com.fasterxml.jackson.annotation.JsonBackReference | import com.fasterxml.jackson.annotation.JsonBackReference | ||||
| import com.fasterxml.jackson.annotation.JsonManagedReference | import com.fasterxml.jackson.annotation.JsonManagedReference | ||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrder | |||||
| import com.ffii.fpsms.modules.master.entity.Items | import com.ffii.fpsms.modules.master.entity.Items | ||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder | import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder | ||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine | import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine | ||||
| @@ -37,6 +38,11 @@ open class StockInLine : BaseEntity<Long>() { | |||||
| @JoinColumn(name = "purchaseOrderLineId") | @JoinColumn(name = "purchaseOrderLineId") | ||||
| open var purchaseOrderLine: PurchaseOrderLine? = null | open var purchaseOrderLine: PurchaseOrderLine? = null | ||||
| @JsonBackReference | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "jobOrderId") | |||||
| open var jobOrder: JobOrder? = null | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "stockTakeLineId") | @JoinColumn(name = "stockTakeLineId") | ||||
| open var stockTakeLine: StockTakeLine? = null | open var stockTakeLine: StockTakeLine? = null | ||||
| @@ -20,10 +20,12 @@ interface StockInLineInfo { | |||||
| val purchaseOrderLineId: Long? | val purchaseOrderLineId: Long? | ||||
| @get:Value("#{target.purchaseOrder?.id}") | @get:Value("#{target.purchaseOrder?.id}") | ||||
| val purchaseOrderId: Long? | val purchaseOrderId: Long? | ||||
| @get:Value("#{target.jobOrder?.id}") | |||||
| val jobOrderId: Long? | |||||
| val demandQty: BigDecimal? | val demandQty: BigDecimal? | ||||
| val acceptedQty: BigDecimal | val acceptedQty: BigDecimal | ||||
| @get:Value("#{target.purchaseOrderLine?.qty}") | @get:Value("#{target.purchaseOrderLine?.qty}") | ||||
| val qty: BigDecimal | |||||
| val qty: BigDecimal? | |||||
| val price: BigDecimal? | val price: BigDecimal? | ||||
| val priceUnit: BigDecimal? | val priceUnit: BigDecimal? | ||||
| @get:Value("#{target.item?.shelfLife}") | @get:Value("#{target.item?.shelfLife}") | ||||
| @@ -36,19 +38,21 @@ interface StockInLineInfo { | |||||
| var productLotNo: String? | var productLotNo: String? | ||||
| @get:Value("#{target.stockIn?.supplier?.name}") | @get:Value("#{target.stockIn?.supplier?.name}") | ||||
| val supplier: String? | val supplier: String? | ||||
| @get:Value("#{target.purchaseOrderLine?.uom ?: target.stockTakeLine?.uom}") | |||||
| val uom: UomConversion | |||||
| @get:Value("#{target.item?.itemUoms.^[salesUnit == true && deleted == false]?.uom}") //TODO review | |||||
| val uom: UomConversion? | |||||
| @get:Value("#{target.stockIn?.purchaseOrder?.code}") | @get:Value("#{target.stockIn?.purchaseOrder?.code}") | ||||
| val poCode: String? | val poCode: String? | ||||
| @get:Value("#{target.jobOrder?.code}") | |||||
| val joCode: String? | |||||
| @get:Value("#{target.item?.type}") | @get:Value("#{target.item?.type}") | ||||
| val itemType: String | |||||
| val dnNo: String | |||||
| // val dnDate: LocalDateTime? | |||||
| val itemType: String? | |||||
| val dnNo: String? | |||||
| // val dnDate: LocalDateTime? | |||||
| // val qcDecision: LocalDateTime? | // val qcDecision: 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()}") | @get:Value("#{target.inventoryLot?.inventoryLotLines ?: new java.util.ArrayList()}") | ||||
| val putAwayLines: List<PutAwayLineForSil>; | |||||
| val putAwayLines: List<PutAwayLineForSil>? | |||||
| } | } | ||||
| interface PutAwayLineForSil { | interface PutAwayLineForSil { | ||||
| @@ -104,7 +104,7 @@ open class InventoryLotLineService( | |||||
| open fun updateInventoryLotLineStatus(request: UpdateInventoryLotLineStatusRequest): MessageResponse { | open fun updateInventoryLotLineStatus(request: UpdateInventoryLotLineStatusRequest): MessageResponse { | ||||
| // Get the existing inventory lot line | // Get the existing inventory lot line | ||||
| val existingLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | val existingLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | ||||
| // Create update request with existing data and new status | // Create update request with existing data and new status | ||||
| val updateRequest = SaveInventoryLotLineRequest( | val updateRequest = SaveInventoryLotLineRequest( | ||||
| id = existingLotLine.id, | id = existingLotLine.id, | ||||
| @@ -118,12 +118,12 @@ open class InventoryLotLineService( | |||||
| status = request.status, | status = request.status, | ||||
| remarks = existingLotLine.remarks | remarks = existingLotLine.remarks | ||||
| ) | ) | ||||
| val updatedLotLine = saveInventoryLotLine(updateRequest) | val updatedLotLine = saveInventoryLotLine(updateRequest) | ||||
| // ✅ ADD THIS: Update inventory table after lot line status change | // ✅ ADD THIS: Update inventory table after lot line status change | ||||
| updateInventoryTable(updatedLotLine) | updateInventoryTable(updatedLotLine) | ||||
| return MessageResponse( | return MessageResponse( | ||||
| id = updatedLotLine.id, | id = updatedLotLine.id, | ||||
| name = updatedLotLine.id.toString(), | name = updatedLotLine.id.toString(), | ||||
| @@ -143,27 +143,27 @@ open class InventoryLotLineService( | |||||
| println("Cannot update inventory table: itemId is null for lot line ${inventoryLotLine.id}") | println("Cannot update inventory table: itemId is null for lot line ${inventoryLotLine.id}") | ||||
| return | return | ||||
| } | } | ||||
| // Calculate onHoldQty (sum of holdQty from available lots only) | // Calculate onHoldQty (sum of holdQty from available lots only) | ||||
| val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE.value) | val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE.value) | ||||
| .sumOf { it.holdQty ?: BigDecimal.ZERO } | .sumOf { it.holdQty ?: BigDecimal.ZERO } | ||||
| // Calculate unavailableQty (sum of inQty from unavailable lots only) | // Calculate unavailableQty (sum of inQty from unavailable lots only) | ||||
| val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.UNAVAILABLE.value) | val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.UNAVAILABLE.value) | ||||
| .sumOf { | |||||
| .sumOf { | |||||
| val inQty = it.inQty ?: BigDecimal.ZERO | val inQty = it.inQty ?: BigDecimal.ZERO | ||||
| val outQty = it.outQty ?: BigDecimal.ZERO | val outQty = it.outQty ?: BigDecimal.ZERO | ||||
| val remainingQty = inQty.minus(outQty) | val remainingQty = inQty.minus(outQty) | ||||
| remainingQty | remainingQty | ||||
| } | } | ||||
| // Update the inventory table | // Update the inventory table | ||||
| val inventory = inventoryRepository.findByItemId(itemId).orElse(null) | val inventory = inventoryRepository.findByItemId(itemId).orElse(null) | ||||
| if (inventory != null) { | if (inventory != null) { | ||||
| inventory.onHoldQty = onHoldQty | inventory.onHoldQty = onHoldQty | ||||
| inventory.unavailableQty = unavailableQty | inventory.unavailableQty = unavailableQty | ||||
| inventoryRepository.save(inventory) | inventoryRepository.save(inventory) | ||||
| println("Updated inventory for item $itemId: onHoldQty=$onHoldQty, unavailableQty=$unavailableQty") | println("Updated inventory for item $itemId: onHoldQty=$onHoldQty, unavailableQty=$unavailableQty") | ||||
| } else { | } else { | ||||
| println("Inventory not found for item $itemId") | println("Inventory not found for item $itemId") | ||||
| @@ -200,11 +200,11 @@ open class InventoryLotLineService( | |||||
| field["itemName"] = info.itemName ?: "N/A" | field["itemName"] = info.itemName ?: "N/A" | ||||
| field["itemNo"] = info.itemNo | field["itemNo"] = info.itemNo | ||||
| field["poCode"] = info.poCode ?: "N/A" | field["poCode"] = info.poCode ?: "N/A" | ||||
| field["itemType"] = info.itemType | |||||
| field["itemType"] = info.itemType ?: "N/A" | |||||
| field["acceptedQty"] = info.acceptedQty.toString() | field["acceptedQty"] = info.acceptedQty.toString() | ||||
| field["uom"] = (info.uom.udfudesc ?: "N/A").toString() | |||||
| field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "N/A" | |||||
| field["expiryDate"] = info.expiryDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "N/A" | |||||
| field["uom"] = info.uom?.udfudesc.toString() ?: "N/A" | |||||
| 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 ?: "N/A" | field["supplier"] = info.supplier ?: "N/A" | ||||
| val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) | val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) | ||||
| @@ -224,11 +224,11 @@ open class InventoryLotLineService( | |||||
| open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantitiesRequest): MessageResponse { | open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantitiesRequest): MessageResponse { | ||||
| try { | try { | ||||
| val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | ||||
| // Handle quantity updates based on operation | // Handle quantity updates based on operation | ||||
| var newHoldQty = inventoryLotLine.holdQty ?: BigDecimal.ZERO | var newHoldQty = inventoryLotLine.holdQty ?: BigDecimal.ZERO | ||||
| var newOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO | var newOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO | ||||
| when (request.operation) { | when (request.operation) { | ||||
| "pick" -> { | "pick" -> { | ||||
| // Move from hold_qty to out_qty | // Move from hold_qty to out_qty | ||||
| @@ -244,12 +244,12 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit | |||||
| throw IllegalArgumentException("Unknown operation: ${request.operation}") | throw IllegalArgumentException("Unknown operation: ${request.operation}") | ||||
| } | } | ||||
| } | } | ||||
| // Validate quantities | // Validate quantities | ||||
| if (newHoldQty < BigDecimal.ZERO || newOutQty < BigDecimal.ZERO) { | if (newHoldQty < BigDecimal.ZERO || newOutQty < BigDecimal.ZERO) { | ||||
| throw IllegalArgumentException("Invalid quantities: holdQty=$newHoldQty, outQty=$newOutQty") | throw IllegalArgumentException("Invalid quantities: holdQty=$newHoldQty, outQty=$newOutQty") | ||||
| } | } | ||||
| val updateRequest = SaveInventoryLotLineRequest( | val updateRequest = SaveInventoryLotLineRequest( | ||||
| id = inventoryLotLine.id, | id = inventoryLotLine.id, | ||||
| inventoryLotId = inventoryLotLine.inventoryLot?.id, | inventoryLotId = inventoryLotLine.inventoryLot?.id, | ||||
| @@ -261,9 +261,9 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit | |||||
| status = inventoryLotLine.status?.value, // Keep existing status | status = inventoryLotLine.status?.value, // Keep existing status | ||||
| remarks = inventoryLotLine.remarks | remarks = inventoryLotLine.remarks | ||||
| ) | ) | ||||
| val updatedInventoryLotLine = saveInventoryLotLine(updateRequest) | val updatedInventoryLotLine = saveInventoryLotLine(updateRequest) | ||||
| return MessageResponse( | return MessageResponse( | ||||
| id = updatedInventoryLotLine.id, | id = updatedInventoryLotLine.id, | ||||
| name = "Inventory lot line quantities updated", | name = "Inventory lot line quantities updated", | ||||
| @@ -23,6 +23,8 @@ 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.core.utils.ZebraPrinterUtil | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||||
| 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.master.service.PrinterService | ||||
| @@ -61,6 +63,7 @@ open class StockInLineService( | |||||
| private val stockInService: StockInService, | private val stockInService: StockInService, | ||||
| private val stockInRepository: StockInRepository, | private val stockInRepository: StockInRepository, | ||||
| private val stockInLineRepository: StockInLineRepository, | private val stockInLineRepository: StockInLineRepository, | ||||
| private val jobOrderRepository: JobOrderRepository, | |||||
| private val inventoryLotRepository: InventoryLotRepository, | private val inventoryLotRepository: InventoryLotRepository, | ||||
| private val inventoryLotLineRepository: InventoryLotLineRepository, | private val inventoryLotLineRepository: InventoryLotLineRepository, | ||||
| private val itemRepository: ItemsRepository, | private val itemRepository: ItemsRepository, | ||||
| @@ -86,6 +89,9 @@ open class StockInLineService( | |||||
| val stl = if (request.stockTakeLineId != null) | val stl = if (request.stockTakeLineId != null) | ||||
| request.stockTakeLineId?.let { stockTakeLineRepository.findById(it).getOrNull() } | request.stockTakeLineId?.let { stockTakeLineRepository.findById(it).getOrNull() } | ||||
| else null | else null | ||||
| val jo = if (request.jobOrderId != null) | |||||
| request.jobOrderId?.let { jobOrderRepository.findById(it).getOrNull() } | |||||
| else null | |||||
| var stockIn = if (request.stockInId != null) request.stockInId?.let { stockInRepository.findByIdAndDeletedFalse(it) } | var stockIn = if (request.stockInId != null) request.stockInId?.let { stockInRepository.findByIdAndDeletedFalse(it) } | ||||
| else if (pol != null) pol.purchaseOrder?.id?.let { stockInRepository.findByPurchaseOrderIdAndDeletedFalse(it) } | else if (pol != null) pol.purchaseOrder?.id?.let { stockInRepository.findByPurchaseOrderIdAndDeletedFalse(it) } | ||||
| @@ -97,6 +103,7 @@ open class StockInLineService( | |||||
| // stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | // stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | ||||
| // var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | // var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | ||||
| } | } | ||||
| val item = itemRepository.findById(request.itemId).orElseThrow() | val item = itemRepository.findById(request.itemId).orElseThrow() | ||||
| // If request contains valid POL | // If request contains valid POL | ||||
| if (pol != null) { | if (pol != null) { | ||||
| @@ -128,6 +135,13 @@ open class StockInLineService( | |||||
| } | } | ||||
| } | } | ||||
| // If request contains valid job order id | |||||
| if (jo != null) { | |||||
| stockInLine.apply { | |||||
| this.jobOrder = jo | |||||
| } | |||||
| } | |||||
| // val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!) | // val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!) | ||||
| // if (pol.qty!! < request.acceptedQty) { | // if (pol.qty!! < request.acceptedQty) { | ||||
| // throw BadRequestException() | // throw BadRequestException() | ||||
| @@ -401,6 +415,14 @@ open class StockInLineService( | |||||
| StockInLineStatus.COMPLETE.status else StockInLineStatus.PARTIALLY_COMPLETE.status | StockInLineStatus.COMPLETE.status else StockInLineStatus.PARTIALLY_COMPLETE.status | ||||
| // this.inventoryLotLine = savedInventoryLotLine | // this.inventoryLotLine = savedInventoryLotLine | ||||
| } | } | ||||
| // Update JO Status | |||||
| if (stockInLine.jobOrder != null) { //TODO Improve | |||||
| val jo = stockInLine.jobOrder | |||||
| jo?.apply { | |||||
| status = JobOrderStatus.COMPLETED | |||||
| } | |||||
| jobOrderRepository.save(jo!!) | |||||
| } | |||||
| } | } | ||||
| } else if (request.status == StockInLineStatus.PENDING.status || request.status == StockInLineStatus.ESCALATED.status) { | } else if (request.status == StockInLineStatus.PENDING.status || request.status == StockInLineStatus.ESCALATED.status) { | ||||
| var escLogId : Long? = 0L; | var escLogId : Long? = 0L; | ||||
| @@ -496,11 +518,11 @@ open class StockInLineService( | |||||
| field["itemName"] = info.itemName ?: "N/A" | field["itemName"] = info.itemName ?: "N/A" | ||||
| field["itemNo"] = info.itemNo | field["itemNo"] = info.itemNo | ||||
| field["poCode"] = info.poCode ?: "N/A" | field["poCode"] = info.poCode ?: "N/A" | ||||
| field["itemType"] = info.itemType | |||||
| field["itemType"] = info.itemType ?: "N/A" | |||||
| field["acceptedQty"] = info.acceptedQty.toString() | field["acceptedQty"] = info.acceptedQty.toString() | ||||
| field["uom"] = (info.uom.udfudesc ?: "N/A").toString() | |||||
| field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "N/A" | |||||
| field["expiryDate"] = info.expiryDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "N/A" | |||||
| field["uom"] = info.uom?.udfudesc.toString() ?: "N/A" | |||||
| field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" | |||||
| field["expiryDate"] = info.expiryDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" | |||||
| field["lotNo"] = info.lotNo ?: "N/A" | field["lotNo"] = info.lotNo ?: "N/A" | ||||
| field["supplier"] = info.supplier ?: "N/A" | field["supplier"] = info.supplier ?: "N/A" | ||||
| val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) | val image = QrCodeUtil.generateQRCodeImage(qrCodeContent) | ||||
| @@ -508,11 +530,11 @@ open class StockInLineService( | |||||
| fields.add(field) | fields.add(field) | ||||
| } | } | ||||
| val params: MutableMap<String, Any> = mutableMapOf( | val params: MutableMap<String, Any> = mutableMapOf( | ||||
| "poCode" to (qrCodeInfo[0].poCode ?: "N/A") | |||||
| "poCode" to (qrCodeInfo[0].poCode ?: "N/A") //TODO change to JO code for JO | |||||
| ) | ) | ||||
| return mapOf( | return mapOf( | ||||
| "report" to PdfUtils.fillReport(poLabel,fields, params), | "report" to PdfUtils.fillReport(poLabel,fields, params), | ||||
| "fileName" to (qrCodeInfo[0].poCode ?: qrCodeInfo[0].lotNo ?: "N/A") | |||||
| "fileName" to (qrCodeInfo[0].poCode ?: qrCodeInfo[0].joCode ?: qrCodeInfo[0].lotNo).toString() | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -45,6 +45,7 @@ data class SaveStockInLineRequest( | |||||
| var itemId: Long, | var itemId: Long, | ||||
| var purchaseOrderId: Long? = null, | var purchaseOrderId: Long? = null, | ||||
| var purchaseOrderLineId: Long? = null, | var purchaseOrderLineId: Long? = null, | ||||
| var jobOrderId: Long? = null, | |||||
| var acceptedQty: BigDecimal, | var acceptedQty: BigDecimal, | ||||
| var acceptQty: BigDecimal?, | var acceptQty: BigDecimal?, | ||||
| var acceptedWeight: BigDecimal? = null, | var acceptedWeight: BigDecimal? = null, | ||||
| @@ -0,0 +1,4 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset kelvin:update stock in line table | |||||
| ALTER TABLE `stock_in_line` | |||||
| ADD COLUMN `jobOrderId` INT NULL DEFAULT NULL AFTER `stockTakeLineId`; | |||||