|
|
|
@@ -56,6 +56,7 @@ 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, |
|
|
|
@@ -77,7 +78,9 @@ open class PickOrderService( |
|
|
|
private val truckRepository: TruckRepository, |
|
|
|
private val doPickOrderService: DoPickOrderService, |
|
|
|
private val routerRepository: RouterRepository, |
|
|
|
private val doPickOrderRecordRepository: DoPickOrderRecordRepository |
|
|
|
private val doPickOrderRecordRepository: DoPickOrderRecordRepository, |
|
|
|
private val doPickOrderRepository: DoPickOrderRepository |
|
|
|
|
|
|
|
|
|
|
|
) : AbstractBaseEntityService<PickOrder, Long, PickOrderRepository>(jdbcDao, pickOrderRepository) { |
|
|
|
open fun create(request: SavePickOrderRequest): MessageResponse { |
|
|
|
@@ -1584,113 +1587,224 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun getAllPickOrderLotsWithDetails(pickOrderIds: List<Long>): List<Map<String, Any>> { |
|
|
|
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<String, Any>()) |
|
|
|
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<Long>): GetPickOrderInfoResponse { |
|
|
|
@@ -2263,10 +2377,13 @@ open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): Me |
|
|
|
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") |
|
|
|
|
|
|
|
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 -> |
|
|
|
@@ -2698,7 +2815,15 @@ if (existingRecords.isNotEmpty()) { |
|
|
|
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 result = mapOf( |
|
|
|
"pickOrderId" to (pickOrder.id ?: 0L), |
|
|
|
"pickOrderCode" to (pickOrder.code ?: ""), |
|
|
|
@@ -2716,6 +2841,7 @@ if (existingRecords.isNotEmpty()) { |
|
|
|
"numberOfCartons" to (pickOrder.pickOrderLines.size), |
|
|
|
"truckNo" to (truck?.truckNo ?: ""), // ✅ Use entity property |
|
|
|
"DepartureTime" to (truck?.departureTime?.toString() ?: ""), |
|
|
|
"ticketNo" to ticketNo, |
|
|
|
"qrCodeData" to (pickOrder.id ?: 0L) |
|
|
|
) |
|
|
|
|
|
|
|
@@ -2829,6 +2955,7 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map |
|
|
|
i.name as itemName, |
|
|
|
uc.code as uomCode, |
|
|
|
uc.udfudesc as uomDesc, |
|
|
|
uc.udfShortDesc as uomShortDesc, |
|
|
|
|
|
|
|
-- Lot Information (similar to lot-details endpoint) |
|
|
|
ill.id as lotId, |
|
|
|
@@ -2841,6 +2968,7 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map |
|
|
|
r.id as routerId, |
|
|
|
r.index as routerIndex, |
|
|
|
r.route as routerRoute, |
|
|
|
|
|
|
|
-- ✅ FIXED: Set quantities to NULL for rejected lots |
|
|
|
CASE |
|
|
|
WHEN sol.status = 'rejected' THEN NULL |
|
|
|
@@ -3013,4 +3141,434 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map |
|
|
|
|
|
|
|
return enrichedResults |
|
|
|
} |
|
|
|
// ... existing code ... |
|
|
|
|
|
|
|
// Fix the method signature and return types |
|
|
|
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> { |
|
|
|
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<Map<String, Any>>() 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<Map<String, Any>>() 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<String, Any?> { |
|
|
|
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<Map<String, Any?>>() |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// 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<Map<String, Any?>>() |
|
|
|
|
|
|
|
// 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<String, Any?> } |
|
|
|
) |
|
|
|
} 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<String, Any?>( |
|
|
|
"doPickOrderId" to parts[0].toLongOrNull(), |
|
|
|
"ticketNo" to parts[1], |
|
|
|
"pickOrderId" to parts[2].toLongOrNull() |
|
|
|
) |
|
|
|
} else { |
|
|
|
mapOf<String, Any?>( |
|
|
|
"doPickOrderId" to null, |
|
|
|
"ticketNo" to "", |
|
|
|
"pickOrderId" to null |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
mapOf<String, Any?>( |
|
|
|
"orderTotal" to row["orderTotal"], |
|
|
|
"orderCompleted" to row["orderCompleted"], |
|
|
|
"orderDate" to row["orderDate"], |
|
|
|
"pickOrderIds" to ((row["pickOrderIds"] as String?)?.split(",")?.mapNotNull { it.toLongOrNull() } ?: emptyList<Long>()), |
|
|
|
"pickOrderCodes" to ((row["pickOrderCodes"] as String?)?.split(",") ?: emptyList<String>()), |
|
|
|
"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<String, Any?>( |
|
|
|
"storeId" to storeId, |
|
|
|
"orders" to finalOrders |
|
|
|
) |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
println("❌ Error in getPickOrdersByDateAndStore: ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
return mapOf<String, Any?>( |
|
|
|
"storeId" to storeId, |
|
|
|
"orders" to emptyList<Map<String, Any?>>(), |
|
|
|
"error" to e.message |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |