diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrder.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrder.kt index f7c20f9..8b80fd4 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrder.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrder.kt @@ -93,6 +93,8 @@ class DoPickOrder { @Column(name = "handler_name", length = 100) var handlerName: String? = null + @Column(name = "release_type", length = 100) + var releaseType: String? = null // Default constructor for Hibernate constructor() @@ -119,6 +121,7 @@ class DoPickOrder { pickOrderCode: String? = null, deliveryOrderCode: String? = null, loadingSequence: Int? = null, + releaseType: String? = null ) { this.storeId = storeId this.ticketNo = ticketNo @@ -141,5 +144,6 @@ class DoPickOrder { this.pickOrderCode = pickOrderCode this.deliveryOrderCode = deliveryOrderCode this.loadingSequence = loadingSequence + this.releaseType = releaseType } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt index 3283d20..2c91be0 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt @@ -94,6 +94,8 @@ class DoPickOrderRecord { var deleted: Boolean = false @Column(name = "handler_name", length = 100) var handlerName: String? = null +@Column(name = "release_type", length = 100) +var releaseType: String? = null // Default constructor for Hibernate constructor() diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt index b41a34e..cff3b77 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt @@ -39,5 +39,10 @@ fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( @Param("startDate") startDate: LocalDate, @Param("endDate") endDate: LocalDate ): List - + fun findByShopIdAndStoreIdAndReleaseTypeAndTicketStatusAndDeletedFalse( + shopId: Long?, + storeId: String, + releaseType: String, + ticketStatus: DoPickOrderStatus + ): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderQueryService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderQueryService.kt index 9f607a7..4643830 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderQueryService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderQueryService.kt @@ -18,9 +18,9 @@ class DoPickOrderQueryService( private val doPickOrderRecordRepository: DoPickOrderRecordRepository ) { - fun getSummaryByStore(storeId: String, requiredDate: LocalDate?): StoreLaneSummary { + fun getSummaryByStore(storeId: String, requiredDate: LocalDate?, releaseType: String): StoreLaneSummary { val targetDate = requiredDate ?: LocalDate.now() - println("🔍 DEBUG: Getting summary for store=$storeId, date=$targetDate") + println("🔍 DEBUG: Getting summary for store=$storeId, date=$targetDate, releaseType=$releaseType") val actualStoreId = when (storeId) { "2/F" -> "2/F" @@ -35,6 +35,13 @@ class DoPickOrderQueryService( listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed) ) + // 根据 releaseType 过滤 activeRecords + val filteredActiveRecordsByReleaseType = when (releaseType.lowercase()) { + "batch" -> activeRecords.filter { it.releaseType == "batch" } + "single" -> activeRecords.filter { it.releaseType == "single" } + else -> activeRecords // "all" 或其他值,不过滤 + } + // Query completed records from do_pick_order_record table val completedRecords = doPickOrderRecordRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( actualStoreId, @@ -42,11 +49,20 @@ class DoPickOrderQueryService( listOf(DoPickOrderStatus.completed) ) + // 根据 releaseType 过滤 completedRecords + val filteredCompletedRecordsByReleaseType = when (releaseType.lowercase()) { + "batch" -> completedRecords.filter { it.releaseType == "batch" } + "single" -> completedRecords.filter { it.releaseType == "single" } + else -> completedRecords // "all" 或其他值,不过滤 + } + println("🔍 DEBUG: Found ${activeRecords.size} active records for date $targetDate") + println("🔍 DEBUG: After releaseType filter: ${filteredActiveRecordsByReleaseType.size} active records") println("🔍 DEBUG: Found ${completedRecords.size} completed records for date $targetDate") + println("🔍 DEBUG: After releaseType filter: ${filteredCompletedRecordsByReleaseType.size} completed records") // Filter active records (check for non-issue lines) - val filteredActiveRecords = activeRecords.filter { doPickOrder -> + val filteredActiveRecords = filteredActiveRecordsByReleaseType.filter { doPickOrder -> val hasNonIssueLines = checkDoPickOrderHasNonIssueLines(doPickOrder.id!!) if (!hasNonIssueLines) { println("🔍 DEBUG: Filtering out DoPickOrder ${doPickOrder.id} - all lines are issues") @@ -55,7 +71,7 @@ class DoPickOrderQueryService( } // For completed records, check if they have non-issue lines in the record table - val filteredCompletedRecords = completedRecords.filter { record -> + val filteredCompletedRecords = filteredCompletedRecordsByReleaseType.filter { record -> val hasNonIssueLines = checkDoPickOrderRecordHasNonIssueLines(record.id!!) if (!hasNonIssueLines) { println("🔍 DEBUG: Filtering out DoPickOrderRecord ${record.id} - all lines are issues") diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt index a1b209b..b95e4ec 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt @@ -91,7 +91,7 @@ open class DoPickOrderService( return RecordsRes(records, total.toInt()); } open fun getNextTicketNumber(datePrefix: String, storeId: String): String { - println("🔍 DEBUG: Getting next ticket number for date prefix: $datePrefix, store: $storeId") + println(" DEBUG: Getting next ticket number for date prefix: $datePrefix, store: $storeId") try { val sanitizedStoreId = storeId.replace("/", "") val shortDatePrefix = if (datePrefix.length == 8) { @@ -102,14 +102,14 @@ open class DoPickOrderService( // 修改搜索模式为新格式 val searchPattern = "TI-${shortDatePrefix}-${sanitizedStoreId}-" // T-20250915-4F- val todayTickets = doPickOrderRepository.findByTicketNoStartingWith(searchPattern) - println("🔍 DEBUG: Found ${todayTickets.size} existing tickets with prefix $searchPattern") + println(" DEBUG: Found ${todayTickets.size} existing tickets with prefix $searchPattern") todayTickets.forEach { ticket -> - println("🔍 DEBUG: Existing ticket: ${ticket.ticketNo}, Status: ${ticket.ticketStatus}") + println(" DEBUG: Existing ticket: ${ticket.ticketNo}, Status: ${ticket.ticketStatus}") } val nextNumber = (todayTickets.size + 1).toString().padStart(3, '0') // 修改生成格式 val ticketNumber = "TI-${datePrefix}-${sanitizedStoreId}-${nextNumber}" // T-20250915-4F-001 - println("🔍 DEBUG: Generated ticket number: $ticketNumber") + println(" DEBUG: Generated ticket number: $ticketNumber") return ticketNumber } catch (e: Exception) { println("❌ ERROR in getNextTicketNumber: ${e.message}") @@ -297,41 +297,49 @@ open class DoPickOrderService( } return doPickOrderRepository.saveAll(doPickOrders) } - fun getSummaryByStore(storeId: String, requiredDate: LocalDate?): StoreLaneSummary { + fun getSummaryByStore(storeId: String, requiredDate: LocalDate?, releaseType: String): StoreLaneSummary { val targetDate = requiredDate ?: LocalDate.now() - println("🔍 DEBUG: Getting summary for store=$storeId, date=$targetDate") + println(" DEBUG: Getting summary for store=$storeId, date=$targetDate") val actualStoreId = when (storeId) { "2/F" -> "2/F" "4/F" -> "4/F" else -> storeId } - + val allRecords = doPickOrderRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( actualStoreId, targetDate, listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed) ) - + val filteredByReleaseType = when (releaseType.lowercase()) { + "batch" -> allRecords.filter { it.releaseType == "batch" } + "single" -> allRecords.filter { it.releaseType == "single" } + else -> allRecords // "all" 或其他值,不过滤 + } // 添加 finishedRecords 查询 val finishedRecords = doPickOrderRecordRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( actualStoreId, targetDate, listOf(DoPickOrderStatus.completed) ) + val filteredFinishedRecords = when (releaseType.lowercase()) { + "batch" -> finishedRecords.filter { it.releaseType == "batch" } + "single" -> finishedRecords.filter { it.releaseType == "single" } + else -> finishedRecords // "all" 或其他值,不过滤 + } + println(" DEBUG: Found ${allRecords.size} records for date $targetDate") + println(" DEBUG: Found ${finishedRecords.size} finished records for date $targetDate") - println("🔍 DEBUG: Found ${allRecords.size} records for date $targetDate") - println("🔍 DEBUG: Found ${finishedRecords.size} finished records for date $targetDate") - - val filteredRecords = allRecords.filter { doPickOrder -> + val filteredRecords = filteredByReleaseType.filter { doPickOrder -> val hasNonIssueLines = checkDoPickOrderHasNonIssueLines(doPickOrder.id!!) if (!hasNonIssueLines) { - println("🔍 DEBUG: Filtering out DoPickOrder ${doPickOrder.id} - all lines are issues") + println(" DEBUG: Filtering out DoPickOrder ${doPickOrder.id} - all lines are issues") } hasNonIssueLines } - println("🔍 DEBUG: After filtering, ${filteredRecords.size} records remain") + println(" DEBUG: After filtering, ${filteredRecords.size} records remain") val grouped = filteredRecords.groupBy { it.truckDepartureTime to it.truckLanceCode } .mapValues { (key, list) -> @@ -341,9 +349,9 @@ open class DoPickOrderService( (record.truckDepartureTime == truckDepartureTime) && (record.truckLanceCode == truckLanceCode) } - println("🔍 DEBUG: Group key - truckDepartureTime: $truckDepartureTime, truckLanceCode: $truckLanceCode") - println("🔍 DEBUG: Found ${list.size} active records in this group") - println("🔍 DEBUG: Found $matchingFinishedCount finished records matching this group") + println(" DEBUG: Group key - truckDepartureTime: $truckDepartureTime, truckLanceCode: $truckLanceCode") + println(" DEBUG: Found ${list.size} active records in this group") + println(" DEBUG: Found $matchingFinishedCount finished records matching this group") LaneBtn( truckLanceCode = list.first().truckLanceCode ?: "", unassigned = list.count { it.handledBy == null }, @@ -403,7 +411,7 @@ open class DoPickOrderService( // 3. 只有当所有 lines 都是 "issue" 状态时才过滤掉 val hasNonIssueLines = nonIssueLines > 0 - println("🔍 DEBUG: DoPickOrder $doPickOrderId - Total lines: $totalLines, Non-issue lines: $nonIssueLines, Has non-issue lines: $hasNonIssueLines") + println(" DEBUG: DoPickOrder $doPickOrderId - Total lines: $totalLines, Non-issue lines: $nonIssueLines, Has non-issue lines: $hasNonIssueLines") return hasNonIssueLines @@ -426,7 +434,7 @@ open class DoPickOrderService( "4/F" -> "4/F" // 保持原格式 else -> request.storeId } - println("🔍 DEBUG: assignByLane - Converting storeId from '${request.storeId}' to '$actualStoreId'") + println(" DEBUG: assignByLane - Converting storeId from '${request.storeId}' to '$actualStoreId'") val candidates = doPickOrderRepository .findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc( @@ -453,7 +461,7 @@ open class DoPickOrderService( // 关键修改:获取这个 do_pick_order 下的所有 pick orders 并分配给用户 val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(firstOrder.id!!) - println("🔍 DEBUG: Found ${doPickOrderLines.size} pick orders in do_pick_order ${firstOrder.id}") + println(" DEBUG: Found ${doPickOrderLines.size} pick orders in do_pick_order ${firstOrder.id}") doPickOrderLines.forEach { line -> if (line.pickOrderId != null) { @@ -462,7 +470,7 @@ open class DoPickOrderService( pickOrder.assignTo = user pickOrder.status = PickOrderStatus.RELEASED pickOrderRepository.save(pickOrder) - println("🔍 DEBUG: Assigned pick order ${line.pickOrderId} to user ${request.userId}") + println(" DEBUG: Assigned pick order ${line.pickOrderId} to user ${request.userId}") } else { println("⚠️ WARNING: Pick order ${line.pickOrderId} not found") } @@ -481,7 +489,7 @@ open class DoPickOrderService( } if (records.isNotEmpty()) { doPickOrderRecordRepository.saveAll(records) - println("🔍 DEBUG: Updated ${records.size} do_pick_order_record for pick order ${line.pickOrderId}") + println(" DEBUG: Updated ${records.size} do_pick_order_record for pick order ${line.pickOrderId}") } } } diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt index 5694ecd..067a710 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt @@ -157,7 +157,7 @@ class DoReleaseCoordinatorService( ) SELECT dpo2.id, - CONCAT('TI-', + CONCAT('TI-B-', DATE_FORMAT(dpo2.RequiredDeliveryDate, '%Y%m%d'), '-', REPLACE(COALESCE(dpo2.store_id, ts.preferred_floor, '2F'), '/', ''), @@ -212,6 +212,14 @@ class DoReleaseCoordinatorService( AND w.store_id IN ('2F', '4F') WHERE dol.deleted = 0 AND dol.deliveryOrderId IN (${ids.joinToString(",")}) + AND dol.deliveryOrderId NOT IN ( + SELECT DISTINCT dpol.do_order_id + FROM fpsmsdb.do_pick_order_line dpol + INNER JOIN fpsmsdb.do_pick_order dpo ON dpo.id = dpol.do_pick_order_id + WHERE dpo.release_type = 'single' + AND dpo.deleted = 0 + AND dpol.deleted = 0 + ) GROUP BY dol.deliveryOrderId, w.store_id ), DoFloorSummary AS ( @@ -507,7 +515,9 @@ class DoReleaseCoordinatorService( truckLanceCode = first.truckLanceCode, shopCode = first.shopCode, shopName = first.shopName, - requiredDeliveryDate = first.estimatedArrivalDate + requiredDeliveryDate = first.estimatedArrivalDate, + releaseType = "batch" + ) // 直接使用 doPickOrderRepository.save() 而不是 doPickOrderService.save() @@ -616,4 +626,206 @@ class DoReleaseCoordinatorService( ) ) } + + fun startBatchReleaseAsyncSingle(doId: Long, userId: Long): MessageResponse { + val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId) + ?: return MessageResponse( + id = null, code = "NOT_FOUND", name = null, type = null, + message = "Delivery Order not found", errorPosition = null, entity = null + ) + + executor.submit { + try { + println("📦 Starting single release for DO $doId") + + // 调用 releaseDeliveryOrderWithoutTicket 创建 pick order + val result = deliveryOrderService.releaseDeliveryOrderWithoutTicket( + ReleaseDoRequest(id = doId, userId = userId) + ) + + // 确定 storeId + val storeId = when (result.preferredFloor) { + "2F" -> "2/F" + "4F" -> "4/F" + else -> "2/F" + } + + // 查找是否已有相同 shop、storeId 和 releaseType='single' 的 do_pick_order + val existingDoPickOrder = doPickOrderRepository + .findByShopIdAndStoreIdAndReleaseTypeAndTicketStatusAndDeletedFalse( + result.shopId, + storeId, + "single", + DoPickOrderStatus.pending + ) + .firstOrNull { + it.requiredDeliveryDate == result.estimatedArrivalDate && + it.truckDepartureTime == result.truckDepartureTime && + it.truckLanceCode == result.truckLanceCode + } + + if (existingDoPickOrder != null) { + // 如果已存在,创建 do_pick_order_line 关联到已存在的 do_pick_order + val existingLine = doPickOrderLineRepository + .findByPickOrderIdAndDeletedFalse(result.pickOrderId) + .firstOrNull { it.doPickOrderId == existingDoPickOrder.id } + + if (existingLine == null) { + val line = DoPickOrderLine().apply { + doPickOrderId = existingDoPickOrder.id + pickOrderId = result.pickOrderId + doOrderId = result.deliveryOrderId + pickOrderCode = result.pickOrderCode + deliveryOrderCode = result.deliveryOrderCode + status = "pending" + } + doPickOrderLineRepository.save(line) + println("🔍 DEBUG: Created DoPickOrderLine for existing DoPickOrder ${existingDoPickOrder.id}") + } + } else { + // 如果不存在,创建新的 do_pick_order + val doPickOrder = DoPickOrder( + storeId = storeId, + ticketNo = "TEMP-${System.currentTimeMillis()}", + ticketStatus = DoPickOrderStatus.pending, + truckId = result.truckId, + truckDepartureTime = result.truckDepartureTime, + shopId = result.shopId, + handledBy = null, + loadingSequence = result.loadingSequence ?: 999, + ticketReleaseTime = null, + truckLanceCode = result.truckLanceCode, + shopCode = result.shopCode, + shopName = result.shopName, + requiredDeliveryDate = result.estimatedArrivalDate, + releaseType = "single" // 设置为 single + ) + val saved = doPickOrderRepository.save(doPickOrder) + + // 创建 do_pick_order_line + val line = DoPickOrderLine().apply { + doPickOrderId = saved.id + pickOrderId = result.pickOrderId + doOrderId = result.deliveryOrderId + pickOrderCode = result.pickOrderCode + deliveryOrderCode = result.deliveryOrderCode + status = "pending" + } + doPickOrderLineRepository.save(line) + println("🔍 DEBUG: Created new DoPickOrder with releaseType=single") + } + + // 更新 ticket numbers(只更新 single 类型的) + updateSingleTicketNumbers() + + } catch (e: Exception) { + println("❌ Single release exception: ${e.message}") + e.printStackTrace() + } + } + + return MessageResponse( + id = null, code = "STARTED", name = null, type = null, + message = "Single release started", errorPosition = null, + entity = mapOf("doId" to doId) + ) + } + + private fun updateSingleTicketNumbers() { + try { + // 1. 查找所有 TEMP- 开头的 single release type 订单 + val tempTickets = doPickOrderRepository.findAll() + .filter { + it.ticketNo?.startsWith("TEMP-") == true && + it.releaseType == "single" && + !it.deleted + } + + if (tempTickets.isEmpty()) { + println("🔍 No single release type tickets to update") + return + } + + println("🔍 DEBUG: Found ${tempTickets.size} single release type tickets to update") + + // 2. 按日期和 storeId 分组 + val grouped = tempTickets.groupBy { ticket -> + val date = ticket.requiredDeliveryDate + val storeId = ticket.storeId?.replace("/", "") ?: "2F" + Pair(date, storeId) + } + + // 3. 为每个组生成 ticket numbers + grouped.forEach { (dateStorePair, tickets) -> + val (date, storeId) = dateStorePair + + if (date == null) { + println("⚠️ WARNING: Skipping tickets with null requiredDeliveryDate") + return@forEach + } + + // 格式化日期为 YYYYMMDD + val datePrefix = date.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd")) + + // 4. 查询该组已存在的 ticket numbers(包括已生成的,不只是 TEMP-) + val existingTicketPattern = "TI-S-$datePrefix-$storeId-" + val existingTickets = doPickOrderRepository.findAll() + .filter { + it.ticketNo?.startsWith(existingTicketPattern) == true && + it.releaseType == "single" && + !it.deleted && + it.requiredDeliveryDate == date && + it.storeId?.replace("/", "") == storeId + } + .mapNotNull { it.ticketNo } + + // 提取已存在的序号 + val existingNumbers = existingTickets.mapNotNull { ticketNo -> + val parts = ticketNo.split("-") + if (parts.size >= 4) { + parts.lastOrNull()?.toIntOrNull() + } else null + }.toMutableSet() + + println("🔍 DEBUG: Group ($date, $storeId) - Existing ticket numbers: $existingTickets, Existing numbers: $existingNumbers") + + // 5. 在组内按 truckDepartureTime, truckLanceCode, loadingSequence, doOrderId 排序 + val sortedTickets = tickets.sortedWith( + compareBy( + { it.truckDepartureTime ?: java.time.LocalTime.of(23, 59, 59) }, + { it.truckLanceCode ?: "ZZ" }, + { it.loadingSequence ?: 999 }, + { it.doOrderId ?: 0L } + ) + ) + + // 6. 为每个订单生成序号并更新 ticket_no(跳过已存在的序号) + var nextNumber = 1 + sortedTickets.forEach { ticket -> + // 找到下一个可用的序号 + while (existingNumbers.contains(nextNumber)) { + nextNumber++ + } + + val sequenceNumber = nextNumber.toString().padStart(3, '0') + val newTicketNo = "TI-S-$datePrefix-$storeId-$sequenceNumber" + + ticket.ticketNo = newTicketNo + doPickOrderRepository.save(ticket) + + // 将新生成的序号添加到已存在集合中,避免同一批次内重复 + existingNumbers.add(nextNumber) + nextNumber++ + + println("🔍 DEBUG: Updated ticket ${ticket.id} to $newTicketNo") + } + } + + println("✅ Updated ${tempTickets.size} single release type ticket numbers") + + } catch (e: Exception) { + println("❌ Error updating single ticket numbers: ${e.message}") + e.printStackTrace() + } + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt index 788f88b..8cda2e0 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt @@ -69,27 +69,32 @@ class DoPickOrderController( @GetMapping("/summary-by-store") fun getSummaryByStore( @RequestParam storeId: String, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) requiredDate: LocalDate? + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) requiredDate: LocalDate?, + @RequestParam releaseType: String? ): StoreLaneSummary { - return doPickOrderQueryService.getSummaryByStore(storeId, requiredDate) + return doPickOrderQueryService.getSummaryByStore(storeId, requiredDate, releaseType?: "ALL") } @PostMapping("/assign-by-lane") fun assignByLane(@RequestBody request: AssignByLaneRequest): MessageResponse { return doPickOrderAssignmentService.assignByLane(request) // 使用新的 Service } -@PostMapping("/batch-release/async") -fun startBatchReleaseAsync( - @RequestBody ids: List, - @RequestParam(defaultValue = "1") userId: Long -): MessageResponse { - return doReleaseCoordinatorService.startBatchReleaseAsync(ids, userId) -} - -@GetMapping("/batch-release/progress/{jobId}") -fun getBatchReleaseProgress(@PathVariable jobId: String): MessageResponse { - return doReleaseCoordinatorService.getBatchReleaseProgress(jobId) -} + @PostMapping("/batch-release/async") + fun startBatchReleaseAsync( + @RequestBody ids: List, + @RequestParam(defaultValue = "1") userId: Long + ): MessageResponse { + return doReleaseCoordinatorService.startBatchReleaseAsync(ids, userId) + } + @PostMapping("/batch-release/async-single") + fun startBatchReleaseAsyncSingle( @RequestBody doId: Long, @RequestParam(defaultValue = "1") userId: Long + ): MessageResponse { + return doReleaseCoordinatorService.startBatchReleaseAsyncSingle(doId, userId) + } + @GetMapping("/batch-release/progress/{jobId}") + fun getBatchReleaseProgress(@PathVariable jobId: String): MessageResponse { + return doReleaseCoordinatorService.getBatchReleaseProgress(jobId) + } @GetMapping("/ticket-release-table") fun getTicketReleaseTable( diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt index 9ea0570..7ec3c4e 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt @@ -58,6 +58,8 @@ data class JobOrderInfoWithTypeName( val item: JobOrderItemInfo, val stockInLineId: Long?, val stockInLineStatus: String?, + val sufficientCount: Int?, + val insufficientCount: Int?, val silHandlerId: Long?, val planStart: LocalDateTime?, val status: String, 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 6e1aa67..76342c2 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 @@ -39,6 +39,11 @@ import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus import com.ffii.fpsms.modules.productProcess.entity.ProductProcessLineRepository import com.ffii.fpsms.modules.stock.entity.StockOutRepository import com.ffii.fpsms.modules.jobOrder.entity.JobOrderProcessRepository +import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderLotsHierarchicalResponse +import com.ffii.fpsms.modules.jobOrder.web.model.PickOrderInfoResponse +import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderBasicInfoResponse +import com.ffii.fpsms.modules.jobOrder.web.model.PickOrderLineWithLotsResponse +import com.ffii.fpsms.modules.jobOrder.web.model.LotDetailResponse @Service open class JoPickOrderService( private val joPickOrderRepository: JoPickOrderRepository, @@ -215,7 +220,7 @@ open class JoPickOrderService( } return joPickOrderRecordRepository.saveAll(joPickOrderRecords) } - open fun getAllJobOrderLotsWithDetailsHierarchical(userId: Long): Map { + open fun getAllJobOrderLotsWithDetailsHierarchical(userId: Long): JobOrderLotsHierarchicalResponse { println("=== Debug: getAllJobOrderLotsWithDetailsHierarchical ===") println("today: ${LocalDate.now()}") println("userId filter: $userId") @@ -224,25 +229,38 @@ open class JoPickOrderService( val user = userService.find(userId).orElse(null) if (user == null) { println("❌ User not found: $userId") - return emptyMap() + // ✅ 修复:返回 JobOrderLotsHierarchicalResponse + return JobOrderLotsHierarchicalResponse( + pickOrder = PickOrderInfoResponse( + id = null, + code = null, + consoCode = null, + targetDate = null, + type = null, + status = null, + assignTo = null, + jobOrder = JobOrderBasicInfoResponse(0, "", "") + ), + pickOrderLines = emptyList() + ) } val statusList = listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED) - + // Get all pick orders assigned to user with PENDING or RELEASED status that have joId val allAssignedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn( userId, statusList ).filter { it.jobOrder != null } // Only pick orders with joId - + println("🔍 DEBUG: Found ${allAssignedPickOrders.size} job order pick orders assigned to user $userId") - + // 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 { + 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 job orders, showing only those") @@ -251,7 +269,8 @@ open class JoPickOrderService( // 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 } + val latestCompleted = + completedOrders.maxByOrNull { it.completeDate ?: it.modified ?: LocalDateTime.MIN } println("🔍 DEBUG: No assigned RELEASED job orders, showing latest completed order: ${latestCompleted?.code}") listOfNotNull(latestCompleted) } else { @@ -262,42 +281,68 @@ open class JoPickOrderService( } else { emptyList() } - + val pickOrderIds = filteredPickOrders.map { it.id!! } println(" Job Order Pick order IDs to fetch: $pickOrderIds") - + if (pickOrderIds.isEmpty()) { - return mapOf( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? + return JobOrderLotsHierarchicalResponse( + pickOrder = PickOrderInfoResponse( + id = null, + code = null, + consoCode = null, + targetDate = null, + type = null, + status = null, + assignTo = null, + jobOrder = JobOrderBasicInfoResponse(0, "", "") + ), + pickOrderLines = emptyList() ) } - + // 使用 Repository 获取数据 try { val pickOrders = pickOrderRepository.findAllByIdIn(pickOrderIds) .filter { it.deleted == false && it.assignTo?.id == userId } - + if (pickOrders.isEmpty()) { - return mapOf( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? + return JobOrderLotsHierarchicalResponse( + pickOrder = PickOrderInfoResponse( + id = null, + code = null, + consoCode = null, + targetDate = null, + type = null, + status = null, + assignTo = null, + jobOrder = JobOrderBasicInfoResponse(0, "", "") + ), + pickOrderLines = emptyList() ) } - + val pickOrder = pickOrders.first() // 取第一个(应该只有一个) - val jobOrder = pickOrder.jobOrder ?: return mapOf( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? + 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() ) - // 获取 pick order lines val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id!!) .filter { it.deleted == false } - + // 获取所有 pick order line IDs val pickOrderLineIds = pickOrderLines.map { it.id!! } - + // 获取 suggested pick lots val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) { suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) @@ -305,10 +350,10 @@ open class JoPickOrderService( } else { emptyList() } - + // 获取所有 inventory lot line IDs val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id } - + // 获取 inventory lot lines val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) { inventoryLotLineRepository.findAllByIdIn(inventoryLotLineIds) @@ -316,7 +361,7 @@ open class JoPickOrderService( } else { emptyList() } - + // 获取 inventory lots val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct() val inventoryLots = if (inventoryLotIds.isNotEmpty()) { @@ -325,7 +370,7 @@ open class JoPickOrderService( } else { emptyList() } - + // 获取 stock out lines val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) { pickOrderLineIds.flatMap { polId -> @@ -342,13 +387,13 @@ open class JoPickOrderService( } // 获取 jo_pick_order 记录 val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!) - + // 构建 pick order info val pickOrderInfo = mapOf( "id" to pickOrder.id, "code" to pickOrder.code, "consoCode" to pickOrder.consoCode, - "targetDate" to pickOrder.targetDate?.let { + "targetDate" to pickOrder.targetDate?.let { "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" }, "type" to pickOrder.type?.value, @@ -360,49 +405,35 @@ open class JoPickOrderService( "name" to "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 suggestions = suggestedPickLots.filter { it.pickOrderLine?.id == lineId } - val stockoutsForLine = stockOutLinesByPickOrderLine[lineId].orEmpty() - // 获取该 line 的 suggested pick lots val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id } - + // 构建 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 - - // 获取对应的 stock out line - val sol = stockOutLines.firstOrNull { - it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id + val sol = stockOutLines.firstOrNull { + it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id } - - // 获取对应的 jo_pick_order val jpo = joPickOrders.firstOrNull { it.itemId == item?.id } - - // 计算 available quantity + val availableQty = if (sol?.status == "rejected") { null } else { - (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty ?: BigDecimal.ZERO) + (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty + ?: BigDecimal.ZERO) } - - // 计算 total picked by all pick orders - val totalPickedByAllPickOrders = stockOutLines - .filter { it.inventoryLotLine?.id == ill.id && it.deleted == false } - .filter { it.status in listOf("pending", "checked", "partially_completed", "completed") } - .sumOf { it.qty?.toBigDecimal() ?: BigDecimal.ZERO } - - // 计算 lot availability + val lotAvailability = when { il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired" sol?.status == "rejected" -> "rejected" @@ -410,72 +441,97 @@ open class JoPickOrderService( ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable" else -> "available" } - - // 计算 processing status + val processingStatus = when (sol?.status) { "completed" -> "completed" "rejected" -> "rejected" "created" -> "pending" else -> "pending" } - - mapOf( - "lotId" to ill.id, - "lotNo" to il.lotNo, - "expiryDate" to il.expiryDate?.let { + + LotDetailResponse( + lotId = ill.id, + lotNo = il.lotNo, + expiryDate = il.expiryDate?.let { "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" }, - "location" to warehouse?.name, - "availableQty" to availableQty?.toDouble(), - "requiredQty" to (spl.qty?.toDouble() ?: 0.0), - "actualPickQty" to (sol?.qty ?: 0.0), - "processingStatus" to processingStatus, - "lotAvailability" to lotAvailability, - "pickOrderId" to pickOrder.id, - "pickOrderCode" to pickOrder.code, - "pickOrderConsoCode" to pickOrder.consoCode, - "pickOrderLineId" to pol.id, - "stockOutLineId" to sol?.id, - "suggestedPickLotId" to spl.id, - "stockOutLineQty" to (sol?.qty ?: 0.0), - "stockOutLineStatus" to sol?.status, - "routerIndex" to warehouse?.order, - "routerArea" to warehouse?.code, - "routerRoute" to warehouse?.code, - "uomShortDesc" to uom?.udfShortDesc, - "matchStatus" to jpo?.matchStatus?.value, - "matchBy" to jpo?.matchBy, - "matchQty" to jpo?.matchQty + location = warehouse?.name, + 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, + suggestedPickLotId = spl.id, + stockOutLineQty = sol?.qty ?: 0.0, + stockOutLineStatus = sol?.status, + routerIndex = warehouse?.order, + routerArea = warehouse?.code, + routerRoute = warehouse?.code, + uomShortDesc = uom?.udfShortDesc, + matchStatus = jpo?.matchStatus?.value, + matchBy = jpo?.matchBy, + matchQty = jpo?.matchQty?.toDouble() ) } - - mapOf( - "id" to pol.id, - "itemId" to item?.id, - "itemCode" to item?.code, - "itemName" to item?.name, - "requiredQty" to pol.qty?.toDouble(), - "uomCode" to uom?.code, - "uomDesc" to uom?.udfudesc, - "lots" to lots + + PickOrderLineWithLotsResponse( + id = pol.id!!, + itemId = item?.id, + itemCode = item?.code, + itemName = item?.name, + requiredQty = pol.qty?.toDouble(), + uomCode = uom?.code, + uomDesc = uom?.udfudesc, + status = pol.status?.value, + lots = lots ) } - - return mapOf( - "pickOrder" to pickOrderInfo as Any?, - "pickOrderLines" to pickOrderLinesResult as Any? + + // ✅ 修复第469行:返回 JobOrderLotsHierarchicalResponse + return JobOrderLotsHierarchicalResponse( + pickOrder = 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 ?: "", // ✅ 修复第482行:处理空值 + name = "Job Order ${jobOrder.code ?: ""}" + ) + ), + pickOrderLines = pickOrderLinesResult // ✅ 修复第486行:使用转换后的结果 ) - + } catch (e: Exception) { println("❌ Error executing Job Order hierarchical query: ${e.message}") e.printStackTrace() - return mapOf( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? + // ✅ 修复 catch 块:返回 JobOrderLotsHierarchicalResponse 而不是 Map + return JobOrderLotsHierarchicalResponse( + pickOrder = PickOrderInfoResponse( + id = null, + code = null, + consoCode = null, + targetDate = null, + type = null, + status = null, + assignTo = null, + jobOrder = JobOrderBasicInfoResponse(0, "", "") + ), + pickOrderLines = emptyList() ) } } - // Get completed job order pick orders (for second tab) // Fix the getCompletedJobOrderLotsHierarchical method open fun getCompletedJobOrderLotsHierarchical(userId: Long): Map { @@ -1725,7 +1781,7 @@ open fun getAllJoPickOrders(): List { emptyList() } } -open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): Map { +open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLotsHierarchicalResponse { println("=== getJobOrderLotsHierarchicalByPickOrderId ===") println("pickOrderId: $pickOrderId") @@ -1733,17 +1789,36 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): Map>() as Any? + 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 mapOf( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? + 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 } @@ -1799,20 +1874,20 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): Map "expired" sol?.status == "rejected" -> "rejected" @@ -1866,7 +1926,6 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): Map "available" } - // 计算 processing status val processingStatus = when (sol?.status) { "completed" -> "completed" "rejected" -> "rejected" @@ -1874,59 +1933,69 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): Map "pending" } - mapOf( - "lotId" to ill.id, - "lotNo" to il.lotNo, - "expiryDate" to il.expiryDate?.let { + LotDetailResponse( + lotId = ill.id, + lotNo = il.lotNo, + expiryDate = il.expiryDate?.let { "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" }, - "location" to warehouse?.name, - "availableQty" to availableQty?.toDouble(), - "requiredQty" to (spl.qty?.toDouble() ?: 0.0), - "actualPickQty" to (sol?.qty ?: 0.0), - "processingStatus" to processingStatus, - "lotAvailability" to lotAvailability, - "pickOrderId" to pickOrder.id, - "pickOrderCode" to pickOrder.code, - "pickOrderConsoCode" to pickOrder.consoCode, - "pickOrderLineId" to pol.id, - "stockOutLineId" to sol?.id, - "suggestedPickLotId" to spl.id, - "stockOutLineQty" to (sol?.qty ?: 0.0), - "stockOutLineStatus" to sol?.status, - "routerIndex" to warehouse?.order, - "routerArea" to warehouse?.code, - "routerRoute" to warehouse?.code, - "uomShortDesc" to uom?.udfShortDesc, - "matchStatus" to jpo?.matchStatus?.value, - "matchBy" to jpo?.matchBy, - "matchQty" to jpo?.matchQty + location = warehouse?.name, + 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, + suggestedPickLotId = spl.id, + stockOutLineQty = sol?.qty ?: 0.0, + stockOutLineStatus = sol?.status, + routerIndex = warehouse?.order, + routerArea = warehouse?.code, + routerRoute = warehouse?.code, + uomShortDesc = uom?.udfShortDesc, + matchStatus = jpo?.matchStatus?.value, + matchBy = jpo?.matchBy, + matchQty = jpo?.matchQty?.toDouble() ) } - mapOf( - "id" to pol.id, - "itemId" to item?.id, - "itemCode" to item?.code, - "itemName" to item?.name, - "requiredQty" to pol.qty?.toDouble(), - "uomCode" to uom?.code, - "uomDesc" to uom?.udfudesc, - "lots" to lots + PickOrderLineWithLotsResponse( + id = pol.id!!, + itemId = item?.id, + itemCode = item?.code, + itemName = item?.name, + requiredQty = pol.qty?.toDouble(), + uomCode = uom?.code, + uomDesc = uom?.udfudesc, + status = pol.status?.value, + lots = lots ) } - return mapOf( - "pickOrder" to pickOrderInfo as Any?, - "pickOrderLines" to pickOrderLinesResult as Any? + return JobOrderLotsHierarchicalResponse( + pickOrder = pickOrderInfo, + pickOrderLines = pickOrderLinesResult ) } catch (e: Exception) { println("❌ Error in getJobOrderLotsHierarchicalByPickOrderId: ${e.message}") e.printStackTrace() - return mapOf( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? + return JobOrderLotsHierarchicalResponse( + pickOrder = PickOrderInfoResponse( + id = null, + code = null, + consoCode = null, + targetDate = null, + type = null, + status = null, + assignTo = null, + jobOrder = JobOrderBasicInfoResponse(0, "", "") + ), + pickOrderLines = emptyList() ) } } diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt index b496b74..1c9d236 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt @@ -56,7 +56,7 @@ import java.io.FileNotFoundException import java.io.IOException import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName import com.ffii.fpsms.modules.jobOrder.entity.projections.JobTypeResponse - +import com.ffii.fpsms.modules.stock.entity.InventoryRepository @Service open class JobOrderService( @@ -73,7 +73,8 @@ open class JobOrderService( val stockOutRepository: StockOutRepository, val stockOutLineRepository: StockOutLIneRepository, private val printerService: PrinterService, - val jobTypeRepository: JobTypeRepository + val jobTypeRepository: JobTypeRepository, + val inventoryRepository: InventoryRepository ) { open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes { @@ -116,6 +117,27 @@ open class JobOrderService( emptyMap() } + // 获取所有 JobOrder IDs,用于批量加载 jobms + val jobOrderIds = response.content.map { it.id }.distinct() + val jobOrdersMap = if (jobOrderIds.isNotEmpty()) { + jobOrderRepository.findAllById(jobOrderIds).associateBy { it.id } + } else { + emptyMap() + } + + // 获取所有涉及的 itemIds,用于批量加载 inventory + val allItemIds = jobOrdersMap.values + .flatMap { it.jobms } + .mapNotNull { it.item?.id } + .distinct() + + val inventoriesMap = if (allItemIds.isNotEmpty()) { + inventoryRepository.findInventoryInfoByItemIdInAndDeletedIsFalse(allItemIds) + .associateBy { it.itemId } + } else { + emptyMap() + } + val planStartFrom = request.planStart val planStartTo = request.planStartTo val records = response.content @@ -124,6 +146,15 @@ open class JobOrderService( (planStartTo == null || (it.planStart != null && (planStartTo.isEqual(it.planStart) || planStartTo.isAfter(it.planStart)))) } .map { info -> + val jobOrder = jobOrdersMap[info.id] + + // 计算 sufficientCount 和 insufficientCount + val (sufficientCount, insufficientCount) = if (jobOrder != null) { + calculateStockCounts(jobOrder, inventoriesMap) + } else { + Pair(null, null) + } + JobOrderInfoWithTypeName( id = info.id, code = info.code, @@ -134,6 +165,8 @@ open class JobOrderService( item = info.item, stockInLineId = info.stockInLineId, stockInLineStatus = info.stockInLineStatus, + sufficientCount = sufficientCount, + insufficientCount = insufficientCount, silHandlerId = info.silHandlerId, planStart = info.planStart, status = info.status, @@ -152,6 +185,55 @@ open class JobOrderService( val total = response.totalElements return RecordsRes(records, total.toInt()); } + + // 添加辅助方法计算库存统计 + private fun calculateStockCounts( + jobOrder: JobOrder, + inventoriesMap: Map + ): Pair { + // 过滤掉 consumables 和 CMB 类型的物料 + val nonConsumablesJobms = jobOrder.jobms.filter { jobm -> + val itemType = jobm.item?.type?.lowercase() + itemType != "consumables" && itemType != "cmb" + } + + if (nonConsumablesJobms.isEmpty()) { + return Pair(0, 0) + } + + var sufficientCount = 0 + var insufficientCount = 0 + + nonConsumablesJobms.forEach { jobm -> + val itemId = jobm.item?.id + val reqQty = jobm.reqQty ?: BigDecimal.ZERO + + if (itemId != null) { + val inventory = inventoriesMap[itemId] + val availableQty = if (inventory != null) { + // 使用 availableQty,如果没有则计算:onHandQty - onHoldQty - unavailableQty + inventory.availableQty ?: ( + (inventory.onHandQty ?: BigDecimal.ZERO) - + (inventory.onHoldQty ?: BigDecimal.ZERO) - + (inventory.unavailableQty ?: BigDecimal.ZERO) + ) + } else { + BigDecimal.ZERO + } + + if (availableQty >= reqQty) { + sufficientCount++ + } else { + insufficientCount++ + } + } else { + // 如果没有 itemId,视为不足 + insufficientCount++ + } + } + + return Pair(sufficientCount, insufficientCount) + } open fun jobOrderDetail(id: Long): JobOrderDetail { val sqlResult = jobOrderRepository.findJobOrderDetailById(id) ?: throw NoSuchElementException(); @@ -308,13 +390,26 @@ open class JobOrderService( // 添加 suggested pick lots 创建逻辑 val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!) + println("DEBUG: pickOrderLines=${lines.map { it.id to it.item?.code }}") if (lines.isNotEmpty()) { - val suggestions = suggestedPickLotService.suggestionForPickOrderLines( + val suggestions = suggestedPickLotService.suggestionForPickOrderLinesForJobOrder( SuggestedPickLotForPolRequest(pickOrderLines = lines) ) - + println("DEBUG: suggestions.size=${suggestions.suggestedList.size}") + suggestions.suggestedList.forEach { s -> + println( + "DEBUG: suggestion polId=${s.pickOrderLine?.id}, lotLineId=${s.suggestedLotLine?.id}, " + + "lotNo=${s.suggestedLotLine?.inventoryLot?.lotNo}, qty=${s.qty}, pickSuggested=${s.pickSuggested}" + ) + } val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) - + println("DEBUG: saved suggestions size=${saveSuggestedPickLots.size}") + saveSuggestedPickLots.forEach { s -> + println( + "DEBUG: saved polId=${s.pickOrderLine?.id}, lotLineId=${s.suggestedLotLine?.id}, " + + "lotNo=${s.suggestedLotLine?.inventoryLot?.lotNo}, qty=${s.qty}" + ) + } // Hold inventory quantities val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } 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 3103cb0..db1fda7 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 @@ -100,7 +100,7 @@ class JobOrderController( return jo } @GetMapping("/all-lots-hierarchical/{userId}") - fun getAllJobOrderLotsHierarchical(@PathVariable userId: Long): Map { + fun getAllJobOrderLotsHierarchical(@PathVariable userId: Long): JobOrderLotsHierarchicalResponse { return joPickOrderService.getAllJobOrderLotsWithDetailsHierarchical(userId) } @@ -235,7 +235,7 @@ fun recordSecondScanIssue( } @GetMapping("/all-lots-hierarchical-by-pick-order/{pickOrderId}") - fun getJobOrderLotsHierarchicalByPickOrderId(@PathVariable pickOrderId: Long): Map { + fun getJobOrderLotsHierarchicalByPickOrderId(@PathVariable pickOrderId: Long): JobOrderLotsHierarchicalResponse { return joPickOrderService.getJobOrderLotsHierarchicalByPickOrderId(pickOrderId) } @PostMapping("/update-jo-pick-order-handled-by") diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt index 8e10c10..f2b9a07 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt @@ -51,18 +51,72 @@ data class AllJoPickOrderResponse( val finishedPickOLineCount: Int, ) -data class JobOrderLotsHierarchicalResponse( - val id: Long, - val lotId: Long?, - val lotCode: String?, - val lotName: String?, - val lotQty: BigDecimal?, - val lotUomId: Long?, - val lotUomName: String?, -) + data class UpdateJoPickOrderHandledByRequest( val pickOrderId: Long, val itemId: Long, val userId: Long -) \ No newline at end of file +) +data class JobOrderLotsHierarchicalResponse( + val pickOrder: PickOrderInfoResponse, + val pickOrderLines: List +) + +data class PickOrderInfoResponse( + val id: Long?, + val code: String?, + val consoCode: String?, + val targetDate: String?, + val type: String?, + val status: String?, + val assignTo: Long?, + val jobOrder: JobOrderBasicInfoResponse +) + +data class JobOrderBasicInfoResponse( + val id: Long, + val code: String, + val name: String +) + +data class PickOrderLineWithLotsResponse( + val id: Long, + val itemId: Long?, + val itemCode: String?, + val itemName: String?, + val requiredQty: Double?, + val uomCode: String?, + val uomDesc: String?, + val status: String?, + val lots: List +) + +data class LotDetailResponse( + val lotId: Long?, + val lotNo: String?, + val expiryDate: String?, + val location: String?, + val availableQty: Double?, + val requiredQty: Double?, + val actualPickQty: Double?, + val processingStatus: String?, + val lotAvailability: String?, + val pickOrderId: Long?, + val pickOrderCode: String?, + val pickOrderConsoCode: String?, + val pickOrderLineId: Long?, + val stockOutLineId: Long?, + val suggestedPickLotId: Long?, + val stockOutLineQty: Double?, + val stockOutLineStatus: String?, + val routerIndex: Int?, + val routerArea: String?, + val routerRoute: String?, + val uomShortDesc: String?, + val matchStatus: String?, + val matchBy: Long?, + val matchQty: Double? +) + + diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductionProcessIssue.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductionProcessIssue.kt index 4eccc5b..52494c6 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductionProcessIssue.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductionProcessIssue.kt @@ -14,6 +14,11 @@ open class ProductionProcessIssue : BaseEntity() { // 修复:改为正 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "productprocessid", nullable = false) open var productProcess: ProductProcess? = null + + + @Column(name = "productProcessLineId") + open var productProcessLineId: Long? = null + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "operatorId") @@ -36,4 +41,6 @@ open class ProductionProcessIssue : BaseEntity() { // 修复:改为正 @Column(name = "totalTime") open var totalTime: Int? = null // 分钟 + @Column(name = "status") + open var status: String? = null } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductionProcessIssueRepository.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductionProcessIssueRepository.kt index 5b3d633..7ef4859 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductionProcessIssueRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductionProcessIssueRepository.kt @@ -6,4 +6,5 @@ import org.springframework.stereotype.Repository @Repository interface ProductionProcessIssueRepository : JpaRepository { fun findByProductProcess_Id(productProcessId: Long): List + fun findByProductProcessLineId(productProcessLineId: Long):List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt index cb463b1..eed3eb4 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt @@ -28,6 +28,7 @@ import com.ffii.fpsms.modules.master.entity.Process import com.ffii.fpsms.modules.master.entity.Equipment import com.ffii.fpsms.modules.master.entity.BomProcess import com.ffii.fpsms.modules.master.entity.Bom + import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterialRepository import com.ffii.fpsms.modules.master.service.ProductionScheduleService @@ -39,8 +40,10 @@ import com.ffii.fpsms.modules.stock.service.StockInLineService import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest import com.ffii.fpsms.modules.master.entity.BomProcessMaterialRepository import com.ffii.fpsms.modules.master.entity.BomMaterialRepository - +import com.ffii.fpsms.modules.productProcess.entity.ProductionProcessIssue import com.ffii.fpsms.modules.jobOrder.entity.JobTypeRepository + +import java.time.ZoneOffset @Service @Transactional open class ProductProcessService( @@ -532,11 +535,20 @@ open class ProductProcessService( planEndDate.atStartOfDay(), "detailed" ) - scheduleLine?.itemPriority?.toString() ?: "0" + if (scheduleLine != null) { + // 计算 targetMinStock + val targetMinStock = scheduleLine.lastMonthAvgSales * 2 + // 调整后的生产数量(与 generateDetailedScheduleByDay 逻辑一致) + val adjustedProdQty = scheduleLine.prodQty * 2 + // 计算 difference(缺口) + val difference = -(targetMinStock + adjustedProdQty - scheduleLine.estCloseBal) + difference.toString() + } else { + "0" + } } else { "0" - } - + } fun calculateColourScore(value: Int?): String { return when (value) { 0 -> "淺" @@ -899,6 +911,9 @@ open class ProductProcessService( } open fun getJobOrderProcessLineDetail(productProcessLineId: Long): JobOrderProcessLineDetailResponse { val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) + val bomProcess = bomProcessRepository.findById(productProcessLine?.bomProcess?.id?:0L).orElse(null) + val productProcessIssue = productionProcessIssueRepository.findByProductProcessLineId(productProcessLineId).filter{it.status == "Paused"}.firstOrNull() + return JobOrderProcessLineDetailResponse( id = productProcessLine.id?:0, productProcessId = productProcessLine.productProcess ?.id?:0, @@ -906,7 +921,11 @@ open class ProductProcessService( operatorId = productProcessLine.operator?.id?:0, operatorName = productProcessLine.operator?.name?:"", handlerId = productProcessLine.handler?.id?:0, - durationInMinutes = productProcessLine.bomProcess?.durationInMinute?:0, + durationInMinutes = bomProcess?.durationInMinute?:0, + + productProcessIssueId = productProcessIssue?.id?:0, + productProcessIssueStatus = productProcessIssue?.status?:"", + seqNo = productProcessLine.seqNo?:0, name = productProcessLine.name?:"", description = productProcessLine.description?:"", @@ -1233,5 +1252,70 @@ open class ProductProcessService( errorPosition = null, ) } + + open fun SaveProductProcessIssueTime(request: SaveProductProcessIssueTimeRequest): MessageResponse { + println("📋 Service: Saving ProductProcess Issue Time: ${request.productProcessLineId}") + val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) + val productProcess = productProcessRepository.findById(productProcessLine.productProcess?.id?:0L).orElse(null) + //val productProcessLines=productProcessLineRepository.findByProductProcess_Id(productProcessId) + + + val startTime=productProcessLine?.startTime + println("📋 Service: Start Time: $startTime") + val stopTime=LocalDateTime.now() + println("📋 Service: Stop Time: $stopTime") + val operatorId=productProcessLine.operator?.id + val Operator=userRepository.findById(operatorId).orElse(null) + val reason = request.reason + val productProcessIssue = ProductionProcessIssue().apply { + this.productProcess = productProcess + this.productProcessLineId = productProcessLine.id + this.operator = productProcessLine.operator + this.operatorName = Operator.name + this.reason = reason + this.status = "Paused" + //this.productProcess = operator.name + //this.startTime = startTime + this.stopTime = stopTime + } + productionProcessIssueRepository.save(productProcessIssue) + productProcessLine.status = "Paused" + productProcessLineRepository.save(productProcessLine) + return MessageResponse( + id = 0, + code = "200", + name = "ProductProcess IssueTime Updated", + type = "success", + message = "ProductProcess IssueTime Updated", + errorPosition = null, + ) + } + + + open fun SaveProductProcessResumeTime(productProcessIssueId: Long): MessageResponse { + println("📋 Service: Saving ProductProcess Resume Time: $productProcessIssueId") + val productProcessLineIssue = productionProcessIssueRepository.findById(productProcessIssueId).orElse(null) + val productProcessLine = productProcessLineRepository.findById(productProcessLineIssue.productProcessLineId?:0L).orElse(null) + val resumeTime = LocalDateTime.now() + println("📋 Service: Resume Time: $resumeTime") + productProcessLineIssue?.resumeTime = resumeTime + println("📋 Service: Resume Time: $resumeTime") + val totalTime = resumeTime.toEpochSecond(ZoneOffset.UTC) - (productProcessLineIssue.stopTime?.toEpochSecond(ZoneOffset.UTC) ?: 0L) + productProcessLineIssue?.totalTime = totalTime.toInt() + productProcessLineIssue?.status = "Resumed" + productionProcessIssueRepository.save(productProcessLineIssue) + productProcessLine.status = "InProgress" + productProcessLineRepository.save(productProcessLine) + + return MessageResponse( + id = 0, + code = "200", + name = "ProductProcess ResumeTime Updated", + type = "success", + message = "ProductProcess ResumeTime Updated", + errorPosition = null, + ) + + } } diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/web/ProductProcessController.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/web/ProductProcessController.kt index 943ac1b..f7d9a37 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/web/ProductProcessController.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/web/ProductProcessController.kt @@ -197,4 +197,12 @@ class ProductProcessController( fun completeProductProcessLine(@PathVariable lineId: Long): MessageResponse { return productProcessService.CompleteProductProcessLine(lineId) } + @PostMapping("/Demo/ProcessLine/issue") + fun issueProductProcessLine(@RequestBody request: SaveProductProcessIssueTimeRequest): MessageResponse { + return productProcessService.SaveProductProcessIssueTime(request) + } + @PostMapping("/Demo/ProcessLine/resume/{productProcessIssueId}") + fun resumeProductProcessLine(@PathVariable productProcessIssueId: Long): MessageResponse { + return productProcessService.SaveProductProcessResumeTime(productProcessIssueId) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt index 4002621..e3c4c59 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt @@ -132,7 +132,8 @@ data class JobOrderProcessLineDetailResponse( val equipmentId: Long?, val startTime: LocalDateTime?, val endTime: LocalDateTime?, - + val productProcessIssueId: Long?, + val productProcessIssueStatus: String?, val status: String, val outputFromProcessQty: Int?, val outputFromProcessUom: String?, @@ -190,4 +191,12 @@ data class NewUpdateProductProcessLineOperatorIdOrEquipmentIdAndEquipmentDetailR val EquipmentTypeSubTypeEquipmentNo: String, val staffNo: String?, val Name: String?, +) +data class SaveProductProcessIssueTimeRequest( + val productProcessLineId: Long, + val reason: String +) +data class SaveProductProcessResumeTimeRequest( + val productProcessLineId: Long, + val resumeTime: LocalDateTime? ) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt index 2a99994..0e4beef 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt @@ -235,6 +235,148 @@ open class SuggestedPickLotService( } return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList) } + open fun suggestionForPickOrderLinesForJobOrder(request: SuggestedPickLotForPolRequest): SuggestedPickLotResponse { + val pols = request.pickOrderLines + val itemIds = pols.mapNotNull { it.item?.id } + val zero = BigDecimal.ZERO + val one = BigDecimal.ONE + val today = LocalDate.now() + + val suggestedList: MutableList = mutableListOf() + val holdQtyMap: MutableMap = request.holdQtyMap + + // get current inventory lot line qty & grouped by item id + val availableInventoryLotLines = inventoryLotLineService + .allInventoryLotLinesByItemIdIn(itemIds) + .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } + .filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero } + .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today)} + .sortedBy { it.expiryDate } + .groupBy { it.item?.id } + + // loop for suggest pick lot line + pols.forEach { line -> + val salesUnit = line.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } + val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() + val ratio = one // (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) + + // FIX: Calculate remaining quantity needed (not the full required quantity) + val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(line.id!!) + val totalPickedQty = stockOutLines + .filter { + it.status == "completed" || + it.status == "partially_completed" || + (it.status == "rejected" && (it.qty ?: zero) > zero) // 包含已 picked 的 rejected + } + .sumOf { it.qty ?: zero } + val requiredQty = line.qty ?: zero + val remainingQty = requiredQty.minus(totalPickedQty) + + println("=== SUGGESTION DEBUG for Pick Order Line ${line.id} ===") + println("Required qty: $requiredQty") + println("Total picked qty: $totalPickedQty") + println("Remaining qty needed: $remainingQty") + println("Stock out lines: ${stockOutLines.map { "${it.id}(status=${it.status}, qty=${it.qty})" }}") + + // FIX: Use remainingQty instead of line.qty + var remainingQtyToAllocate = remainingQty + println("remaining1 $remainingQtyToAllocate (sales units)") + val updatedLotLines = mutableListOf() + + lotLines.forEachIndexed { index, lotLine -> + if (remainingQtyToAllocate <= zero) return@forEachIndexed + + println("calculateRemainingQtyForInfo(lotLine) ${calculateRemainingQtyForInfo(lotLine)}") + val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } + + // 修复:计算可用数量,转换为销售单位 + val availableQtyInBaseUnits = calculateRemainingQtyForInfo(lotLine) + val holdQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero + val availableQtyInSalesUnits = availableQtyInBaseUnits + .minus(holdQtyInBaseUnits) + .divide(ratio, 2, RoundingMode.HALF_UP) + + println("holdQtyMap[lotLine.id] ?: zero ${holdQtyMap[lotLine.id] ?: zero}") + + if (availableQtyInSalesUnits <= zero) { + updatedLotLines += lotLine + return@forEachIndexed + } + println("$index : ${lotLine.id}") + // val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } + val originalHoldQty = inventoryLotLine?.holdQty + + // 修复:在销售单位中计算分配数量 + val assignQtyInSalesUnits = minOf(availableQtyInSalesUnits, remainingQtyToAllocate) + remainingQtyToAllocate = remainingQtyToAllocate.minus(assignQtyInSalesUnits) + val newHoldQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero + // 修复:将销售单位转换为基础单位来更新 holdQty + val assignQtyInBaseUnits = assignQtyInSalesUnits.multiply(ratio) + holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQtyInBaseUnits) + + suggestedList += SuggestedPickLot().apply { + type = SuggestedPickLotType.PICK_ORDER + suggestedLotLine = inventoryLotLine + pickOrderLine = line + qty = assignQtyInSalesUnits // 保存销售单位 + } + } + // 修复:计算现有 suggestions 中 pending/checked 状态满足的数量 + var existingSatisfiedQty = BigDecimal.ZERO + + // 查询现有的 suggestions 用于这个 pick order line + val existingSuggestions = suggestedPickLotRepository.findAllByPickOrderLineId(line.id!!) + existingSuggestions.forEach { existingSugg -> + if (existingSugg.suggestedLotLine?.id != null) { + val stockOutLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( + line.id!!, existingSugg.suggestedLotLine?.id!! + ) + val canCountAsSatisfied = stockOutLines.isEmpty() || stockOutLines.any { + it.status == "pending" || it.status == "checked" || it.status == "partially_completed" + } + + if (canCountAsSatisfied) { + existingSatisfiedQty = existingSatisfiedQty.plus(existingSugg.qty ?: BigDecimal.ZERO) + } + } + } + + // 调整 remainingQtyToAllocate,减去已经通过现有 suggestions 满足的数量 + remainingQtyToAllocate = remainingQtyToAllocate.minus(existingSatisfiedQty) + println("Existing satisfied qty: $existingSatisfiedQty") + println("Adjusted remaining qty: $remainingQtyToAllocate") + + // if still have remainingQty + println("remaining2 $remainingQtyToAllocate (sales units)") + + // if still have remainingQty + println("remaining2 $remainingQtyToAllocate (sales units)") + if (remainingQtyToAllocate > zero) { + suggestedList += SuggestedPickLot().apply { + type = SuggestedPickLotType.PICK_ORDER + suggestedLotLine = null + pickOrderLine = line + qty = remainingQtyToAllocate // 保存销售单位 + } + try { + /* + val pickOrder = line.pickOrder + if (pickOrder != null) { + createInsufficientStockIssue( + pickOrder = pickOrder, + pickOrderLine = line, + insufficientQty = remainingQtyToAllocate + ) + } + */ + } catch (e: Exception) { + println("❌ Error creating insufficient stock issue: ${e.message}") + e.printStackTrace() + } + } + } + return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList) + } // Convertion open fun convertRequestsToEntities(request: List): List { diff --git a/src/main/resources/db/changelog/changes/20251203_01_Enson/01_add_dopickordercolumn.sql b/src/main/resources/db/changelog/changes/20251203_01_Enson/01_add_dopickordercolumn.sql new file mode 100644 index 0000000..d9d2545 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251203_01_Enson/01_add_dopickordercolumn.sql @@ -0,0 +1,8 @@ +-- liquibase formatted sql +-- changeset KelvinY:add_company_to_items + +ALTER TABLE `fpsmsdb`.`do_pick_order` +ADD COLUMN `release_type` VARCHAR(100) NULL after `ticket_status`; + +ALTER TABLE `fpsmsdb`.`do_pick_order_record` +ADD COLUMN `release_type` VARCHAR(100) NULL after `ticket_status`; \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20251209_01_Enson/01_add_dopickordercolumn.sql b/src/main/resources/db/changelog/changes/20251209_01_Enson/01_add_dopickordercolumn.sql new file mode 100644 index 0000000..486784b --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251209_01_Enson/01_add_dopickordercolumn.sql @@ -0,0 +1,6 @@ +-- liquibase formatted sql +-- changeset KelvinY:add_company_to_items + +ALTER TABLE `fpsmsdb`.`productionprocessissue` +ADD COLUMN `productProcessLineId` Int NULL after `productprocessid`; + diff --git a/src/main/resources/db/changelog/changes/20251210_01_Enson/01_add_column.sql b/src/main/resources/db/changelog/changes/20251210_01_Enson/01_add_column.sql new file mode 100644 index 0000000..37c04cb --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251210_01_Enson/01_add_column.sql @@ -0,0 +1,5 @@ +-- liquibase formatted sql +-- changeset Enson:add_column + +ALTER TABLE `fpsmsdb`.`productionprocessissue` +ADD COLUMN `status` VARCHAR(255) NULL after `totalTime`;