Browse Source

update

master
kelvin.yau 1 month ago
parent
commit
571edfe68e
6 changed files with 540 additions and 554 deletions
  1. +3
    -0
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  2. +180
    -206
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt
  3. +102
    -72
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt
  4. +18
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ReleaseDoRequest.kt
  5. +1
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt
  6. +236
    -274
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt

+ 3
- 0
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx View File

@@ -0,0 +1,3 @@




+ 180
- 206
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt View File

@@ -399,242 +399,216 @@ open class DeliveryOrderService(
return savedDeliveryOrder
}

@Transactional(rollbackFor = [Exception::class])
open fun releaseDeliveryOrder(request: ReleaseDoRequest): MessageResponse {
println("�� DEBUG: Starting releaseDeliveryOrder for DO ID: ${request.id}, User ID: ${request.userId}")
val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id)
?: throw NoSuchElementException("Delivery Order not found")
@Transactional(rollbackFor = [Exception::class])
open fun releaseDeliveryOrder(request: ReleaseDoRequest): MessageResponse {
println("🔍 DEBUG: Starting releaseDeliveryOrder for DO ID: ${request.id}, User ID: ${request.userId}")
val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id)
?: throw NoSuchElementException("Delivery Order not found")
println("🔍 DEBUG: Found delivery order - ID: ${deliveryOrder.id}, Shop: ${deliveryOrder.shop?.code}, Status: ${deliveryOrder.status}")
deliveryOrder.apply {
status = DeliveryOrderStatus.PENDING
}
deliveryOrderRepository.save(deliveryOrder)

val pols = deliveryOrder.deliveryOrderLines.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)
println("🔍 DEBUG: Created pick order - ID: ${createdPickOrder.id}")
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)
println("🔍 DEBUG: Assigned consoCode $consoCode to pick order ${createdPickOrder.id}")
println("�� DEBUG: Found delivery order - ID: ${deliveryOrder.id}, Shop: ${deliveryOrder.shop?.code}, Status: ${deliveryOrder.status}")
val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!)
println("🔍 DEBUG: Loaded ${lines.size} pick order lines from DB")
deliveryOrder.apply {
status = DeliveryOrderStatus.PENDING
}
deliveryOrderRepository.save(deliveryOrder)

val pols = deliveryOrder.deliveryOrderLines.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 suggestions = suggestedPickLotService.suggestionForPickOrderLines(
SuggestedPickLotForPolRequest(pickOrderLines = lines)
)

