|
|
@@ -350,9 +350,10 @@ open class DeliveryOrderService( |
|
|
truckLanceCode: String?, |
|
|
truckLanceCode: String?, |
|
|
floor: String? = null, |
|
|
floor: String? = null, |
|
|
isExtra: Boolean? = null, |
|
|
isExtra: Boolean? = null, |
|
|
|
|
|
excludeUnassignedTruck: Boolean = false, |
|
|
): RecordsRes<DeliveryOrderInfoLiteDto> { |
|
|
): RecordsRes<DeliveryOrderInfoLiteDto> { |
|
|
val mode = TruckLaneSearchSpec.parse(truckLanceCode) |
|
|
val mode = TruckLaneSearchSpec.parse(truckLanceCode) |
|
|
if (mode is TruckLaneSearchSpec.Mode.NoFilter) { |
|
|
|
|
|
|
|
|
if (mode is TruckLaneSearchSpec.Mode.NoFilter && !excludeUnassignedTruck) { |
|
|
return searchDoLiteByPage( |
|
|
return searchDoLiteByPage( |
|
|
code, |
|
|
code, |
|
|
shopName, |
|
|
shopName, |
|
|
@@ -371,7 +372,10 @@ open class DeliveryOrderService( |
|
|
val etaStart = estimatedArrivalDate |
|
|
val etaStart = estimatedArrivalDate |
|
|
val etaEnd = estimatedArrivalDate?.plusDays(1) |
|
|
val etaEnd = estimatedArrivalDate?.plusDays(1) |
|
|
|
|
|
|
|
|
val lanePredicate: (String?) -> Boolean = { lane -> TruckLaneSearchSpec.matches(mode, lane) } |
|
|
|
|
|
|
|
|
val lanePredicate: (String?) -> Boolean = { lane -> |
|
|
|
|
|
TruckLaneSearchSpec.matches(mode, lane) && |
|
|
|
|
|
(!excludeUnassignedTruck || !TruckLaneSearchSpec.isUnassignedResolvedLane(lane)) |
|
|
|
|
|
} |
|
|
val matched = scanDoLiteSupplierFilteredWithLanePredicate( |
|
|
val matched = scanDoLiteSupplierFilteredWithLanePredicate( |
|
|
code = code?.ifBlank { null }, |
|
|
code = code?.ifBlank { null }, |
|
|
shopName = shopName?.ifBlank { null }, |
|
|
shopName = shopName?.ifBlank { null }, |
|
|
@@ -2231,62 +2235,16 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } |
|
|
println(" - Preferred floor: $preferredFloor") |
|
|
println(" - Preferred floor: $preferredFloor") |
|
|
|
|
|
|
|
|
val truck = deliveryOrder.shop?.id?.let { shopId -> |
|
|
val truck = deliveryOrder.shop?.id?.let { shopId -> |
|
|
val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) |
|
|
|
|
|
val preferredStoreId = when (preferredFloor) { |
|
|
|
|
|
"2F" -> "2F" |
|
|
|
|
|
"4F" -> "4F" |
|
|
|
|
|
"3F" -> "3F" |
|
|
|
|
|
else -> "2F" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } |
|
|
|
|
|
|
|
|
|
|
|
if (matchedTrucks.isEmpty()) { |
|
|
|
|
|
|
|
|
val eta = deliveryOrder.estimatedArrivalDate?.toLocalDate() |
|
|
|
|
|
if (preferredFloor == "4F" && eta == null) { |
|
|
null |
|
|
null |
|
|
} else { |
|
|
} else { |
|
|
if (preferredStoreId == "4F" && matchedTrucks.size > 1) { |
|
|
|
|
|
deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate -> |
|
|
|
|
|
val targetDate = estimatedArrivalDate.toLocalDate() |
|
|
|
|
|
val dayOfWeek = targetDate.dayOfWeek |
|
|
|
|
|
|
|
|
|
|
|
val dayAbbr = when (dayOfWeek) { |
|
|
|
|
|
java.time.DayOfWeek.MONDAY -> "Mon" |
|
|
|
|
|
java.time.DayOfWeek.TUESDAY -> "Tue" |
|
|
|
|
|
java.time.DayOfWeek.WEDNESDAY -> "Wed" |
|
|
|
|
|
java.time.DayOfWeek.THURSDAY -> "Thu" |
|
|
|
|
|
java.time.DayOfWeek.FRIDAY -> "Fri" |
|
|
|
|
|
java.time.DayOfWeek.SATURDAY -> "Sat" |
|
|
|
|
|
java.time.DayOfWeek.SUNDAY -> "Sun" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
println(" DEBUG: DO ${deliveryOrder.id} - Target date: $targetDate ($dayAbbr), Shop: $shopId") |
|
|
|
|
|
println(" DEBUG: Found ${matchedTrucks.size} matched 4F trucks") |
|
|
|
|
|
|
|
|
|
|
|
val dayMatchedTrucks = matchedTrucks.filter { |
|
|
|
|
|
it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
println(" DEBUG: Found ${dayMatchedTrucks.size} trucks matching $dayAbbr") |
|
|
|
|
|
dayMatchedTrucks.forEach { t -> |
|
|
|
|
|
println(" - Truck ID=${t.id}, Code=${t.truckLanceCode}, Time=${t.departureTime}") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (dayMatchedTrucks.isNotEmpty()) { |
|
|
|
|
|
val selected = dayMatchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
println("✅ DEBUG: Selected truck matching $dayAbbr - ID=${selected?.id}, Code=${selected?.truckLanceCode}") |
|
|
|
|
|
selected |
|
|
|
|
|
} else { |
|
|
|
|
|
println("⚠️ WARNING: No truck matching $dayAbbr, using first available truck") |
|
|
|
|
|
matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
} |
|
|
|
|
|
} ?: run { |
|
|
|
|
|
matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
resolveTruckForShopFloorAndDay( |
|
|
|
|
|
shopId, |
|
|
|
|
|
preferredFloor, |
|
|
|
|
|
getDayOfWeekAbbr(eta ?: LocalDate.now()), |
|
|
|
|
|
) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
val defaultTruckId = 5577L |
|
|
val defaultTruckId = 5577L |
|
|
val effectiveTruck = truck ?: truckRepository.findById(defaultTruckId).orElse(null) |
|
|
val effectiveTruck = truck ?: truckRepository.findById(defaultTruckId).orElse(null) |
|
|
@@ -2396,46 +2354,15 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } |
|
|
val preferredFloor = preferredStoreFloorForSupplier(supplierCode, floorSuppliers2F, floorSuppliers4F) |
|
|
val preferredFloor = preferredStoreFloorForSupplier(supplierCode, floorSuppliers2F, floorSuppliers4F) |
|
|
|
|
|
|
|
|
val truck = deliveryOrder.shop?.id?.let { shopId -> |
|
|
val truck = deliveryOrder.shop?.id?.let { shopId -> |
|
|
val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) |
|
|
|
|
|
val preferredStoreId = when (preferredFloor) { |
|
|
|
|
|
"2F" -> "2F" |
|
|
|
|
|
"4F" -> "4F" |
|
|
|
|
|
"3F" -> "3F" |
|
|
|
|
|
else -> "2F" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } |
|
|
|
|
|
if (matchedTrucks.isEmpty()) { |
|
|
|
|
|
|
|
|
val eta = deliveryOrder.estimatedArrivalDate?.toLocalDate() |
|
|
|
|
|
if (preferredFloor == "4F" && eta == null) { |
|
|
null |
|
|
null |
|
|
} else { |
|
|
} else { |
|
|
if (preferredStoreId == "4F" && matchedTrucks.size > 1) { |
|
|
|
|
|
deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate -> |
|
|
|
|
|
val targetDate2 = estimatedArrivalDate.toLocalDate() |
|
|
|
|
|
val dayAbbr = when (targetDate2.dayOfWeek) { |
|
|
|
|
|
java.time.DayOfWeek.MONDAY -> "Mon" |
|
|
|
|
|
java.time.DayOfWeek.TUESDAY -> "Tue" |
|
|
|
|
|
java.time.DayOfWeek.WEDNESDAY -> "Wed" |
|
|
|
|
|
java.time.DayOfWeek.THURSDAY -> "Thu" |
|
|
|
|
|
java.time.DayOfWeek.FRIDAY -> "Fri" |
|
|
|
|
|
java.time.DayOfWeek.SATURDAY -> "Sat" |
|
|
|
|
|
java.time.DayOfWeek.SUNDAY -> "Sun" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val dayMatchedTrucks = matchedTrucks.filter { |
|
|
|
|
|
it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (dayMatchedTrucks.isNotEmpty()) { |
|
|
|
|
|
dayMatchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
} else { |
|
|
|
|
|
matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
} |
|
|
|
|
|
} ?: run { |
|
|
|
|
|
matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
resolveTruckForShopFloorAndDay( |
|
|
|
|
|
shopId, |
|
|
|
|
|
preferredFloor, |
|
|
|
|
|
getDayOfWeekAbbr(eta ?: LocalDate.now()), |
|
|
|
|
|
) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@@ -2563,7 +2490,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } |
|
|
/** |
|
|
/** |
|
|
* 依店鋪 + 揀貨樓層解析當日應顯示之車線。 |
|
|
* 依店鋪 + 揀貨樓層解析當日應顯示之車線。 |
|
|
* - **2F**(P07/P06D/P06Y):`TruckLanceCode` 多為 `車線-…` 且不含星期縮寫,不依 `LIKE '%Mon%'` 篩選;取該店該樓層未刪除車輛中出發時間最早者。 |
|
|
* - **2F**(P07/P06D/P06Y):`TruckLanceCode` 多為 `車線-…` 且不含星期縮寫,不依 `LIKE '%Mon%'` 篩選;取該店該樓層未刪除車輛中出發時間最早者。 |
|
|
* - **4F**(P06B):維持以星期縮寫篩選 [TruckRepository.findByShopIdAndStoreIdAndDayOfWeek];無命中時再退回同樓層最早出發。 |
|
|
|
|
|
|
|
|
* - **4F**(P06B):只取車線代碼含當天星期的車。沒有對到當天車時回傳 null,搜尋顯示為車線-X,不退回其他日子的車。 |
|
|
*/ |
|
|
*/ |
|
|
private fun resolveTruckForShopFloorAndDay( |
|
|
private fun resolveTruckForShopFloorAndDay( |
|
|
shopId: Long, |
|
|
shopId: Long, |
|
|
@@ -2575,13 +2502,16 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } |
|
|
.filter { it.storeId == preferredFloor } |
|
|
.filter { it.storeId == preferredFloor } |
|
|
.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
} |
|
|
} |
|
|
val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, preferredFloor, dayAbbr) |
|
|
|
|
|
return if (trucks.isEmpty()) { |
|
|
|
|
|
|
|
|
val dayMatched = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, preferredFloor, dayAbbr) |
|
|
|
|
|
if (preferredFloor == "4F") { |
|
|
|
|
|
return dayMatched.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
} |
|
|
|
|
|
return if (dayMatched.isEmpty()) { |
|
|
truckRepository.findByShopIdAndDeletedFalse(shopId) |
|
|
truckRepository.findByShopIdAndDeletedFalse(shopId) |
|
|
.filter { it.storeId == preferredFloor } |
|
|
.filter { it.storeId == preferredFloor } |
|
|
.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
} else { |
|
|
} else { |
|
|
trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
|
|
|
|
|
|
dayMatched.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@@ -2598,31 +2528,20 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } |
|
|
|
|
|
|
|
|
open fun check4FTruckAvailabilityForDoList(doIds: List<Long>): Check4FTruckBatchResponse { |
|
|
open fun check4FTruckAvailabilityForDoList(doIds: List<Long>): Check4FTruckBatchResponse { |
|
|
val problems = mutableListOf<ProblemDoDto>() |
|
|
val problems = mutableListOf<ProblemDoDto>() |
|
|
|
|
|
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists() |
|
|
|
|
|
|
|
|
doIds.forEach { doId -> |
|
|
doIds.forEach { doId -> |
|
|
val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId) ?: return@forEach |
|
|
val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId) ?: return@forEach |
|
|
val shopId = deliveryOrder.shop?.id ?: return@forEach |
|
|
val shopId = deliveryOrder.shop?.id ?: return@forEach |
|
|
val eta = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: return@forEach |
|
|
val eta = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: return@forEach |
|
|
|
|
|
|
|
|
// 1) Check if this DO is a 4F order (your “all items on 4F, no 2F” rule) |
|
|
|
|
|
val sql = """ |
|
|
|
|
|
SELECT |
|
|
|
|
|
COUNT(DISTINCT CASE WHEN i.store_id = '4F' THEN dol.itemId END) AS count_4f, |
|
|
|
|
|
COUNT(DISTINCT CASE WHEN i.store_id = '2F' THEN dol.itemId END) AS count_2f, |
|
|
|
|
|
COUNT(DISTINCT dol.itemId) AS total_items |
|
|
|
|
|
FROM fpsmsdb.delivery_order_line dol |
|
|
|
|
|
INNER JOIN fpsmsdb.items i ON i.id = dol.itemId AND i.deleted = 0 |
|
|
|
|
|
WHERE dol.deliveryOrderId = :doId AND dol.deleted = 0 |
|
|
|
|
|
""".trimIndent() |
|
|
|
|
|
|
|
|
|
|
|
val res = jdbcDao.queryForList(sql, mapOf("doId" to doId)).firstOrNull() ?: return@forEach |
|
|
|
|
|
val count4F = (res["count_4f"] as? Number)?.toInt() ?: 0 |
|
|
|
|
|
val count2F = (res["count_2f"] as? Number)?.toInt() ?: 0 |
|
|
|
|
|
val totalItems = (res["total_items"] as? Number)?.toInt() ?: 0 |
|
|
|
|
|
|
|
|
|
|
|
val is4FOrder = (totalItems > 0 && count4F == totalItems && count2F == 0) |
|
|
|
|
|
if (!is4FOrder) { |
|
|
|
|
|
// Not a 4F order → no problem, skip |
|
|
|
|
|
|
|
|
// Same floor rule as release: supplier code (P06B → 4F), not item store_id. |
|
|
|
|
|
val preferredFloor = preferredStoreFloorForSupplier( |
|
|
|
|
|
deliveryOrder.supplier?.code, |
|
|
|
|
|
floorSuppliers2F, |
|
|
|
|
|
floorSuppliers4F, |
|
|
|
|
|
) |
|
|
|
|
|
if (preferredFloor != "4F") { |
|
|
return@forEach |
|
|
return@forEach |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|