@@ -159,6 +159,7 @@ open class DeliveryOrderService(
}
}
}
}
/** FP-MTMS Version Checklist | Functions Ref. No. 88 | v1.0.1 | 2026-10-28 */
open fun searchDoLiteByPage(
open fun searchDoLiteByPage(
code: String?,
code: String?,
shopName: String?,
shopName: String?,
@@ -203,10 +204,14 @@ open class DeliveryOrderService(
val preFilteredContent = allResult.content
val preFilteredContent = allResult.content
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val ticketLaneByDoId = loadTicketTruckLanesByDoId(
deliveryOrdersMap.values.filter { isReleasedDeliveryOrder(it) }.mapNotNull { it.id },
)
// ✅ 优化3: 收集所有需要查询的 shopId 和日期组合(只处理预过滤后的记录)
// 未放單才依供應商樓層查 truck;已放單用票據車線
val shopIdAndDatePairs = preFilteredContent.mapNotNull { info ->
val shopIdAndDatePairs = preFilteredContent.mapNotNull { info ->
val deliveryOrder = deliveryOrdersMap[info.id]
val deliveryOrder = deliveryOrdersMap[info.id]
if (isReleasedDeliveryOrder(deliveryOrder)) return@mapNotNull null
val shopId = deliveryOrder?.shop?.id
val shopId = deliveryOrder?.shop?.id
val estimatedArrivalDate = info.estimatedArrivalDate
val estimatedArrivalDate = info.estimatedArrivalDate
if (shopId != null && estimatedArrivalDate != null) {
if (shopId != null && estimatedArrivalDate != null) {
@@ -234,23 +239,16 @@ open class DeliveryOrderService(
// 处理预过滤后的记录:计算 truckLanceCode 并过滤
// 处理预过滤后的记录:计算 truckLanceCode 并过滤
val processedRecords = preFilteredContent.map { info ->
val processedRecords = preFilteredContent.map { info ->
val deliveryOrder = deliveryOrdersMap[info.id]
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 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(
DeliveryOrderInfoLiteDto(
id = info.id,
id = info.id,
@@ -299,22 +297,22 @@ open class DeliveryOrderService(
)
)
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
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 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(
DeliveryOrderInfoLiteDto(
id = info.id,
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(
open fun searchDoLiteUnassignedTruckByPage(
code: String?,
code: String?,
@@ -425,23 +424,20 @@ open class DeliveryOrderService(
val deliveryOrderIds = allResult.content.mapNotNull { it.id }
val deliveryOrderIds = allResult.content.mapNotNull { it.id }
val deliveryOrdersMap = deliveryOrderRepository.findAllById(deliveryOrderIds).associateBy { it.id }
val deliveryOrdersMap = deliveryOrderRepository.findAllById(deliveryOrderIds).associateBy { it.id }
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val ticketLaneByDoId = loadTicketTruckLanesByDoId(
deliveryOrdersMap.values.filter { isReleasedDeliveryOrder(it) }.mapNotNull { it.id },
)
val processedRecords = allResult.content.map { info ->
val processedRecords = allResult.content.map { info ->
val deliveryOrder = deliveryOrdersMap[info.id]
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(
DeliveryOrderInfoLiteDto(
id = info.id,
id = info.id,
@@ -1387,6 +1383,7 @@ open class DeliveryOrderService(
)
)
}
}
/** FP-MTMS Version Checklist | Functions Ref. No. 89 | v1.0.0 | 2026-10-28 */
private fun exportDeliveryNoteFromRecord(
private fun exportDeliveryNoteFromRecord(
request: ExportDeliveryNoteRequest,
request: ExportDeliveryNoteRequest,
deliveryNote: net.sf.jasperreports.engine.JasperReport,
deliveryNote: net.sf.jasperreports.engine.JasperReport,
@@ -1480,15 +1477,19 @@ open class DeliveryOrderService(
val deliveryOrderCodeById = deliveryOrders.associate { it.id!! to (it.code ?: "") }
val deliveryOrderCodeById = deliveryOrders.associate { it.id!! to (it.code ?: "") }
val isExtraByDoId = deliveryOrders.associate { it.id!! to it.isExtra }
val isExtraByDoId = deliveryOrders.associate { it.id!! to it.isExtra }
val replenishPdfIndex = doReplenishmentService.buildReplenishPdfIndex(deliveryOrderIds)
val replenishPdfIndex = doReplenishmentService.buildReplenishPdfIndex(deliveryOrderIds)
val pickOrderLineIdByDeliveryLine = matchDeliveryNotePickOrderLines(
exportLines = exportLines,
pickOrderIdByDoId = pickOrderIdByDoId,
pickOrderLines = pickOrderLines,
replenishmentPickOrderLineIds = replenishPdfIndex.replenishmentPickOrderLineIds(),
)
sortedLines.forEach { row ->
sortedLines.forEach { row ->
fields.add(
fields.add(
buildDeliveryNotePdfLineField(
buildDeliveryNotePdfLineField(
deliveryOrderId = row.deliveryOrderId,
deliveryOrderId = row.deliveryOrderId,
line = row.line,
line = row.line,
sequenceNumber = fields.size + 1,
sequenceNumber = fields.size + 1,
pickOrderIdByDoId = pickOrderIdByDoId,
pickOrderLines = pickOrderLines,
stockOutLinesByPickOrderLineId = stockOutLinesByPickOrderLineId,
stockOutLinesByPickOrderLineId = stockOutLinesByPickOrderLineId,
illById = illById,
illById = illById,
itemsById = itemsById,
itemsById = itemsById,
@@ -1497,6 +1498,7 @@ open class DeliveryOrderService(
headerTicketNo = null,
headerTicketNo = null,
headerIsMerge = false,
headerIsMerge = false,
replenishPdfIndex = replenishPdfIndex,
replenishPdfIndex = replenishPdfIndex,
pickOrderLineIdByDeliveryLine = pickOrderLineIdByDeliveryLine,
),
),
)
)
}
}
@@ -1557,17 +1559,47 @@ open class DeliveryOrderService(
info.deliveryOrderLines.filter { it.deleted != true }.map { line -> DeliveryNoteExportLine(info.id, line) }
info.deliveryOrderLines.filter { it.deleted != true }.map { line -> DeliveryNoteExportLine(info.id, line) }
}
}
fun resolvePickOrderLineIdForDeliveryNoteLine(
deliveryOrderId: Long,
itemId: Long?,
/**
* FP-MTMS Version Checklist | Functions Ref. No. 89 | v1.0.0 | 2026-10-28
* Pair each delivery-order line with its own pick line.
* Same item on one pick order is matched by qty first (4↔4, 1↔1).
* Equal qty is paired in id order. Replenishment pick lines are left out;
* those rows still print from do_replenishment.pickOrderLineId.
*/
fun matchDeliveryNotePickOrderLines(
exportLines: List<DeliveryNoteExportLine>,
pickOrderIdByDoId: Map<Long, Long>,
pickOrderIdByDoId: Map<Long, Long>,
pickOrderLines: List<PickOrderLine>,
pickOrderLines: List<PickOrderLine>,
): Long? {
if (itemId == null) return null
val pickOrderId = pickOrderIdByDoId[deliveryOrderId] ?: return null
return pickOrderLines
.firstOrNull { pol -> pol.pickOrder?.id == pickOrderId && pol.item?.id == itemId }
?.id
replenishmentPickOrderLineIds: Set<Long> = emptySet(),
): Map<Pair<Long, Long>, Long> {
val keys = exportLines.mapNotNull { row ->
val itemId = row.line.itemId ?: return@mapNotNull null
if (row.line.deleted == true) return@mapNotNull null
DeliveryNoteLineMatchKey(
deliveryOrderId = row.deliveryOrderId,
lineId = row.line.id,
itemId = itemId,
qty = row.line.qty,
)
}
val candidates = pickOrderLines.mapNotNull { pol ->
val polId = pol.id ?: return@mapNotNull null
val pickOrderId = pol.pickOrder?.id ?: return@mapNotNull null
val itemId = pol.item?.id ?: return@mapNotNull null
val qty = pol.qty ?: return@mapNotNull null
DeliveryNotePickLineCandidate(
id = polId,
pickOrderId = pickOrderId,
itemId = itemId,
qty = qty,
)
}
return DeliveryNotePickLineMatcher.match(
lines = keys,
pickOrderIdByDoId = pickOrderIdByDoId,
pickLines = candidates,
replenishmentPickOrderLineIds = replenishmentPickOrderLineIds,
)
}
}
fun parseTicketTypeLetter(ticketNo: String?): String? {
fun parseTicketTypeLetter(ticketNo: String?): String? {
@@ -1679,12 +1711,11 @@ open class DeliveryOrderService(
return field
return field
}
}
/** FP-MTMS Version Checklist | Functions Ref. No. 89 | v1.0.0 | 2026-10-28 */
fun buildDeliveryNotePdfLineField(
fun buildDeliveryNotePdfLineField(
deliveryOrderId: Long,
deliveryOrderId: Long,
line: DeliveryOrderLineInfo,
line: DeliveryOrderLineInfo,
sequenceNumber: Int,
sequenceNumber: Int,
pickOrderIdByDoId: Map<Long, Long>,
pickOrderLines: List<PickOrderLine>,
stockOutLinesByPickOrderLineId: Map<Long, List<StockOutLineInfo>>,
stockOutLinesByPickOrderLineId: Map<Long, List<StockOutLineInfo>>,
illById: Map<Long, InventoryLotLine>,
illById: Map<Long, InventoryLotLine>,
itemsById: Map<Long, Items>,
itemsById: Map<Long, Items>,
@@ -1693,6 +1724,7 @@ open class DeliveryOrderService(
headerTicketNo: String? = null,
headerTicketNo: String? = null,
headerIsMerge: Boolean = false,
headerIsMerge: Boolean = false,
replenishPdfIndex: DoReplenishmentService.ReplenishPdfIndex = DoReplenishmentService.ReplenishPdfIndex.EMPTY,
replenishPdfIndex: DoReplenishmentService.ReplenishPdfIndex = DoReplenishmentService.ReplenishPdfIndex.EMPTY,
pickOrderLineIdByDeliveryLine: Map<Pair<Long, Long>, Long> = emptyMap(),
): MutableMap<String, Any> {
): MutableMap<String, Any> {
val field = mutableMapOf<String, Any>()
val field = mutableMapOf<String, Any>()
val isExtra = isExtraDeliveryTicket(
val isExtra = isExtraDeliveryTicket(
@@ -1701,12 +1733,7 @@ open class DeliveryOrderService(
headerIsMerge = headerIsMerge,
headerIsMerge = headerIsMerge,
)
)
val polId = resolvePickOrderLineIdForDeliveryNoteLine(
deliveryOrderId = deliveryOrderId,
itemId = line.itemId,
pickOrderIdByDoId = pickOrderIdByDoId,
pickOrderLines = pickOrderLines,
)
val polId = pickOrderLineIdByDeliveryLine[deliveryOrderId to line.id]
val isReplenish = replenishPdfIndex.matches(deliveryOrderId, line.itemId, polId)
val isReplenish = replenishPdfIndex.matches(deliveryOrderId, line.itemId, polId)
field["sequenceNumber"] = formatSequenceNumber(sequenceNumber, isExtra, isReplenish)
field["sequenceNumber"] = formatSequenceNumber(sequenceNumber, isExtra, isReplenish)
@@ -2502,6 +2529,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }
return out
return out
}
}
/** FP-MTMS Version Checklist | Functions Ref. No. 88 | v1.0.1 | 2026-10-28 */
private fun buildResolvedTruckDtosForLiteRows(
private fun buildResolvedTruckDtosForLiteRows(
rows: List<DeliveryOrderInfoLite>,
rows: List<DeliveryOrderInfoLite>,
): List<DeliveryOrderInfoLiteDto> {
): List<DeliveryOrderInfoLiteDto> {
@@ -2509,9 +2537,13 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }
if (ids.isEmpty()) return emptyList()
if (ids.isEmpty()) return emptyList()
val deliveryOrdersMap = deliveryOrderRepository.findAllById(ids).associateBy { it.id }
val deliveryOrdersMap = deliveryOrderRepository.findAllById(ids).associateBy { it.id }
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val (floorSuppliers2F, floorSuppliers4F) = loadDoFloorSupplierLists()
val ticketLaneByDoId = loadTicketTruckLanesByDoId(
deliveryOrdersMap.values.filter { isReleasedDeliveryOrder(it) }.mapNotNull { it.id },
)
val shopIdAndDatePairs = rows.mapNotNull { info ->
val shopIdAndDatePairs = rows.mapNotNull { info ->
val d = deliveryOrdersMap[info.id]
val d = deliveryOrdersMap[info.id]
if (isReleasedDeliveryOrder(d)) return@mapNotNull null
val shopId = d?.shop?.id
val shopId = d?.shop?.id
val eta = info.estimatedArrivalDate
val eta = info.estimatedArrivalDate
if (shopId != null && eta != null) {
if (shopId != null && eta != null) {
@@ -2533,18 +2565,14 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }
return rows.map { info ->
return rows.map { info ->
val deliveryOrder = deliveryOrdersMap[info.id]
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(
DeliveryOrderInfoLiteDto(
id = info.id,
id = info.id,
code = info.code,
code = info.code,
@@ -2560,6 +2588,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%'` 篩選;取該店該樓層未刪除車輛中出發時間最早者。
* - **2F**(P07/P06D/P06Y):`TruckLanceCode` 多為 `車線-…` 且不含星期縮寫,不依 `LIKE '%Mon%'` 篩選;取該店該樓層未刪除車輛中出發時間最早者。
@@ -2676,3 +2837,52 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }
}
}
}
}
}
}
internal data class DeliveryNoteLineMatchKey(
val deliveryOrderId: Long,
val lineId: Long,
val itemId: Long,
val qty: BigDecimal,
)
internal data class DeliveryNotePickLineCandidate(
val id: Long,
val pickOrderId: Long,
val itemId: Long,
val qty: BigDecimal,
)
internal object DeliveryNotePickLineMatcher {
/** FP-MTMS Version Checklist | Functions Ref. No. 89 | v1.0.0 | 2026-10-28 */
fun match(
lines: List<DeliveryNoteLineMatchKey>,
pickOrderIdByDoId: Map<Long, Long>,
pickLines: List<DeliveryNotePickLineCandidate>,
replenishmentPickOrderLineIds: Set<Long> = emptySet(),
): Map<Pair<Long, Long>, Long> {
val result = mutableMapOf<Pair<Long, Long>, Long>()
val rowsByDoAndItem = lines.groupBy { it.deliveryOrderId to it.itemId }
for ((key, rows) in rowsByDoAndItem) {
val (deliveryOrderId, itemId) = key
val pickOrderId = pickOrderIdByDoId[deliveryOrderId] ?: continue
val available = pickLines
.filter { pol ->
pol.pickOrderId == pickOrderId &&
pol.itemId == itemId &&
pol.id !in replenishmentPickOrderLineIds
}
.sortedBy { it.id }
.toMutableList()
for (row in rows.sortedBy { it.lineId }) {
val qtyIndex = available.indexOfFirst { it.qty.compareTo(row.qty) == 0 }
val chosen = when {
qtyIndex >= 0 -> available.removeAt(qtyIndex)
available.isNotEmpty() -> available.removeAt(0)
else -> null
} ?: continue
result[row.deliveryOrderId to row.lineId] = chosen.id
}
}
return result
}
}