val createdPickOrder = pickOrderService.create(po)
println("🔍 DEBUG: Created pick order - ID: ${createdPickOrder.id}")
println("🔍 DEBUG: Got ${suggestions.suggestedList.size} suggested pick lots")
val consoCode = pickOrderService.assignConsoCode()
val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null)
val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
println("🔍 DEBUG: Saved ${saveSuggestedPickLots.size} suggested pick lots")
if (pickOrderEntity != null) {
pickOrderEntity.consoCode = consoCode
pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
pickOrderRepository.saveAndFlush(pickOrderEntity)
println("�� DEBUG: Assigned consoCode $consoCode to pick order ${createdPickOrder.id}")
// ✅ Debug: Check pick order lines
println("�� DEBUG: Pick order has ${pickOrderEntity.pickOrderLines?.size ?: 0} pick order lines")
pickOrderEntity.pickOrderLines?.forEach { line ->
println("🔍 DEBUG: Pick order line - Item ID: ${line.item?.id}, Qty: ${line.qty}")
}
val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!)
println("🔍 DEBUG: Loaded ${lines.size} pick order lines from DB")
if (lines.isEmpty()) {
println("⚠️ No pick order lines found; suggestions will be empty")
}
// ✅ Create suggested pick lots and hold inventory (like normal release)
println("🔍 DEBUG: About to call suggestionForPickOrderLines for pick order ${pickOrderEntity.id}")
val suggestions = suggestedPickLotService.suggestionForPickOrderLines(
SuggestedPickLotForPolRequest(pickOrderLines = lines)
)
println("🔍 DEBUG: Got ${suggestions.suggestedList.size} suggested pick lots")
if (suggestions.suggestedList.isEmpty()) {
println("⚠️ WARNING: No suggested pick lots generated - this might be due to no inventory available")
}
val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
println("🔍 DEBUG: Saved ${saveSuggestedPickLots.size} suggested pick lots")
// ✅ Hold inventory quantities
val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
)
saveSuggestedPickLots.forEach { lot ->
if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) {
val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine)
if (lineIndex >= 0) {
inventoryLotLines[lineIndex].holdQty =
(inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO)
}
// ✅ Hold inventory quantities
val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
)
saveSuggestedPickLots.forEach { lot ->
if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) {
val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine)
if (lineIndex >= 0) {
inventoryLotLines[lineIndex].holdQty =
(inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO)
}
}
inventoryLotLineRepository.saveAll(inventoryLotLines)
// ✅ Create stock out record and pre-create stock out lines
val stockOut = StockOut().apply {
this.type = "job"
this.consoPickOrderCode = consoCode
this.status = StockOutStatus.PENDING.status
this.handler = request.userId
}
val savedStockOut = stockOutRepository.saveAndFlush(stockOut)
// ✅ Pre-create stock out lines for suggested lots
saveSuggestedPickLots.forEach { lot ->
val polId = lot.pickOrderLine?.id
val illId = lot.suggestedLotLine?.id
if (polId != null && illId != null) {
val existingLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
if (existingLines.isEmpty()) {
val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null)
val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null)
if (pickOrderLine != null && inventoryLotLine != null) {
val line = StockOutLine().apply {
this.stockOut = savedStockOut
this.pickOrderLine = pickOrderLine
this.inventoryLotLine = inventoryLotLine
this.item = pickOrderLine.item
this.status = StockOutLineStatus.PENDING.status
this.qty = 0.0
}
stockOutLineRepository.save(line)
}
inventoryLotLineRepository.saveAll(inventoryLotLines)
// ✅ Create stock out record and pre-create stock out lines
val stockOut = StockOut().apply {
this.type = "job"
this.consoPickOrderCode = consoCode
this.status = StockOutStatus.PENDING.status
this.handler = request.userId
}
val savedStockOut = stockOutRepository.saveAndFlush(stockOut)
// ✅ Pre-create stock out lines for suggested lots
saveSuggestedPickLots.forEach { lot ->
val polId = lot.pickOrderLine?.id
val illId = lot.suggestedLotLine?.id
if (polId != null && illId != null) {
val existingLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
if (existingLines.isEmpty()) {
val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null)
val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null)
if (pickOrderLine != null && inventoryLotLine != null) {
val line = StockOutLine().apply {
this.stockOut = savedStockOut
this.pickOrderLine = pickOrderLine
this.inventoryLotLine = inventoryLotLine
this.item = pickOrderLine.item
this.status = StockOutLineStatus.PENDING.status
this.qty = 0.0
}
stockOutLineRepository.save(line)
}
}
}
}
}

// ✅ CREATE do_pick_order_record entries
// 第 471-555 行附近 - 修复创建逻辑

// ✅ CREATE do_pick_order_record entries
val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()
val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
// ✅ CREATE do_pick_order entries
val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()
val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"))

