Przeglądaj źródła

efficent improve V1

master
CANCERYS\kw093 13 godzin temu
rodzic
commit
b3f737ce13
4 zmienionych plików z 606 dodań i 67 usunięć
  1. +18
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt
  2. +234
    -50
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt
  3. +25
    -14
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt
  4. +329
    -3
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt

+ 18
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt Wyświetl plik

@@ -79,4 +79,22 @@ fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn(
@Param("requiredDate") requiredDate: LocalDate,
@Param("statuses") statuses: List<DoPickOrderStatus>,
): List<DoPickOrder>

@Query("""
SELECT dpo FROM DoPickOrder dpo
WHERE dpo.storeId = :storeId
AND dpo.requiredDeliveryDate = :requiredDeliveryDate
AND dpo.ticketStatus IN :statuses
AND EXISTS (
SELECT 1 FROM DoPickOrderLine dpol
WHERE dpol.doPickOrderId = dpo.id
AND dpol.deleted = false
AND (dpol.status IS NULL OR dpol.status != 'issue')
)
""")
fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusInWithNonIssueLines(
storeId: String,
requiredDeliveryDate: LocalDate,
statuses: List<DoPickOrderStatus>
): List<DoPickOrder>
}

+ 234
- 50
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt Wyświetl plik

@@ -81,6 +81,9 @@ import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfoLite
import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfoLiteDto
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.projection.StockOutLineInfo
import java.util.Locale
@Service
open class DeliveryOrderService(
private val deliveryOrderRepository: DeliveryOrderRepository,
@@ -1133,107 +1136,133 @@ open class DeliveryOrderService(
fields: MutableList<MutableMap<String, Any>>,
params: MutableMap<String, Any>
): Map<String, Any> {
val doPickOrderRecord = doPickOrderRecordRepository.findById(request.doPickOrderId).orElseThrow {
NoSuchElementException("DoPickOrderRecord not found with ID: ${request.doPickOrderId}")
}
val doPickOrderLineRecords = doPickOrderLineRecordRepository.findByDoPickOrderId(doPickOrderRecord.recordId)
val pickOrderIds = doPickOrderLineRecords.mapNotNull { it.pickOrderId }.distinct()
if (pickOrderIds.isEmpty()) {
throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated pick orders")
}
val deliveryOrderIds = doPickOrderLineRecords.mapNotNull { it.doOrderId }.distinct()
if (deliveryOrderIds.isEmpty()) {
throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated delivery orders")
}
val deliveryNoteInfo = deliveryOrderIds.flatMap { deliveryOrderId ->
deliveryOrderRepository.findDeliveryOrderInfoById(deliveryOrderId)
}.toMutableList()
val pickOrderId = pickOrderIds.first()
val truckNo = doPickOrderRecord.truckLanceCode ?: ""
val selectedPickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
val allLines = deliveryNoteInfo.flatMap { info ->
info.deliveryOrderLines.map { line -> line }
}
val pickOrderLines = pickOrderIds.flatMap { pickOrderId ->
pickOrderLineRepository.findAllByPickOrderId(pickOrderId)
val pickOrderLines = pickOrderIds.flatMap { pid ->
pickOrderLineRepository.findAllByPickOrderId(pid)
}
val pickOrderLineIdsByItemId = pickOrderLines
.groupBy { it.item?.id }
.mapValues { (_, lines) -> lines.mapNotNull { it.id } }
val allPickOrderLineIds = pickOrderLines.mapNotNull { it.id }
val stockOutLinesByPickOrderLineId = if (allPickOrderLineIds.isNotEmpty()) {
allPickOrderLineIds.associateWith { pickOrderLineId ->
stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
val stockOutLinesByPickOrderLineId: Map<Long, List<StockOutLineInfo>> =
if (allPickOrderLineIds.isNotEmpty()) {
allPickOrderLineIds.associateWith { polId ->
stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId)
}
} else {
emptyMap()
}
val doStoreFloorKey = doPickOrderRecord.storeId?.toString()?.trim()?.uppercase(Locale.ROOT)
?.replace("/", "")
?.replace(" ", "")
?: ""
val uniqueItemIdsForSort = allLines.mapNotNull { it.itemId }.distinct()
val itemsById = if (uniqueItemIdsForSort.isNotEmpty()) {
itemsRepository.findAllById(uniqueItemIdsForSort).associateBy { it.id!! }
} else {
emptyMap()
}

val sortedLines = allLines.sortedBy { line ->
line.itemId?.let { itemId ->
getWarehouseOrderByItemId(itemId)
} ?: Int.MAX_VALUE
val sortedLines = when (doStoreFloorKey) {
"2F" -> allLines.sortedWith(
compareBy(
{ line -> itemsById[line.itemId]?.item_Order ?: Int.MAX_VALUE },
{ line -> line.itemNo },
),
)
"4F" -> allLines.sortedBy { line ->
line.itemId?.let { getWarehouseOrderByItemId(it) } ?: Int.MAX_VALUE
}
else -> allLines.sortedBy { line ->
line.itemId?.let { getWarehouseOrderByItemId(it) } ?: Int.MAX_VALUE
}
}

val uniqueItemIds = sortedLines.mapNotNull { it.itemId }.distinct()
val itemsMap = if (uniqueItemIds.isNotEmpty()) {
itemsRepository.findAllById(uniqueItemIds).associateBy { it.id }
val allIllIds = stockOutLinesByPickOrderLineId.values
.flatMap { lines -> lines.mapNotNull { it.inventoryLotLineId } }
.distinct()
val illById: Map<Long, InventoryLotLine> = if (allIllIds.isNotEmpty()) {
inventoryLotLineRepository.findAllById(allIllIds).associateBy { it.id!! }
} else {
emptyMap()
}

val itemsMap = itemsById
sortedLines.forEach { line ->
val field = mutableMapOf<String, Any>()
field["sequenceNumber"] = (fields.size + 1).toString()
field["itemNo"] = line.itemNo
field["itemName"] = line.itemName ?: ""
field["uom"] = line.uom ?: ""
val actualPickQty = if (line.itemId != null) {
val pickOrderLineIdsForItem = pickOrderLineIdsByItemId[line.itemId] ?: emptyList()
val totalQty = pickOrderLineIdsForItem.sumOf { pickOrderLineId ->
val stockOutLines = stockOutLinesByPickOrderLineId[pickOrderLineId] ?: emptyList()
stockOutLines.sumOf { it.qty }
val totalQty = pickOrderLineIdsForItem.sumOf { polId ->
stockOutLinesByPickOrderLineId[polId].orEmpty().sumOf { it.qty }
}
totalQty.toString()
} else {
line.qty.toString()
}
field["qty"] = actualPickQty
field["shortName"] = line.uomShortDesc ?: ""
val route = line.itemId?.let { itemId ->
getWarehouseCodeByItemId(itemId)
routeFromStockOutsForItem(
itemId,
pickOrderLineIdsByItemId,
stockOutLinesByPickOrderLineId,
illById,
).takeIf { it != "-" } ?: getWarehouseCodeByItemId(itemId)
} ?: "-"
field["route"] = route

//USE STOCK OUT LINE
val lotNo = line.itemId?.let { itemId ->
val pickOrderLineIdsForItem = pickOrderLineIdsByItemId[itemId] ?: emptyList()
val lotNumbers = pickOrderLineIdsForItem.flatMap { pickOrderLineId ->
val stockOutLines = stockOutLinesByPickOrderLineId[pickOrderLineId] ?: emptyList()
stockOutLines.mapNotNull { it.lotNo }
val lotNumbers = pickOrderLineIdsForItem.flatMap { polId ->
stockOutLinesByPickOrderLineId[polId].orEmpty().mapNotNull { it.lotNo }
}.distinct().joinToString(", ")

lotNumbers.ifBlank {
"沒有庫存"
}
lotNumbers.ifBlank { "沒有庫存" }
} ?: "沒有庫存"
field["lotNo"] = lotNo
val signOff = line.itemId?.let { itemId ->
val item = itemsMap[itemId]
if (item?.isEgg == true) {
@@ -1243,21 +1272,21 @@ open class DeliveryOrderService(
}
} ?: ""
field["signOff"] = signOff
fields.add(field)
}
params["dnTitle"] = "送貨單"
params["colQty"] = "已提數量"
params["totalCartonTitle"] = "總箱數:"
params["deliveryNoteCodeTitle"] = "送貨單編號:"
params["deliveryNoteCode"] = doPickOrderRecord.deliveryNoteCode ?: ""
params["numOfCarton"] = request.numOfCarton.toString()
if (params["numOfCarton"] == "0") {
params["numOfCarton"] = ""
}
params["shopName"] = doPickOrderRecord.shopName ?: deliveryNoteInfo[0].shopName ?: ""
params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: ""
params["deliveryDate"] =
@@ -1269,13 +1298,30 @@ open class DeliveryOrderService(
params["loadingSequence"] = doPickOrderRecord.loadingSequence?.let {
"裝載順序:$it"
} ?: ""
return mapOf(
"report" to PdfUtils.fillReport(deliveryNote, fields, params),
"filename" to deliveryNoteInfo.joinToString("_") { it.code }
)
}

private fun routeFromStockOutsForItem(
itemId: Long,
pickOrderLineIdsByItemId: Map<Long?, List<Long>>,
stockOutLinesByPickOrderLineId: Map<Long, List<StockOutLineInfo>>,
illById: Map<Long, InventoryLotLine>,
): String {
val polIds = pickOrderLineIdsByItemId[itemId] ?: return "-"
val codes = linkedSetOf<String>()
for (polId in polIds) {
for (sol in stockOutLinesByPickOrderLineId[polId].orEmpty()) {
val illId = sol.inventoryLotLineId ?: continue
val ill = illById[illId] ?: continue
ill.warehouse?.code?.takeIf { it.isNotBlank() }?.let { codes.add(it) }
}
}
return if (codes.isEmpty()) "-" else codes.joinToString(", ")
}
//Print Delivery Note
@Transactional
open fun printDeliveryNote(request: PrintDeliveryNoteRequest) {
@@ -1674,6 +1720,144 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }
)
}

/**
* Workbench NO-HOLD release:
* - Create pick_order + stock_out
* - Do NOT create suggested_pick_lot / stock_out_line
* - Do NOT update inventory_lot_line.holdQty
*
* Downstream should be handled by workbench services (no-hold suggestion + stock_out_line creation).
*/
@Transactional(rollbackFor = [Exception::class])
open fun releaseDeliveryOrderWithoutTicketNoHold(request: ReleaseDoRequest): ReleaseDoResult {
println(" DEBUG: Starting releaseDeliveryOrderWithoutTicketNoHold for DO ID: ${request.id}")

val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id)
?: throw NoSuchElementException("Delivery Order not found")

if (deliveryOrder.status == DeliveryOrderStatus.COMPLETED || deliveryOrder.status == DeliveryOrderStatus.RECEIVING) {
throw IllegalStateException("Delivery Order ${deliveryOrder.id} is already ${deliveryOrder.status?.value}, skipping release")
}

// Mark released (same semantics as normal release)
deliveryOrder.status = DeliveryOrderStatus.RECEIVING
deliveryOrderRepository.save(deliveryOrder)

// Create pick order (same as normal release)
val pols = deliveryOrder.deliveryOrderLines
.filter { it.deleted != true }
.map {
SavePickOrderLineRequest(
itemId = it.item?.id,
qty = it.qty ?: BigDecimal.ZERO,
uomId = it.uom?.id,
)
}
val po = SavePickOrderRequest(
doId = deliveryOrder.id,
type = PickOrderType.DELIVERY_ORDER,
targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(),
pickOrderLine = pols
)

val createdPickOrder = pickOrderService.create(po)
val consoCode = pickOrderService.assignConsoCode()
val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null)

if (pickOrderEntity != null) {
pickOrderEntity.consoCode = consoCode
pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
pickOrderRepository.saveAndFlush(pickOrderEntity)

// Create stock out header only; stock_out_line is created by workbench service
val stockOut = StockOut().apply {
this.type = "do"
this.consoPickOrderCode = consoCode
this.status = StockOutStatus.PENDING.status
this.handler = request.userId
}
stockOutRepository.saveAndFlush(stockOut)
}

// Truck selection (reuse normal logic)
val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()
val supplierCode = deliveryOrder.supplier?.code
val preferredFloor = when (supplierCode) {
"P06B" -> "4F"
"P07", "P06D" -> "2F"
else -> "2F"
}

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()) {
null
} 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) }
}
}
}

