From 45e63f4f0cf34cf71eeb51b6b4c5abdc91b223e7 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Sun, 28 Sep 2025 21:43:38 +0800 Subject: [PATCH] update --- .../jobOrder/entity/JoPickOrderRepository.kt | 2 +- .../jobOrder/service/JoPickOrderService.kt | 731 ++++++++++++++++-- .../jobOrder/web/JobOrderController.kt | 60 +- .../web/model/SecondScanIssueRequest.kt | 11 + .../web/model/SecondScanSubmitRequest.kt | 15 + .../pickOrder/entity/PickExecutionIssue.kt | 5 +- .../pickOrder/entity/PickOrderRepository.kt | 3 + 7 files changed, 755 insertions(+), 72 deletions(-) create mode 100644 src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/SecondScanIssueRequest.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/SecondScanSubmitRequest.kt 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 a694525..8765dfe 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 @@ -15,5 +15,5 @@ import java.time.LocalDateTime interface JoPickOrderRepository : JpaRepository { fun findByPickOrderId(pickOrderId: Long): List - + fun findByPickOrderIdAndItemId(pickOrderId: Long, itemId: Long): java.util.Optional } \ 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 7b22ee4..22369e1 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 @@ -13,15 +13,25 @@ import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDate -import java.time.LocalDateTime +import java.time.LocalDateTime +import com.ffii.core.support.JdbcDao +import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest +import java.util.Optional +import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanIssueRequest +import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue +import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository +import java.math.BigDecimal +import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus @Service open class JoPickOrderService( private val joPickOrderRepository: JoPickOrderRepository, private val joPickOrderRecordRepository: JoPickOrderRecordRepository, private val pickOrderService: PickOrderService, private val userService: UserService, - private val pickOrderRepository: PickOrderRepository + private val pickOrderRepository: PickOrderRepository, + private val jdbcDao: JdbcDao, + private val pickExecutionIssueRepository: PickExecutionIssueRepository ) { open fun save(record: JoPickOrder): JoPickOrder { @@ -108,7 +118,6 @@ open class JoPickOrderService( return joPickOrderRecordRepository.saveAll(joPickOrderRecords) } - // ✅ Get all job order lots with details hierarchical open fun getAllJobOrderLotsWithDetailsHierarchical(userId: Long): Map { println("=== Debug: getAllJobOrderLotsWithDetailsHierarchical ===") println("today: ${LocalDate.now()}") @@ -172,7 +181,7 @@ open class JoPickOrderService( ) } - // Use the same SQL query but transform the results into hierarchical structure + // ✅ 修复:简化 SQL 查询,移除不存在的字段 val pickOrderIdsStr = pickOrderIds.joinToString(",") val sql = """ @@ -189,7 +198,6 @@ open class JoPickOrderService( -- Job Order Information jo.id as jobOrderId, jo.code as jobOrderCode, - jo.name as jobOrderName, -- Pick Order Line Information pol.id as pickOrderLineId, @@ -311,87 +319,361 @@ open class JoPickOrderService( println("🔍 Executing SQL for job order hierarchical structure: $sql") println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") - // ✅ 修复:使用 PickOrderService 的现有方法 - val results = pickOrderService.getAllPickOrderLotsWithDetailsHierarchical(userId) - - // 如果结果为空,返回空结构 - if (results.isEmpty()) { + // ✅ 修复:直接执行 SQL 查询并构建分层结构 + try { + val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) + + if (results.isEmpty()) { + return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) + } + + // 构建分层结构 + val pickOrderInfo = results.firstOrNull()?.let { firstRow: Map -> + mapOf( + "id" to firstRow["pickOrderId"], + "code" to firstRow["pickOrderCode"], + "consoCode" to firstRow["pickOrderConsoCode"], + "targetDate" to firstRow["pickOrderTargetDate"], + "type" to firstRow["pickOrderType"], + "status" to firstRow["pickOrderStatus"], + "assignTo" to firstRow["pickOrderAssignTo"], + "jobOrder" to mapOf( + "id" to firstRow["jobOrderId"], + "code" to firstRow["jobOrderCode"], + "name" to "Job Order ${firstRow["jobOrderCode"]}" // ✅ 使用生成的名称 + ) + ) + } + + // 按 pick order line 分组 + val groupedByLine = results.groupBy { row: Map -> row["pickOrderLineId"] } + val pickOrderLines = groupedByLine.map { (lineId: Any?, lineRows: List>) -> + val firstLineRow = lineRows.first() + mapOf( + "id" to lineId, + "itemId" to firstLineRow["itemId"], + "itemCode" to firstLineRow["itemCode"], + "itemName" to firstLineRow["itemName"], + "requiredQty" to firstLineRow["pickOrderLineRequiredQty"], + "uomCode" to firstLineRow["uomCode"], + "uomDesc" to firstLineRow["uomDesc"], + "lots" to lineRows.map { row: Map -> + mapOf( + "lotId" to row["lotId"], + "lotNo" to row["lotNo"], + "expiryDate" to row["expiryDate"], + "location" to row["location"], + "availableQty" to row["availableQty"], + "requiredQty" to row["requiredQty"], + "actualPickQty" to row["actualPickQty"], + "processingStatus" to row["processingStatus"], + "lotAvailability" to row["lotAvailability"], + "pickOrderId" to row["pickOrderId"], + "pickOrderCode" to row["pickOrderCode"], + "pickOrderConsoCode" to row["pickOrderConsoCode"], + "pickOrderLineId" to row["pickOrderLineId"], + "stockOutLineId" to row["stockOutLineId"], + "stockOutLineStatus" to row["stockOutLineStatus"], + "routerIndex" to row["routerIndex"], + "routerArea" to row["routerArea"], + "routerRoute" to row["routerRoute"], + "uomShortDesc" to row["uomShortDesc"], + "secondQrScanStatus" to row["secondQrScanStatus"], + "secondQrScanBy" to row["secondQrScanBy"], + "secondQrScanQty" to row["secondQrScanQty"] + ) + } + ) + } + + return mapOf( + "pickOrder" to pickOrderInfo as Any?, + "pickOrderLines" to pickOrderLines as Any? + ) + + } catch (e: Exception) { + println("❌ Error executing Job Order SQL: ${e.message}") + e.printStackTrace() return mapOf( "pickOrder" to null as Any?, "pickOrderLines" to emptyList>() as Any? ) } - - // 获取 pick order 信息 - val pickOrderInfo = results["pickOrder"] as? Map - val pickOrderLines = results["pickOrderLines"] as? List> - - // 添加 Job Order 信息到 pick order info - val enhancedPickOrderInfo = pickOrderInfo?.toMutableMap() ?: mutableMapOf() - enhancedPickOrderInfo["jobOrder"] = mapOf( - "id" to pickOrderInfo?.get("id"), - "code" to "JO-${pickOrderInfo?.get("code")}", - "name" to "Job Order ${pickOrderInfo?.get("code")}" - ) - - return mapOf( - "pickOrder" to enhancedPickOrderInfo as Any?, - "pickOrderLines" to (pickOrderLines ?: emptyList()) as Any? - ) } // Get completed job order pick orders (for second tab) - open fun getCompletedJobOrderPickOrders(userId: Long): List> { - println("=== getCompletedJobOrderPickOrders ===") - println("userId: $userId") - - return try { - // Get completed pick orders for job orders + // ✅ Fix the getCompletedJobOrderLotsHierarchical method +open fun getCompletedJobOrderLotsHierarchical(userId: Long): Map { + println("=== Debug: getCompletedJobOrderLotsHierarchical ===") + println("today: ${LocalDate.now()}") + println("userId filter: $userId") + + // Get all COMPLETED pick orders assigned to the user with joId + val user = userService.find(userId).orElse(null) + if (user == null) { + println("❌ User not found: $userId") + return emptyMap() + } + + // Get all COMPLETED pick orders assigned to user that have joId val completedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn( userId, listOf(PickOrderStatus.COMPLETED) - ).filter { it.jobOrder != null } // Only job order pick orders + ).filter { it.jobOrder != null } // Only pick orders with joId - println("Found ${completedPickOrders.size} completed job order pick orders for user $userId") + println("🔍 DEBUG: Found ${completedPickOrders.size} completed job order pick orders assigned to user $userId") - val jobOrderPickOrders = completedPickOrders.mapNotNull { pickOrder -> - // Get job order details - val jobOrder = pickOrder.jobOrder - if (jobOrder != null) { + val visiblePickOrders = completedPickOrders.filter { pickOrder -> val joPickOrders = findByPickOrderId(pickOrder.id!!) - val isHidden = joPickOrders.any { it.hide } - if (!isHidden) { + joPickOrders.none { it.hide } // Only show hide = false orders + } + + if (visiblePickOrders.isEmpty()) { + println("🔍 DEBUG: No visible completed job orders found") + return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) + } + + // For completed orders, show the latest one + val latestCompletedOrder = visiblePickOrders.maxByOrNull { + it.completeDate ?: it.modified ?: LocalDateTime.MIN + } + + if (latestCompletedOrder == null) { + println("🔍 DEBUG: No latest completed order found") + return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) + } + + println("🔍 DEBUG: Using latest completed order: ${latestCompletedOrder.code}") + + // ✅ Use the same SQL query approach as getAllJobOrderLotsWithDetailsHierarchical + val pickOrderIds = listOf(latestCompletedOrder.id!!) + val pickOrderIdsStr = pickOrderIds.joinToString(",") + + val sql = """ + SELECT + -- Pick Order Information + po.id as pickOrderId, + po.code as pickOrderCode, + po.consoCode as pickOrderConsoCode, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, + 'jo' as pickOrderType, + po.status as pickOrderStatus, + po.assignTo as pickOrderAssignTo, + + -- Job Order Information + jo.id as jobOrderId, + jo.code as jobOrderCode, + + -- Pick Order Line Information + pol.id as pickOrderLineId, + pol.qty as pickOrderLineRequiredQty, + pol.status as pickOrderLineStatus, + + -- Item Information + i.id as itemId, + i.code as itemCode, + i.name as itemName, + uc.code as uomCode, + uc.udfudesc as uomDesc, + uc.udfShortDesc as uomShortDesc, + + -- Lot Information + ill.id as lotId, + il.lotNo, + DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, + w.name as location, + COALESCE(uc.udfudesc, 'N/A') as stockUnit, + + -- Router Information + r.id as routerId, + r.index as routerIndex, + r.route as routerRoute, + r.area as routerArea, + + -- Set quantities to NULL for rejected lots + CASE + WHEN sol.status = 'rejected' THEN NULL + ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) + END as availableQty, + + -- Required quantity for this lot + COALESCE(spl.qty, 0) as requiredQty, + + -- Actual picked quantity + COALESCE(sol.qty, 0) as actualPickQty, + + -- Suggested pick lot information + spl.id as suggestedPickLotId, + ill.status as lotStatus, + + -- Stock out line information + sol.id as stockOutLineId, + sol.status as stockOutLineStatus, + COALESCE(sol.qty, 0) as stockOutLineQty, + + -- Additional detailed fields + COALESCE(ill.inQty, 0) as inQty, + COALESCE(ill.outQty, 0) as outQty, + COALESCE(ill.holdQty, 0) as holdQty, + COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId, + ill.inventoryLotId as debugInventoryLotId, + + -- Calculate total picked quantity by ALL pick orders for this lot + COALESCE(( + SELECT SUM(sol_all.qty) + FROM fpsmsdb.stock_out_line sol_all + WHERE sol_all.inventoryLotLineId = ill.id + AND sol_all.deleted = false + AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed') + ), 0) as totalPickedByAllPickOrders, + + -- Calculate remaining available quantity correctly + (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders, + + -- Lot availability status + CASE + WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' + WHEN sol.status = 'rejected' THEN 'rejected' + WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' + WHEN ill.status = 'unavailable' THEN 'status_unavailable' + ELSE 'available' + END as lotAvailability, + + -- Processing status + CASE + WHEN sol.status = 'completed' THEN 'completed' + WHEN sol.status = 'rejected' THEN 'rejected' + WHEN sol.status = 'created' THEN 'pending' + ELSE 'pending' + END as processingStatus, + + -- JoPickOrder second scan status + jpo.second_qr_scan_status as secondQrScanStatus, + jpo.second_qr_scan_by as secondQrScanBy, + jpo.second_qr_scan_qty as secondQrScanQty + + FROM fpsmsdb.pick_order po + JOIN fpsmsdb.job_order jo ON jo.id = po.joId + JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id + JOIN fpsmsdb.items i ON i.id = pol.itemId + LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId + LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId + LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id + LEFT JOIN fpsmsdb.router r ON r.inventoryLotId = ill.id AND r.deleted = false + LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId + LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId + LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false + LEFT JOIN fpsmsdb.jo_pick_order jpo ON jpo.pick_order_id = po.id AND jpo.item_id = pol.itemId + WHERE po.deleted = false + AND po.id IN ($pickOrderIdsStr) + AND pol.deleted = false + AND po.status = 'COMPLETED' + AND po.assignTo = :userId + AND ill.deleted = false + AND il.deleted = false + AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) + AND jpo.second_qr_scan_status = 'pending' OR jpo.second_qr_scan_status = 'scanned' + ORDER BY + CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, + COALESCE(r.index, 0) ASC, + po.code ASC, + i.code ASC, + il.expiryDate ASC, + il.lotNo ASC + """.trimIndent() + + println("🔍 Executing SQL for completed job order hierarchical structure: $sql") + println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") + + // ✅ Execute SQL query and build hierarchical structure + try { + val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) + + if (results.isEmpty()) { + return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) + } + + // Build hierarchical structure + val pickOrderInfo = results.firstOrNull()?.let { firstRow: Map -> mapOf( - "pickOrderId" to pickOrder.id, - "pickOrderCode" to pickOrder.code, - "pickOrderConsoCode" to pickOrder.consoCode, - "pickOrderTargetDate" to pickOrder.targetDate, - "pickOrderStatus" to pickOrder.status, - "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, - "completeDate" to pickOrder.completeDate - ) - } else { - println("❌ Pick order ${pickOrder.id} is hidden, skipping.") - null - } - } else { - println("❌ Pick order ${pickOrder.id} has no job order, skipping.") - null + "id" to firstRow["pickOrderId"], + "code" to firstRow["pickOrderCode"], + "consoCode" to firstRow["pickOrderConsoCode"], + "targetDate" to firstRow["pickOrderTargetDate"], + "type" to firstRow["pickOrderType"], + "status" to firstRow["pickOrderStatus"], + "assignTo" to firstRow["pickOrderAssignTo"], + "jobOrder" to mapOf( + "id" to firstRow["jobOrderId"], + "code" to firstRow["jobOrderCode"], + "name" to "Job Order ${firstRow["jobOrderCode"]}" + ) + ) + } + + // Group by pick order line + val groupedByLine = results.groupBy { row: Map -> row["pickOrderLineId"] } + val pickOrderLines = groupedByLine.map { (lineId: Any?, lineRows: List>) -> + val firstLineRow = lineRows.first() + mapOf( + "id" to lineId, + "itemId" to firstLineRow["itemId"], + "itemCode" to firstLineRow["itemCode"], + "itemName" to firstLineRow["itemName"], + "requiredQty" to firstLineRow["pickOrderLineRequiredQty"], + "uomCode" to firstLineRow["uomCode"], + "uomDesc" to firstLineRow["uomDesc"], + "lots" to lineRows.map { row: Map -> + mapOf( + "lotId" to row["lotId"], + "lotNo" to row["lotNo"], + "expiryDate" to row["expiryDate"], + "location" to row["location"], + "availableQty" to row["availableQty"], + "requiredQty" to row["requiredQty"], + "actualPickQty" to row["actualPickQty"], + "processingStatus" to row["processingStatus"], + "lotAvailability" to row["lotAvailability"], + "pickOrderId" to row["pickOrderId"], + "pickOrderCode" to row["pickOrderCode"], + "pickOrderConsoCode" to row["pickOrderConsoCode"], + "pickOrderLineId" to row["pickOrderLineId"], + "stockOutLineId" to row["stockOutLineId"], + "stockOutLineStatus" to row["stockOutLineStatus"], + "routerIndex" to row["routerIndex"], + "routerArea" to row["routerArea"], + "routerRoute" to row["routerRoute"], + "uomShortDesc" to row["uomShortDesc"], + "secondQrScanStatus" to row["secondQrScanStatus"] + ) } - } - - println("Returning ${jobOrderPickOrders.size} completed job order pick orders") - jobOrderPickOrders + ) + } + + return mapOf( + "pickOrder" to pickOrderInfo as Any?, + "pickOrderLines" to pickOrderLines as Any? + ) + } catch (e: Exception) { - println("❌ Error in getCompletedJobOrderPickOrders: ${e.message}") + println("❌ Error executing completed Job Order SQL: ${e.message}") e.printStackTrace() - emptyList() + return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) } } @@ -444,4 +726,317 @@ open class JoPickOrderService( emptyList() } } + + // 修复 getUnassignedJobOrderPickOrders 方法 +open fun getUnassignedJobOrderPickOrders(): List> { + println("=== getUnassignedJobOrderPickOrders ===") + + return try { + // ✅ 修复:使用正确的 repository 方法 + val unassignedPickOrders = pickOrderRepository.findAllByStatusAndAssignToIsNullAndDeletedFalse( + com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED + ).filter { pickOrder -> + pickOrder.jobOrder != null // Only job order pick orders + } + + println("Found ${unassignedPickOrders.size} unassigned job order pick orders") + + val jobOrderPickOrders = unassignedPickOrders.mapNotNull { pickOrder -> + val jobOrder = pickOrder.jobOrder + if (jobOrder != null) { + mapOf( + "pickOrderId" to pickOrder.id, + "pickOrderCode" to pickOrder.code, + "pickOrderConsoCode" to pickOrder.consoCode, + "pickOrderTargetDate" to pickOrder.targetDate, + "pickOrderStatus" to pickOrder.status, + "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 + ) + } else { + println("❌ Pick order ${pickOrder.id} has no job order, skipping.") + null + } + } + + println("Returning ${jobOrderPickOrders.size} unassigned job order pick orders") + jobOrderPickOrders + } catch (e: Exception) { + println("❌ Error in getUnassignedJobOrderPickOrders: ${e.message}") + e.printStackTrace() + emptyList() + } +} + +// ✅ 修复:assignJobOrderPickOrderToUser 方法中的自引用问题 +@Transactional +open fun assignJobOrderPickOrderToUser(pickOrderId: Long, userId: Long): MessageResponse { + println("=== assignJobOrderPickOrderToUser ===") + println("pickOrderId: $pickOrderId, userId: $userId") + + return try { + val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) + if (pickOrder == null) { + return MessageResponse( + id = null, + code = null, + name = null, + type = null, + message = "Pick order not found", + errorPosition = null + ) + } + + if (pickOrder.assignTo != null) { + return MessageResponse( + id = null, + code = null, + name = null, + type = null, + message = "Pick order is already assigned", + errorPosition = null + ) + } + + // Assign pick order to user + pickOrder.assignTo = userService.find(userId).orElse(null) + pickOrderRepository.save(pickOrder) + + // ✅ 修复:使用 this 而不是 joPickOrderService + this.updateHandledByForPickOrder(pickOrderId, userId) + + MessageResponse( + id = pickOrder.id, + code = pickOrder.code, + name = pickOrder.jobOrder?.bom?.name, + type = null, + message = "Successfully assigned", + errorPosition = null + ) + } catch (e: Exception) { + println("❌ Error in assignJobOrderPickOrderToUser: ${e.message}") + e.printStackTrace() + MessageResponse( + id = null, + code = null, + name = null, + type = null, + message = "Error occurred: ${e.message}", + errorPosition = null + ) + } + } + // ✅ Fix the updateSecondQrScanStatus method +open fun updateSecondQrScanStatus(pickOrderId: Long, itemId: Long): MessageResponse { + try { + println("=== Debug: updateSecondQrScanStatus ===") + println("pickOrderId: $pickOrderId, itemId: $itemId") + + val joPickOrder = joPickOrderRepository.findByPickOrderIdAndItemId(pickOrderId, itemId) + + if (joPickOrder.isEmpty) { + return MessageResponse( + id = null, + name = null, + code = "ERROR", + type = "NOT_FOUND", + message = "Job Order Pick Order not found for pickOrderId: $pickOrderId, itemId: $itemId", + errorPosition = null + ) + } + + val joPickOrderEntity = joPickOrder.get() + joPickOrderEntity.second_qr_scan_status = JoPickOrderStatus.scanned // ✅ Use enum instead of string + joPickOrderRepository.save(joPickOrderEntity) + + return MessageResponse( + id = null, + name = null, + code = "SUCCESS", + type = "UPDATED", + message = "Second QR scan status updated to checked", + errorPosition = null + ) + + } catch (e: Exception) { + println("❌ Error updating second QR scan status: ${e.message}") + return MessageResponse( + id = null, + name = null, + code = "ERROR", + type = "EXCEPTION", + message = "Error updating second QR scan status: ${e.message}", + errorPosition = null + ) + } +} + +open fun submitSecondScanQty(request: SecondScanSubmitRequest): MessageResponse { + try { + println("=== Debug: submitSecondScanQty ===") + println("pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}, qty: ${request.qty}") + + val joPickOrder = joPickOrderRepository.findByPickOrderIdAndItemId(request.pickOrderId, request.itemId) + + if (joPickOrder.isEmpty) { + return MessageResponse( + id = null, + name = null, + code = "ERROR", + type = "NOT_FOUND", + message = "Job Order Pick Order not found for pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}", + errorPosition = null + ) + } + + val joPickOrderEntity = joPickOrder.get() + joPickOrderEntity.second_qr_scan_qty = request.qty.toInt() + + // ✅ Always set status to completed when submitting quantity + joPickOrderEntity.second_qr_scan_status = JoPickOrderStatus.completed + + joPickOrderRepository.save(joPickOrderEntity) + + println("✅ Updated jo_pick_order: status=${joPickOrderEntity.second_qr_scan_status}, qty=${joPickOrderEntity.second_qr_scan_qty}") + + return MessageResponse( + id = null, + name = null, + code = "SUCCESS", + type = "UPDATED", + message = "Second scan quantity submitted successfully", + errorPosition = null + ) + + } catch (e: Exception) { + println("❌ Error submitting second scan quantity: ${e.message}") + return MessageResponse( + id = null, + name = null, + code = "ERROR", + type = "EXCEPTION", + message = "Error submitting second scan quantity: ${e.message}", + errorPosition = null + ) + } +} + + +// ✅ Fix the recordSecondScanIssue method +open fun recordSecondScanIssue(request: SecondScanIssueRequest): MessageResponse { + try { + println("=== Debug: recordSecondScanIssue ===") + println("pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}") + + val joPickOrder = joPickOrderRepository.findByPickOrderIdAndItemId(request.pickOrderId, request.itemId) + + if (joPickOrder.isEmpty) { + return MessageResponse( + id = null, + name = null, + code = "ERROR", + type = "NOT_FOUND", + message = "Job Order Pick Order not found for pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}", + errorPosition = null + ) + } + + // ✅ Get pick order line details with item information + val pickOrderLineResults = jdbcDao.queryForList( + """ + SELECT + pol.id as pickOrderLineId, + i.code as itemCode, + i.name as itemName, + pol.qty as requiredQty + FROM fpsmsdb.pick_order_line pol + JOIN fpsmsdb.items i ON i.id = pol.itemId + WHERE pol.poId = :pickOrderId AND pol.itemId = :itemId AND pol.deleted = false + """, + mapOf( + "pickOrderId" to request.pickOrderId, + "itemId" to request.itemId + ) + ) + + if (pickOrderLineResults.isEmpty()) { + return MessageResponse( + id = null, + name = null, + code = "ERROR", + type = "NOT_FOUND", + message = "Pick order line not found for pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}", + errorPosition = null + ) + } + + val lineData = pickOrderLineResults.first() + val pickOrderLineId = (lineData["pickOrderLineId"] as Number).toLong() + val itemCode = lineData["itemCode"] as String? + val itemName = lineData["itemName"] as String? + val requiredQty = lineData["requiredQty"]?.let { + BigDecimal(it.toString()) + } + + + val joPickOrderEntity = joPickOrder.get() + joPickOrderEntity.second_qr_scan_status = JoPickOrderStatus.completed + joPickOrderEntity.second_qr_scan_qty = request.qty.toInt() + joPickOrderRepository.save(joPickOrderEntity) + + // ✅ Create pick execution issue with complete data + val pickExecutionIssue = PickExecutionIssue( + pickOrderId = request.pickOrderId, + pickOrderCode = "JO-${request.pickOrderId}", + pickOrderLineId = pickOrderLineId, + itemId = request.itemId, + itemCode = itemCode, // ✅ Include item code + itemDescription = itemName, // ✅ Include item name + lotId = null, + lotNo = null, + storeLocation = null, + requiredQty = requiredQty, // ✅ Include required quantity + actualPickQty = request.qty.toBigDecimal(), // ✅ Include actual pick quantity + missQty = if (request.isMissing) request.qty.toBigDecimal() else BigDecimal.ZERO, + badItemQty = if (request.isBad) request.qty.toBigDecimal() else BigDecimal.ZERO, + issueRemark = request.reason, + pickerName = null, + handleStatus = HandleStatus.jopending, + handleDate = null, + handledBy = null, + created = LocalDateTime.now(), + createdBy = request.createdBy.toString(), + version = 0, + modified = LocalDateTime.now(), + modifiedBy = null, + deleted = false + ) + pickExecutionIssueRepository.save(pickExecutionIssue) + + return MessageResponse( + id = null, + name = null, + code = "SUCCESS", + type = "CREATED", + message = "Second scan issue recorded successfully", + errorPosition = null + ) + + } catch (e: Exception) { + println("❌ Error recording second scan issue: ${e.message}") + return MessageResponse( + id = null, + name = null, + code = "ERROR", + type = "EXCEPTION", + message = "Error recording second scan issue: ${e.message}", + errorPosition = null + ) + } +} } \ No newline at end of file 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 32ee623..95366d9 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 @@ -19,6 +19,8 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService +import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest +import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanIssueRequest @RestController @RequestMapping("/jo") class JobOrderController( @@ -65,12 +67,66 @@ class JobOrderController( } @GetMapping("/completed-job-order-pick-orders/{userId}") - fun getCompletedJobOrderPickOrders(@PathVariable userId: Long): List> { - return joPickOrderService.getCompletedJobOrderPickOrders(userId) + fun getCompletedJobOrderLotsHierarchical(@PathVariable userId: Long): Map { + return joPickOrderService.getCompletedJobOrderLotsHierarchical(userId) } @GetMapping("/completed-job-order-pick-order-records/{userId}") fun getCompletedJobOrderPickOrderRecords(@PathVariable userId: Long): List> { return joPickOrderService.getCompletedJobOrderPickOrderRecords(userId) } + @GetMapping("/unassigned-job-order-pick-orders") + fun getUnassignedJobOrderPickOrders(): List> { + return joPickOrderService.getUnassignedJobOrderPickOrders() + } + + @PostMapping("/assign-job-order-pick-order/{pickOrderId}/{userId}") + fun assignJobOrderPickOrderToUser( + @PathVariable pickOrderId: Long, + @PathVariable userId: Long + ): MessageResponse { + return joPickOrderService.assignJobOrderPickOrderToUser(pickOrderId, userId) + } + + @PostMapping("/second-scan-qr/{pickOrderId}/{itemId}") + fun updateSecondQrScanStatus( + @PathVariable pickOrderId: Long, + @PathVariable itemId: Long + ): MessageResponse { + return joPickOrderService.updateSecondQrScanStatus(pickOrderId, itemId) + } + + @PostMapping("/second-scan-submit/{pickOrderId}/{itemId}") + fun submitSecondScanQuantity( + @PathVariable pickOrderId: Long, + @PathVariable itemId: Long, + @RequestBody data: Map + ): MessageResponse { + val request = SecondScanSubmitRequest( + pickOrderId = pickOrderId, // ✅ Use path variable + itemId = itemId, // ✅ Use path variable + qty = (data["qty"] as Number).toDouble(), + isMissing = data["isMissing"] as? Boolean ?: false, + isBad = data["isBad"] as? Boolean ?: false + ) + return joPickOrderService.submitSecondScanQty(request) + } + + @PostMapping("/second-scan-issue/{pickOrderId}/{itemId}") + fun recordSecondScanIssue( + @PathVariable pickOrderId: Long, + @PathVariable itemId: Long, + @RequestBody data: Map + ): MessageResponse { + val request = SecondScanIssueRequest( + pickOrderId = pickOrderId, // ✅ Use path variable + itemId = itemId, // ✅ Use path variable + qty = (data["qty"] as Number).toDouble(), + isMissing = data["isMissing"] as Boolean, + isBad = data["isBad"] as Boolean, + reason = data["reason"] as String, + createdBy = (data["createdBy"] as Number).toLong() + ) + return joPickOrderService.recordSecondScanIssue(request) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/SecondScanIssueRequest.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/SecondScanIssueRequest.kt new file mode 100644 index 0000000..08159f5 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/SecondScanIssueRequest.kt @@ -0,0 +1,11 @@ +package com.ffii.fpsms.modules.jobOrder.web.model + +data class SecondScanIssueRequest( + val pickOrderId: Long, + val itemId: Long, + val qty: Double, + val isMissing: Boolean, + val isBad: Boolean, + val reason: String, + val createdBy: Long +) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/SecondScanSubmitRequest.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/SecondScanSubmitRequest.kt new file mode 100644 index 0000000..9c46e3e --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/SecondScanSubmitRequest.kt @@ -0,0 +1,15 @@ +package com.ffii.fpsms.modules.jobOrder.web.model + +import jakarta.validation.constraints.NotNull + +data class SecondScanSubmitRequest( + @NotNull + val pickOrderId: Long, // ✅ Add missing property + @NotNull + val itemId: Long, // ✅ Add missing property + @NotNull + val qty: Double, + val isMissing: Boolean = false, + val isBad: Boolean = false, + val reason: String? = null +) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt index a472991..e06ca81 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt @@ -127,5 +127,8 @@ class PickExecutionIssue( enum class HandleStatus { pending, // ✅ Change to lowercase to match database handled, // ✅ Change to lowercase to match database - resolved // ✅ Change to lowercase to match database + resolved, + jopending, + johandled, + joresolved // ✅ Change to lowercase to match database } \ 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 439a505..196335a 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 @@ -75,4 +75,7 @@ interface PickOrderRepository : AbstractRepository { @Query("SELECT p FROM PickOrder p WHERE p.deliveryOrder.id = :deliveryOrderId AND p.deleted = false") fun findByDeliveryOrderId(@Param("deliveryOrderId") deliveryOrderId: Long): List + + // 在 PickOrderRepository 中添加: +fun findAllByStatusAndAssignToIsNullAndDeletedFalse(status: PickOrderStatus): List } \ No newline at end of file