println("🔍 DEBUG: Target date: $targetDate, Date prefix: $datePrefix")
val truck = deliveryOrder.shop?.id?.let { shopId ->
println("🔍 DEBUG: Looking for truck with shop ID: $shopId")
val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId")
if (trucks.size <= 1) {
// 如果只有一个或没有 truck,直接返回
return@let trucks.firstOrNull()
}
println("🔍 DEBUG: Target date: $targetDate, Date prefix: $datePrefix")
// ✅ 分析 DO order lines 中的 items 分布
// ✅ 分析 items 来确定 preferredFloor
val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct()
println("🔍 DEBUG: Analyzing ${itemIds.size} unique items in DO order lines")
// 使用 SQL 查询统计每个楼层的库存数量
val inventoryQuery = """
SELECT w.store_id as floor, COUNT(*) as inventory_count
FROM inventory_lot_line ill
JOIN inventory_lot il ON il.id = ill.inventoryLotId
JOIN warehouse w ON w.id = ill.warehouseId
WHERE il.itemId IN (${itemIds.joinToString(",")})
AND ill.deleted = false
AND il.deleted = false
AND w.deleted = false
AND ill.inQty > ill.outQty + COALESCE(ill.holdQty, 0)
SELECT
w.store_id as floor,
COUNT(DISTINCT il.itemId) as item_count
FROM inventory_lot il
INNER JOIN inventory i ON i.itemId = il.itemId AND i.deleted = 0 AND i.onHandQty > 0
INNER JOIN inventory_lot_line ill ON ill.inventoryLotId = il.id AND ill.deleted = 0
INNER JOIN warehouse w ON w.id = ill.warehouseId AND w.deleted = 0 AND w.store_id IN ('2F', '4F')
WHERE il.itemId IN (${itemIds.joinToString(",")}) AND il.deleted = 0
GROUP BY w.store_id
""".trimIndent()
val inventoryResults = jdbcDao.queryForList(inventoryQuery)
val floorInventoryCount = mutableMapOf<String, Int>()
inventoryResults.forEach { row: Map<String, Any> ->
val floor = row["floor"] as? String ?: "Other"
val count = (row["inventory_count"] as? Number)?.toInt() ?: 0
floorInventoryCount[floor] = count
val floorItemCount = mutableMapOf<String, Int>()
inventoryResults.forEach { row ->
floorItemCount[row["floor"] as? String ?: "Other"] = (row["item_count"] as? Number)?.toInt() ?: 0
}
println("🔍 DEBUG: Floor inventory distribution: $floorInventoryCount")

// 决定使用哪个楼层
val preferredFloor = when {
floorInventoryCount["2F"] ?: 0 > floorInventoryCount["4F"] ?: 0 -> "2F"
floorInventoryCount["4F"] ?: 0 > floorInventoryCount["2F"] ?: 0 -> "4F"
else -> "2F" // 默认使用 2F
println("🔍 DEBUG: Floor item count distribution: $floorItemCount")
// ✅ 新逻辑:只有所有 items 都在 4F,才算 4F,否则算 2F
val preferredFloor = if ((floorItemCount["4F"] ?: 0) == itemIds.size && (floorItemCount["2F"] ?: 0) == 0) {
"4F" // 所有 items 都在 4F
} else {
"2F" // 只要有任何 item 不在 4F,就算 2F
}
println("🔍 DEBUG: Preferred floor based on inventory: $preferredFloor")
println("🔍 DEBUG: Preferred floor: $preferredFloor")
val preferredStoreId = when (preferredFloor) {
"2F" -> 2
"4F" -> 4
else -> 2
// ✅ 查找匹配 preferred floor 的 truck
val truck = deliveryOrder.shop?.id?.let { shopId ->
val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
val preferredStoreId = when (preferredFloor) {
"2F" -> 2
"4F" -> 4
else -> 2
}
// 只选择 store_id 匹配的 truck
val matchedTrucks = trucks.filter { it.storeId == preferredStoreId }
if (matchedTrucks.isEmpty()) {
null // 没有匹配的 truck
} else {
matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
}
}
val selectedTruck = if (trucks.size > 1) {
// Multiple trucks: prefer matching preferredStoreId, then earliest departure
trucks.find { it.storeId == preferredStoreId }
?: trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
} else {
trucks.firstOrNull()

// ✅ 如果没有匹配的 truck,抛出异常跳过
if (truck == null) {
val errorMsg = "No matching truck found for preferredFloor ($preferredFloor). Skipping DO ${deliveryOrder.id}."
println("⚠️ $errorMsg")
throw IllegalStateException(errorMsg)
}
println("🔍 DEBUG: Selected truck: ID=${selectedTruck?.id}, StoreId=${selectedTruck?.storeId}, DepartureTime=${selectedTruck?.departureTime}")
selectedTruck
}
println("✅ DEBUG: Truck matches preferred floor - Truck Store: ${truck.storeId}, Preferred: $preferredFloor")

// ✅ 根据 truck 的 Store_id 字段确定 storeId
val storeId = when (truck?.storeId) {
4 -> "4/F"
2 -> "2/F"
else -> "2/F" // 默认值
// ✅ 根据 truck 的 Store_id 字段确定 storeId
val storeId = when (truck.storeId) {
4 -> "4/F"
2 -> "2/F"
else -> "2/F" // 默认值
}
val loadingSequence = truck.loadingSequence ?: 999
// ✅ 每个 pick order 只创建一条 DoPickOrder
val doPickOrder = DoPickOrder(
storeId = storeId,
ticketNo = "TEMP-${System.currentTimeMillis()}",
ticketStatus = DoPickOrderStatus.pending,
truckId = truck.id,
doOrderId = deliveryOrder.id,
pickOrderId = createdPickOrder.id,
truckDepartureTime = truck.departureTime,
shopId = deliveryOrder.shop?.id,
handledBy = null,
pickOrderCode = createdPickOrder.code,
deliveryOrderCode = deliveryOrder.code,
loadingSequence = loadingSequence,
ticketReleaseTime = null,
truckLanceCode = truck.truckLanceCode,
shopCode = deliveryOrder.shop?.code,
shopName = deliveryOrder.shop?.name,
requiredDeliveryDate = targetDate
)

println("🔍 DEBUG: Creating DoPickOrder - Store: $storeId, Truck: ${truck.id}")
val savedDoPickOrder = doPickOrderService.save(doPickOrder)
println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}")

return MessageResponse(
id = deliveryOrder.id,
code = deliveryOrder.code,
name = deliveryOrder.shop?.name,
type = null,
message = null,
errorPosition = null,
entity = mapOf("status" to deliveryOrder.status?.value)
)
}
val loadingSequence = truck?.loadingSequence ?: 999
// ✅ 每个 pick order 只创建一条 DoPickOrder
val doPickOrder = DoPickOrder(
storeId = storeId,
ticketNo = "TEMP-${System.currentTimeMillis()}",
ticketStatus = DoPickOrderStatus.pending,
truckId = truck?.id,
doOrderId = deliveryOrder.id,
pickOrderId = createdPickOrder.id,
truckDepartureTime = truck?.departureTime,
shopId = deliveryOrder.shop?.id,
handledBy = null,
pickOrderCode = createdPickOrder.code,
deliveryOrderCode = deliveryOrder.code,
loadingSequence = loadingSequence,
ticketReleaseTime = null,
// ✅ 填充新增字段
truckLanceCode = truck?.truckLanceCode,
shopCode = deliveryOrder.shop?.code,
shopName = deliveryOrder.shop?.name,
requiredDeliveryDate = targetDate
)

println("🔍 DEBUG: Creating DoPickOrder - Store: $storeId, Ticket: , Truck: ${truck?.id}")
val savedDoPickOrder = doPickOrderService.save(doPickOrder)
println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}")

return MessageResponse(
id = deliveryOrder.id,
code = deliveryOrder.code,
name = deliveryOrder.shop?.name,
type = null,
message = null,
errorPosition = null,
entity = mapOf("status" to deliveryOrder.status?.value)
)

// ... existing code ...
}
open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String {
try {


+ 102
- 72
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt View File

@@ -214,13 +214,20 @@ class DoPickOrderService(
println("🔍 DEBUG: Found ${allRecords.size} records for date $targetDate")

val grouped = allRecords.groupBy { it.truckDepartureTime to it.truckLanceCode }
.mapValues { (_, list) ->
LaneBtn(
truckLanceCode = list.first().truckLanceCode ?: "",
unassigned = list.count { it.handledBy == null }, // 未分配的订单数
total = list.size // 总订单数(包括已分配和未分配)
)
}
.mapValues { (_, list) ->
// Group by shop_id within this lane
val shopGroups = list.groupBy { it.shopId }
LaneBtn(
truckLanceCode = list.first().truckLanceCode ?: "",
// Count unique shops where ALL records are unassigned
unassigned = shopGroups.count { (_, shopRecords) ->
shopRecords.all { it.handledBy == null }
},
// Count total unique shops in this lane
total = shopGroups.size
)
}

val timeGroups = grouped.entries
.groupBy { it.key.first }
@@ -243,68 +250,87 @@ class DoPickOrderService(
}

// ✅ 修复:把 assignByLane 移到类里面
fun assignByLane(request: AssignByLaneRequest): MessageResponse {
val existingOrders = doPickOrderRepository.findByHandledByAndTicketStatusIn(
request.userId,
listOf(DoPickOrderStatus.released, DoPickOrderStatus.pending)
fun assignByLane(request: AssignByLaneRequest): MessageResponse {
// Check if user already has active orders
val existingOrders = doPickOrderRepository.findByHandledByAndTicketStatusIn(
request.userId,
listOf(DoPickOrderStatus.released, DoPickOrderStatus.pending)
)

if (existingOrders.isNotEmpty()) {
return MessageResponse(
id = null, code = "USER_BUSY", name = null, type = null,
message = "User already has an active pick order. Please complete it first.",
errorPosition = null, entity = null
)
}

if (existingOrders.isNotEmpty()) {
return MessageResponse(
id = null, code = "USER_BUSY", name = null, type = null,
message = "User already has an active pick order. Please complete it first.",
errorPosition = null, entity = null
)
// Find all orders for this lane
val allLaneOrders = doPickOrderRepository
.findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc(
request.storeId,
DoPickOrderStatus.pending
)
.filter {
it.handledBy == null &&
it.truckLanceCode == request.truckLanceCode &&
(request.truckDepartureTime == null ||
it.truckDepartureTime?.toString() == request.truckDepartureTime)
}

val candidates = doPickOrderRepository
.findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc(
request.storeId,
DoPickOrderStatus.pending
)
.filter {
it.handledBy == null &&
it.truckLanceCode == request.truckLanceCode &&
(request.truckDepartureTime == null ||
it.truckDepartureTime?.toString() == request.truckDepartureTime)
}
if (allLaneOrders.isEmpty()) {
return MessageResponse(
id = null, code = "NO_ORDERS", name = null, type = null,
message = "No available orders for lane ${request.truckLanceCode}",
errorPosition = null, entity = null
)
}

if (candidates.isEmpty()) {
return MessageResponse(
id = null, code = "NO_ORDERS", name = null, type = null,
message = "No available orders for lane ${request.truckLanceCode}",
errorPosition = null, entity = null
)
}
// ✅ Group by shop and pick the first shop (with earliest loading sequence)
val shopGroups = allLaneOrders.groupBy { it.shopId }
val firstShopOrders = shopGroups.values
.sortedBy { shopOrders -> shopOrders.minOf { it.loadingSequence ?: Int.MAX_VALUE } }
.firstOrNull()

val firstOrder = candidates.first()
val user = userRepository.findById(request.userId).orElse(null)
val handlerName = user?.name ?: "Unknown"
if (firstShopOrders == null || firstShopOrders.isEmpty()) {
return MessageResponse(
id = null, code = "NO_ORDERS", name = null, type = null,
message = "No available orders for lane ${request.truckLanceCode}",
errorPosition = null, entity = null
)
}

// ✅ 更新 do_pick_order - 保持原有的卡车信息
firstOrder.handledBy = request.userId
firstOrder.handlerName = handlerName
firstOrder.ticketStatus = DoPickOrderStatus.released
firstOrder.ticketReleaseTime = LocalDateTime.now()

// ✅ 重要:不要修改 truckDepartureTime 和 truckLanceCode
// 这些信息应该保持用户选择的值

doPickOrderRepository.save(firstOrder)

// ✅ 同步更新 pick_order 表
if (firstOrder.pickOrderId != null) {
val pickOrder = pickOrderRepository.findById(firstOrder.pickOrderId!!).orElse(null)
if (pickOrder != null) {
val user = userRepository.findById(request.userId).orElse(null)
pickOrder.assignTo = user
pickOrder.status = PickOrderStatus.RELEASED
pickOrderRepository.save(pickOrder)
}
val user = userRepository.findById(request.userId).orElse(null)
val handlerName = user?.name ?: "Unknown"
// ✅ Get shop info from first order
val firstOrder = firstShopOrders.first()
val shopName = firstOrder.shopName ?: ""
val shopCode = firstOrder.shopCode ?: ""

// ✅ Assign ALL records for this shop to the user
firstShopOrders.forEach { order ->
order.handledBy = request.userId
order.handlerName = handlerName
order.ticketStatus = DoPickOrderStatus.released
order.ticketReleaseTime = LocalDateTime.now()
}
doPickOrderRepository.saveAll(firstShopOrders)

// ✅ Update ALL pick_order tables for this shop
val pickOrderIds = firstShopOrders.mapNotNull { it.pickOrderId }.distinct()
pickOrderIds.forEach { pickOrderId ->
val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
if (pickOrder != null) {
pickOrder.assignTo = user
pickOrder.status = PickOrderStatus.RELEASED
pickOrderRepository.save(pickOrder)
}
}

// 同步更新 record
val records = doPickOrderRecordRepository.findByPickOrderId(firstOrder.pickOrderId!!)
// ✅ Update ALL do_pick_order_record entries for this shop
pickOrderIds.forEach { pickOrderId ->
val records = doPickOrderRecordRepository.findByPickOrderId(pickOrderId)
records.forEach {
it.handledBy = request.userId
it.handlerName = handlerName
@@ -312,20 +338,24 @@ class DoPickOrderService(
it.ticketReleaseTime = LocalDateTime.now()
}
doPickOrderRecordRepository.saveAll(records)
}

return MessageResponse(
id = firstOrder.pickOrderId,
code = "SUCCESS",
name = null,
type = null,
message = "Assigned pick order from lane ${request.truckLanceCode}",
errorPosition = null,
entity = mapOf(
"pickOrderId" to firstOrder.pickOrderId,
"ticketNo" to firstOrder.ticketNo
)
return MessageResponse(
id = pickOrderIds.firstOrNull(),
code = "SUCCESS",
name = null,
type = null,
message = "Assigned ${firstShopOrders.size} order(s) for $shopName from lane ${request.truckLanceCode}",
errorPosition = null,
entity = mapOf(
"pickOrderIds" to pickOrderIds,
"ticketNos" to firstShopOrders.map { it.ticketNo },
"shopCode" to shopCode,
"shopName" to shopName,
"orderCount" to firstShopOrders.size
)
}
)
}

// 在 DoPickOrderService 类中添加这个方法
open fun determineStoreId(doOrderId: Long): String {


+ 18
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ReleaseDoRequest.kt View File

@@ -1,6 +1,23 @@
package com.ffii.fpsms.modules.deliveryOrder.web.models

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
data class ReleaseDoRequest(
val id: Long,
val userId: Long
)
data class ReleaseDoResult(
val deliveryOrderId: Long,
val deliveryOrderCode: String?,
val pickOrderId: Long,
val pickOrderCode: String?,
val shopId: Long?,
val shopCode: String?,
val shopName: String?,
val estimatedArrivalDate: LocalDate,
val preferredFloor: String,
val truckId: Long?,
val truckDepartureTime: LocalTime?,
val truckLanceCode: String?,
val loadingSequence: Int?
)

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt View File

@@ -1325,7 +1325,7 @@ open fun getCompletedJobOrderPickOrderLotDetails(pickOrderId: Long): List<Map<St
w.id as routerId,
w.order as routerIndex,
w.code as routerRoute,
w.area as routerArea,
w.code as routerArea,
-- Set quantities to NULL for rejected lots
CASE


+ 236
- 274
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt View File

@@ -3361,308 +3361,270 @@ ORDER BY
}
// ... existing code ...

// Fix the method signature and return types
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical ===")
println("today: ${LocalDate.now()}")
println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (CURRENT STRUCTURE) ===")
println("userId filter: $userId")
// Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId)
val user = userService.find(userId).orElse(null)
if (user == null) {
println("❌ User not found: $userId")
return emptyMap()
}
val statusList = listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED,
//PickOrderStatus.COMPLETED
)
// Get all pick orders assigned to user with PENDING or RELEASED status that have doId
val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn(
user,
statusList
).filter { it.deliveryOrder != null } // Only pick orders with doId
println("🔍 DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId")

// ✅ NEW LOGIC: Filter based on assignment and status
val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) {
// Check if there are any RELEASED orders assigned to this user (active work)
val assignedReleasedOrders = allAssignedPickOrders.filter {
it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId
}
if (assignedReleasedOrders.isNotEmpty()) {
// ✅ If there are assigned RELEASED orders, show only those
println("🔍 DEBUG: Found ${assignedReleasedOrders.size} assigned RELEASED orders, showing only those")
assignedReleasedOrders
} else {
// ✅ If no assigned RELEASED orders, show only the latest COMPLETED order
val completedOrders = allAssignedPickOrders.filter { it.status == PickOrderStatus.COMPLETED }
if (completedOrders.isNotEmpty()) {
val latestCompleted = completedOrders.maxByOrNull { it.completeDate ?: it.modified ?: LocalDateTime.MIN }
println("🔍 DEBUG: No assigned RELEASED orders, showing latest completed order: ${latestCompleted?.code}")
listOfNotNull(latestCompleted)
} else {
println("🔍 DEBUG: No orders found")
emptyList()
}
}
} else {
emptyList()
}
val pickOrderIds = filteredPickOrders.map { it.id!! }
println("🎯 Pick order IDs to fetch: $pickOrderIds")
// ✅ Step 1: 获取 do_pick_order 基本信息 (直接通过 pickOrderId 关联)
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.pick_order_id,
dpo.shop_id
FROM fpsmsdb.do_pick_order dpo
INNER JOIN fpsmsdb.pick_order po ON po.id = dpo.pick_order_id
WHERE po.assignTo = :userId
AND po.type = 'do'
AND po.status IN ('assigned', 'released', 'picking')
AND po.deleted = false
AND dpo.deleted = false
LIMIT 1
""".trimIndent()
if (pickOrderIds.isEmpty()) {
val doPickOrderInfo = jdbcDao.queryForMap(doPickOrderSql, mapOf("userId" to userId)).orElse(null)
if (doPickOrderInfo == null) {
println("❌ No do_pick_order found for user $userId")
return mapOf(
"pickOrder" to null as Any?,
"pickOrderLines" to emptyList<Map<String, Any>>() as Any?
"fgInfo" to null,
"pickOrders" to emptyList<Any>()
)
}
// Use the same SQL query but transform the results into hierarchical structure
val pickOrderIdsStr = pickOrderIds.joinToString(",")
val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong()
val shopId = (doPickOrderInfo["shop_id"] as? Number)?.toLong()
println("🔍 Found do_pick_order ID: $doPickOrderId, shop_id: $shopId")
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.type as pickOrderType,
po.status as pickOrderStatus,
po.assignTo as pickOrderAssignTo,
-- 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,
-- ✅ 修改:直接使用 warehouse 作为路由信息
w.`order` as routerIndex,
w.code as routerRoute,
w.code as routerArea,
-- ✅ FIXED: 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,
COALESCE(spl.suggestedLotLineId, sol.inventoryLotLineId) as debugSuggestedLotLineId,
ill.inventoryLotId as debugInventoryLotId,
// ✅ Step 2: 获取该用户分配的所有 pick orders (通过 shop_id 关联)
val pickOrdersSql = """
SELECT DISTINCT
dpo.pick_order_id,
po.code as pick_order_code,
dpo.do_order_id,
dpo.delivery_order_code,
po.consoCode,
po.status,
DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate,
dpo.ticket_no,
dpo.id as do_pick_order_id
FROM fpsmsdb.do_pick_order dpo
INNER JOIN fpsmsdb.pick_order po ON po.id = dpo.pick_order_id
WHERE po.assignTo = :userId
AND po.type = 'do'
AND po.status IN ('assigned', 'released', 'picking')
AND po.deleted = false
AND dpo.deleted = false
AND dpo.shop_id = :shopId
ORDER BY dpo.pick_order_id
""".trimIndent()
val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("userId" to userId, "shopId" to shopId))
println("🔍 Found ${pickOrdersInfo.size} pick orders for user $userId in shop $shopId")
// ✅ Step 3: 为每个 pick order 获取 lines 和 lots
val pickOrders = pickOrdersInfo.map { poInfo ->
val currentPickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong()
-- 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')
), 0) as totalPickedByAllPickOrders,
// ✅ 查询该 pick order 的所有 lines 和 lots
val linesSql = """
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,
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,
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 来包含没有 lot 的 pick order lines
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 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 = :pickOrderId
AND po.deleted = false
ORDER BY
COALESCE(w.`order`, 999999) ASC,
pol.id ASC,
il.lotNo ASC
""".trimIndent()
-- Calculate remaining available quantity correctly
(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
val linesResults = jdbcDao.queryForList(linesSql, mapOf("pickOrderId" to currentPickOrderId))
println("🔍 Pick order $currentPickOrderId has ${linesResults.size} line-lot records")
-- 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,
// ✅ 按 pickOrderLineId 分组
val lineGroups = linesResults.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() }
-- Processing status
CASE
WHEN sol.status = 'completed' THEN 'completed'
WHEN sol.status = 'rejected' THEN 'rejected'
WHEN sol.status = 'created' THEN 'pending'
ELSE 'pending'
END as processingStatus
val pickOrderLines = lineGroups.map { (lineId, lineRows) ->
val firstLineRow = lineRows.firstOrNull()
if (firstLineRow == null) {
null
} else {
// ✅ 构建 lots 列表(如果没有 lot,返回空数组)
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"],
"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() // ✅ 返回空数组而不是 null
}
mapOf(
"id" to lineId,
"requiredQty" to firstLineRow["pickOrderLineRequiredQty"],
"status" to firstLineRow["pickOrderLineStatus"],
"item" to mapOf(
"id" to firstLineRow["itemId"],
"code" to firstLineRow["itemCode"],
"name" to firstLineRow["itemName"],
"uomCode" to firstLineRow["uomCode"],
"uomDesc" to firstLineRow["uomDesc"],
"uomShortDesc" to firstLineRow["uomShortDesc"],
),
"lots" to lots // ✅ 即使是空数组也返回
)
}
}.filterNotNull()
FROM fpsmsdb.pick_order po
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

-- Base lot links: all lot lines referenced by either suggestions or stock out lines for this POL
LEFT JOIN (
SELECT spl.pickOrderLineId AS 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

-- Re-bind spl/sol strictly on the same lot line
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 AND sol.deleted = false

LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId
-- ✅ 删除 router 相关的 JOIN
-- LEFT JOIN router r ON r.inventoryLotId = ill.inventoryLotId AND r.deleted = false
-- LEFT JOIN router_order ro ON r.router_id = ro.id
LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId -- ✅ 保留 warehouse JOIN

WHERE po.deleted = false
AND po.id IN ($pickOrderIdsStr)
AND pol.deleted = false
AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED')
AND po.assignTo = :userId
AND ill.deleted = false
AND il.deleted = false
AND ll.lotLineId IS NOT NULL
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, -- ✅ 使用 warehouse.order 排序
po.code ASC,
i.code ASC,
il.expiryDate ASC,
il.lotNo ASC
""".trimIndent()
println("�� Executing SQL for hierarchical structure: $sql")
println("�� With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr")
val results = jdbcDao.queryForList(sql, mapOf("userId" to userId))
println("✅ Total result count: ${results.size}")
// Filter out lots with null availableQty (rejected lots)
// val filteredResults = results.filter { row ->
//val availableQty = row["availableQty"]
// availableQty != null
// }
val filteredResults = results
println("✅ Filtered result count: ${filteredResults.size}")
// ✅ Transform flat results into hierarchical structure
if (filteredResults.isEmpty()) {
return mapOf(
"pickOrder" to null as Any?,
"pickOrderLines" to emptyList<Map<String, Any>>() as Any?
mapOf(
"pickOrderId" to currentPickOrderId,
"pickOrderCode" to poInfo["pick_order_code"],
"doOrderId" to poInfo["do_order_id"],
"deliveryOrderCode" to poInfo["delivery_order_code"],
"consoCode" to poInfo["consoCode"],
"status" to poInfo["status"],
"targetDate" to poInfo["targetDate"],
"ticketNo" to poInfo["ticket_no"],
"doPickOrderId" to poInfo["do_pick_order_id"],
"pickOrderLines" to pickOrderLines
)
}
// Get pick order info from first row (all rows have same pick order info)
val firstRow = filteredResults.first()
val pickOrderInfo = mapOf(
"id" to firstRow["pickOrderId"],
"code" to firstRow["pickOrderCode"],
"consoCode" to firstRow["pickOrderConsoCode"],
"targetDate" to firstRow["pickOrderTargetDate"],
"type" to firstRow["pickOrderType"],
"status" to firstRow["pickOrderStatus"],
"assignTo" to firstRow["pickOrderAssignTo"]
// ✅ 构建 FG 信息
val fgInfo = mapOf(
"doPickOrderId" to doPickOrderId,
"ticketNo" to pickOrdersInfo.map { it["ticket_no"] }.joinToString(", "),
"storeId" to doPickOrderInfo["store_id"],
"shopCode" to doPickOrderInfo["ShopCode"],
"shopName" to doPickOrderInfo["ShopName"],
"truckLanceCode" to doPickOrderInfo["TruckLanceCode"],
"departureTime" to doPickOrderInfo["truck_departure_time"]
)
// Group by pick order line ID to create hierarchical structure
val pickOrderLinesMap = filteredResults
.groupBy { it["pickOrderLineId"] as Number }
.map { (pickOrderLineId, lots) ->
val firstLot = lots.first()
// Item information (same for all lots of this line)
val itemInfo = mapOf(
"id" to firstLot["itemId"],
"code" to firstLot["itemCode"],
"name" to firstLot["itemName"],
"uomCode" to firstLot["uomCode"],
"uomDesc" to firstLot["uomDesc"]
)
// Transform lots for this pick order line
val lotsInfo = lots.map { lot ->
mapOf(
"id" to lot["lotId"],
"lotNo" to lot["lotNo"],
"expiryDate" to lot["expiryDate"],
"location" to lot["location"],
"stockUnit" to lot["stockUnit"],
"availableQty" to lot["availableQty"],
"requiredQty" to lot["requiredQty"],
"actualPickQty" to lot["actualPickQty"],
"inQty" to lot["inQty"],
"outQty" to lot["outQty"],
"holdQty" to lot["holdQty"],
"lotStatus" to lot["lotStatus"],
"lotAvailability" to lot["lotAvailability"],
"processingStatus" to lot["processingStatus"],
"suggestedPickLotId" to lot["suggestedPickLotId"],
"stockOutLineId" to lot["stockOutLineId"],
"stockOutLineStatus" to lot["stockOutLineStatus"],
"stockOutLineQty" to lot["stockOutLineQty"],
"router" to mapOf(
"id" to lot["routerId"],
"index" to lot["routerIndex"],
"route" to lot["routerRoute"],
"area" to lot["routerArea"],
"itemCode" to firstLot["itemId"],
"itemName" to firstLot["itemName"],
"uomId" to firstLot["uomShortDesc"],
"noofCarton" to lot["requiredQty"] // Use required qty as carton count
)
)
}
// Pick order line with item and lots
mapOf(
"id" to pickOrderLineId,
"requiredQty" to firstLot["pickOrderLineRequiredQty"],
"status" to firstLot["pickOrderLineStatus"],
"item" to itemInfo,
"lots" to lotsInfo
)
}
return mapOf(
"pickOrder" to pickOrderInfo as Any?,
"pickOrderLines" to pickOrderLinesMap as Any?
"fgInfo" to fgInfo,
"pickOrders" to pickOrders
)
}

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


Loading…
Cancel
Save