val defaultTruckId = 5577L
val effectiveTruck = truck ?: truckRepository.findById(defaultTruckId).orElse(null)
val usedDefaultTruck = (truck == null)
if (effectiveTruck == null) {
val errorMsg = "No matching truck for preferredFloor ($preferredFloor) and default truck $defaultTruckId not found. Skipping DO ${deliveryOrder.id}."
throw IllegalStateException(errorMsg)
}

return ReleaseDoResult(
deliveryOrderId = deliveryOrder.id!!,
deliveryOrderCode = deliveryOrder.code,
pickOrderId = createdPickOrder.id!!,
pickOrderCode = pickOrderEntity?.code,
shopId = deliveryOrder.shop?.id,
shopCode = deliveryOrder.shop?.code,
shopName = deliveryOrder.shop?.name,
estimatedArrivalDate = targetDate,
preferredFloor = preferredFloor,
truckId = effectiveTruck.id,
truckDepartureTime = effectiveTruck.departureTime,
truckLanceCode = effectiveTruck.truckLanceCode,
loadingSequence = effectiveTruck.loadingSequence,
usedDefaultTruck = usedDefaultTruck
)
}

private fun getDayOfWeekAbbr(date: LocalDate): String =
when (date.dayOfWeek) {
java.time.DayOfWeek.MONDAY -> "Mon"


+ 25
- 14
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt Wyświetl plik

@@ -379,7 +379,7 @@ open class DoPickOrderService(
}
fun getSummaryByStore(storeId: String, requiredDate: LocalDate?, releaseType: String): StoreLaneSummary {
val targetDate = requiredDate ?: LocalDate.now()
println(" DEBUG: Getting summary for store=$storeId, date=$targetDate")
//println(" DEBUG: Getting summary for store=$storeId, date=$targetDate")
val actualStoreId = when (storeId) {
"2/F" -> "2/F"
@@ -387,11 +387,18 @@ open class DoPickOrderService(
else -> storeId
}

/*
val allRecords = doPickOrderRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn(
actualStoreId,
targetDate,
listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed)
)
*/
val allRecords = doPickOrderRepository
.findByStoreIdAndRequiredDeliveryDateAndTicketStatusInWithNonIssueLines(
actualStoreId, targetDate,
listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed)
)
val filteredByReleaseType = when (releaseType.lowercase()) {
"batch" -> allRecords.filter { it.releaseType == "batch" }
"single" -> allRecords.filter { it.releaseType == "single" }
@@ -408,9 +415,9 @@ open class DoPickOrderService(
"single" -> finishedRecords.filter { it.releaseType == "single" }
else -> finishedRecords // "all" 或其他值,不过滤
}
println(" DEBUG: Found ${allRecords.size} records for date $targetDate")
println(" DEBUG: Found ${finishedRecords.size} finished records for date $targetDate")
//println(" DEBUG: Found ${allRecords.size} records for date $targetDate")
// println(" DEBUG: Found ${finishedRecords.size} finished records for date $targetDate")
/*
val filteredRecords = filteredByReleaseType.filter { doPickOrder ->
val hasNonIssueLines = checkDoPickOrderHasNonIssueLines(doPickOrder.id!!)
if (!hasNonIssueLines) {
@@ -418,20 +425,24 @@ open class DoPickOrderService(
}
hasNonIssueLines
}
*/
// println(" DEBUG: After filtering, ${filteredRecords.size} records remain")
println(" DEBUG: After filtering, ${filteredRecords.size} records remain")
val grouped = filteredRecords.groupBy { it.truckDepartureTime to it.truckLanceCode }
val grouped = filteredByReleaseType.groupBy { it.truckDepartureTime to it.truckLanceCode }
.mapValues { (key, list) ->
val (truckDepartureTime, truckLanceCode) = key
// 计算匹配的 finishedRecords 数量
val matchingFinishedCount = finishedRecords.count { record ->
(record.truckDepartureTime == truckDepartureTime) &&
(record.truckLanceCode == truckLanceCode)
}
println(" DEBUG: Group key - truckDepartureTime: $truckDepartureTime, truckLanceCode: $truckLanceCode")
println(" DEBUG: Found ${list.size} active records in this group")
println(" DEBUG: Found $matchingFinishedCount finished records matching this group")
// val matchingFinishedCount = finishedRecords.count { record ->
//(record.truckDepartureTime == truckDepartureTime) &&
// (record.truckLanceCode == truckLanceCode)
// }
val matchingFinishedCount = filteredFinishedRecords.count { record ->
record.truckDepartureTime == truckDepartureTime &&
record.truckLanceCode == truckLanceCode
}
// println(" DEBUG: Group key - truckDepartureTime: $truckDepartureTime, truckLanceCode: $truckLanceCode")
//println(" DEBUG: Found ${list.size} active records in this group")
//println(" DEBUG: Found $matchingFinishedCount finished records matching this group")
LaneBtn(
truckLanceCode = list.first().truckLanceCode ?: "",
unassigned = list.count { it.handledBy == null },


+ 329
- 3
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt Wyświetl plik

@@ -3512,10 +3512,10 @@ ORDER BY
val enrichedResults = filteredResults
return enrichedResults
}
/*
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===")
println("userId filter: $userId")
//println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===")
// println("userId filter: $userId")
val user = userService.find(userId).orElse(null)
if (user == null) {
@@ -3889,6 +3889,332 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto
"pickOrders" to listOfNotNull(mergedPickOrder)
)
}
*/
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
val user = userService.find(userId).orElse(null)
if (user == null) {
println("❌ User not found: $userId")
return emptyMap()
}
// Step 1: 找到該 user 目前處理中的 do_pick_order(你原本就是 LIMIT 1)
val doPickOrderSql = """
SELECT DISTINCT
dpo.id as do_pick_order_id,
dpo.ticket_no,
dpo.store_id,
dpo.TruckLanceCode,
dpo.truck_departure_time,
dpo.ShopCode,
dpo.ShopName,
dpo.ticket_status as doTicketStatus
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
INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id
WHERE po.assignTo = :userId
AND po.type = 'do'
AND EXISTS (
SELECT 1
FROM fpsmsdb.do_pick_order_line dpol2
INNER JOIN fpsmsdb.pick_order po2 ON po2.id = dpol2.pick_order_id
WHERE dpol2.do_pick_order_id = dpo.id
AND dpol2.deleted = 0
AND po2.status IN ('assigned', 'released', 'picking')
AND po2.deleted = false
)
AND dpo.handled_by = :userId
AND dpo.ticket_status IN ('released','picking')
AND po.deleted = false
AND dpo.deleted = false
LIMIT 1
""".trimIndent()
val doPickOrderInfo = jdbcDao.queryForMap(doPickOrderSql, mapOf("userId" to userId)).orElse(null)
?: return mapOf("fgInfo" to null, "pickOrders" to emptyList<Any>())
val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong()
?: return mapOf("fgInfo" to null, "pickOrders" to emptyList<Any>())
val doTicketStatus = doPickOrderInfo["doTicketStatus"]
// Step 2: 找到該 do_pick_order 底下所有 pick orders
val pickOrdersSql = """
SELECT DISTINCT
dpol.pick_order_id,
dpol.pick_order_code,
dpol.do_order_id,
dpol.delivery_order_code,
po.consoCode,
po.status,
DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate
FROM fpsmsdb.do_pick_order_line dpol
INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id
WHERE dpol.do_pick_order_id = :doPickOrderId
AND dpol.deleted = 0
AND po.deleted = false
ORDER BY dpol.pick_order_id
""".trimIndent()
val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("doPickOrderId" to doPickOrderId))
if (pickOrdersInfo.isEmpty()) {
val fgInfo = mapOf(
"doPickOrderId" to doPickOrderId,
"ticketNo" to doPickOrderInfo["ticket_no"],
"storeId" to doPickOrderInfo["store_id"],
"shopCode" to doPickOrderInfo["ShopCode"],
"shopName" to doPickOrderInfo["ShopName"],
"truckLanceCode" to doPickOrderInfo["TruckLanceCode"],
"departureTime" to doPickOrderInfo["truck_departure_time"],
)
return mapOf("fgInfo" to fgInfo, "pickOrders" to emptyList<Any>())
}
val pickOrderIds = pickOrdersInfo.mapNotNull { (it["pick_order_id"] as? Number)?.toLong() }.distinct()
val pickOrderCodes = pickOrdersInfo.mapNotNull { it["pick_order_code"] as? String }
val doOrderIds = pickOrdersInfo.mapNotNull { (it["do_order_id"] as? Number)?.toLong() }
val deliveryOrderCodes = pickOrdersInfo.mapNotNull { it["delivery_order_code"] as? String }
val allConsoCodes = pickOrdersInfo.mapNotNull { it["consoCode"] as? String }.distinct()
// Step 3: 一次查完所有 pickOrder 的 line/lot/stockout 明細(避免 N+1)
val linesSqlAll = """
SELECT
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,
pol.id as pickOrderLineId,
pol.qty as pickOrderLineRequiredQty,
pol.status as pickOrderLineStatus,
i.id as itemId,
i.code as itemCode,
i.name as itemName,
i.item_Order as itemOrder,
uc.code as uomCode,
uc.udfudesc as uomDesc,
uc.udfShortDesc as uomShortDesc,
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,
il.stockInLineId as stockInLineId,
w.`order` as routerIndex,
w.code as routerRoute,
CASE
WHEN sol.status = 'rejected' THEN NULL
ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
END as availableQty,
COALESCE(spl.qty, 0) as requiredQty,
COALESCE(sol.qty, 0) as actualPickQty,
spl.id as suggestedPickLotId,
ill.status as lotStatus,
sol.id as stockOutLineId,
sol.status as stockOutLineStatus,
COALESCE(sol.qty, 0) as stockOutLineQty,
COALESCE(ill.inQty, 0) as inQty,
COALESCE(ill.outQty, 0) as outQty,
COALESCE(ill.holdQty, 0) as holdQty,
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,
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 AND pol.deleted = false
JOIN fpsmsdb.items i ON i.id = pol.itemId
LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
LEFT JOIN (
SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId
FROM fpsmsdb.suggested_pick_lot spl
UNION
SELECT sol.pickOrderLineId, sol.inventoryLotLineId
FROM fpsmsdb.stock_out_line sol
WHERE sol.deleted = false
) ll ON ll.pickOrderLineId = pol.id
LEFT JOIN fpsmsdb.suggested_pick_lot spl
ON spl.pickOrderLineId = pol.id AND spl.suggestedLotLineId = ll.lotLineId
LEFT JOIN fpsmsdb.stock_out_line sol
ON sol.pickOrderLineId = pol.id
AND ( (sol.inventoryLotLineId = ll.lotLineId)
OR (sol.inventoryLotLineId IS NULL AND ll.lotLineId IS NULL) )
AND sol.deleted = false
LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false
LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
WHERE po.id IN (:pickOrderIds)
AND po.deleted = false
ORDER BY
po.id ASC,
COALESCE(w.`order`, 999999) ASC,
pol.id ASC,
il.lotNo ASC
""".trimIndent()
val allLineRows = jdbcDao.queryForList(linesSqlAll, mapOf("pickOrderIds" to pickOrderIds))
// Kotlin 端組裝:pickOrderId -> lineId -> rows
val byPickOrderId = allLineRows.groupBy { (it["pickOrderId"] as? Number)?.toLong() }
// 用來計算每個 pick order 的 lineCounts(保持你原本回傳欄位)
val lineCountsPerPickOrder = mutableListOf<Int>()
val allPickOrderLines = mutableListOf<Map<String, Any?>>()
// 依照 Step2 的 pickOrdersInfo 順序處理(避免順序變動)
pickOrdersInfo.forEach { poInfo ->
val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() ?: return@forEach
val rows = byPickOrderId[pickOrderId].orEmpty()
val lineGroups = rows.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() }
val pickOrderLines = lineGroups.mapNotNull { (lineId, lineRows) ->
val first = lineRows.firstOrNull() ?: return@mapNotNull null
val lots = if (lineRows.any { it["lotId"] != null }) {
lineRows
.filter { it["lotId"] != null }
.map { lotRow ->
mapOf(
"id" to lotRow["lotId"],
"lotNo" to lotRow["lotNo"],
"expiryDate" to lotRow["expiryDate"],
"location" to lotRow["location"],
"stockUnit" to lotRow["stockUnit"],
"availableQty" to lotRow["availableQty"],
"requiredQty" to lotRow["requiredQty"],
"actualPickQty" to lotRow["actualPickQty"],
"inQty" to lotRow["inQty"],
"outQty" to lotRow["outQty"],
"holdQty" to lotRow["holdQty"],
"lotStatus" to lotRow["lotStatus"],
"lotAvailability" to lotRow["lotAvailability"],
"processingStatus" to lotRow["processingStatus"],
"suggestedPickLotId" to lotRow["suggestedPickLotId"],
"stockOutLineId" to lotRow["stockOutLineId"],
"stockOutLineStatus" to lotRow["stockOutLineStatus"],
"stockOutLineQty" to lotRow["stockOutLineQty"],
"stockInLineId" to lotRow["stockInLineId"],
"router" to mapOf(
"id" to null,
"index" to lotRow["routerIndex"],
"route" to lotRow["routerRoute"],
"area" to lotRow["routerRoute"],
"itemCode" to lotRow["itemId"],
"itemName" to lotRow["itemName"],
"uomId" to lotRow["uomCode"],
"noofCarton" to lotRow["requiredQty"]
)
)
}
} else emptyList()
val stockouts = lineRows
.filter { it["stockOutLineId"] != null }
.distinctBy { it["stockOutLineId"] }
.map { row ->
val lotId = row["lotId"]
val noLot = (lotId == null)
mapOf(
"id" to row["stockOutLineId"],
"status" to row["stockOutLineStatus"],
"qty" to row["stockOutLineQty"],
"lotId" to lotId,
"lotNo" to (row["lotNo"] ?: ""),
"location" to (row["location"] ?: ""),
"availableQty" to row["availableQty"],
"stockInLineId" to row["stockInLineId"],
"noLot" to noLot
)
}
mapOf(
"id" to lineId,
"requiredQty" to first["pickOrderLineRequiredQty"],
"status" to first["pickOrderLineStatus"],
"itemOrder" to first["itemOrder"],
"item" to mapOf(
"id" to first["itemId"],
"code" to first["itemCode"],
"name" to first["itemName"],
"uomCode" to first["uomCode"],
"uomDesc" to first["uomDesc"],
"uomShortDesc" to first["uomShortDesc"],
),
"lots" to lots,
"stockouts" to stockouts
)
}
lineCountsPerPickOrder.add(pickOrderLines.size)
allPickOrderLines.addAll(pickOrderLines)
}
// 保留你原本按 store 樓層的排序規則
val doStoreFloorKey = doPickOrderInfo["store_id"]?.toString()?.trim()?.uppercase(Locale.ROOT)
?.replace("/", "")
?.replace(" ", "")
?: ""
when (doStoreFloorKey) {
"2F" -> {
allPickOrderLines.sortWith(
compareBy(
{ line ->
val v = line["itemOrder"] ?: line["itemorder"]
when (v) {
is Number -> v.toInt()
else -> 999999
}
},
{ line -> (line["id"] as? Number)?.toLong() ?: Long.MAX_VALUE }
)
)
}
"4F" -> {
// 4F:保留 SQL 順序,不做二次排序
}
else -> {
allPickOrderLines.sortWith(
compareBy { line ->
val lots = line["lots"] as? List<Map<String, Any?>>
val firstLot = lots?.firstOrNull()
val router = firstLot?.get("router") as? Map<String, Any?>
val indexValue = router?.get("index")
when (indexValue) {
is Number -> indexValue.toInt()
is String -> indexValue.toIntOrNull() ?: 999999
else -> 999999
}
}
)
}
}
val fgInfo = mapOf(
"doPickOrderId" to doPickOrderId,
"ticketNo" to doPickOrderInfo["ticket_no"],
"storeId" to doPickOrderInfo["store_id"],
"shopCode" to doPickOrderInfo["ShopCode"],
"shopName" to doPickOrderInfo["ShopName"],
"truckLanceCode" to doPickOrderInfo["TruckLanceCode"],
"departureTime" to doPickOrderInfo["truck_departure_time"]
)
val mergedPickOrder = run {
val firstPickOrderInfo = pickOrdersInfo.first()
mapOf(
"pickOrderIds" to pickOrderIds,
"pickOrderCodes" to pickOrderCodes,
"doOrderIds" to doOrderIds,
"deliveryOrderCodes" to deliveryOrderCodes,
"lineCountsPerPickOrder" to lineCountsPerPickOrder,
"consoCodes" to allConsoCodes,
"status" to doTicketStatus,
"targetDate" to firstPickOrderInfo["targetDate"],
"pickOrderLines" to allPickOrderLines
)
}
return mapOf(
"fgInfo" to fgInfo,
"pickOrders" to listOf(mergedPickOrder)
)
}

// Fix the type issues in the getPickOrdersByDateAndStore method
open fun getPickOrdersByDateAndStore(storeId: String): Map<String, Any?> {
println("=== Debug: getPickOrdersByDateAndStore ===")


Ładowanie…
Anuluj
Zapisz