diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt new file mode 100644 index 0000000..9f01462 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt @@ -0,0 +1,97 @@ +package com.ffii.fpsms.modules.deliveryOrder.entity + +import jakarta.persistence.* +import java.time.LocalDateTime +import java.time.LocalTime +import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus +import org.hibernate.annotations.CreationTimestamp +import org.hibernate.annotations.UpdateTimestamp + +@Entity +@Table(name = "do_pick_order_record") +class DoPickOrderRecord { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null + + @Column(name = "store_id", length = 10) + var storeId: String? = null + + @Column(name = "ticket_no", length = 50) + var ticketNo: String? = null + @Column(name = "pick_order_id") + var pickOrderId: Long? = null + @Enumerated(EnumType.STRING) + @Column(name = "ticket_status") + var ticketStatus: DoPickOrderStatus? = null + + @Column(name = "truck_id") + var truckId: Long? = null + + @Column(name = "truck_departure_time") + var truckDepartureTime: LocalTime? = null + + @Column(name = "item_id") + var itemId: Long? = null + + @Column(name = "shop_id") + var shopId: Long? = null + + @Column(name = "shop_po_supplier_id") + var shopPoSupplierId: Long? = null + + @Column(name = "handled_by") + var handledBy: Long? = null + + @CreationTimestamp + @Column(name = "created") + var created: LocalDateTime? = null + + @Column(name = "createdBy", length = 30) + var createdBy: String? = null + + @Version + var version: Int = 0 + + @UpdateTimestamp + @Column(name = "modified") + var modified: LocalDateTime? = null + + @Column(name = "modifiedBy", length = 30) + var modifiedBy: String? = null + + @Column(name = "deleted") + var deleted: Boolean = false + + // Default constructor for Hibernate + constructor() + + // Constructor for creating new instances + constructor( + storeId: String, + ticketNo: String, + ticketStatus: DoPickOrderStatus, + truckId: Long? = null, + truckDepartureTime: LocalTime? = null, + pickOrderId: Long? = null, + itemId: Long? = null, + shopId: Long? = null, + shopPoSupplierId: Long? = null, + handledBy: Long? = null, + createdBy: String? = null, + modifiedBy: String? = null + ) { + this.storeId = storeId + this.ticketNo = ticketNo + this.pickOrderId = pickOrderId + this.ticketStatus = ticketStatus + this.truckId = truckId + this.truckDepartureTime = truckDepartureTime + this.itemId = itemId + this.shopId = shopId + this.shopPoSupplierId = shopPoSupplierId + this.handledBy = handledBy + this.createdBy = createdBy + this.modifiedBy = modifiedBy + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecordRepository.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecordRepository.kt new file mode 100644 index 0000000..d75e929 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecordRepository.kt @@ -0,0 +1,18 @@ +package com.ffii.fpsms.modules.deliveryOrder.entity + +import com.ffii.core.support.AbstractRepository +import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfo +import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus +import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus +import com.ffii.fpsms.modules.master.entity.projections.SearchId +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.stereotype.Repository +import java.io.Serializable +import java.time.LocalDateTime + +@Repository +interface DoPickOrderRecordRepository : JpaRepository { + fun findByPickOrderId(pickOrderId: Long): List + fun findByTicketNoStartingWith(ticketPrefix: String): List +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/enums/DoPickOrderStatus.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/enums/DoPickOrderStatus.kt index c00f717..d7a6f70 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/enums/DoPickOrderStatus.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/enums/DoPickOrderStatus.kt @@ -2,5 +2,6 @@ package com.ffii.fpsms.modules.deliveryOrder.enums enum class DoPickOrderStatus(val value: String) { pending("pending"), + released("released"), completed("completed") } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt index c403a0a..c57d875 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt @@ -57,6 +57,9 @@ import org.springframework.core.io.ClassPathResource import java.io.File import java.io.FileNotFoundException +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository + @Service open class DeliveryOrderService( private val deliveryOrderRepository: DeliveryOrderRepository, @@ -75,7 +78,8 @@ open class DeliveryOrderService( private val stockOutRepository: StockOutRepository, private val stockOutLineRepository: StockOutLIneRepository, private val pickOrderLineRepository: PickOrderLineRepository, - private val printerService: PrinterService + private val printerService: PrinterService, + private val doPickOrderRecordRepository: DoPickOrderRecordRepository ) { open fun findByM18DataLogId(m18DataLogId: Long): DeliveryOrder? { @@ -452,14 +456,9 @@ open class DeliveryOrderService( val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("ddMMyy")) println("�� DEBUG: Target date: $targetDate, Date prefix: $datePrefix") - - // Get next ticket number for this date - val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix) - println("🔍 DEBUG: Next ticket number: $nextTicketNumber") - - // ✅ Find truck by shop ID with earliest departure time + val truck = deliveryOrder.shop?.id?.let { shopId -> - println("�� DEBUG: Looking for truck with shop ID: $shopId") + println("🔍 DEBUG: Looking for truck with shop ID: $shopId") val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId") trucks.forEach { t -> @@ -472,29 +471,59 @@ open class DeliveryOrderService( println("🔍 DEBUG: Processing ${deliveryOrder.deliveryOrderLines.size} delivery order lines") - deliveryOrder.deliveryOrderLines.forEach { line -> - val storeId = if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F" - println("�� DEBUG: Processing line - Item ID: ${line.item?.id}, Store ID: $storeId") - - val doPickOrder = DoPickOrder( - storeId = storeId, - ticketNo = nextTicketNumber, - ticketStatus = DoPickOrderStatus.pending, - truckId = truck?.id, - pickOrderId = createdPickOrder.id, - truckDepartureTime = truck?.departureTime, - itemId = line.item?.id, - shopId = deliveryOrder.shop?.id, - shopPoSupplierId = deliveryOrder.shop?.id, - handledBy = null - ) - - println("�� DEBUG: Creating DoPickOrder - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}") - - val savedDoPickOrder = doPickOrderService.save(doPickOrder) - println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") + // ✅ Group lines by store ID to get unique ticket numbers per store + val linesByStore = deliveryOrder.deliveryOrderLines.groupBy { line -> + if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F" } + linesByStore.forEach { (storeId, lines) -> + // ✅ Get ticket number for this specific store + val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix, storeId) + println("🔍 DEBUG: Next ticket number for store $storeId: $nextTicketNumber") + + lines.forEach { line -> + println("�� DEBUG: Processing line - Item ID: ${line.item?.id}, Store ID: $storeId") + + val doPickOrderRecord = DoPickOrderRecord( + storeId = storeId, + ticketNo = nextTicketNumber, + ticketStatus = DoPickOrderStatus.pending, + truckId = truck?.id, + pickOrderId = createdPickOrder.id, + truckDepartureTime = truck?.departureTime, + itemId = line.item?.id, + shopId = deliveryOrder.shop?.id, + shopPoSupplierId = deliveryOrder.shop?.id, + handledBy = null + ) + + println("�� DEBUG: Creating DoPickOrderRecord - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}") + + val savedDoPickOrderRecord = doPickOrderRecordRepository.save(doPickOrderRecord) + println("🔍 DEBUG: Saved DoPickOrderRecord - ID: ${savedDoPickOrderRecord.id}") + } + + // ✅ Also create DoPickOrder records for this store + lines.forEach { line -> + println("�� DEBUG: Creating DoPickOrder - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}") + + val doPickOrder = DoPickOrder( + storeId = storeId, + ticketNo = nextTicketNumber, + ticketStatus = DoPickOrderStatus.pending, + truckId = truck?.id, + pickOrderId = createdPickOrder.id, + truckDepartureTime = truck?.departureTime, + itemId = line.item?.id, + shopId = deliveryOrder.shop?.id, + shopPoSupplierId = deliveryOrder.shop?.id, + handledBy = null + ) + + val savedDoPickOrder = doPickOrderService.save(doPickOrder) + println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") + } + } return MessageResponse( id = deliveryOrder.id, code = deliveryOrder.code, diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt index a2c64ad..d6201e6 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt @@ -33,21 +33,27 @@ import java.math.BigDecimal import com.ffii.fpsms.modules.master.web.models.MessageResponse import java.time.LocalDateTime import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByStoreRequest +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository @Service class DoPickOrderService( - private val doPickOrderRepository: DoPickOrderRepository + private val doPickOrderRepository: DoPickOrderRepository, + private val doPickOrderRecordRepository: DoPickOrderRecordRepository ) { - fun getNextTicketNumber(datePrefix: String): String { - println("🔍 DEBUG: Getting next ticket number for date prefix: $datePrefix") + fun getNextTicketNumber(datePrefix: String, storeId: String): String { + println("🔍 DEBUG: Getting next ticket number for date prefix: $datePrefix, store: $storeId") try { - val todayTickets = doPickOrderRepository.findByTicketNoStartingWith("${datePrefix}_") - println("🔍 DEBUG: Found ${todayTickets.size} existing tickets with prefix ${datePrefix}_") + val sanitizedStoreId = storeId.replace("/", "") + // ✅ Include store ID in the search pattern + val searchPattern = "${datePrefix}_${sanitizedStoreId}_" + val todayTickets = doPickOrderRepository.findByTicketNoStartingWith(searchPattern) + println("🔍 DEBUG: Found ${todayTickets.size} existing tickets with prefix $searchPattern") todayTickets.forEach { ticket -> println("�� DEBUG: Existing ticket: ${ticket.ticketNo}, Status: ${ticket.ticketStatus}") } val nextNumber = (todayTickets.size + 1).toString().padStart(3, '0') - val ticketNumber = "${datePrefix}_${nextNumber}" + val ticketNumber = "${datePrefix}_${sanitizedStoreId}_${nextNumber}" println("🔍 DEBUG: Generated ticket number: $ticketNumber") return ticketNumber } catch (e: Exception) { @@ -94,7 +100,52 @@ class DoPickOrderService( } fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long): List { val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) - doPickOrders.forEach { it.handledBy = userId } + doPickOrders.forEach { it.handledBy = userId + it.ticketStatus = DoPickOrderStatus.released } return doPickOrderRepository.saveAll(doPickOrders) } + fun completeDoPickOrdersForPickOrder(pickOrderId: Long): List { + val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) + doPickOrders.forEach { + it.ticketStatus = DoPickOrderStatus.completed // ✅ Update status to "completed" + } + return doPickOrderRepository.saveAll(doPickOrders) + } + + // ✅ New method to remove do_pick_order records when auto-assigning by store + fun removeDoPickOrdersForPickOrder(pickOrderId: Long): Int { + val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) + if (doPickOrders.isNotEmpty()) { + // Mark as deleted instead of physically deleting + doPickOrders.forEach { + it.ticketStatus = DoPickOrderStatus.completed + it.deleted = true } + doPickOrderRepository.saveAll(doPickOrders) + return doPickOrders.size + } + return 0 + } + + fun saveRecord(record: DoPickOrderRecord): DoPickOrderRecord { + return doPickOrderRecordRepository.save(record) + } + + // ✅ Add method to update DoPickOrderRecord status + fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long): List { + val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) + doPickOrderRecords.forEach { + it.handledBy = userId + it.ticketStatus = DoPickOrderStatus.released + } + return doPickOrderRecordRepository.saveAll(doPickOrderRecords) + } + + // ✅ Add method to complete DoPickOrderRecord + fun completeDoPickOrderRecordsForPickOrder(pickOrderId: Long): List { + val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) + doPickOrderRecords.forEach { + it.ticketStatus = DoPickOrderStatus.completed + } + return doPickOrderRecordRepository.saveAll(doPickOrderRecords) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/RouterRepository.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/RouterRepository.kt index 4850458..478774b 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/RouterRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/RouterRepository.kt @@ -22,5 +22,5 @@ interface RouterRepository : AbstractRepository { @Query("SELECT r FROM Router r WHERE r.route = :route AND r.deleted = false") fun findByRouteAndDeletedFalse(@Param("route") route: String): List - + } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index 287379a..e54efd7 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -10,6 +10,8 @@ import com.ffii.fpsms.modules.master.entity.UomConversionRepository import com.ffii.fpsms.modules.master.service.ItemUomService import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.pickOrder.entity.PickOrder +import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus +import java.time.LocalTime import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository @@ -51,6 +53,10 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository import com.ffii.fpsms.modules.pickOrder.entity.RouterRepository import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository @Service open class PickOrderService( private val jdbcDao: JdbcDao, @@ -70,8 +76,12 @@ open class PickOrderService( private val itemUomService: ItemUomService, private val deliveryOrderRepository: DeliveryOrderRepository, private val truckRepository: TruckRepository, - private val routerRepository: RouterRepository, private val doPickOrderService: DoPickOrderService, + private val routerRepository: RouterRepository, + private val doPickOrderRecordRepository: DoPickOrderRecordRepository, + private val doPickOrderRepository: DoPickOrderRepository + + ) : AbstractBaseEntityService(jdbcDao, pickOrderRepository) { open fun create(request: SavePickOrderRequest): MessageResponse { val code = assignPickCode() @@ -1209,9 +1219,17 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas pickOrder.completeDate = LocalDateTime.now() pickOrderRepository.save(pickOrder) println("✅ Updated pick order ${pickOrder.code} to COMPLETED status") + val removedCount = doPickOrderService.removeDoPickOrdersForPickOrder(pickOrderId) + println("✅ Removed $removedCount do_pick_order records for completed pick order ${pickOrderId}") + + // ✅ Update do_pick_order_record status to completed (don't remove) + doPickOrderService.completeDoPickOrderRecordsForPickOrder(pickOrderId) + println("✅ Updated do_pick_order_record status to COMPLETED for pick order ${pickOrderId}") } } } + // After updating pick order to COMPLETED status, add: + } return MessageResponse( @@ -1569,113 +1587,224 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas } - fun getAllPickOrderLotsWithDetails(pickOrderIds: List): List> { - val today = LocalDate.now() + // Add this new method to PickOrderService.kt +@Transactional(rollbackFor = [java.lang.Exception::class]) +open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo: String, userId: Long): MessageResponse { + try { + println("=== DEBUG: autoAssignAndReleasePickOrderByStoreAndTicket ===") + println("storeId: $storeId, ticketNo: $ticketNo, userId: $userId") + val zero = BigDecimal.ZERO - - println("=== Debug: getAllPickOrderLotsWithDetails ===") - println("today: $today") - println("pickOrderIds: $pickOrderIds") + val releasedBy = SecurityUtils.getUser().getOrNull() + val user = userService.find(userId).orElse(null) - if (pickOrderIds.isEmpty()) { - return emptyList() + if (user == null) { + return MessageResponse( + id = null, + name = "User not found", + code = "ERROR", + type = "pickorder", + message = "User with ID $userId not found", + errorPosition = null + ) } - + + // Find the do_pick_order by store_id and ticket_no val sql = """ - SELECT - -- Pick Order Information - po.id as pickOrderId, - po.code as pickOrderCode, - po.targetDate as pickOrderTargetDate, - po.consoCode as pickOrderConsoCode, - po.type as pickOrderType, - po.status as pickOrderStatus, - po.assignTo as pickOrderAssignTo, - pog.name as groupName, - - -- Pick Order Line Information - pol.id as pickOrderLineId, - pol.qty as pickOrderLineRequiredQty, - pol.status as pickOrderLineStatus, - - -- Item Information - i.id as itemId, - i.code as itemCode, - i.name as itemName, - uc.code as uomCode, - uc.udfudesc as uomDesc, - - -- Lot Information - ill.id as lotId, - il.lotNo, - il.expiryDate, - w.name as location, - COALESCE(uc.udfudesc, 'N/A') as stockUnit, - - -- ✅ FIXED: Set quantities to NULL for rejected lots - CASE - WHEN sol.status = 'rejected' THEN NULL - ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) - END as availableQty, - - -- Required quantity for this lot - COALESCE(spl.qty, 0) as requiredQty, - - -- Actual picked quantity - COALESCE(sol.qty, 0) as actualPickQty, - - -- Suggested pick lot information - spl.id as suggestedPickLotId, - sol.status as lotStatus, - - -- Stock out line information - sol.id as stockOutLineId, - sol.status as stockOutLineStatus, - COALESCE(sol.qty, 0) as stockOutLineQty, - - -- Lot availability status - CASE - WHEN sol.status = 'rejected' THEN 'rejected' - WHEN sol.status = 'completed' THEN 'completed' - WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'unavailable' - ELSE 'available' - END as lotAvailability, - - -- Processing status - CASE - WHEN sol.status = 'completed' THEN 'completed' - WHEN sol.status = 'rejected' THEN 'rejected' - WHEN sol.status = 'created' THEN 'pending' - ELSE 'pending' - END as processingStatus - - FROM pick_order po - LEFT JOIN pick_order_group pog ON po.groupId = pog.id - LEFT JOIN pick_order_line pol ON po.id = pol.pickOrderId - LEFT JOIN items i ON pol.itemId = i.id - LEFT JOIN uom_conversion uc ON i.uomId = uc.id - LEFT JOIN suggested_pick_lot spl ON pol.id = spl.pickOrderLineId - LEFT JOIN inventory_lot_line ill ON spl.suggestedLotLineId = ill.id - LEFT JOIN inventory_lot il ON ill.lotId = il.id - LEFT JOIN warehouse w ON ill.warehouseId = w.id - LEFT JOIN stock_out_line sol ON spl.id = sol.suggestedPickLotId - WHERE po.id IN (${pickOrderIds.joinToString(",")}) - AND po.status IN ('assigned', 'released', 'picking') - AND pol.status IN ('assigned', 'released', 'picking') - AND (sol.status IS NULL OR sol.status != 'completed') - ORDER BY po.id, pol.id, ill.id + SELECT DISTINCT dpo.pick_order_id AS pickOrderId + FROM do_pick_order dpo + WHERE dpo.deleted = false + AND dpo.store_id = :storeId + AND dpo.ticket_no = :ticketNo + AND dpo.pick_order_id IS NOT NULL """.trimIndent() - - println("🔍 Executing SQL for all pick order lots: $sql") - val result = jdbcDao.queryForList(sql, emptyMap()) + val idRows = jdbcDao.queryForList(sql, mapOf("storeId" to storeId, "ticketNo" to ticketNo)) + val pickOrderIdsByTicket = idRows.mapNotNull { row -> + when (val id = row["pickOrderId"]) { + is Number -> id.toLong() + is String -> id.toLongOrNull() + else -> null + } + }.toSet() - println("Total result count (including completed and rejected): ${result.size}") - result.forEach { row -> - println("Row: $row") + println("Candidate pickOrderIds by ticket $ticketNo: $pickOrderIdsByTicket") + + if (pickOrderIdsByTicket.isEmpty()) { + return MessageResponse( + id = null, + name = "No pick orders found", + code = "NO_ORDERS", + type = "pickorder", + message = "No pick orders found for store $storeId with ticket $ticketNo", + errorPosition = null + ) } - - return result + + // Check if user already has pick orders in progress + val userExistingOrders = pickOrderRepository.findAllByAssignToAndStatusIn( + user, + listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED) + ).filter { it.deliveryOrder != null } + + if (userExistingOrders.isNotEmpty()) { + println("🔍 DEBUG: User $userId already has ${userExistingOrders.size} pick orders in progress") + return MessageResponse( + id = null, + name = "User already has pick orders", + code = "USER_BUSY", + type = "pickorder", + message = "User $userId already has ${userExistingOrders.size} pick orders in progress. Cannot assign new orders.", + errorPosition = null, + entity = mapOf( + "existingOrders" to userExistingOrders.map { mapOf( + "id" to it.id, + "code" to it.code, + "status" to it.status?.value + )} + ) + ) + } + + // Find available pick orders for the specific ticket + val availablePickOrders = pickOrderRepository + .findAll() + .filter { it.id != null && pickOrderIdsByTicket.contains(it.id!!) } + .filter { it.deliveryOrder != null } + .filter { it.assignTo == null } + .filter { it.status == PickOrderStatus.PENDING || it.status == PickOrderStatus.RELEASED } + .sortedBy { it.targetDate } + .take(1) // Take only one pick order + + if (availablePickOrders.isEmpty()) { + return MessageResponse( + id = null, + name = "No available pick orders", + code = "NO_ORDERS", + type = "pickorder", + message = "No unassigned pick orders available for store $storeId with ticket $ticketNo", + errorPosition = null + ) + } + + val selected = availablePickOrders.first() + val currUser = SecurityUtils.getUser().orElseThrow() + + // If still PENDING, perform full release flow + if (selected.status == PickOrderStatus.PENDING) { + val newConsoCode = assignConsoCode() + + val stockOut = StockOut().apply { + this.type = "job" + this.consoPickOrderCode = newConsoCode + this.status = StockOutStatus.PENDING.status + this.handler = currUser.id + } + val savedStockOut = stockOutRepository.saveAndFlush(stockOut) + + selected.apply { + this.releasedBy = releasedBy + status = PickOrderStatus.RELEASED + this.consoCode = newConsoCode + } + + val suggestions = suggestedPickLotService.suggestionForPickOrders( + SuggestedPickLotForPoRequest(pickOrders = listOf(selected)) + ) + val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) + pickOrderRepository.saveAndFlush(selected) + + val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( + saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } + ) + saveSuggestedPickLots.forEach { lot -> + val lotLineId = lot.suggestedLotLine?.id + if (lotLineId != null) { + val idx = inventoryLotLines.indexOf(lot.suggestedLotLine) + if (idx >= 0) { + val currentHold = inventoryLotLines[idx].holdQty ?: zero + val addHold = lot.qty ?: zero + inventoryLotLines[idx].holdQty = currentHold.plus(addHold) + } + } + } + inventoryLotLineRepository.saveAll(inventoryLotLines) + + // Pre-create stock out lines + var precreated = 0 + saveSuggestedPickLots.forEach { lot -> + val polId = lot.pickOrderLine?.id + val illId = lot.suggestedLotLine?.id + if (polId != null && illId != null) { + val existing = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) + if (existing.isEmpty()) { + val pol = pickOrderLineRepository.findById(polId).orElse(null) + val ill = inventoryLotLineRepository.findById(illId).orElse(null) + if (pol != null && ill != null) { + val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply { + this.stockOut = savedStockOut + this.pickOrderLine = pol + this.inventoryLotLine = ill + this.item = pol.item + this.status = com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status + this.qty = 0.0 + } + stockOutLIneRepository.save(line) + precreated++ + } + } + } + } + println("Precreated $precreated stock out lines for ticket $ticketNo") + } + + // Assign user (both pending->released and already released) + selected.assignTo = user + pickOrderRepository.saveAndFlush(selected) + + // Update do_pick_order_record entries + val existingRecords = doPickOrderRecordRepository.findByPickOrderId(selected.id!!) + println("🔍 DEBUG: Found ${existingRecords.size} existing DoPickOrderRecord entries for pick order ${selected.id}") + + if (existingRecords.isNotEmpty()) { + existingRecords.forEach { record -> + record.handledBy = user.id + record.ticketStatus = DoPickOrderStatus.released + println("🔍 DEBUG: Updating existing DoPickOrderRecord ID: ${record.id} - handledBy: ${user.id}, status: released") + } + doPickOrderRecordRepository.saveAll(existingRecords) + println("✅ Updated ${existingRecords.size} existing DoPickOrderRecord entries") + } + + doPickOrderService.updateHandledByForPickOrder(selected.id!!, user.id!!) + println("✅ Updated DoPickOrder handledBy to user $userId for pick order ${selected.id}") + + return MessageResponse( + id = null, + name = "Pick order assigned by ticket", + code = "SUCCESS", + type = "pickorder", + message = "Assigned to user $userId for store $storeId with ticket $ticketNo", + errorPosition = null, + entity = mapOf( + "pickOrderIds" to listOf(selected.id!!), + "storeId" to storeId, + "ticketNo" to ticketNo, + "status" to selected.status?.value + ) + ) + + } catch (e: Exception) { + e.printStackTrace() + return MessageResponse( + id = null, + name = "Failed to auto-assign by ticket", + code = "ERROR", + type = "pickorder", + message = "Failed to auto-assign by ticket: ${e.message}", + errorPosition = null + ) + } } open fun getPickOrderDetailsOptimized(pickOrderIds: List): GetPickOrderInfoResponse { @@ -2124,7 +2253,37 @@ open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): Me return MessageResponse(id = null, name = "No pick orders", code = "NO_ORDERS", type = "pickorder", message = "No pending pick orders found for store $storeId", errorPosition = null) } + + // Add this check after line 2142 (before finding availablePickOrders) + // ✅ Check if user already has pick orders in progress + val userExistingOrders = pickOrderRepository.findAllByAssignToAndStatusIn( + user, + listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED) // Only active orders, not completed + ).filter { it.deliveryOrder != null } + + if (userExistingOrders.isNotEmpty()) { + println("🔍 DEBUG: User $userId already has ${userExistingOrders.size} pick orders in progress:") + userExistingOrders.forEach { po -> + println("�� DEBUG: Existing order ${po.id}: code=${po.code}, status=${po.status}") + } + return MessageResponse( + id = null, + name = "User already has pick orders", + code = "USER_BUSY", + type = "pickorder", + message = "User $userId already has ${userExistingOrders.size} pick orders in progress. Cannot assign new orders.", + errorPosition = null, + entity = mapOf( + "existingOrders" to userExistingOrders.map { mapOf( + "id" to it.id, + "code" to it.code, + "status" to it.status?.value + )} + ) + ) + } + // Continue with existing logic... val availablePickOrders = pickOrderRepository .findAll() .filter { it.id != null && pickOrderIdsByStore.contains(it.id!!) } @@ -2212,7 +2371,73 @@ open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): Me // Assign user (both pending->released and already released) selected.assignTo = user pickOrderRepository.saveAndFlush(selected) + val deliveryOrder = selected.deliveryOrder + if (deliveryOrder != null) { + val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() + val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("ddMMyy")) + + println("�� DEBUG: Target date: $targetDate, Date prefix: $datePrefix") + val storeId = if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F" + println("🔍 DEBUG: Determined store ID: $storeId") + + // Get next ticket number for this date and store + val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix, storeId) + println("🔍 DEBUG: Next ticket number: $nextTicketNumber") + + // ✅ Find truck by shop ID with earliest departure time + val truck = deliveryOrder.shop?.id?.let { shopId -> + println("🔍 DEBUG: Looking for truck with shop ID: $shopId") + val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) + println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId") + val selectedTruck = trucks.minByOrNull { it.departureTime ?: LocalTime.MAX } + println("🔍 DEBUG: Selected truck: ID=${selectedTruck?.id}, DepartureTime=${selectedTruck?.departureTime}") + selectedTruck + } + + println("🔍 DEBUG: Processing ${deliveryOrder.deliveryOrderLines.size} delivery order lines") + + // Replace lines 2253-2275 with this: +// ✅ UPDATE existing do_pick_order_record entries instead of creating new ones +val existingRecords = doPickOrderRecordRepository.findByPickOrderId(selected.id!!) +println("🔍 DEBUG: Found ${existingRecords.size} existing DoPickOrderRecord entries for pick order ${selected.id}") + +if (existingRecords.isNotEmpty()) { + // ✅ Update existing records + existingRecords.forEach { record -> + record.handledBy = user.id + record.ticketStatus = DoPickOrderStatus.released + println("🔍 DEBUG: Updating existing DoPickOrderRecord ID: ${record.id} - handledBy: ${user.id}, status: released") + } + doPickOrderRecordRepository.saveAll(existingRecords) + println("✅ Updated ${existingRecords.size} existing DoPickOrderRecord entries") +} else { + // ✅ Only create new records if none exist (fallback) + println("⚠️ No existing DoPickOrderRecord entries found, creating new ones") + deliveryOrder.deliveryOrderLines.forEach { line -> + val storeId = if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F" + println(" DEBUG: Processing line - Item ID: ${line.item?.id}, Store ID: $storeId") + + val doPickOrderRecord = DoPickOrderRecord( + storeId = storeId, + ticketNo = nextTicketNumber, + ticketStatus = DoPickOrderStatus.released, // ✅ Set to released + truckId = truck?.id, + pickOrderId = selected.id, + truckDepartureTime = truck?.departureTime, + itemId = line.item?.id, + shopId = deliveryOrder.shop?.id, + shopPoSupplierId = deliveryOrder.shop?.id, + handledBy = user.id + ) + + println(" DEBUG: Creating new DoPickOrderRecord - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}") + + val savedDoPickOrderRecord = doPickOrderRecordRepository.save(doPickOrderRecord) + println("🔍 DEBUG: Saved new DoPickOrderRecord - ID: ${savedDoPickOrderRecord.id}") + } +} + } doPickOrderService.updateHandledByForPickOrder(selected.id!!, user.id!!) println("✅ Updated DoPickOrder handledBy to user $userId for pick order ${selected.id}") return MessageResponse( @@ -2251,11 +2476,12 @@ open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): Me // Get all pick orders assigned to user with PENDING or RELEASED status that have doId val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( user, - listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED) + listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED,PickOrderStatus.COMPLETED) + ).filter { it.deliveryOrder != null } // Only pick orders with doId - + println("🔍 DEBUG: All assigned pick orders: ${allAssignedPickOrders.size}") val pickOrderIds = allAssignedPickOrders.map { it.id!! } - + println("🔍 DEBUG: All assigned pick orders: ${pickOrderIds.size}") println(" Pick order IDs to fetch: $pickOrderIds") if (pickOrderIds.isEmpty()) { @@ -2546,7 +2772,7 @@ open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): Me return emptyList() } - if (pickOrder.status?.value !in listOf("assigned", "released", "picking")) { + if (pickOrder.status?.value !in listOf("assigned", "released", "picking", "completed")) { println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") return emptyList() } @@ -2589,7 +2815,22 @@ open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): Me println("⚠️ Shop ID is null") null } - + val ticketNo = try { + val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) + val ticketNo = doPickOrders.firstOrNull()?.ticketNo ?: "" + println("🔍 Found ticket number: $ticketNo for pick order $pickOrderId") + ticketNo + } catch (e: Exception) { + println("⚠️ Error getting ticket number for pick order $pickOrderId: ${e.message}") + "" + } + val dpoStoreId = try { + val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) + doPickOrders.firstOrNull()?.storeId ?: "" + } catch (e: Exception) { + println("⚠️ Error getting storeId for pick order $pickOrderId: ${e.message}") + "" + } val result = mapOf( "pickOrderId" to (pickOrder.id ?: 0L), "pickOrderCode" to (pickOrder.code ?: ""), @@ -2607,6 +2848,8 @@ open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): Me "numberOfCartons" to (pickOrder.pickOrderLines.size), "truckNo" to (truck?.truckNo ?: ""), // ✅ Use entity property "DepartureTime" to (truck?.departureTime?.toString() ?: ""), + "ticketNo" to ticketNo, + "storeId" to dpoStoreId, "qrCodeData" to (pickOrder.id ?: 0L) ) @@ -2650,11 +2893,45 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List + println("🔍 DEBUG: Pick order ${po.id}: code=${po.code}, status=${po.status}, assignTo=${po.assignTo?.id}, doId=${po.deliveryOrder?.id}") + } + // ✅ NEW LOGIC: Filter based on assignment and status + val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) { + // Check if there are any RELEASED orders assigned to this user (active work) + val assignedReleasedOrders = allAssignedPickOrders.filter { + it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId + } + + if (assignedReleasedOrders.isNotEmpty()) { + // ✅ If there are assigned RELEASED orders, show only those + println("🔍 DEBUG: Found ${assignedReleasedOrders.size} assigned RELEASED orders, showing only those") + assignedReleasedOrders + } else { + // ✅ If no assigned RELEASED orders, show only the latest COMPLETED order + val completedOrders = allAssignedPickOrders.filter { it.status == PickOrderStatus.COMPLETED } + if (completedOrders.isNotEmpty()) { + val latestCompleted = completedOrders.maxByOrNull { it.completeDate ?: it.modified ?: LocalDateTime.MIN } + println("�� DEBUG: No assigned RELEASED orders, showing latest completed order: ${latestCompleted?.code}") + listOfNotNull(latestCompleted) + } else { + println("🔍 DEBUG: No orders found") + emptyList() + } + } + } else { + emptyList() + } - val pickOrderIds = allAssignedPickOrders.map { it.id!! } + println("�� DEBUG: After assignment filtering, ${filteredPickOrders.size} pick orders remain") + filteredPickOrders.forEach { po -> + println("�� DEBUG: Final pick order ${po.id}: code=${po.code}, status=${po.status}, assignTo=${po.assignTo?.id}, doId=${po.deliveryOrder?.id}") + } + val pickOrderIds = filteredPickOrders.map { it.id!! } println(" Pick order IDs to fetch: $pickOrderIds") if (pickOrderIds.isEmpty()) { @@ -2686,6 +2963,7 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List { + println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical ===") + println("today: ${LocalDate.now()}") + println("userId filter: $userId") + + // Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId) + val user = userService.find(userId).orElse(null) + if (user == null) { + println("❌ User not found: $userId") + return emptyMap() + } + + // Get all pick orders assigned to user with PENDING or RELEASED status that have doId + val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( + user, + listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED) + ).filter { it.deliveryOrder != null } // Only pick orders with doId + + println("🔍 DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId") + + // ✅ NEW LOGIC: Filter based on assignment and status + val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) { + // Check if there are any RELEASED orders assigned to this user (active work) + val assignedReleasedOrders = allAssignedPickOrders.filter { + it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId + } + + if (assignedReleasedOrders.isNotEmpty()) { + // ✅ If there are assigned RELEASED orders, show only those + println("🔍 DEBUG: Found ${assignedReleasedOrders.size} assigned RELEASED orders, showing only those") + assignedReleasedOrders + } else { + // ✅ If no assigned RELEASED orders, show only the latest COMPLETED order + val completedOrders = allAssignedPickOrders.filter { it.status == PickOrderStatus.COMPLETED } + if (completedOrders.isNotEmpty()) { + val latestCompleted = completedOrders.maxByOrNull { it.completeDate ?: it.modified ?: LocalDateTime.MIN } + println(" DEBUG: No assigned RELEASED orders, showing latest completed order: ${latestCompleted?.code}") + listOfNotNull(latestCompleted) + } else { + println("🔍 DEBUG: No orders found") + emptyList() + } + } + } else { + emptyList() + } + + val pickOrderIds = filteredPickOrders.map { it.id!! } + println("🎯 Pick order IDs to fetch: $pickOrderIds") + + if (pickOrderIds.isEmpty()) { + return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) + } + + // Use the same SQL query but transform the results into hierarchical structure + val pickOrderIdsStr = pickOrderIds.joinToString(",") + + val sql = """ + SELECT + -- Pick Order Information + po.id as pickOrderId, + po.code as pickOrderCode, + po.consoCode as pickOrderConsoCode, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, + po.type as pickOrderType, + po.status as pickOrderStatus, + po.assignTo as pickOrderAssignTo, + + -- Pick Order Line Information + pol.id as pickOrderLineId, + pol.qty as pickOrderLineRequiredQty, + pol.status as pickOrderLineStatus, + + -- Item Information + i.id as itemId, + i.code as itemCode, + i.name as itemName, + uc.code as uomCode, + uc.udfudesc as uomDesc, + uc.udfShortDesc as uomShortDesc, + + -- Lot Information + ill.id as lotId, + il.lotNo, + DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, + w.name as location, + COALESCE(uc.udfudesc, 'N/A') as stockUnit, + + -- Router Information + r.id as routerId, + r.index as routerIndex, + r.route as routerRoute, + r.area as routerArea, + + -- ✅ FIXED: Set quantities to NULL for rejected lots + CASE + WHEN sol.status = 'rejected' THEN NULL + ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) + END as availableQty, + + -- Required quantity for this lot + COALESCE(spl.qty, 0) as requiredQty, + + -- Actual picked quantity + COALESCE(sol.qty, 0) as actualPickQty, + + -- Suggested pick lot information + spl.id as suggestedPickLotId, + ill.status as lotStatus, + + -- Stock out line information + sol.id as stockOutLineId, + sol.status as stockOutLineStatus, + COALESCE(sol.qty, 0) as stockOutLineQty, + + -- Additional detailed fields + COALESCE(ill.inQty, 0) as inQty, + COALESCE(ill.outQty, 0) as outQty, + COALESCE(ill.holdQty, 0) as holdQty, + COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId, + ill.inventoryLotId as debugInventoryLotId, + + -- Calculate total picked quantity by ALL pick orders for this lot + COALESCE(( + SELECT SUM(sol_all.qty) + FROM fpsmsdb.stock_out_line sol_all + WHERE sol_all.inventoryLotLineId = ill.id + AND sol_all.deleted = false + AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed') + ), 0) as totalPickedByAllPickOrders, + + -- Calculate remaining available quantity correctly + (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders, + + -- Lot availability status + CASE + WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' + WHEN sol.status = 'rejected' THEN 'rejected' + WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' + WHEN ill.status = 'unavailable' THEN 'status_unavailable' + ELSE 'available' + END as lotAvailability, + + -- Processing status + CASE + WHEN sol.status = 'completed' THEN 'completed' + WHEN sol.status = 'rejected' THEN 'rejected' + WHEN sol.status = 'created' THEN 'pending' + ELSE 'pending' + END as processingStatus + + FROM fpsmsdb.pick_order po + JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id + JOIN fpsmsdb.items i ON i.id = pol.itemId + LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId + LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId + LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id + LEFT JOIN fpsmsdb.router r ON r.inventoryLotId = ill.id AND r.deleted = false + LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId + LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId + LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false + WHERE po.deleted = false + AND po.id IN ($pickOrderIdsStr) + AND pol.deleted = false + AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED') + AND po.assignTo = :userId + AND ill.deleted = false + AND il.deleted = false + AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) + ORDER BY + CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, + COALESCE(r.index, 0) ASC, + po.code ASC, + i.code ASC, + il.expiryDate ASC, + il.lotNo ASC + """.trimIndent() + + println("�� Executing SQL for hierarchical structure: $sql") + println("�� With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") + + val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) + println("✅ Total result count: ${results.size}") + + // Filter out lots with null availableQty (rejected lots) + val filteredResults = results.filter { row -> + val availableQty = row["availableQty"] + availableQty != null + } + + println("✅ Filtered result count: ${filteredResults.size}") + + // ✅ Transform flat results into hierarchical structure + if (filteredResults.isEmpty()) { + return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) + } + + // Get pick order info from first row (all rows have same pick order info) + val firstRow = filteredResults.first() + val pickOrderInfo = mapOf( + "id" to firstRow["pickOrderId"], + "code" to firstRow["pickOrderCode"], + "consoCode" to firstRow["pickOrderConsoCode"], + "targetDate" to firstRow["pickOrderTargetDate"], + "type" to firstRow["pickOrderType"], + "status" to firstRow["pickOrderStatus"], + "assignTo" to firstRow["pickOrderAssignTo"] + ) + + // Group by pick order line ID to create hierarchical structure + val pickOrderLinesMap = filteredResults + .groupBy { it["pickOrderLineId"] as Number } + .map { (pickOrderLineId, lots) -> + val firstLot = lots.first() + + // Item information (same for all lots of this line) + val itemInfo = mapOf( + "id" to firstLot["itemId"], + "code" to firstLot["itemCode"], + "name" to firstLot["itemName"], + "uomCode" to firstLot["uomCode"], + "uomDesc" to firstLot["uomDesc"] + ) + + // Transform lots for this pick order line + val lotsInfo = lots.map { lot -> + mapOf( + "id" to lot["lotId"], + "lotNo" to lot["lotNo"], + "expiryDate" to lot["expiryDate"], + "location" to lot["location"], + "stockUnit" to lot["stockUnit"], + "availableQty" to lot["availableQty"], + "requiredQty" to lot["requiredQty"], + "actualPickQty" to lot["actualPickQty"], + "inQty" to lot["inQty"], + "outQty" to lot["outQty"], + "holdQty" to lot["holdQty"], + "lotStatus" to lot["lotStatus"], + "lotAvailability" to lot["lotAvailability"], + "processingStatus" to lot["processingStatus"], + "suggestedPickLotId" to lot["suggestedPickLotId"], + "stockOutLineId" to lot["stockOutLineId"], + "stockOutLineStatus" to lot["stockOutLineStatus"], + "stockOutLineQty" to lot["stockOutLineQty"], + "router" to mapOf( + "id" to lot["routerId"], + "index" to lot["routerIndex"], + "route" to lot["routerRoute"], + "area" to lot["routerArea"], + "itemCode" to firstLot["itemId"], + "itemName" to firstLot["itemName"], + "uomId" to firstLot["uomShortDesc"], + "noofCarton" to lot["requiredQty"] // Use required qty as carton count + ) + ) + } + + // Pick order line with item and lots + mapOf( + "id" to pickOrderLineId, + "requiredQty" to firstLot["pickOrderLineRequiredQty"], + "status" to firstLot["pickOrderLineStatus"], + "item" to itemInfo, + "lots" to lotsInfo + ) + } + + return mapOf( + "pickOrder" to pickOrderInfo as Any?, + "pickOrderLines" to pickOrderLinesMap as Any? + ) +} +// Fix the type issues in the getPickOrdersByDateAndStore method +open fun getPickOrdersByDateAndStore(storeId: String): Map { + println("=== Debug: getPickOrdersByDateAndStore ===") + println("storeId: $storeId") + + try { + val today = LocalDate.now() + println("Today's date: $today") + + // SQL query to get pick orders grouped by date and store + // Get all available orders for this store + val sql = """ + SELECT + dpo.store_id as storeId, + DATE_FORMAT(dpo.created, '%Y-%m-%d') as orderDate, + COUNT(DISTINCT po.id) as orderTotal, + COUNT(DISTINCT CASE WHEN po.status = 'COMPLETED' THEN po.id END) as orderCompleted, + GROUP_CONCAT(DISTINCT po.id ORDER BY po.id) as pickOrderIds, + GROUP_CONCAT(DISTINCT po.code ORDER BY po.id SEPARATOR ',') as pickOrderCodes, + -- Get do_pick_order details + GROUP_CONCAT( + CONCAT( + dpo.id, ':', + dpo.ticket_no, ':', + dpo.pick_order_id + ) + ORDER BY dpo.id SEPARATOR '|' + ) as doPickOrderDetails + FROM do_pick_order dpo + JOIN pick_order po ON po.id = dpo.pick_order_id + WHERE dpo.deleted = false + AND po.deleted = false + AND dpo.store_id = :storeId + GROUP BY dpo.store_id, DATE_FORMAT(dpo.created, '%Y-%m-%d') + ORDER BY orderDate ASC + """.trimIndent() + + println("🔍 Executing SQL: $sql") + println("🔍 With parameters: storeId = $storeId") + + val results = jdbcDao.queryForList(sql, mapOf("storeId" to storeId)) + println("✅ Found ${results.size} records") + + if (results.isEmpty()) { + return mapOf( + "storeId" to storeId, + "orders" to emptyList>() + ) + } + + // Analyze each date's completion status + val ordersByDate = results.map { row -> + val orderDate = LocalDate.parse(row["orderDate"] as String) + val orderTotal = (row["orderTotal"] as Number).toInt() + val orderCompleted = (row["orderCompleted"] as Number).toInt() + val isAllCompleted = orderTotal == orderCompleted && orderTotal > 0 + + mapOf( + "orderDate" to orderDate, + "orderTotal" to orderTotal, + "orderCompleted" to orderCompleted, + "isAllCompleted" to isAllCompleted, + "rawData" to row + ) + } + + println("📊 Orders by date analysis:") + ordersByDate.forEach { order -> + println(" Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}, AllCompleted: ${order["isAllCompleted"]}") + } + + // Simple logic: Find the first incomplete date and return it + next 2 days + val ordersToReturn = mutableListOf>() + + // Find the first incomplete order date + val firstIncompleteDate = ordersByDate + .firstOrNull { !(it["isAllCompleted"] as Boolean) } + + if (firstIncompleteDate != null) { + val startDate = firstIncompleteDate["orderDate"] as LocalDate + println("�� First incomplete date: $startDate") + + // Get the incomplete date + next 2 days (total 3 days) + val targetDates = (0..2).map { days -> startDate.plusDays(days.toLong()) } + println("🎯 Target dates to return: $targetDates") + + // Only return dates that actually exist in the database + ordersToReturn.addAll( + ordersByDate.filter { order -> + val orderDate = order["orderDate"] as LocalDate + targetDates.contains(orderDate) + }.map { it["rawData"] as Map } + ) + } else { + println("🎯 All orders are completed, returning empty list") + } + + // Transform results into the required structure + val finalOrders = ordersToReturn.map { row -> + val doPickOrderDetails = (row["doPickOrderDetails"] as String?)?.split("|") ?: emptyList() + + val doPickOrderList = doPickOrderDetails.map { detail -> + val parts = detail.split(":") + if (parts.size >= 3) { + mapOf( + "doPickOrderId" to parts[0].toLongOrNull(), + "ticketNo" to parts[1], + "pickOrderId" to parts[2].toLongOrNull() + ) + } else { + mapOf( + "doPickOrderId" to null, + "ticketNo" to "", + "pickOrderId" to null + ) + } + } + + mapOf( + "orderTotal" to row["orderTotal"], + "orderCompleted" to row["orderCompleted"], + "orderDate" to row["orderDate"], + "pickOrderIds" to ((row["pickOrderIds"] as String?)?.split(",")?.mapNotNull { it.toLongOrNull() } ?: emptyList()), + "pickOrderCodes" to ((row["pickOrderCodes"] as String?)?.split(",") ?: emptyList()), + "doPickOrderDetails" to doPickOrderList + ) + } + + println("✅ Final orders to return: ${finalOrders.size}") + finalOrders.forEach { order -> + println(" 📅 Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}") + } + + return mapOf( + "storeId" to storeId, + "orders" to finalOrders + ) + + } catch (e: Exception) { + println("❌ Error in getPickOrdersByDateAndStore: ${e.message}") + e.printStackTrace() + return mapOf( + "storeId" to storeId, + "orders" to emptyList>(), + "error" to e.message + ) + } +} } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt index 706f3e6..7c263f6 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt @@ -266,4 +266,20 @@ class PickOrderController( fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(@PathVariable userId: Long): List> { return pickOrderService.getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId) } + @GetMapping("/all-lots-hierarchical/{userId}") +fun getAllPickOrderLotsHierarchical(@PathVariable userId: Long): Map { + return pickOrderService.getAllPickOrderLotsWithDetailsHierarchical(userId) +} +@PostMapping("/auto-assign-release-by-ticket") +fun autoAssignAndReleasePickOrderByTicket( + @RequestParam storeId: String, + @RequestParam ticketNo: String, + @RequestParam userId: Long +): MessageResponse { + return pickOrderService.autoAssignAndReleasePickOrderByStoreAndTicket(storeId, ticketNo, userId) +} +@GetMapping("/orders-by-store/{storeId}") +fun getPickOrdersByStore(@PathVariable storeId: String): Map { + return pickOrderService.getPickOrdersByDateAndStore(storeId) +} } \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20251098_01_enson/02_altertable_enson.sql b/src/main/resources/db/changelog/changes/20251098_01_enson/02_altertable_enson.sql new file mode 100644 index 0000000..e5dbb20 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251098_01_enson/02_altertable_enson.sql @@ -0,0 +1,7 @@ +-- liquibase formatted sql +-- changeset enson:altertable_enson + +ALTER TABLE `fpsmsdb`.`do_pick_order` +CHANGE COLUMN `ticket_status` `ticket_status` ENUM('pending', 'released', 'completed') NULL DEFAULT 'pending' ; +ALTER TABLE `fpsmsdb`.`do_pick_order_record` +CHANGE COLUMN `ticket_status` `ticket_status` ENUM('pending', 'released', 'completed') NULL DEFAULT 'pending' ;