|
|
|
@@ -1702,7 +1702,233 @@ open fun getCompletedJobOrderPickOrders(completedDate: LocalDate?): List<Map<Str |
|
|
|
emptyList() |
|
|
|
} |
|
|
|
} |
|
|
|
// ... rest of the code ... |
|
|
|
open fun getJobOrderPickOrders(date: LocalDate?, status: PickOrderStatus?): List<Map<String, Any?>> { |
|
|
|
println("=== getJobOrderPickOrders ===") |
|
|
|
println("date: $date, status: $status") |
|
|
|
|
|
|
|
return try { |
|
|
|
val allPickOrders = |
|
|
|
when { |
|
|
|
date != null -> { |
|
|
|
val from = date.atStartOfDay() |
|
|
|
val toExclusive = date.plusDays(1).atStartOfDay() |
|
|
|
pickOrderRepository.findByJobOrderPlanStartOnDayAndOptionalStatus(from, toExclusive, status) |
|
|
|
} |
|
|
|
status != null -> { |
|
|
|
pickOrderRepository.findAllByStatusAndDeletedFalse(status) |
|
|
|
.filter { it.jobOrder != null && it.jobOrder?.isHidden != true } |
|
|
|
} |
|
|
|
else -> { |
|
|
|
pickOrderRepository.findAllByStatusIn(PickOrderStatus.entries.toList()) |
|
|
|
.filter { !it.deleted && it.jobOrder != null && it.jobOrder?.isHidden != true } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
val pickOrderIds = allPickOrders.mapNotNull { it.id }.distinct() |
|
|
|
val joByPickOrderId: Map<Long, List<JoPickOrder>> = |
|
|
|
if (pickOrderIds.isNotEmpty()) { |
|
|
|
joPickOrderRepository.findByPickOrderIdIn(pickOrderIds) |
|
|
|
.filterNot { it.deleted } |
|
|
|
.groupBy { it.pickOrderId ?: 0L } |
|
|
|
.filterKeys { it != 0L } |
|
|
|
} else { |
|
|
|
emptyMap() |
|
|
|
} |
|
|
|
|
|
|
|
allPickOrders.mapNotNull { pickOrder -> |
|
|
|
val jobOrder = pickOrder.jobOrder ?: return@mapNotNull null |
|
|
|
val poId = pickOrder.id ?: return@mapNotNull null |
|
|
|
val joPickOrders = joByPickOrderId[poId].orEmpty() |
|
|
|
|
|
|
|
val secondScanCompleted = joPickOrders.isNotEmpty() && |
|
|
|
joPickOrders.all { it.matchStatus == com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus.completed } |
|
|
|
|
|
|
|
mapOf( |
|
|
|
"id" to pickOrder.id, |
|
|
|
"pickOrderId" to pickOrder.id, |
|
|
|
"pickOrderCode" to pickOrder.code, |
|
|
|
"pickOrderConsoCode" to pickOrder.consoCode, |
|
|
|
"pickOrderTargetDate" to pickOrder.targetDate?.let { |
|
|
|
"${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}" |
|
|
|
}, |
|
|
|
"pickOrderStatus" to pickOrder.status, |
|
|
|
"completedDate" to jobOrder.planStart, |
|
|
|
"jobOrderId" to jobOrder.id, |
|
|
|
"jobOrderCode" to jobOrder.code, |
|
|
|
"jobOrderName" to jobOrder.bom?.name, |
|
|
|
"reqQty" to jobOrder.reqQty, |
|
|
|
"uom" to jobOrder.bom?.uom?.code, |
|
|
|
"planStart" to jobOrder.planStart, |
|
|
|
"planEnd" to jobOrder.planEnd, |
|
|
|
"secondScanCompleted" to secondScanCompleted, |
|
|
|
"totalItems" to joPickOrders.size, |
|
|
|
"completedItems" to joPickOrders.count { |
|
|
|
it.matchStatus == com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus.completed |
|
|
|
}, |
|
|
|
) |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
println("❌ Error in getJobOrderPickOrders: ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
emptyList() |
|
|
|
} |
|
|
|
} |
|
|
|
open fun getJobOrderPickOrderLotDetails( |
|
|
|
pickOrderId: Long, |
|
|
|
status: String? = null, // 可選,不傳=全部狀態 |
|
|
|
): List<Map<String, Any?>> { |
|
|
|
println("=== getJobOrderPickOrderLotDetails (all status) ===") |
|
|
|
println("pickOrderId: $pickOrderId, status: $status") |
|
|
|
|
|
|
|
return try { |
|
|
|
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.status as pickOrderStatus, |
|
|
|
|
|
|
|
-- Job Order Information |
|
|
|
jo.id as jobOrderId, |
|
|
|
jo.code as jobOrderCode, |
|
|
|
|
|
|
|
-- 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 |
|
|
|
w.id as routerId, |
|
|
|
w.`order` as routerIndex, |
|
|
|
w.code as routerRoute, |
|
|
|
w.code as routerArea, |
|
|
|
|
|
|
|
-- 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, |
|
|
|
|
|
|
|
-- 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', 'picking') |
|
|
|
), 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 (all-status friendly) |
|
|
|
CASE |
|
|
|
WHEN sol.status = 'completed' THEN 'completed' |
|
|
|
WHEN sol.status = 'rejected' THEN 'rejected' |
|
|
|
WHEN sol.status IN ('checked', 'partially_completed', 'picking') THEN 'in_progress' |
|
|
|
WHEN sol.status IN ('created', 'pending') THEN 'pending' |
|
|
|
ELSE COALESCE(sol.status, 'pending') |
|
|
|
END as processingStatus, |
|
|
|
|
|
|
|
-- JoPickOrder second scan status |
|
|
|
jpo.match_status as match_status, |
|
|
|
jpo.match_by as match_by, |
|
|
|
jpo.match_qty as match_qty |
|
|
|
|
|
|
|
FROM fpsmsdb.pick_order po |
|
|
|
JOIN fpsmsdb.job_order jo ON jo.id = po.joId |
|
|
|
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.inventory_lot il ON il.id = ill.inventoryLotId |
|
|
|
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId |
|
|
|
-- no-lot:ill.id 為 null 時,匹配 sol.inventoryLotLineId IS NULL |
|
|
|
LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false |
|
|
|
AND ( |
|
|
|
sol.inventoryLotLineId = ill.id |
|
|
|
OR (sol.inventoryLotLineId IS NULL AND ill.id IS NULL) |
|
|
|
) |
|
|
|
LEFT JOIN fpsmsdb.jo_pick_order jpo ON jpo.pick_order_id = po.id AND jpo.item_id = pol.itemId |
|
|
|
WHERE po.deleted = false |
|
|
|
AND po.id = :pickOrderId |
|
|
|
AND pol.deleted = false |
|
|
|
AND (:status IS NULL OR po.status = :status) |
|
|
|
-- ill / il 可能為 null(no-lot),避免過濾掉 no-lot 記錄 |
|
|
|
AND (ill.id IS NULL OR ill.deleted = false) |
|
|
|
AND (il.id IS NULL OR 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(w.`order`, 999999) ASC, |
|
|
|
po.code ASC, |
|
|
|
i.code ASC, |
|
|
|
il.expiryDate ASC, |
|
|
|
il.lotNo ASC |
|
|
|
""".trimIndent() |
|
|
|
|
|
|
|
val params = mapOf( |
|
|
|
"pickOrderId" to pickOrderId, |
|
|
|
"status" to status?.trim()?.takeIf { it.isNotEmpty() }, // null = all status |
|
|
|
) |
|
|
|
|
|
|
|
println("With parameters: $params") |
|
|
|
val results = jdbcDao.queryForList(sql, params) |
|
|
|
println("Found ${results.size} lot details for pickOrderId=$pickOrderId") |
|
|
|
results |
|
|
|
} catch (e: Exception) { |
|
|
|
println("Error in getJobOrderPickOrderLotDetails: ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
emptyList() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
open fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId: Long): List<Map<String, Any?>> { |
|
|
|
println("=== getCompletedJobOrderPickOrderLotDetailsForCompletedPick ===") |
|
|
|
|