diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRepository.kt index 2450102..eeba431 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRepository.kt @@ -17,5 +17,6 @@ interface JoPickOrderRepository : JpaRepository { fun findByPickOrderId(pickOrderId: Long): List fun findByPickOrderIdAndItemId(pickOrderId: Long, itemId: Long): java.util.Optional fun findByJobOrderId(jobOrderId: Long): List? + fun findByPickOrderIdIn(pickOrderIds: Collection): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt index 735e281..1945ba8 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt @@ -1549,7 +1549,7 @@ open fun getCompletedJobOrderPickOrderLotDetails(pickOrderId: Long): List> { println("=== getCompletedJobOrderPickOrders (no user filter) ===") @@ -1559,46 +1559,54 @@ open fun getCompletedJobOrderPickOrders(): List> { .findAllByStatusIn(listOf(PickOrderStatus.COMPLETED)) .filter { it.jobOrder != null } - println("Found ${allCompletedPickOrders.size} completed job order pick orders") - - // 2. 组装返回数据 - val completedJobOrderPickOrders = allCompletedPickOrders.mapNotNull { pickOrder -> - val jobOrder = pickOrder.jobOrder - if (jobOrder != null) { - // 对应的 JoPickOrder 记录(用于 second scan 状态统计) - val joPickOrders = findByPickOrderId(pickOrder.id!!) - println("Pick Order ${pickOrder.code}: joPickOrders.size=${joPickOrders.size}") + println("Found ${allCompletedPickOrders.size} completed job order pick orders") - val secondScanCompleted = joPickOrders.isNotEmpty() && + val pickOrderIds = allCompletedPickOrders.mapNotNull { it.id }.distinct() + val joByPickOrderId: Map> = + if (pickOrderIds.isNotEmpty()) { + joPickOrderRepository.findByPickOrderIdIn(pickOrderIds) + .filterNot { it.deleted } + .groupBy { it.pickOrderId ?: 0L } + .filterKeys { it != 0L } + } else { + emptyMap() + } + + val completedJobOrderPickOrders = allCompletedPickOrders.mapNotNull { pickOrder -> + val jobOrder = pickOrder.jobOrder + if (jobOrder != null) { + val poId = pickOrder.id ?: return@mapNotNull null + val joPickOrders = joByPickOrderId[poId].orEmpty() + + val secondScanCompleted = joPickOrders.isNotEmpty() && joPickOrders.all { it.matchStatus == JoPickOrderStatus.completed } - - mapOf( - "id" to pickOrder.id, - "pickOrderId" to pickOrder.id, - "pickOrderCode" to pickOrder.code, - "pickOrderConsoCode" to pickOrder.consoCode, - "pickOrderTargetDate" to pickOrder.targetDate?.let { - "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}" - }, - "pickOrderStatus" to pickOrder.status, - // 完成时间:按你的需求用 jobOrder.planEnd - "completedDate" to jobOrder.planEnd, - "jobOrderId" to jobOrder.id, - "jobOrderCode" to jobOrder.code, - "jobOrderName" to jobOrder.bom?.name, - "reqQty" to jobOrder.reqQty, - "uom" to jobOrder.bom?.uom?.code, - "planStart" to jobOrder.planStart, - "planEnd" to jobOrder.planEnd, - "secondScanCompleted" to secondScanCompleted, - "totalItems" to joPickOrders.size, - "completedItems" to joPickOrders.count { it.matchStatus == JoPickOrderStatus.completed } - ) - } else { - println("❌ Pick order ${pickOrder.id} has no job order, skipping.") - null + + mapOf( + "id" to pickOrder.id, + "pickOrderId" to pickOrder.id, + "pickOrderCode" to pickOrder.code, + "pickOrderConsoCode" to pickOrder.consoCode, + "pickOrderTargetDate" to pickOrder.targetDate?.let { + "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}" + }, + "pickOrderStatus" to pickOrder.status, + "completedDate" to jobOrder.planEnd, + "jobOrderId" to jobOrder.id, + "jobOrderCode" to jobOrder.code, + "jobOrderName" to jobOrder.bom?.name, + "reqQty" to jobOrder.reqQty, + "uom" to jobOrder.bom?.uom?.code, + "planStart" to jobOrder.planStart, + "planEnd" to jobOrder.planEnd, + "secondScanCompleted" to secondScanCompleted, + "totalItems" to joPickOrders.size, + "completedItems" to joPickOrders.count { it.matchStatus == JoPickOrderStatus.completed }, + ) + } else { + println("❌ Pick order ${pickOrder.id} has no job order, skipping.") + null + } } - } println("Returning ${completedJobOrderPickOrders.size} completed job order pick orders") completedJobOrderPickOrders @@ -1608,15 +1616,93 @@ open fun getCompletedJobOrderPickOrders(): List> { emptyList() } } +*/ +open fun getCompletedJobOrderPickOrders(completedDate: LocalDate?): List> { + + println("=== getCompletedJobOrderPickOrders (no user filter) ===") + + return try { + // 1. 找出所有 COMPLETED 状态、且有关联 JobOrder 的 pick order(不再按 assignTo 过滤) + val allCompletedPickOrders = + if (completedDate != null) { + val from = completedDate.atStartOfDay() + val toExclusive = completedDate.plusDays(1).atStartOfDay() + pickOrderRepository.findAllCompletedWithJobOrderPlanEndOnDay( + PickOrderStatus.COMPLETED, + from, + toExclusive, + ) + } else { + pickOrderRepository + .findAllByStatusIn(listOf(PickOrderStatus.COMPLETED)) + .filter { it.jobOrder != null } + } + + println("Found ${allCompletedPickOrders.size} completed job order pick orders") + + val pickOrderIds = allCompletedPickOrders.mapNotNull { it.id }.distinct() + val joByPickOrderId: Map> = + if (pickOrderIds.isNotEmpty()) { + joPickOrderRepository.findByPickOrderIdIn(pickOrderIds) + .filterNot { it.deleted } + .groupBy { it.pickOrderId ?: 0L } + .filterKeys { it != 0L } + } else { + emptyMap() + } + + val completedJobOrderPickOrders = allCompletedPickOrders.mapNotNull { pickOrder -> + val jobOrder = pickOrder.jobOrder + if (jobOrder != null) { + val poId = pickOrder.id ?: return@mapNotNull null + val joPickOrders = joByPickOrderId[poId].orEmpty() + + val secondScanCompleted = joPickOrders.isNotEmpty() && + joPickOrders.all { it.matchStatus == JoPickOrderStatus.completed } + + mapOf( + "id" to pickOrder.id, + "pickOrderId" to pickOrder.id, + "pickOrderCode" to pickOrder.code, + "pickOrderConsoCode" to pickOrder.consoCode, + "pickOrderTargetDate" to pickOrder.targetDate?.let { + "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}" + }, + "pickOrderStatus" to pickOrder.status, + "completedDate" to jobOrder.planEnd, + "jobOrderId" to jobOrder.id, + "jobOrderCode" to jobOrder.code, + "jobOrderName" to jobOrder.bom?.name, + "reqQty" to jobOrder.reqQty, + "uom" to jobOrder.bom?.uom?.code, + "planStart" to jobOrder.planStart, + "planEnd" to jobOrder.planEnd, + "secondScanCompleted" to secondScanCompleted, + "totalItems" to joPickOrders.size, + "completedItems" to joPickOrders.count { it.matchStatus == JoPickOrderStatus.completed }, + ) + } else { + println("❌ Pick order ${pickOrder.id} has no job order, skipping.") + null + } + } + println("Returning ${completedJobOrderPickOrders.size} completed job order pick orders") + completedJobOrderPickOrders + } catch (e: Exception) { + println("❌ Error in getCompletedJobOrderPickOrders: ${e.message}") + e.printStackTrace() + emptyList() + } + } // ... rest of the code ... -open fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId: Long): List> { - println("=== getCompletedJobOrderPickOrderLotDetailsForCompletedPick ===") - println("pickOrderId: $pickOrderId") - - return try { - val sql = """ + open fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId: Long): List> { + println("=== getCompletedJobOrderPickOrderLotDetailsForCompletedPick ===") + println("pickOrderId: $pickOrderId") + + return try { + val sql = """ SELECT -- Pick Order Information po.id as pickOrderId, @@ -1749,45 +1835,46 @@ open fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId: Lo il.expiryDate ASC, il.lotNo ASC """.trimIndent() - - // println(" Executing SQL for completed pick order lot details: $sql") - println(" With parameters: pickOrderId = $pickOrderId") - - val results = jdbcDao.queryForList(sql, mapOf("pickOrderId" to pickOrderId)) - - println("Found ${results.size} lot details for completed pick order $pickOrderId") - results - } catch (e: Exception) { - println("❌ Error in getCompletedJobOrderPickOrderLotDetailsForCompletedPick: ${e.message}") - e.printStackTrace() - emptyList() - } -} -private fun normalizeFloor(raw: String): String { - if (raw.isBlank()) return raw - val cleaned = raw.trim().uppercase() - val num = cleaned.replace(Regex("[^0-9]"), "") - return if (num.isNotEmpty()) "${num}F" else cleaned -} -open fun getAllJoPickOrders(bomType: String?, floor: String?): List { - println("=== getAllJoPickOrders ===") - - return try { - val releasedPickOrders = pickOrderRepository.findAllByStatusAndDeletedFalse( - PickOrderStatus.RELEASED - ).filter { pickOrder -> - pickOrder.jobOrder != null + // println(" Executing SQL for completed pick order lot details: $sql") + println(" With parameters: pickOrderId = $pickOrderId") + + val results = jdbcDao.queryForList(sql, mapOf("pickOrderId" to pickOrderId)) + + println("Found ${results.size} lot details for completed pick order $pickOrderId") + results + } catch (e: Exception) { + println("❌ Error in getCompletedJobOrderPickOrderLotDetailsForCompletedPick: ${e.message}") + e.printStackTrace() + emptyList() } + } - println("Found ${releasedPickOrders.size} released job order pick orders") - val pickOrderIds = releasedPickOrders.mapNotNull { it.id } + private fun normalizeFloor(raw: String): String { + if (raw.isBlank()) return raw + val cleaned = raw.trim().uppercase() + val num = cleaned.replace(Regex("[^0-9]"), "") + return if (num.isNotEmpty()) "${num}F" else cleaned + } - val normalizedFloorFilter = floor?.let { normalizeFloor(it) }?.takeIf { it.isNotBlank() } + open fun getAllJoPickOrders(bomType: String?, floor: String?): List { + println("=== getAllJoPickOrders ===") - // 2. 批量查询每个 pick order 的按楼层统计(若没有则跳过) - val floorCountsByPickOrderId: Map>> = if (pickOrderIds.isNotEmpty()) { - val sql = """ + return try { + val releasedPickOrders = pickOrderRepository.findAllByStatusAndDeletedFalse( + PickOrderStatus.RELEASED + ).filter { pickOrder -> + pickOrder.jobOrder != null + } + + println("Found ${releasedPickOrders.size} released job order pick orders") + val pickOrderIds = releasedPickOrders.mapNotNull { it.id } + + val normalizedFloorFilter = floor?.let { normalizeFloor(it) }?.takeIf { it.isNotBlank() } + + // 2. 批量查询每个 pick order 的按楼层统计(若没有则跳过) + val floorCountsByPickOrderId: Map>> = if (pickOrderIds.isNotEmpty()) { + val sql = """ SELECT po.id AS pickOrderId, COALESCE(NULLIF(TRIM(w.store_id), ''), SUBSTRING_INDEX(w.code, '-', 1)) AS floorKey, @@ -1809,14 +1896,15 @@ open fun getAllJoPickOrders(bomType: String?, floor: String?): List()).groupBy { (it["pickOrderId"] as? Number)?.toLong() ?: 0L } - } else { - emptyMap() - } + jdbcDao.queryForList(sql, emptyMap()) + .groupBy { (it["pickOrderId"] as? Number)?.toLong() ?: 0L } + } else { + emptyMap() + } - // 2b. no-lot counts (lines with stock_out_line.inventoryLotLineId IS NULL) - val noLotCountsByPickOrderId: Map> = if (pickOrderIds.isNotEmpty()) { - val sql = """ + // 2b. no-lot counts (lines with stock_out_line.inventoryLotLineId IS NULL) + val noLotCountsByPickOrderId: Map> = if (pickOrderIds.isNotEmpty()) { + val sql = """ SELECT po.id AS pickOrderId, COUNT(DISTINCT pol.id) AS totalCount, @@ -1834,12 +1922,13 @@ open fun getAllJoPickOrders(bomType: String?, floor: String?): List()).associateBy { (it["pickOrderId"] as? Number)?.toLong() ?: 0L } - } else emptyMap() + jdbcDao.queryForList(sql, emptyMap()) + .associateBy { (it["pickOrderId"] as? Number)?.toLong() ?: 0L } + } else emptyMap() - // 2c. suggested fail (rejected) counts per pick order - val suggestedFailCountByPickOrderId: Map = if (pickOrderIds.isNotEmpty()) { - val sql = """ + // 2c. suggested fail (rejected) counts per pick order + val suggestedFailCountByPickOrderId: Map = if (pickOrderIds.isNotEmpty()) { + val sql = """ SELECT po.id AS pickOrderId, COUNT(DISTINCT sol.id) AS rejectedCount @@ -1853,137 +1942,186 @@ open fun getAllJoPickOrders(bomType: String?, floor: String?): List()).associate { row -> - val id = (row["pickOrderId"] as? Number)?.toLong() ?: 0L - val cnt = (row["rejectedCount"] as? Number)?.toInt() ?: 0 - id to cnt - } - } else emptyMap() - - - val jobOrderPickOrders = releasedPickOrders.mapNotNull { pickOrder -> - println("Processing pick order: ${pickOrder.id}, code: ${pickOrder.code}") - val jobOrder = pickOrder.jobOrder - if (jobOrder == null) { - println("❌ Pick order ${pickOrder.id} has no job order") - return@mapNotNull null - } - if (jobOrder.isHidden == true) { - return@mapNotNull null - } - if (jobOrder.status == JobOrderStatus.COMPLETED) { - return@mapNotNull null - } + jdbcDao.queryForList(sql, emptyMap()).associate { row -> + val id = (row["pickOrderId"] as? Number)?.toLong() ?: 0L + val cnt = (row["rejectedCount"] as? Number)?.toInt() ?: 0 + id to cnt + } + } else emptyMap() + + val linesByPickOrderId = + if (pickOrderIds.isNotEmpty()) { + pickOrderLineRepository.findAllByPickOrderIdInAndDeletedFalse(pickOrderIds) + .mapNotNull { pol -> + val pid = pol.pickOrder?.id ?: return@mapNotNull null + pid to pol + } + .groupBy({ it.first }, { it.second }) + } else { + emptyMap() + } - println("Job order found: ${jobOrder.id}, code: ${jobOrder.code}") - - val bom = jobOrder.bom + val jobOrderIdsForStockIn = releasedPickOrders.mapNotNull { it.jobOrder?.id }.distinct() + val lotNoByJobOrderId = + if (jobOrderIdsForStockIn.isNotEmpty()) { + stockInLineRepository.findAllByJobOrder_IdInAndDeletedFalse(jobOrderIdsForStockIn) + .mapNotNull { sil -> + val joid = sil.jobOrder?.id ?: return@mapNotNull null + joid to sil + } + .groupBy({ it.first }, { it.second }) + .mapValues { (_, sils) -> + sils.minByOrNull { it.id ?: Long.MAX_VALUE }?.lotNo + } + } else { + emptyMap() + } - // 按 bom.type 过滤:null / blank 表示不过滤(全部) - val normalizedType = bomType?.trim()?.lowercase()?.takeIf { it.isNotBlank() } - if (normalizedType != null) { - val currentType = bom?.type?.trim()?.lowercase() - if (currentType != normalizedType) return@mapNotNull null - } - println("BOM found: ${bom?.id}") - - val item = bom?.item - if (item == null) { - println("❌ BOM ${bom?.id} has no item") - return@mapNotNull null - } - - println("Item found: ${item.id}, name: ${item.name}") - - val uom = bom.outputQtyUom - if (uom == null) { - println("❌ BOM ${bom.id} has no uom") - return@mapNotNull null - } - - // println("UOM found: ${uom.id}, code: ${uom.code}") - - val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id ?: return@mapNotNull null) - val finishedLines = pickOrderLines.count { it.status == PickOrderLineStatus.COMPLETED } - val jobOrderType = jobOrder.jobTypeId?.let { jobTypeRepository.findById(it).orElse(null) } - val stockInLine = stockInLineRepository.findFirstByJobOrder_IdAndDeletedFalse( - jobOrder.id ?: return@mapNotNull null - ) - val lotNo = stockInLine?.lotNo - println("✅ Building response for pick order ${pickOrder.id}") - val floorPickCounts = floorCountsByPickOrderId[pickOrder.id]?.map { row -> - com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto( - floor = normalizeFloor((row["floorKey"] as? String).orEmpty()), - finishedCount = (row["finishedCount"] as? Number)?.toInt() ?: 0, - totalCount = (row["totalCount"] as? Number)?.toInt() ?: 0 - ) - }.orEmpty() - - val noLotRow = noLotCountsByPickOrderId[pickOrder.id] - val noLotPickCount = if (noLotRow != null) { - com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto( - floor = "NO_LOT", - finishedCount = (noLotRow["finishedCount"] as? Number)?.toInt() ?: 0, - totalCount = (noLotRow["totalCount"] as? Number)?.toInt() ?: 0 - ) - } else null - - // Backend filtering by floor: - // - When selecting a specific floor (2F/3F/4F), show ONLY remaining on that floor. - // - When selecting NO_LOT, show ONLY remaining for no-lot items. - if (normalizedFloorFilter != null) { - if (normalizedFloorFilter == "NO_LOT") { - val hasNoLotRemaining = (noLotPickCount?.let { it.totalCount - it.finishedCount } ?: 0) > 0 - if (!hasNoLotRemaining) return@mapNotNull null + val jobTypeIds = releasedPickOrders.mapNotNull { it.jobOrder?.jobTypeId }.distinct() + val jobTypesById = + if (jobTypeIds.isNotEmpty()) { + jobTypeRepository.findAllById(jobTypeIds).associateBy { it.id } } else { - val hasRemainingOnFloor = floorPickCounts.any { c -> - c.floor == normalizedFloorFilter && (c.totalCount - c.finishedCount) > 0 + emptyMap() + } + val jobOrderPickOrders = releasedPickOrders.mapNotNull { pickOrder -> + println("Processing pick order: ${pickOrder.id}, code: ${pickOrder.code}") + val jobOrder = pickOrder.jobOrder + if (jobOrder == null) { + println("❌ Pick order ${pickOrder.id} has no job order") + return@mapNotNull null + } + if (jobOrder.isHidden == true) { + return@mapNotNull null + } + if (jobOrder.status == JobOrderStatus.COMPLETED) { + return@mapNotNull null + } + + println("Job order found: ${jobOrder.id}, code: ${jobOrder.code}") + + val bom = jobOrder.bom + + // 按 bom.type 过滤:null / blank 表示不过滤(全部) + val normalizedType = bomType?.trim()?.lowercase()?.takeIf { it.isNotBlank() } + if (normalizedType != null) { + val currentType = bom?.type?.trim()?.lowercase() + if (currentType != normalizedType) return@mapNotNull null + } + println("BOM found: ${bom?.id}") + + val item = bom?.item + if (item == null) { + println("❌ BOM ${bom?.id} has no item") + return@mapNotNull null + } + + println("Item found: ${item.id}, name: ${item.name}") + + val uom = bom.outputQtyUom + if (uom == null) { + println("❌ BOM ${bom.id} has no uom") + return@mapNotNull null + } + + // println("UOM found: ${uom.id}, code: ${uom.code}") + + val poId = pickOrder.id ?: return@mapNotNull null + val pickOrderLines = linesByPickOrderId[poId].orEmpty() + val finishedLines = pickOrderLines.count { it.status == PickOrderLineStatus.COMPLETED } + val jobOrderType = jobOrder.jobTypeId?.let { jobTypesById[it] } + val joId = jobOrder.id ?: return@mapNotNull null + val lotNo = lotNoByJobOrderId[joId] + println("✅ Building response for pick order ${pickOrder.id}") + val floorPickCounts = floorCountsByPickOrderId[pickOrder.id]?.map { row -> + com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto( + floor = normalizeFloor((row["floorKey"] as? String).orEmpty()), + finishedCount = (row["finishedCount"] as? Number)?.toInt() ?: 0, + totalCount = (row["totalCount"] as? Number)?.toInt() ?: 0 + ) + }.orEmpty() + + val noLotRow = noLotCountsByPickOrderId[pickOrder.id] + val noLotPickCount = if (noLotRow != null) { + com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto( + floor = "NO_LOT", + finishedCount = (noLotRow["finishedCount"] as? Number)?.toInt() ?: 0, + totalCount = (noLotRow["totalCount"] as? Number)?.toInt() ?: 0 + ) + } else null + + // Backend filtering by floor: + // - When selecting a specific floor (2F/3F/4F), show ONLY remaining on that floor. + // - When selecting NO_LOT, show ONLY remaining for no-lot items. + if (normalizedFloorFilter != null) { + if (normalizedFloorFilter == "NO_LOT") { + val hasNoLotRemaining = (noLotPickCount?.let { it.totalCount - it.finishedCount } ?: 0) > 0 + if (!hasNoLotRemaining) return@mapNotNull null + } else { + val hasRemainingOnFloor = floorPickCounts.any { c -> + c.floor == normalizedFloorFilter && (c.totalCount - c.finishedCount) > 0 + } + if (!hasRemainingOnFloor) return@mapNotNull null } - if (!hasRemainingOnFloor) return@mapNotNull null } + + val suggestedFailCount = suggestedFailCountByPickOrderId[pickOrder.id] ?: 0 + AllJoPickOrderResponse( + id = pickOrder.id ?: 0L, + pickOrderId = pickOrder.id, + pickOrderCode = pickOrder.code, + jobOrderId = jobOrder.id, + jobOrderCode = jobOrder.code, + jobOrderTypeId = jobOrder.jobTypeId, + jobOrderType = jobOrderType?.name, + itemId = item.id ?: 0L, + itemName = item.name ?: "", + reqQty = jobOrder.reqQty ?: BigDecimal.ZERO, + //uomId = bom.outputQtyUom?.id : 0L, + uomId = 0, + uomName = bom?.outputQtyUom ?: "", + lotNo = lotNo, + jobOrderStatus = jobOrder.status?.value ?: "", + finishedPickOLineCount = finishedLines, + floorPickCounts = floorPickCounts, + noLotPickCount = noLotPickCount, + suggestedFailCount = suggestedFailCount + ) } - val suggestedFailCount = suggestedFailCountByPickOrderId[pickOrder.id] ?: 0 - AllJoPickOrderResponse( - id = pickOrder.id ?: 0L, - pickOrderId = pickOrder.id, - pickOrderCode = pickOrder.code, - jobOrderId = jobOrder.id, - jobOrderCode = jobOrder.code, - jobOrderTypeId = jobOrder.jobTypeId, - jobOrderType = jobOrderType?.name, - itemId = item.id ?: 0L, - itemName = item.name ?: "", - reqQty = jobOrder.reqQty ?: BigDecimal.ZERO, - //uomId = bom.outputQtyUom?.id : 0L, - uomId = 0, - uomName = bom?.outputQtyUom?: "", - lotNo = lotNo, - jobOrderStatus = jobOrder.status?.value ?: "", - finishedPickOLineCount = finishedLines, - floorPickCounts = floorPickCounts, - noLotPickCount = noLotPickCount, - suggestedFailCount = suggestedFailCount - ) + println("Returning ${jobOrderPickOrders.size} released job order pick orders") + jobOrderPickOrders.sortedByDescending { it.id } + } catch (e: Exception) { + println("❌ Error in getAllJoPickOrders: ${e.message}") + e.printStackTrace() + emptyList() } - - println("Returning ${jobOrderPickOrders.size} released job order pick orders") - jobOrderPickOrders.sortedByDescending { it.id } - } catch (e: Exception) { - println("❌ Error in getAllJoPickOrders: ${e.message}") - e.printStackTrace() - emptyList() } -} -open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLotsHierarchicalResponse { - println("=== getJobOrderLotsHierarchicalByPickOrderId ===") - println("pickOrderId: $pickOrderId") - - return try { - val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) - if (pickOrder == null || pickOrder.deleted == true) { - println("❌ Pick order $pickOrderId not found or deleted") - return JobOrderLotsHierarchicalResponse( + + open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLotsHierarchicalResponse { + println("=== getJobOrderLotsHierarchicalByPickOrderId ===") + println("pickOrderId: $pickOrderId") + + return try { + val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) + if (pickOrder == null || pickOrder.deleted == true) { + println("❌ Pick order $pickOrderId not found or deleted") + return JobOrderLotsHierarchicalResponse( + pickOrder = PickOrderInfoResponse( + id = null, + code = null, + consoCode = null, + targetDate = null, + type = null, + status = null, + assignTo = null, + jobOrder = JobOrderBasicInfoResponse(0, "", "") + ), + pickOrderLines = emptyList() + ) + } + + val jobOrder = pickOrder.jobOrder ?: return JobOrderLotsHierarchicalResponse( pickOrder = PickOrderInfoResponse( id = null, code = null, @@ -1996,349 +2134,336 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLo ), pickOrderLines = emptyList() ) - } - - val jobOrder = pickOrder.jobOrder ?: return JobOrderLotsHierarchicalResponse( - pickOrder = PickOrderInfoResponse( - id = null, - code = null, - consoCode = null, - targetDate = null, - type = null, - status = null, - assignTo = null, - jobOrder = JobOrderBasicInfoResponse(0, "", "") - ), - pickOrderLines = emptyList() - ) - - // ✅ 添加数据获取逻辑(从原始 Map 版本复制) - // 获取 pick order lines - val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id!!) - .filter { it.deleted == false } - // Pre-calculate total available qty per item (for UI display). - // Note: we align with pick suggestion logic: AVAILABLE status, not expired, remaining > 0. - val itemIds = pickOrderLines.mapNotNull { it.item?.id }.distinct() - val today = LocalDate.now() - val totalAvailableQtyByItemId: Map = if (itemIds.isNotEmpty()) { - inventoryLotLineRepository.findAllByItemIdIn(itemIds) - .asSequence() - .filter { it.deleted == false } - .filter { it.status == InventoryLotLineStatus.AVAILABLE } // entity enum - .filter { ill -> - val exp = ill.inventoryLot?.expiryDate - exp == null || !exp.isBefore(today) - } - .mapNotNull { ill -> - val iid = ill.inventoryLot?.item?.id ?: return@mapNotNull null - val remaining = (ill.inQty ?: BigDecimal.ZERO) - .minus(ill.outQty ?: BigDecimal.ZERO) - .minus(ill.holdQty ?: BigDecimal.ZERO) - if (remaining > BigDecimal.ZERO) iid to remaining else null - } - .groupBy({ it.first }, { it.second }) - .mapValues { (_, qtys) -> qtys.fold(BigDecimal.ZERO) { acc, q -> acc + q }.toDouble() } - } else { - emptyMap() - } - - // 获取所有 pick order line IDs - val pickOrderLineIds = pickOrderLines.map { it.id!! } - - // 获取 suggested pick lots - val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) { - suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) - .filter { it.deleted == false } - } else { - emptyList() - } - - // 获取所有 inventory lot line IDs - val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id } - - // 获取 inventory lot lines - val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) { - inventoryLotLineRepository.findAllByIdIn(inventoryLotLineIds) - .filter { it.deleted == false } - } else { - emptyList() - } - - // 获取 inventory lots - val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct() - val inventoryLots = if (inventoryLotIds.isNotEmpty()) { - inventoryLotRepository.findAllByIdIn(inventoryLotIds) + // ✅ 添加数据获取逻辑(从原始 Map 版本复制) + // 获取 pick order lines + val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id!!) .filter { it.deleted == false } - } else { - emptyList() - } - - // 获取 stock out lines - val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) { - pickOrderLineIds.flatMap { polId -> - inventoryLotLineIds.flatMap { illId -> - stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) - } + + // Pre-calculate total available qty per item (for UI display). + // Note: we align with pick suggestion logic: AVAILABLE status, not expired, remaining > 0. + val itemIds = pickOrderLines.mapNotNull { it.item?.id }.distinct() + val today = LocalDate.now() + val totalAvailableQtyByItemId: Map = if (itemIds.isNotEmpty()) { + inventoryLotLineRepository.findAllByItemIdIn(itemIds) + .asSequence() + .filter { it.deleted == false } + .filter { it.status == InventoryLotLineStatus.AVAILABLE } // entity enum + .filter { ill -> + val exp = ill.inventoryLot?.expiryDate + exp == null || !exp.isBefore(today) + } + .mapNotNull { ill -> + val iid = ill.inventoryLot?.item?.id ?: return@mapNotNull null + val remaining = (ill.inQty ?: BigDecimal.ZERO) + .minus(ill.outQty ?: BigDecimal.ZERO) + .minus(ill.holdQty ?: BigDecimal.ZERO) + if (remaining > BigDecimal.ZERO) iid to remaining else null + } + .groupBy({ it.first }, { it.second }) + .mapValues { (_, qtys) -> qtys.fold(BigDecimal.ZERO) { acc, q -> acc + q }.toDouble() } + } else { + emptyMap() } - } else { - emptyList() - } - - // 取得所有 stock out line(含無 lot 情況) - val stockOutLinesByPickOrderLine = pickOrderLineIds.associateWith { polId -> - stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId) - } - // stockouts 可能包含不在 suggestedPickLots 內的 inventoryLotLineId,需補齊以便計算 location/availableQty - val stockOutInventoryLotLineIds = stockOutLinesByPickOrderLine.values - .flatten() - .mapNotNull { it.inventoryLotLineId } - .distinct() + // 获取所有 pick order line IDs + val pickOrderLineIds = pickOrderLines.map { it.id!! } - val stockOutInventoryLotLines = if (stockOutInventoryLotLineIds.isNotEmpty()) { - inventoryLotLineRepository.findAllByIdIn(stockOutInventoryLotLineIds) - .filter { it.deleted == false } - } else { - emptyList() - } + // 获取 suggested pick lots + val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) { + suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) + .filter { it.deleted == false } + } else { + emptyList() + } - val inventoryLotLineById = (inventoryLotLines + stockOutInventoryLotLines) - .filter { it.id != null } - .associateBy { it.id!! } - - // 获取 stock in lines 通过 inventoryLotLineId(用于填充 stockInLineId) - val stockInLinesByInventoryLotLineId = if (inventoryLotLineIds.isNotEmpty()) { - // ✅ 修复:直接使用已加载的 inventoryLotLines 实体获取 stockInLineId - inventoryLotLines.associateBy { it.id!! } - .mapNotNull { (illId, ill) -> - // 通过关系链:InventoryLotLine -> InventoryLot -> StockInLine -> id - ill.inventoryLot?.stockInLine?.id?.let { stockInLineId -> - illId to stockInLineId + // 获取所有 inventory lot line IDs + val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id } + + // 获取 inventory lot lines + val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) { + inventoryLotLineRepository.findAllByIdIn(inventoryLotLineIds) + .filter { it.deleted == false } + } else { + emptyList() + } + + // 获取 inventory lots + val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct() + val inventoryLots = if (inventoryLotIds.isNotEmpty()) { + inventoryLotRepository.findAllByIdIn(inventoryLotIds) + .filter { it.deleted == false } + } else { + emptyList() + } + + // 获取 stock out lines + val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) { + pickOrderLineIds.flatMap { polId -> + inventoryLotLineIds.flatMap { illId -> + stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) } } - .toMap() - } else { - emptyMap() - } - - // 获取 jo_pick_order 记录 - val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!) - - // 构建 pick order info - val pickOrderInfo = PickOrderInfoResponse( - id = pickOrder.id, - code = pickOrder.code, - consoCode = pickOrder.consoCode, - targetDate = pickOrder.targetDate?.let { - "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" - }, - type = pickOrder.type?.value, - status = pickOrder.status?.value, - assignTo = pickOrder.assignTo?.id, - jobOrder = JobOrderBasicInfoResponse( - id = jobOrder.id!!, - code = jobOrder.code ?: "", - name = "Job Order ${jobOrder.code}" - ) - ) - - // 构建 pick order lines with lots - val pickOrderLinesResult = pickOrderLines.map { pol -> - val item = pol.item - val uom = pol.uom - val lineId = pol.id!! - val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id } - val jpo = joPickOrders.firstOrNull { it.itemId == item?.id } - val handlerName = jpo?.handledBy?.let { uid -> - userService.find(uid).orElse(null)?.name + } else { + emptyList() } - // 构建 lots 数据 - val lots = lineSuggestedLots.mapNotNull { spl -> - val ill = spl.suggestedLotLine - if (ill == null || ill.deleted == true) return@mapNotNull null - - val il = ill.inventoryLot - if (il == null || il.deleted == true) return@mapNotNull null - - val warehouse = ill.warehouse - val sol = stockOutLines.firstOrNull { - it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id - } + + // 取得所有 stock out line(含無 lot 情況) + val stockOutLinesByPickOrderLine = pickOrderLineIds.associateWith { polId -> + stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId) + } + + // stockouts 可能包含不在 suggestedPickLots 內的 inventoryLotLineId,需補齊以便計算 location/availableQty + val stockOutInventoryLotLineIds = stockOutLinesByPickOrderLine.values + .flatten() + .mapNotNull { it.inventoryLotLineId } + .distinct() + + val stockOutInventoryLotLines = if (stockOutInventoryLotLineIds.isNotEmpty()) { + inventoryLotLineRepository.findAllByIdIn(stockOutInventoryLotLineIds) + .filter { it.deleted == false } + } else { + emptyList() + } + + val inventoryLotLineById = (inventoryLotLines + stockOutInventoryLotLines) + .filter { it.id != null } + .associateBy { it.id!! } + + // 获取 stock in lines 通过 inventoryLotLineId(用于填充 stockInLineId) + val stockInLinesByInventoryLotLineId = if (inventoryLotLineIds.isNotEmpty()) { + // ✅ 修复:直接使用已加载的 inventoryLotLines 实体获取 stockInLineId + inventoryLotLines.associateBy { it.id!! } + .mapNotNull { (illId, ill) -> + // 通过关系链:InventoryLotLine -> InventoryLot -> StockInLine -> id + ill.inventoryLot?.stockInLine?.id?.let { stockInLineId -> + illId to stockInLineId + } + } + .toMap() + } else { + emptyMap() + } + + // 获取 jo_pick_order 记录 + val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!) + + // 构建 pick order info + val pickOrderInfo = PickOrderInfoResponse( + id = pickOrder.id, + code = pickOrder.code, + consoCode = pickOrder.consoCode, + targetDate = pickOrder.targetDate?.let { + "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" + }, + type = pickOrder.type?.value, + status = pickOrder.status?.value, + assignTo = pickOrder.assignTo?.id, + jobOrder = JobOrderBasicInfoResponse( + id = jobOrder.id!!, + code = jobOrder.code ?: "", + name = "Job Order ${jobOrder.code}" + ) + ) + + // 构建 pick order lines with lots + val pickOrderLinesResult = pickOrderLines.map { pol -> + val item = pol.item + val uom = pol.uom + val lineId = pol.id!! + val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id } val jpo = joPickOrders.firstOrNull { it.itemId == item?.id } val handlerName = jpo?.handledBy?.let { uid -> userService.find(uid).orElse(null)?.name } - println("handlerName: $handlerName") - val availableQty = if (sol?.status == "rejected") { - null - } else { - (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty ?: BigDecimal.ZERO) - } - - val lotAvailability = when { - il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired" - sol?.status == "rejected" -> "rejected" - ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable" - availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" - else -> "available" - } - - val processingStatus = when (sol?.status) { - "completed" -> "completed" - "rejected" -> "rejected" - "created" -> "pending" - else -> "pending" - } - - // 获取 stockInLineId - val stockInLineId = ill.id?.let { illId -> - stockInLinesByInventoryLotLineId[illId] + // 构建 lots 数据 + val lots = lineSuggestedLots.mapNotNull { spl -> + val ill = spl.suggestedLotLine + if (ill == null || ill.deleted == true) return@mapNotNull null + + val il = ill.inventoryLot + if (il == null || il.deleted == true) return@mapNotNull null + + val warehouse = ill.warehouse + val sol = stockOutLines.firstOrNull { + it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id + } + val jpo = joPickOrders.firstOrNull { it.itemId == item?.id } + val handlerName = jpo?.handledBy?.let { uid -> + userService.find(uid).orElse(null)?.name + } + println("handlerName: $handlerName") + val availableQty = if (sol?.status == "rejected") { + null + } else { + (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty + ?: BigDecimal.ZERO) + } + + val lotAvailability = when { + il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired" + sol?.status == "rejected" -> "rejected" + ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable" + availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" + else -> "available" + } + + val processingStatus = when (sol?.status) { + "completed" -> "completed" + "rejected" -> "rejected" + "created" -> "pending" + else -> "pending" + } + + // 获取 stockInLineId + val stockInLineId = ill.id?.let { illId -> + stockInLinesByInventoryLotLineId[illId] + } + + LotDetailResponse( + lotId = ill.id, + lotNo = il.lotNo, + expiryDate = il.expiryDate?.let { + "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" + }, + location = warehouse?.code, + availableQty = availableQty?.toDouble(), + requiredQty = spl.qty?.toDouble() ?: 0.0, + actualPickQty = sol?.qty ?: 0.0, + processingStatus = processingStatus, + lotAvailability = lotAvailability, + pickOrderId = pickOrder.id, + pickOrderCode = pickOrder.code, + pickOrderConsoCode = pickOrder.consoCode, + pickOrderLineId = pol.id, + stockOutLineId = sol?.id, + stockInLineId = stockInLineId, + suggestedPickLotId = spl.id, + stockOutLineQty = sol?.qty ?: 0.0, + stockOutLineStatus = sol?.status, + routerIndex = warehouse?.order?.toString(), + routerArea = warehouse?.code, + routerRoute = warehouse?.code, + uomShortDesc = uom?.udfShortDesc, + matchStatus = jpo?.matchStatus?.value, + matchBy = jpo?.matchBy, + matchQty = jpo?.matchQty?.toDouble() + ) } - - LotDetailResponse( - lotId = ill.id, - lotNo = il.lotNo, - expiryDate = il.expiryDate?.let { - "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" - }, - location = warehouse?.code, - availableQty = availableQty?.toDouble(), - requiredQty = spl.qty?.toDouble() ?: 0.0, - actualPickQty = sol?.qty ?: 0.0, - processingStatus = processingStatus, - lotAvailability = lotAvailability, - pickOrderId = pickOrder.id, - pickOrderCode = pickOrder.code, - pickOrderConsoCode = pickOrder.consoCode, - pickOrderLineId = pol.id, - stockOutLineId = sol?.id, - stockInLineId = stockInLineId, - suggestedPickLotId = spl.id, - stockOutLineQty = sol?.qty ?: 0.0, - stockOutLineStatus = sol?.status, - routerIndex = warehouse?.order?.toString(), - routerArea = warehouse?.code, - routerRoute = warehouse?.code, - uomShortDesc = uom?.udfShortDesc, - matchStatus = jpo?.matchStatus?.value, - matchBy = jpo?.matchBy, - matchQty = jpo?.matchQty?.toDouble() - ) - } - // 构建 stockouts 数据:用于无 suggested lot / noLot 场景也能显示并闭环(submit 0) - val stockouts = (stockOutLinesByPickOrderLine[lineId] ?: emptyList()).map { sol -> - val illId = sol.inventoryLotLineId - val ill = if (illId != null) inventoryLotLineById[illId] else null - val lot = ill?.inventoryLot - val warehouse = ill?.warehouse - val availableQty = if (sol.status == "rejected") { - null - } else if (ill == null || ill.deleted == true) { - null - } else { - (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty ?: BigDecimal.ZERO) + // 构建 stockouts 数据:用于无 suggested lot / noLot 场景也能显示并闭环(submit 0) + val stockouts = (stockOutLinesByPickOrderLine[lineId] ?: emptyList()).map { sol -> + val illId = sol.inventoryLotLineId + val ill = if (illId != null) inventoryLotLineById[illId] else null + val lot = ill?.inventoryLot + val warehouse = ill?.warehouse + val availableQty = if (sol.status == "rejected") { + null + } else if (ill == null || ill.deleted == true) { + null + } else { + (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty + ?: BigDecimal.ZERO) + } + + StockOutLineDetailResponse( + id = sol.id, + status = sol.status, + qty = sol.qty.toDouble(), + lotId = illId, + lotNo = sol.lotNo ?: lot?.lotNo, + location = warehouse?.code, + availableQty = availableQty?.toDouble(), + noLot = (illId == null) + ) } - StockOutLineDetailResponse( - id = sol.id, - status = sol.status, - qty = sol.qty.toDouble(), - lotId = illId, - lotNo = sol.lotNo ?: lot?.lotNo, - location = warehouse?.code, - availableQty = availableQty?.toDouble(), - noLot = (illId == null) + PickOrderLineWithLotsResponse( + id = pol.id!!, + itemId = item?.id, + itemCode = item?.code, + itemName = item?.name, + requiredQty = pol.qty?.toDouble(), + totalAvailableQty = item?.id?.let { totalAvailableQtyByItemId[it] }, + uomCode = uom?.code, + uomDesc = uom?.udfudesc, + status = pol.status?.value, + lots = lots, + stockouts = stockouts, + handler = handlerName ) } - - PickOrderLineWithLotsResponse( - id = pol.id!!, - itemId = item?.id, - itemCode = item?.code, - itemName = item?.name, - requiredQty = pol.qty?.toDouble(), - totalAvailableQty = item?.id?.let { totalAvailableQtyByItemId[it] }, - uomCode = uom?.code, - uomDesc = uom?.udfudesc, - status = pol.status?.value, - lots = lots, - stockouts = stockouts, - handler=handlerName + + return JobOrderLotsHierarchicalResponse( + pickOrder = pickOrderInfo, + pickOrderLines = pickOrderLinesResult + ) + + } catch (e: Exception) { + println("❌ Error in getJobOrderLotsHierarchicalByPickOrderId: ${e.message}") + e.printStackTrace() + return JobOrderLotsHierarchicalResponse( + pickOrder = PickOrderInfoResponse( + id = null, + code = null, + consoCode = null, + targetDate = null, + type = null, + status = null, + assignTo = null, + jobOrder = JobOrderBasicInfoResponse(0, "", "") + ), + pickOrderLines = emptyList() ) } - - return JobOrderLotsHierarchicalResponse( - pickOrder = pickOrderInfo, - pickOrderLines = pickOrderLinesResult - ) - - } catch (e: Exception) { - println("❌ Error in getJobOrderLotsHierarchicalByPickOrderId: ${e.message}") - e.printStackTrace() - return JobOrderLotsHierarchicalResponse( - pickOrder = PickOrderInfoResponse( - id = null, - code = null, - consoCode = null, - targetDate = null, - type = null, - status = null, - assignTo = null, - jobOrder = JobOrderBasicInfoResponse(0, "", "") - ), - pickOrderLines = emptyList() - ) } -} -open fun updateHandledByForItem(pickOrderId: Long, itemId: Long, userId: Long): MessageResponse { - val joPickOrderOpt = joPickOrderRepository.findByPickOrderIdAndItemId(pickOrderId, itemId) - - if (joPickOrderOpt.isEmpty) { - println("⚠️ JoPickOrder not found for pickOrderId: $pickOrderId, itemId: $itemId") - return MessageResponse( - id = 0, - code = "404", - name = "Not Found", - type = "error", - message = "JoPickOrder not found", - errorPosition = "operator" - ) - } + open fun updateHandledByForItem(pickOrderId: Long, itemId: Long, userId: Long): MessageResponse { + val joPickOrderOpt = joPickOrderRepository.findByPickOrderIdAndItemId(pickOrderId, itemId) - val joPickOrder = joPickOrderOpt.get() - if (userId != null && joPickOrder.handledBy != null) { - val existingOperatorId = joPickOrder.handledBy - val newOperatorId = userId - - // 如果不是同一个用户,拒绝更新 - if (existingOperatorId != null && existingOperatorId != newOperatorId) { + if (joPickOrderOpt.isEmpty) { + println("⚠️ JoPickOrder not found for pickOrderId: $pickOrderId, itemId: $itemId") return MessageResponse( - id = joPickOrder.id, - code = "409", - name = "Conflict", + id = 0, + code = "404", + name = "Not Found", type = "error", - message = "This pick order is already assigned to another user", + message = "JoPickOrder not found", errorPosition = "operator" ) } + + val joPickOrder = joPickOrderOpt.get() + if (userId != null && joPickOrder.handledBy != null) { + val existingOperatorId = joPickOrder.handledBy + val newOperatorId = userId + + // 如果不是同一个用户,拒绝更新 + if (existingOperatorId != null && existingOperatorId != newOperatorId) { + return MessageResponse( + id = joPickOrder.id, + code = "409", + name = "Conflict", + type = "error", + message = "This pick order is already assigned to another user", + errorPosition = "operator" + ) + } + } + // Update the displayed operator: jo_pick_order.handled_by + joPickOrder.handledBy = userId + joPickOrderRepository.save(joPickOrder) + // Don't update other fields - only handledBy + + return MessageResponse( + id = joPickOrder.id, + code = null, + name = null, + type = null, + message = "Pick order handled by updated", + errorPosition = null + ) } - // Update the displayed operator: jo_pick_order.handled_by - joPickOrder.handledBy = userId - joPickOrderRepository.save(joPickOrder) - // Don't update other fields - only handledBy - - return MessageResponse( - id = joPickOrder.id, - code = null, - name = null, - type = null, - message = "Pick order handled by updated", - errorPosition = null - ) -} open fun updateRecordHandledByForItem(pickOrderId: Long, itemId: Long, userId: Long): MessageResponse { val joPickOrderRecordOpt = joPickOrderRecordRepository.findByPickOrderIdAndItemId(pickOrderId, itemId) diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt index 1ef78e0..f7369cb 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt @@ -226,12 +226,21 @@ fun recordSecondScanIssue( fun printStockInLabel(@ModelAttribute request: PrintFGStockInLabelRequest){ jobOrderService.printFGStockInLabel(request) } - +/* @GetMapping("/completed-job-order-pick-orders-only") fun getCompletedJobOrderPickOrders(): List> { return joPickOrderService.getCompletedJobOrderPickOrders() } +*/ + @GetMapping("/completed-job-order-pick-orders-only") + fun getCompletedJobOrderPickOrders( + @RequestParam(name = "date", required = false) + @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE) + completedDate: LocalDate?, + ): List> { + return joPickOrderService.getCompletedJobOrderPickOrders(completedDate) + } @GetMapping("/joForPrintQrCode/{date}") fun getJoForPrintQrCode(@PathVariable date: String): List { return joPickOrderService.getJobOrderListForPrintQrCode(LocalDate.parse(date)) diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderLineRepository.kt index e79b31e..cf1df98 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderLineRepository.kt @@ -23,4 +23,12 @@ fun findAllPickOrdersByItemId(@Param("itemId") itemId: Long): List fun findAllByPickOrderId(@Param("pickOrderId") pickOrderId: Long): List fun findAllByPickOrderIdAndDeletedFalse(pickOrderId: Long): List fun findByPickOrderId(pickOrderId: Long): List + +@Query( + """ + SELECT pol FROM PickOrderLine pol + WHERE pol.pickOrder.id IN :pickOrderIds AND pol.deleted = false + """ +) +fun findAllByPickOrderIdInAndDeletedFalse(@Param("pickOrderIds") pickOrderIds: List): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderRepository.kt index b713061..c18b7dd 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderRepository.kt @@ -95,4 +95,21 @@ fun findAllByStatusAndAssignToIsNullAndDeletedFalse(status: PickOrderStatus): Li fun findAllByJobOrder_Id(jobOrderId: Long): List fun findTopByJobOrder_IdOrderByCreatedDesc(jobOrderId: Long): PickOrder? fun findAllByStatusIn(statuses: List): List + +@Query( + """ + SELECT po FROM PickOrder po + WHERE po.status = :status + AND po.deleted = false + AND po.jobOrder IS NOT NULL + AND po.jobOrder.planEnd IS NOT NULL + AND po.jobOrder.planEnd >= :planEndFrom + AND po.jobOrder.planEnd < :planEndToExclusive + """ +) +fun findAllCompletedWithJobOrderPlanEndOnDay( + @Param("status") status: PickOrderStatus, + @Param("planEndFrom") planEndFrom: LocalDateTime, + @Param("planEndToExclusive") planEndToExclusive: LocalDateTime, +): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt index 35f8cfb..30e4dd2 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt @@ -154,4 +154,14 @@ fun findLatestLotNoByPrefix(@Param("prefix") prefix: String): String? @Param("since") since: LocalDateTime, pageable: Pageable, ): List + + + @Query( + """ + SELECT sil FROM StockInLine sil + WHERE sil.jobOrder.id IN :jobOrderIds AND sil.deleted = false + ORDER BY sil.id ASC + """ + ) + fun findAllByJobOrder_IdInAndDeletedFalse(@Param("jobOrderIds") jobOrderIds: List): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt index fe492f7..ec644a4 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt @@ -86,4 +86,13 @@ fun findByInventoryLotLineIdAndStatusAndDeletedFalse( inventoryLotLineId: Long, status: String ): List + +@Query( + """ + SELECT sil FROM StockInLine sil + WHERE sil.jobOrder.id IN :jobOrderIds AND sil.deleted = false + ORDER BY sil.id ASC + """ +) +fun findAllByJobOrder_IdInAndDeletedFalse(@Param("jobOrderIds") jobOrderIds: List): List }