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 20a6fa9..5757999 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 @@ -2274,90 +2274,138 @@ open fun deleteJoPickOrderJobOrderProductProcessPickOrder(jobOrderId: Long): Mes errorPosition = null ) } -open fun getMaterialPickStatus(): List { +open fun getMaterialPickStatus(date: String?): List { + // Parse date filter if provided + val filterDate = date?.let { + try { + LocalDate.parse(it) + } catch (e: Exception) { + null + } + } + + // Get all joPickOrders val joPickOrders = joPickOrderRepository.findAll() - return joPickOrders + // Filter by date if provided (filter by jobOrder.planStart date) + val filteredJoPickOrders = if (filterDate != null) { + joPickOrders.filter { joPickOrder -> + val jobOrder = joPickOrder.jobOrderId?.let { + jobOrderRepository.findById(it).orElse(null) + } + jobOrder?.planStart?.toLocalDate() == filterDate + } + } else { + joPickOrders + } + + // Group by jobOrderId + val groupedByJobOrder = filteredJoPickOrders .filter { joPickOrder -> val jobOrder = joPickOrder.jobOrderId?.let { jobOrderRepository.findById(it).orElse(null) } jobOrder?.status != JobOrderStatus.COMPLETED } - .map { joPickOrder -> - // Get related entities for additional data - val jobOrder = joPickOrder.jobOrderId?.let { - jobOrderRepository.findById(it).orElse(null) - } - val item = joPickOrder.itemId?.let { - itemsRepository.findById(it).orElse(null) - } - val pickOrder = joPickOrder.pickOrderId?.let { - pickOrderRepository.findById(it).orElse(null) + .groupBy { it.jobOrderId } + + // Map each job order group to a single MaterialPickStatusItem + return groupedByJobOrder.mapNotNull { (jobOrderId, joPickOrdersForJob) -> + if (jobOrderId == null) return@mapNotNull null + + val jobOrder = jobOrderRepository.findById(jobOrderId).orElse(null) + ?: return@mapNotNull null + + // Get BOM item (finished good/semi-finished product), not BOM Material item + val bomItem = jobOrder.bom?.item + if (bomItem == null) return@mapNotNull null + + // Get all pick orders for this job order + val pickOrderIds = joPickOrdersForJob.mapNotNull { it.pickOrderId }.distinct() + + // Aggregate data from all pick orders for this job order + val allPickOrderLines = pickOrderIds.flatMap { poId -> + pickOrderLineRepository.findByPickOrderId(poId) } - // Get stock out lines for this pick order and item to get start/end times - // First get all pick order lines for this pick order - val pickOrderLines = pickOrder?.let { po -> - pickOrderLineRepository.findByPickOrderId(po.id!!) - } ?: emptyList() - - // Then get stock out lines for each pick order line that matches the item - val stockOutLines = pickOrderLines - .filter { it.item?.id == joPickOrder.itemId } - .flatMap { pol -> - stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(pol.id!!) - .mapNotNull { solInfo -> - // Convert StockOutLineInfo to StockOutLine entity to access startTime/endTime - // We need to fetch the actual entity - stockOutLineRepository.findById(solInfo.id).orElse(null) - } - } + // Get all stock out lines for all pick orders of this job order + val allStockOutLines = allPickOrderLines.flatMap { pol -> + stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(pol.id!!) + .mapNotNull { solInfo -> + stockOutLineRepository.findById(solInfo.id).orElse(null) + } + } - // Get earliest startTime and latest endTime from stock out lines - val pickStartTime = stockOutLines + // ✅ 修复:startTime = 第一个 item 开始提料的时间 + // 只考虑已经开始的 items(status 不是 pending),取最早的 startTime + val pickStartTime = allStockOutLines + .filter { it.status != null && it.status != "pending" } .mapNotNull { it.startTime } .minOrNull() - val pickEndTime = stockOutLines - .mapNotNull { it.endTime } - .maxOrNull() + // Count total items to pick (number of distinct pick order lines) + val numberOfItemsToPick = allPickOrderLines.size + + // ✅ 修复:检查所有 items 是否都已完成 + // 计算已完成的 items 数量(每个 pickOrderLine 至少有一个 completed 的 stockOutLine) + val completedItemsCount = allPickOrderLines.count { pol -> + val stockOutLinesForPol = allStockOutLines.filter { + it.pickOrderLine?.id == pol.id + } + // 如果这个 pickOrderLine 有至少一个 completed 的 stockOutLine,则认为已完成 + stockOutLinesForPol.any { it.status == "completed" } + } - // Count items to pick from pick order lines - val numberOfItemsToPick = pickOrder?.let { po -> - pickOrderLineRepository.findByPickOrderId(po.id!!).size - } ?: 0 + // ✅ 修复:endTime = 最后一个 item 完成提料的时间 + // 只有当所有 items 都完成时,才返回 endTime + val pickEndTime = if (completedItemsCount == numberOfItemsToPick && numberOfItemsToPick > 0) { + // 所有 items 都已完成,取最晚的 endTime + allStockOutLines + .filter { it.status == "completed" } + .mapNotNull { it.endTime } + .maxOrNull() + } else { + // 还有 items 未完成,返回 null + null + } - // Count items with issues from pick execution issues - val numberOfItemsWithIssue = joPickOrder.id?.let { joPickOrderId -> - pickExecutionIssueRepository.findByPickOrderIdAndDeletedFalse(joPickOrder.pickOrderId ?: 0L) - .filter { it.joPickOrderId == joPickOrderId } - .size - } ?: 0 + // Count total items to pick (number of distinct pick order lines) + //val numberOfItemsToPick = allPickOrderLines.size - // Get job order quantity and UOM - access via bom - val jobOrderQty = jobOrder?.reqQty?.toDouble() - val uom = jobOrder?.bom?.uom?.code + // Count total items with issues from all pick orders + val numberOfItemsWithIssue = pickOrderIds.sumOf { poId -> + pickExecutionIssueRepository.findByPickOrderIdAndDeletedFalse(poId).size + } - // Get item name - val itemName = item?.name + // Get job order quantity and UOM + val jobOrderQty = jobOrder.reqQty?.toDouble() + val uom = jobOrder.bom?.uom?.code - // Determine pick status - prioritize pickOrder.status over matchStatus + // Determine pick status - check if all pick orders are completed + val pickOrders = pickOrderIds.mapNotNull { poId -> + pickOrderRepository.findById(poId).orElse(null) + } val pickStatus = when { - pickOrder?.status != null -> pickOrder.status!!.value - joPickOrder.matchStatus != null -> joPickOrder.matchStatus!!.value - else -> null + pickOrders.isEmpty() -> null + pickOrders.all { it.status == com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.COMPLETED } -> "completed" + pickOrders.any { it.status == com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED } -> "released" + else -> "pending" + } + + // Use the first pick order code (or combine if multiple) + val pickOrderCode = pickOrderIds.firstOrNull()?.let { poId -> + pickOrderRepository.findById(poId).orElse(null)?.code } MaterialPickStatusItem( - id = joPickOrder.id ?: 0L, - pickOrderId = joPickOrder.pickOrderId, - pickOrderCode = joPickOrder.pickOrderCode, - jobOrderId = joPickOrder.jobOrderId, - jobOrderCode = joPickOrder.jobOrderCode, - itemId = joPickOrder.itemId, - itemCode = joPickOrder.itemCode, - itemName = itemName, + id = jobOrderId, // Use jobOrderId as id + pickOrderId = pickOrderIds.firstOrNull(), // First pick order ID + pickOrderCode = pickOrderCode, + jobOrderId = jobOrderId, + jobOrderCode = jobOrder.code, + itemId = bomItem.id, // BOM item ID (finished good/semi-finished) + itemCode = bomItem.code, // BOM item code + itemName = bomItem.name, // BOM item name (finished good/semi-finished) jobOrderQty = jobOrderQty, uom = uom, pickStartTime = pickStartTime, 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 33c97d4..a1e209c 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 @@ -323,7 +323,7 @@ fun checkJobOrderCreated(@Valid @RequestBody request: CheckJobOrderCreatedReques return jobOrderService.updateJoReqQty(request) } @GetMapping("/material-pick-status") -fun getMaterialPickStatus(): List { - return joPickOrderService.getMaterialPickStatus() -} + fun getMaterialPickStatus(@RequestParam(required = false) date: String?): List { + return joPickOrderService.getMaterialPickStatus(date) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt index 0dcf624..da15f76 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt @@ -329,7 +329,7 @@ return result } else "" val sql = """ - SELECT + SELECT IFNULL(DATE_FORMAT( IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate), '%Y-%m-%d' @@ -340,10 +340,10 @@ return result IFNULL(dpor.deliveryNoteCode, '') AS dnNo, CAST(IFNULL(sp.id, 0) AS CHAR) AS customerId, IFNULL(sp.name, '') AS customerName, - CAST( - SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code) AS DECIMAL(14,2) + FORMAT( + ROUND(SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code), 0), 0 ) AS qtyNumeric, - CAST(ROUND(IFNULL(sol.qty, 0), 2) AS CHAR) AS qty, + FORMAT(ROUND(IFNULL(sol.qty, 0), 0), 0) AS qty, '' AS truckNo, '' AS driver, IFNULL(do.code, '') AS deliveryOrderNo, @@ -351,7 +351,7 @@ return result IFNULL(po.code, '') AS stockReqNo, IFNULL(il.lotNo, '') AS lotNo, IFNULL(DATE_FORMAT(il.expiryDate, '%Y-%m-%d'), '') AS expiryDate, - CAST(ROUND(IFNULL(sol.qty, 0), 2) AS CHAR) AS stockOutQty, + FORMAT(ROUND(IFNULL(sol.qty, 0), 0), 0) AS stockOutQty, COALESCE( picker_user.name, modified_user.name, @@ -364,8 +364,8 @@ return result ) AS pickedBy, GROUP_CONCAT(DISTINCT wh.code ORDER BY wh.code SEPARATOR ', ') AS storeLocation, '' AS pickRemark, - CAST( - SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code) AS CHAR + FORMAT( + ROUND(SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code), 0), 0 ) AS totalStockOutQty, 0 AS stockSubCategory FROM do_pick_order_line_record dpolr diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt index 2ad6a08..fb040df 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt @@ -587,6 +587,10 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { stockOutLine.startTime = LocalDateTime.now() } if (request.status == "completed") { + // ✅ 修复:如果 startTime 为 null,也设置它(处理直接完成的情况) + if (stockOutLine.startTime == null) { + stockOutLine.startTime = LocalDateTime.now() + } stockOutLine.endTime = LocalDateTime.now() } // 2. 更新自身 status/qty @@ -988,6 +992,10 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta stockOutLine.startTime = LocalDateTime.now() } if (request.status == "completed") { + // ✅ 修复:如果 startTime 为 null,也设置它(处理直接完成的情况) + if (stockOutLine.startTime == null) { + stockOutLine.startTime = LocalDateTime.now() + } stockOutLine.endTime = LocalDateTime.now() } val updateTime = System.currentTimeMillis() - updateStart