| @@ -366,6 +366,91 @@ open class DeliveryOrderService( | |||||
| } | } | ||||
| } | |||||
| /** | |||||
| * 僅回傳依店鋪/ETA 從 truck 排程**推算後** `truckLanceCode` 為 null 或空白的送貨單(畫面上對應「車線-X」)。 | |||||
| * 與 [searchDoLiteByPage] 帶一般車線關鍵字分開,避免 `車線-X` 在 truck 表無 shopId 時走舊邏輯漏單。 | |||||
| */ | |||||
| open fun searchDoLiteUnassignedTruckByPage( | |||||
| code: String?, | |||||
| shopName: String?, | |||||
| status: String?, | |||||
| estimatedArrivalDate: LocalDateTime?, | |||||
| pageNum: Int?, | |||||
| pageSize: Int?, | |||||
| ): RecordsRes<DeliveryOrderInfoLiteDto> { | |||||
| val page = (pageNum ?: 1) - 1 | |||||
| val size = pageSize ?: 10 | |||||
| val statusEnum = status?.let { s -> DeliveryOrderStatus.entries.find { it.value == s } } | |||||
| val etaStart = estimatedArrivalDate | |||||
| val etaEnd = estimatedArrivalDate?.plusDays(1) | |||||
| val allowedSupplierCodes = listOf("P06B", "P07", "P06D") | |||||
| val allResult = deliveryOrderRepository.searchDoLitePageWithSupplierCodes( | |||||
| code = code?.ifBlank { null }, | |||||
| shopName = shopName?.ifBlank { null }, | |||||
| status = statusEnum, | |||||
| etaStart = etaStart, | |||||
| etaEnd = etaEnd, | |||||
| allowedSupplierCodes = allowedSupplierCodes, | |||||
| pageable = PageRequest.of(0, 100_000), | |||||
| ) | |||||
| val deliveryOrderIds = allResult.content.mapNotNull { it.id } | |||||
| val deliveryOrdersMap = deliveryOrderRepository.findAllById(deliveryOrderIds).associateBy { it.id } | |||||
| val processedRecords = allResult.content.map { info -> | |||||
| val deliveryOrder = deliveryOrdersMap[info.id] | |||||
| val supplierCode = deliveryOrder?.supplier?.code | |||||
| val preferredFloor = when (supplierCode) { | |||||
| "P06B" -> "4F" | |||||
| "P07", "P06D" -> "2F" | |||||
| else -> "2F" | |||||
| } | |||||
| val shop = deliveryOrder?.shop | |||||
| val shopId = shop?.id | |||||
| val infoEta = info.estimatedArrivalDate | |||||
| val calculatedTruckLanceCode = | |||||
| if (deliveryOrder != null && shopId != null && infoEta != null) { | |||||
| val targetDate = infoEta.toLocalDate() | |||||
| val dayAbbr = getDayOfWeekAbbr(targetDate) | |||||
| val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, preferredFloor, dayAbbr) | |||||
| val matchedTruck = if (trucks.isEmpty()) { | |||||
| truckRepository.findByShopIdAndDeletedFalse(shopId) | |||||
| .filter { it.storeId == preferredFloor } | |||||
| .minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | |||||
| } else { | |||||
| trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | |||||
| } | |||||
| matchedTruck?.truckLanceCode | |||||
| } else { | |||||
| null | |||||
| } | |||||
| DeliveryOrderInfoLiteDto( | |||||
| id = info.id, | |||||
| code = info.code, | |||||
| orderDate = info.orderDate, | |||||
| estimatedArrivalDate = info.estimatedArrivalDate, | |||||
| status = info.status, | |||||
| shopName = info.shopName, | |||||
| supplierName = info.supplierName, | |||||
| shopAddress = info.shopAddress, | |||||
| truckLanceCode = calculatedTruckLanceCode, | |||||
| ) | |||||
| }.filter { dto -> dto.truckLanceCode.isNullOrBlank() } | |||||
| val totalCount = processedRecords.size | |||||
| val startIndex = page * size | |||||
| val endIndex = minOf(startIndex + size, processedRecords.size) | |||||
| val paginatedRecords = if (startIndex < processedRecords.size) { | |||||
| processedRecords.subList(startIndex, endIndex) | |||||
| } else { | |||||
| emptyList() | |||||
| } | |||||
| return RecordsRes(paginatedRecords, totalCount) | |||||
| } | } | ||||
| open fun findByM18DataLogId(m18DataLogId: Long): DeliveryOrder? { | open fun findByM18DataLogId(m18DataLogId: Long): DeliveryOrder? { | ||||
| @@ -472,8 +472,8 @@ class DoReleaseCoordinatorService( | |||||
| """.trimIndent() | """.trimIndent() | ||||
| println(" DEBUG: SQL length: ${sql.length} characters") | |||||
| println(" DEBUG: SQL first 500 chars: ${sql.take(500)}") | |||||
| // println(" DEBUG: SQL length: ${sql.length} characters") | |||||
| // println(" DEBUG: SQL first 500 chars: ${sql.take(500)}") | |||||
| val results = jdbcDao.queryForList(sql) | val results = jdbcDao.queryForList(sql) | ||||
| println(" DEBUG: Results type: ${results.javaClass.name}") | println(" DEBUG: Results type: ${results.javaClass.name}") | ||||
| @@ -68,6 +68,22 @@ class DeliveryOrderController( | |||||
| truckLanceCode = request.truckLanceCode | truckLanceCode = request.truckLanceCode | ||||
| ) | ) | ||||
| } | } | ||||
| /** | |||||
| * 僅回傳推算車線為 null/空白之送貨單(對應 UI 車線-X)。請求體與 search-do-lite 相同,**忽略** [SearchDeliveryOrderInfoRequest.truckLanceCode]。 | |||||
| */ | |||||
| @PostMapping("/search-do-lite-unassigned-truck") | |||||
| fun searchDoLiteUnassignedTruck(@RequestBody request: SearchDeliveryOrderInfoRequest): RecordsRes<DeliveryOrderInfoLiteDto> { | |||||
| return deliveryOrderService.searchDoLiteUnassignedTruckByPage( | |||||
| code = request.code, | |||||
| shopName = request.shopName, | |||||
| status = request.status, | |||||
| estimatedArrivalDate = request.estimatedArrivalDate, | |||||
| pageNum = request.pageNum, | |||||
| pageSize = request.pageSize, | |||||
| ) | |||||
| } | |||||
| @GetMapping("/list") | @GetMapping("/list") | ||||
| fun getDoList(): List<DeliveryOrderInfo> { | fun getDoList(): List<DeliveryOrderInfo> { | ||||
| return deliveryOrderService.getDoList(); | return deliveryOrderService.getDoList(); | ||||
| @@ -4385,7 +4385,7 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||||
| val newIll = resolveNewInventoryLotLine(req, polItemId) | val newIll = resolveNewInventoryLotLine(req, polItemId) | ||||
| ?: return MessageResponse( | ?: return MessageResponse( | ||||
| id = null, name = "New lot line not found", code = "ERROR", type = "pickorder", | id = null, name = "New lot line not found", code = "ERROR", type = "pickorder", | ||||
| message = "Cannot resolve new inventory lot line", errorPosition = null | |||||
| message = "This lot is not yet putaway", errorPosition = null | |||||
| ) | ) | ||||
| val targetUnavailable = newIll.status == InventoryLotLineStatus.UNAVAILABLE | val targetUnavailable = newIll.status == InventoryLotLineStatus.UNAVAILABLE | ||||
| @@ -92,8 +92,11 @@ open class InventoryLotLineService( | |||||
| outQty: BigDecimal?, | outQty: BigDecimal?, | ||||
| holdQty: BigDecimal? | holdQty: BigDecimal? | ||||
| ): InventoryLotLineStatus { | ): InventoryLotLineStatus { | ||||
| val remainingQty = | |||||
| (inQty ?: BigDecimal.ZERO) - (outQty ?: BigDecimal.ZERO) - (holdQty ?: BigDecimal.ZERO) | |||||
| // val remainingQty = | |||||
| // (inQty ?: BigDecimal.ZERO) - (outQty ?: BigDecimal.ZERO) - (holdQty ?: BigDecimal.ZERO) | |||||
| val remainingQty = | |||||
| (inQty ?: BigDecimal.ZERO) - (outQty ?: BigDecimal.ZERO) | |||||
| val status = previousStatus | val status = previousStatus | ||||
| val qtyStatus = | val qtyStatus = | ||||
| if (remainingQty > BigDecimal.ZERO) InventoryLotLineStatus.AVAILABLE else InventoryLotLineStatus.UNAVAILABLE | if (remainingQty > BigDecimal.ZERO) InventoryLotLineStatus.AVAILABLE else InventoryLotLineStatus.UNAVAILABLE | ||||
| @@ -328,7 +328,7 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||||
| """.trimIndent() | """.trimIndent() | ||||
| val result = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to pickOrderLineId)) | val result = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to pickOrderLineId)) | ||||
| println("SQL result: $result") | |||||
| // println("SQL result: $result") | |||||
| if (result.isEmpty()) { | if (result.isEmpty()) { | ||||
| throw IllegalArgumentException("No StockOut found for pickOrderLineId: $pickOrderLineId. Check if pick order line exists and has associated stock out.") | throw IllegalArgumentException("No StockOut found for pickOrderLineId: $pickOrderLineId. Check if pick order line exists and has associated stock out.") | ||||
| @@ -338,7 +338,7 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||||
| val consoPickOrderCode = result[0]["consoPickOrderCode"] as? String | val consoPickOrderCode = result[0]["consoPickOrderCode"] as? String | ||||
| val consoCode = result[0]["consoCode"] as? String | val consoCode = result[0]["consoCode"] as? String | ||||
| println("Found stockOutId: $stockOutId, consoPickOrderCode: $consoPickOrderCode, consoCode: $consoCode") | |||||
| //println("Found stockOutId: $stockOutId, consoPickOrderCode: $consoPickOrderCode, consoCode: $consoCode") | |||||
| if (stockOutId == null) { | if (stockOutId == null) { | ||||
| throw IllegalArgumentException("StockOut ID is null for pickOrderLineId: $pickOrderLineId. ConsoCode: $consoCode, ConsoPickOrderCode: $consoPickOrderCode") | throw IllegalArgumentException("StockOut ID is null for pickOrderLineId: $pickOrderLineId. ConsoCode: $consoCode, ConsoPickOrderCode: $consoPickOrderCode") | ||||
| @@ -382,11 +382,7 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||||
| val allStockOutLines = stockOutLineRepository | val allStockOutLines = stockOutLineRepository | ||||
| .findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId) | .findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId) | ||||
| println("=== checkIsStockOutLineCompleted for pickOrderLineId: $pickOrderLineId ===") | |||||
| println("Total stock out lines: ${allStockOutLines.size}") | |||||
| allStockOutLines.forEach { sol -> | |||||
| println(" StockOutLine ${sol.id}: status=${sol.status}, qty=${sol.qty}") | |||||
| } | |||||
| // 计算当前行的需求数量 | // 计算当前行的需求数量 | ||||
| val pickOrderLine = pickOrderLineRepository.findById(pickOrderLineId).orElse(null) | val pickOrderLine = pickOrderLineRepository.findById(pickOrderLineId).orElse(null) | ||||
| @@ -413,11 +409,11 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||||
| acc + (issue.issueQty ?: BigDecimal.ZERO) | acc + (issue.issueQty ?: BigDecimal.ZERO) | ||||
| } | } | ||||
| } catch (e: Exception) { | } catch (e: Exception) { | ||||
| println("⚠️ Error fetching issues for pickOrderLineId $pickOrderLineId: ${e.message}") | |||||
| println(" Error fetching issues for pickOrderLineId $pickOrderLineId: ${e.message}") | |||||
| BigDecimal.ZERO | BigDecimal.ZERO | ||||
| } | } | ||||
| println(" totalPickedQty = $totalPickedQty, totalIssueQty = $totalIssueQty, requiredQty = $requiredQty") | |||||
| // println(" totalPickedQty = $totalPickedQty, totalIssueQty = $totalIssueQty, requiredQty = $requiredQty") | |||||
| val unfinishedLine = allStockOutLines.filter { | val unfinishedLine = allStockOutLines.filter { | ||||
| val rawStatus = it.status?.trim() | val rawStatus = it.status?.trim() | ||||