Pārlūkot izejas kodu

已放單的送貨單搜尋改讀票據車線,避免放單後再依供應商重查車輛。

Co-authored-by: Cursor <[email protected]>
stockTakeUpdate
CANCERYS\kw093 pirms 1 nedēļas
vecāks
revīzija
b200778250
1 mainītis faili ar 189 papildinājumiem un 59 dzēšanām
  1. +189
    -59
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt

+ 189
- 59
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt Parādīt failu

@@ -159,6 +159,7 @@ open class DeliveryOrderService(
}
}

/** FP-MTMS Version Checklist | Functions Ref. No. 88 | v1.0.1 | 2026-10-28 */
open fun searchDoLiteByPage(
code: String?,
shopName: String?,
@@ -203,10 +204,14 @@ open class DeliveryOrderService(

val preFilteredContent = allResult.content
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val ticketLaneByDoId = loadTicketTruckLanesByDoId(
deliveryOrdersMap.values.filter { isReleasedDeliveryOrder(it) }.mapNotNull { it.id },
)

// ✅ 优化3: 收集所有需要查询的 shopId 和日期组合(只处理预过滤后的记录)
// 未放單才依供應商樓層查 truck;已放單用票據車線
val shopIdAndDatePairs = preFilteredContent.mapNotNull { info ->
val deliveryOrder = deliveryOrdersMap[info.id]
if (isReleasedDeliveryOrder(deliveryOrder)) return@mapNotNull null
val shopId = deliveryOrder?.shop?.id
val estimatedArrivalDate = info.estimatedArrivalDate
if (shopId != null && estimatedArrivalDate != null) {
@@ -234,23 +239,16 @@ open class DeliveryOrderService(
// 处理预过滤后的记录:计算 truckLanceCode 并过滤
val processedRecords = preFilteredContent.map { info ->
val deliveryOrder = deliveryOrdersMap[info.id]
val supplierCode = deliveryOrder?.supplier?.code
val preferredFloor = preferredStoreFloorForSupplier(supplierCode, floorSuppliers2F, floorSuppliers4F)
val shop = deliveryOrder?.shop
val shopId = shop?.id
val estimatedArrivalDate = info.estimatedArrivalDate

val calculatedTruckLanceCode =
if (deliveryOrder != null && shopId != null && estimatedArrivalDate != null) {
val targetDate = estimatedArrivalDate.toLocalDate()
val dayAbbr = getDayOfWeekAbbr(targetDate)

// 从缓存中获取 Truck
val matchedTruck = truckCache[Triple(shopId, preferredFloor, dayAbbr)]
matchedTruck?.truckLanceCode
} else {
null
}
val calculatedTruckLanceCode = displayedTruckLanceCode(
deliveryOrder = deliveryOrder,
estimatedArrivalDate = estimatedArrivalDate,
floorSuppliers2F = floorSuppliers2F,
floorSuppliers4F = floorSuppliers4F,
truckCache = truckCache,
ticketLaneByDoId = ticketLaneByDoId,
)

DeliveryOrderInfoLiteDto(
id = info.id,
@@ -299,22 +297,22 @@ open class DeliveryOrderService(
)

val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val loadedOrders = result.content.associate { info ->
info.id to deliveryOrderRepository.findByIdAndDeletedIsFalse(info.id)
}
val ticketLaneByDoId = loadTicketTruckLanesByDoId(
loadedOrders.values.filter { isReleasedDeliveryOrder(it) }.mapNotNull { it?.id },
)
val records = result.content.map { info ->
val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(info.id)
val supplierCode = deliveryOrder?.supplier?.code
val preferredFloor = preferredStoreFloorForSupplier(supplierCode, floorSuppliers2F, floorSuppliers4F)
val shop = deliveryOrder?.shop
val shopId = shop?.id
val estimatedArrivalDate = info.estimatedArrivalDate

val calculatedTruckLanceCode =
if (deliveryOrder != null && shopId != null && estimatedArrivalDate != null) {
val targetDate = estimatedArrivalDate.toLocalDate()
val dayAbbr = getDayOfWeekAbbr(targetDate)
resolveTruckForShopFloorAndDay(shopId, preferredFloor, dayAbbr)?.truckLanceCode
} else {
null
}
val deliveryOrder = loadedOrders[info.id]
val calculatedTruckLanceCode = displayedTruckLanceCode(
deliveryOrder = deliveryOrder,
estimatedArrivalDate = info.estimatedArrivalDate,
floorSuppliers2F = floorSuppliers2F,
floorSuppliers4F = floorSuppliers4F,
truckCache = null,
ticketLaneByDoId = ticketLaneByDoId,
)

DeliveryOrderInfoLiteDto(
id = info.id,
@@ -391,8 +389,9 @@ open class DeliveryOrderService(
}

/**
* 僅回傳依店鋪/ETA 從 truck 排程**推算後** `truckLanceCode` 為 null 或空白的送貨單(畫面上對應「車線-X」)。
* 與 [searchDoLiteByPage] 帶一般車線關鍵字分開,避免 `車線-X` 在 truck 表無 shopId 時走舊邏輯漏單。
* FP-MTMS Version Checklist | Functions Ref. No. 88 | v1.0.1 | 2026-10-28
* 僅回傳顯示車線為 null 或空白的送貨單(畫面上對應「車線-X」)。
* 已放單(receiving/completed)用票據車線;未放單才依店鋪/ETA 從 truck 排程推算。
*/
open fun searchDoLiteUnassignedTruckByPage(
code: String?,
@@ -425,23 +424,20 @@ open class DeliveryOrderService(
val deliveryOrderIds = allResult.content.mapNotNull { it.id }
val deliveryOrdersMap = deliveryOrderRepository.findAllById(deliveryOrderIds).associateBy { it.id }
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val ticketLaneByDoId = loadTicketTruckLanesByDoId(
deliveryOrdersMap.values.filter { isReleasedDeliveryOrder(it) }.mapNotNull { it.id },
)

val processedRecords = allResult.content.map { info ->
val deliveryOrder = deliveryOrdersMap[info.id]
val supplierCode = deliveryOrder?.supplier?.code
val preferredFloor = preferredStoreFloorForSupplier(supplierCode, floorSuppliers2F, floorSuppliers4F)
val shop = deliveryOrder?.shop
val shopId = shop?.id
val infoEta = info.estimatedArrivalDate

val calculatedTruckLanceCode =
if (deliveryOrder != null && shopId != null && infoEta != null) {
val targetDate = infoEta.toLocalDate()
val dayAbbr = getDayOfWeekAbbr(targetDate)
resolveTruckForShopFloorAndDay(shopId, preferredFloor, dayAbbr)?.truckLanceCode
} else {
null
}
val calculatedTruckLanceCode = displayedTruckLanceCode(
deliveryOrder = deliveryOrder,
estimatedArrivalDate = info.estimatedArrivalDate,
floorSuppliers2F = floorSuppliers2F,
floorSuppliers4F = floorSuppliers4F,
truckCache = null,
ticketLaneByDoId = ticketLaneByDoId,
)

DeliveryOrderInfoLiteDto(
id = info.id,
@@ -2502,6 +2498,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }
return out
}

/** FP-MTMS Version Checklist | Functions Ref. No. 88 | v1.0.1 | 2026-10-28 */
private fun buildResolvedTruckDtosForLiteRows(
rows: List<DeliveryOrderInfoLite>,
): List<DeliveryOrderInfoLiteDto> {
@@ -2509,9 +2506,13 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }
if (ids.isEmpty()) return emptyList()
val deliveryOrdersMap = deliveryOrderRepository.findAllById(ids).associateBy { it.id }
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val ticketLaneByDoId = loadTicketTruckLanesByDoId(
deliveryOrdersMap.values.filter { isReleasedDeliveryOrder(it) }.mapNotNull { it.id },
)

val shopIdAndDatePairs = rows.mapNotNull { info ->
val d = deliveryOrdersMap[info.id]
if (isReleasedDeliveryOrder(d)) return@mapNotNull null
val shopId = d?.shop?.id
val eta = info.estimatedArrivalDate
if (shopId != null && eta != null) {
@@ -2533,18 +2534,14 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }

return rows.map { info ->
val deliveryOrder = deliveryOrdersMap[info.id]
val supplierCode = deliveryOrder?.supplier?.code
val preferredFloor = preferredStoreFloorForSupplier(supplierCode, floorSuppliers2F, floorSuppliers4F)
val shopId = deliveryOrder?.shop?.id
val infoEta = info.estimatedArrivalDate
val calculatedTruckLanceCode =
if (deliveryOrder != null && shopId != null && infoEta != null) {
val targetDate = infoEta.toLocalDate()
val dayAbbr = getDayOfWeekAbbr(targetDate)
truckCache[Triple(shopId, preferredFloor, dayAbbr)]?.truckLanceCode
} else {
null
}
val calculatedTruckLanceCode = displayedTruckLanceCode(
deliveryOrder = deliveryOrder,
estimatedArrivalDate = info.estimatedArrivalDate,
floorSuppliers2F = floorSuppliers2F,
floorSuppliers4F = floorSuppliers4F,
truckCache = truckCache,
ticketLaneByDoId = ticketLaneByDoId,
)
DeliveryOrderInfoLiteDto(
id = info.id,
code = info.code,
@@ -2560,6 +2557,139 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }
}
}

private class TicketLaneHit(
val lane: String?,
val ticketId: Long,
val srcRank: Int,
)

/**
* FP-MTMS Version Checklist | Functions Ref. No. 88 | v1.0.1 | 2026-10-28
* 放單後狀態:receiving 表示已發布,completed 表示已完成。
*/
private fun isReleasedDeliveryOrder(deliveryOrder: DeliveryOrder?): Boolean =
deliveryOrder?.status == DeliveryOrderStatus.RECEIVING ||
deliveryOrder?.status == DeliveryOrderStatus.COMPLETED

/**
* FP-MTMS Version Checklist | Functions Ref. No. 88 | v1.0.1 | 2026-10-28
* 已放單讀票據車線(工作台 `delivery_order_pick_order`,否則 `do_pick_order`/record)。
* 未放單才依供應商樓層 + 店鋪 + 星期從 truck 推算。
* [truckCache] 為 null 時,未放單即時查 truck。
*/
private fun displayedTruckLanceCode(
deliveryOrder: DeliveryOrder?,
estimatedArrivalDate: LocalDateTime?,
floorSuppliers2F: List<String>,
floorSuppliers4F: List<String>,
truckCache: Map<Triple<Long, String, String>, Truck?>?,
ticketLaneByDoId: Map<Long, String?>,
): String? {
if (isReleasedDeliveryOrder(deliveryOrder)) {
return deliveryOrder?.id?.let { ticketLaneByDoId[it] }
}
val shopId = deliveryOrder?.shop?.id ?: return null
val eta = estimatedArrivalDate ?: return null
val preferredFloor = preferredStoreFloorForSupplier(
deliveryOrder.supplier?.code,
floorSuppliers2F,
floorSuppliers4F,
)
val dayAbbr = getDayOfWeekAbbr(eta.toLocalDate())
val key = Triple(shopId, preferredFloor, dayAbbr)
if (truckCache != null) {
return truckCache[key]?.truckLanceCode
}
return resolveTruckForShopFloorAndDay(shopId, preferredFloor, dayAbbr)?.truckLanceCode
}

/**
* FP-MTMS Version Checklist | Functions Ref. No. 88 | v1.0.1 | 2026-10-28
* 一批送貨單對應的票據車線。同一張單有多張票時,工作台票優先,同來源取 id 最大的一張。
* 票上的車線為空白時仍回傳 null,呼叫端不再改查 truck。
*/
private fun loadTicketTruckLanesByDoId(doIds: Collection<Long>): Map<Long, String?> {
val ids = doIds.distinct()
if (ids.isEmpty()) return emptyMap()
val best = HashMap<Long, TicketLaneHit>()
val sql = """
SELECT do_id, truck_lance_code, ticket_id, src_rank
FROM (
SELECT po.doId AS do_id,
NULLIF(TRIM(dop.truckLanceCode), '') AS truck_lance_code,
dop.id AS ticket_id,
1 AS src_rank
FROM fpsmsdb.delivery_order_pick_order dop
INNER JOIN fpsmsdb.pick_order po
ON po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0
WHERE dop.deleted = 0
AND po.doId IN (:doIds)

UNION ALL

SELECT dpo.do_order_id AS do_id,
NULLIF(TRIM(dpo.TruckLanceCode), '') AS truck_lance_code,
dpo.id AS ticket_id,
2 AS src_rank
FROM fpsmsdb.do_pick_order dpo
WHERE dpo.deleted = 0
AND dpo.do_order_id IN (:doIds)

UNION ALL

SELECT dpol.do_order_id AS do_id,
NULLIF(TRIM(dpo.TruckLanceCode), '') AS truck_lance_code,
dpo.id AS ticket_id,
2 AS src_rank
FROM fpsmsdb.do_pick_order dpo
INNER JOIN fpsmsdb.do_pick_order_line dpol
ON dpol.do_pick_order_id = dpo.id AND dpol.deleted = 0
WHERE dpo.deleted = 0
AND dpol.do_order_id IN (:doIds)

UNION ALL

SELECT rec.do_order_id AS do_id,
NULLIF(TRIM(rec.TruckLanceCode), '') AS truck_lance_code,
rec.id AS ticket_id,
3 AS src_rank
FROM fpsmsdb.do_pick_order_record rec
WHERE rec.deleted = 0
AND rec.do_order_id IN (:doIds)
) lanes
""".trimIndent()

ids.chunked(400).forEach { chunk ->
val rows = jdbcDao.queryForList(sql, mapOf("doIds" to chunk))
for (row in rows) {
val doId = jdbcNumber(row, "do_id")?.toLong() ?: continue
val ticketId = jdbcNumber(row, "ticket_id")?.toLong() ?: 0L
val srcRank = jdbcNumber(row, "src_rank")?.toInt() ?: 9
val lane = jdbcText(row, "truck_lance_code")
val hit = TicketLaneHit(lane, ticketId, srcRank)
val current = best[doId]
if (
current == null ||
hit.srcRank < current.srcRank ||
(hit.srcRank == current.srcRank && hit.ticketId > current.ticketId)
) {
best[doId] = hit
}
}
}
return best.mapValues { it.value.lane }
}

private fun jdbcNumber(row: Map<*, *>, name: String): Number? {
val value = row.entries.firstOrNull { it.key?.toString().equals(name, true) }?.value ?: return null
return value as? Number
}

private fun jdbcText(row: Map<*, *>, name: String): String? {
val value = row.entries.firstOrNull { it.key?.toString().equals(name, true) }?.value ?: return null
return value.toString().trim().takeIf { it.isNotEmpty() }
}

/**
* 依店鋪 + 揀貨樓層解析當日應顯示之車線。
* - **2F**(P07/P06D/P06Y):`TruckLanceCode` 多為 `車線-…` 且不含星期縮寫,不依 `LIKE '%Mon%'` 篩選;取該店該樓層未刪除車輛中出發時間最早者。


Notiek ielāde…
Atcelt
Saglabāt