| @@ -0,0 +1,80 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity | |||||
| import jakarta.persistence.* | |||||
| import java.time.LocalDateTime | |||||
| import java.time.LocalTime | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | |||||
| import org.hibernate.annotations.CreationTimestamp | |||||
| import org.hibernate.annotations.UpdateTimestamp | |||||
| @Entity | |||||
| @Table(name = "jo_pick_order") | |||||
| class JoPickOrder { | |||||
| @Id | |||||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |||||
| var id: Long? = null | |||||
| @Column(name = "pick_order_id") | |||||
| var pickOrderId: Long? = null | |||||
| @Column(name = "item_id") | |||||
| var itemId: Long? = null | |||||
| @Enumerated(EnumType.STRING) | |||||
| @Column(name = "second_qr_scan_status") | |||||
| var second_qr_scan_status: JoPickOrderStatus? = null | |||||
| @Column(name = "second_qr_scan_by") | |||||
| var second_qr_scan_by: Long? = null | |||||
| @Column(name = "second_qr_scan_qty") | |||||
| var second_qr_scan_qty: Int? = null | |||||
| @Column(name = "handled_by") | |||||
| var handledBy: Long? = null | |||||
| @CreationTimestamp | |||||
| @Column(name = "created") | |||||
| var created: LocalDateTime? = null | |||||
| @Column(name = "createdBy", length = 30) | |||||
| var createdBy: String? = null | |||||
| @Version | |||||
| var version: Int = 0 | |||||
| @UpdateTimestamp | |||||
| @Column(name = "modified") | |||||
| var modified: LocalDateTime? = null | |||||
| @Column(name = "modifiedBy", length = 30) | |||||
| var modifiedBy: String? = null | |||||
| @Column(name = "deleted") | |||||
| var deleted: Boolean = false | |||||
| @Column(name = "hide", nullable = false) | |||||
| var hide: Boolean = false | |||||
| // Default constructor for Hibernate | |||||
| constructor() | |||||
| // Constructor for creating new instances | |||||
| constructor( | |||||
| pickOrderId: Long? = null, | |||||
| itemId: Long? = null, | |||||
| second_qr_scan_status: JoPickOrderStatus? = null, | |||||
| second_qr_scan_by: Long? = null, | |||||
| second_qr_scan_qty: Int? = null, | |||||
| handledBy: Long? = null, | |||||
| createdBy: String? = null, | |||||
| modifiedBy: String? = null | |||||
| ) { | |||||
| this.pickOrderId = pickOrderId | |||||
| this.itemId = itemId | |||||
| this.second_qr_scan_status = second_qr_scan_status | |||||
| this.second_qr_scan_by = second_qr_scan_by | |||||
| this.second_qr_scan_qty = second_qr_scan_qty | |||||
| this.handledBy = handledBy | |||||
| this.createdBy = createdBy | |||||
| this.modifiedBy = modifiedBy | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity | |||||
| import jakarta.persistence.* | |||||
| import java.time.LocalDateTime | |||||
| import java.time.LocalTime | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | |||||
| import org.hibernate.annotations.CreationTimestamp | |||||
| import org.hibernate.annotations.UpdateTimestamp | |||||
| @Entity | |||||
| @Table(name = "jo_pick_order_record") | |||||
| class JoPickOrderRecord { | |||||
| @Id | |||||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |||||
| var id: Long? = null | |||||
| @Column(name = "pick_order_id") | |||||
| var pickOrderId: Long? = null | |||||
| @Column(name = "item_id") | |||||
| var itemId: Long? = null | |||||
| @Enumerated(EnumType.STRING) | |||||
| @Column(name = "second_qr_scan_status") | |||||
| var second_qr_scan_status: JoPickOrderStatus? = null | |||||
| @Column(name = "second_qr_scan_by") | |||||
| var second_qr_scan_by: Long? = null | |||||
| @Column(name = "second_qr_scan_qty") | |||||
| var second_qr_scan_qty: Int? = null | |||||
| @Column(name = "handled_by") | |||||
| var handledBy: Long? = null | |||||
| @CreationTimestamp | |||||
| @Column(name = "created") | |||||
| var created: LocalDateTime? = null | |||||
| @Column(name = "createdBy", length = 30) | |||||
| var createdBy: String? = null | |||||
| @Version | |||||
| var version: Int = 0 | |||||
| @UpdateTimestamp | |||||
| @Column(name = "modified") | |||||
| var modified: LocalDateTime? = null | |||||
| @Column(name = "modifiedBy", length = 30) | |||||
| var modifiedBy: String? = null | |||||
| @Column(name = "deleted") | |||||
| var deleted: Boolean = false | |||||
| @Column(name = "hide", nullable = false) | |||||
| var hide: Boolean = false | |||||
| // Default constructor for Hibernate | |||||
| constructor() | |||||
| // Constructor for creating new instances | |||||
| constructor( | |||||
| pickOrderId: Long? = null, | |||||
| itemId: Long? = null, | |||||
| second_qr_scan_status: JoPickOrderStatus? = null, | |||||
| second_qr_scan_by: Long? = null, | |||||
| second_qr_scan_qty: Int? = null, | |||||
| handledBy: Long? = null, | |||||
| createdBy: String? = null, | |||||
| modifiedBy: String? = null | |||||
| ) { | |||||
| this.pickOrderId = pickOrderId | |||||
| this.itemId = itemId | |||||
| this.second_qr_scan_status = second_qr_scan_status | |||||
| this.second_qr_scan_by = second_qr_scan_by | |||||
| this.second_qr_scan_qty = second_qr_scan_qty | |||||
| this.handledBy = handledBy | |||||
| this.createdBy = createdBy | |||||
| this.modifiedBy = modifiedBy | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | |||||
| import com.ffii.fpsms.modules.master.entity.projections.SearchId | |||||
| import org.springframework.data.jpa.repository.JpaRepository | |||||
| import org.springframework.data.jpa.repository.Query | |||||
| import org.springframework.stereotype.Repository | |||||
| import java.io.Serializable | |||||
| import java.time.LocalDateTime | |||||
| @Repository | |||||
| interface JoPickOrderRecordRepository : JpaRepository<JoPickOrderRecord, Long> { | |||||
| fun findByPickOrderId(pickOrderId: Long): List<JoPickOrderRecord> | |||||
| //fun findByTicketNoStartingWith(ticketPrefix: String): List<JoPickOrderRecord> | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrder | |||||
| import com.ffii.fpsms.modules.master.entity.projections.SearchId | |||||
| import org.springframework.data.jpa.repository.JpaRepository | |||||
| import org.springframework.data.jpa.repository.Query | |||||
| import org.springframework.stereotype.Repository | |||||
| import java.io.Serializable | |||||
| import java.time.LocalDateTime | |||||
| @Repository | |||||
| interface JoPickOrderRepository : JpaRepository<JoPickOrder, Long> { | |||||
| fun findByPickOrderId(pickOrderId: Long): List<JoPickOrder> | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.enums | |||||
| enum class JoPickOrderStatus(val value: String) { | |||||
| pending("pending"), | |||||
| scanned("scanned"), | |||||
| completed("completed") | |||||
| } | |||||
| @@ -0,0 +1,447 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.service | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrder | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRecord | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRecordRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | |||||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||||
| import com.ffii.fpsms.modules.pickOrder.service.PickOrderService | |||||
| import com.ffii.fpsms.modules.user.service.UserService | |||||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||||
| 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 | |||||
| @Service | |||||
| open class JoPickOrderService( | |||||
| private val joPickOrderRepository: JoPickOrderRepository, | |||||
| private val joPickOrderRecordRepository: JoPickOrderRecordRepository, | |||||
| private val pickOrderService: PickOrderService, | |||||
| private val userService: UserService, | |||||
| private val pickOrderRepository: PickOrderRepository | |||||
| ) { | |||||
| open fun save(record: JoPickOrder): JoPickOrder { | |||||
| return joPickOrderRepository.save(record) | |||||
| } | |||||
| open fun saveRecord(record: JoPickOrderRecord): JoPickOrderRecord { | |||||
| return joPickOrderRecordRepository.save(record) | |||||
| } | |||||
| // ✅ Update JoPickOrder status to released | |||||
| open fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long): List<JoPickOrder> { | |||||
| val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId) | |||||
| joPickOrders.forEach { | |||||
| it.handledBy = userId | |||||
| it.second_qr_scan_status = JoPickOrderStatus.pending // Set initial status | |||||
| } | |||||
| return joPickOrderRepository.saveAll(joPickOrders) | |||||
| } | |||||
| // ✅ Complete JoPickOrder | |||||
| open fun completeJoPickOrdersForPickOrder(pickOrderId: Long): List<JoPickOrder> { | |||||
| val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId) | |||||
| joPickOrders.forEach { | |||||
| it.second_qr_scan_status = JoPickOrderStatus.completed | |||||
| } | |||||
| return joPickOrderRepository.saveAll(joPickOrders) | |||||
| } | |||||
| // ✅ Update JoPickOrderRecord status to released | |||||
| open fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long): List<JoPickOrderRecord> { | |||||
| val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||||
| joPickOrderRecords.forEach { | |||||
| it.handledBy = userId | |||||
| it.second_qr_scan_status = JoPickOrderStatus.pending // Set initial status | |||||
| } | |||||
| return joPickOrderRecordRepository.saveAll(joPickOrderRecords) | |||||
| } | |||||
| // ✅ Complete JoPickOrderRecord | |||||
| open fun completeJoPickOrderRecordsForPickOrder(pickOrderId: Long): List<JoPickOrderRecord> { | |||||
| val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||||
| joPickOrderRecords.forEach { | |||||
| it.second_qr_scan_status = JoPickOrderStatus.completed | |||||
| } | |||||
| return joPickOrderRecordRepository.saveAll(joPickOrderRecords) | |||||
| } | |||||
| // ✅ Find JoPickOrder records by pick order ID | |||||
| open fun findByPickOrderId(pickOrderId: Long): List<JoPickOrder> { | |||||
| return joPickOrderRepository.findByPickOrderId(pickOrderId) | |||||
| } | |||||
| // ✅ Find JoPickOrderRecord records by pick order ID | |||||
| open fun findRecordsByPickOrderId(pickOrderId: Long): List<JoPickOrderRecord> { | |||||
| return joPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||||
| } | |||||
| // ✅ Create JoPickOrder records for a pick order | |||||
| @Transactional | |||||
| open fun createJoPickOrdersForPickOrder(pickOrderId: Long, itemIds: List<Long>): List<JoPickOrder> { | |||||
| val joPickOrders = itemIds.map { itemId -> | |||||
| JoPickOrder( | |||||
| pickOrderId = pickOrderId, | |||||
| itemId = itemId, | |||||
| second_qr_scan_status = JoPickOrderStatus.pending, | |||||
| handledBy = null | |||||
| ) | |||||
| } | |||||
| return joPickOrderRepository.saveAll(joPickOrders) | |||||
| } | |||||
| // ✅ Create JoPickOrderRecord records for a pick order | |||||
| @Transactional | |||||
| open fun createJoPickOrderRecordsForPickOrder(pickOrderId: Long, itemIds: List<Long>): List<JoPickOrderRecord> { | |||||
| val joPickOrderRecords = itemIds.map { itemId -> | |||||
| JoPickOrderRecord( | |||||
| pickOrderId = pickOrderId, | |||||
| itemId = itemId, | |||||
| second_qr_scan_status = JoPickOrderStatus.pending, | |||||
| handledBy = null | |||||
| ) | |||||
| } | |||||
| return joPickOrderRecordRepository.saveAll(joPickOrderRecords) | |||||
| } | |||||
| // ✅ Get all job order lots with details hierarchical | |||||
| open fun getAllJobOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> { | |||||
| println("=== Debug: getAllJobOrderLotsWithDetailsHierarchical ===") | |||||
| println("today: ${LocalDate.now()}") | |||||
| println("userId filter: $userId") | |||||
| // Get all pick order IDs assigned to the user (both RELEASED and PENDING with joId) | |||||
| val user = userService.find(userId).orElse(null) | |||||
| if (user == null) { | |||||
| println("❌ User not found: $userId") | |||||
| return emptyMap() | |||||
| } | |||||
| 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") | |||||
| val visiblePickOrders = allAssignedPickOrders.filter { pickOrder -> | |||||
| val joPickOrders = findByPickOrderId(pickOrder.id!!) | |||||
| joPickOrders.none { it.hide } // Only show hide = false orders | |||||
| } | |||||
| // Filter based on assignment and status | |||||
| val filteredPickOrders = if (visiblePickOrders.isNotEmpty()) { | |||||
| // Check if there are any RELEASED orders assigned to this user (active work) | |||||
| 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") | |||||
| assignedReleasedOrders | |||||
| } else { | |||||
| // 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 } | |||||
| println("🔍 DEBUG: No assigned RELEASED job orders, showing latest completed order: ${latestCompleted?.code}") | |||||
| listOfNotNull(latestCompleted) | |||||
| } else { | |||||
| println("🔍 DEBUG: No job orders found") | |||||
| emptyList() | |||||
| } | |||||
| } | |||||
| } 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<Map<String, Any>>() as Any? | |||||
| ) | |||||
| } | |||||
| // Use the same SQL query but transform the results into hierarchical structure | |||||
| 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, | |||||
| po.type as pickOrderType, | |||||
| po.status as pickOrderStatus, | |||||
| po.assignTo as pickOrderAssignTo, | |||||
| -- Job Order Information | |||||
| jo.id as jobOrderId, | |||||
| jo.code as jobOrderCode, | |||||
| jo.name as jobOrderName, | |||||
| -- 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 IN ('PENDING', 'RELEASED', '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) | |||||
| 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 job order hierarchical structure: $sql") | |||||
| println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") | |||||
| // ✅ 修复:使用 PickOrderService 的现有方法 | |||||
| val results = pickOrderService.getAllPickOrderLotsWithDetailsHierarchical(userId) | |||||
| // 如果结果为空,返回空结构 | |||||
| if (results.isEmpty()) { | |||||
| return mapOf( | |||||
| "pickOrder" to null as Any?, | |||||
| "pickOrderLines" to emptyList<Map<String, Any>>() as Any? | |||||
| ) | |||||
| } | |||||
| // 获取 pick order 信息 | |||||
| val pickOrderInfo = results["pickOrder"] as? Map<String, Any?> | |||||
| val pickOrderLines = results["pickOrderLines"] as? List<Map<String, Any?>> | |||||
| // 添加 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<Map<String, Any?>> { | |||||
| println("=== getCompletedJobOrderPickOrders ===") | |||||
| println("userId: $userId") | |||||
| return try { | |||||
| // Get completed pick orders for job orders | |||||
| val completedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn( | |||||
| userId, | |||||
| listOf(PickOrderStatus.COMPLETED) | |||||
| ).filter { it.jobOrder != null } // Only job order pick orders | |||||
| println("Found ${completedPickOrders.size} completed job order pick orders for user $userId") | |||||
| val jobOrderPickOrders = completedPickOrders.mapNotNull { pickOrder -> | |||||
| // Get job order details | |||||
| val jobOrder = pickOrder.jobOrder | |||||
| if (jobOrder != null) { | |||||
| val joPickOrders = findByPickOrderId(pickOrder.id!!) | |||||
| val isHidden = joPickOrders.any { it.hide } | |||||
| if (!isHidden) { | |||||
| 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 | |||||
| } | |||||
| } | |||||
| println("Returning ${jobOrderPickOrders.size} completed job order pick orders") | |||||
| jobOrderPickOrders | |||||
| } catch (e: Exception) { | |||||
| println("❌ Error in getCompletedJobOrderPickOrders: ${e.message}") | |||||
| e.printStackTrace() | |||||
| emptyList() | |||||
| } | |||||
| } | |||||
| // Get completed job order pick order records (for third tab) | |||||
| open fun getCompletedJobOrderPickOrderRecords(userId: Long): List<Map<String, Any?>> { | |||||
| println("=== getCompletedJobOrderPickOrderRecords ===") | |||||
| println("userId: $userId") | |||||
| return try { | |||||
| // Get all completed JoPickOrderRecord records for the user | |||||
| val allRecords = joPickOrderRecordRepository.findAll() | |||||
| val completedRecords = allRecords.filter { | |||||
| it.second_qr_scan_status == JoPickOrderStatus.completed && | |||||
| it.handledBy == userId | |||||
| } | |||||
| println("Found ${completedRecords.size} completed job order pick order records for user $userId") | |||||
| val recordDetails = completedRecords.mapNotNull { record -> | |||||
| // Get pick order details | |||||
| val pickOrder = pickOrderRepository.findById(record.pickOrderId!!).orElse(null) | |||||
| if (pickOrder != null && pickOrder.jobOrder != null) { | |||||
| val jobOrder = pickOrder.jobOrder | |||||
| mapOf( | |||||
| "recordId" to record.id, | |||||
| "pickOrderId" to pickOrder.id, | |||||
| "pickOrderCode" to pickOrder.code, | |||||
| "jobOrderId" to jobOrder?.id, | |||||
| "jobOrderCode" to jobOrder?.code, | |||||
| "jobOrderName" to jobOrder?.bom?.name, | |||||
| "itemId" to record.itemId, | |||||
| "secondQrScanStatus" to record.second_qr_scan_status, | |||||
| "secondQrScanBy" to record.second_qr_scan_by, | |||||
| "secondQrScanQty" to record.second_qr_scan_qty, | |||||
| "handledBy" to record.handledBy, | |||||
| "created" to record.created, | |||||
| "completeDate" to pickOrder.completeDate | |||||
| ) | |||||
| } else { | |||||
| println("❌ Record ${record.id} has no valid pick order or job order, skipping.") | |||||
| null | |||||
| } | |||||
| } | |||||
| println("Returning ${recordDetails.size} completed job order pick order records") | |||||
| recordDetails | |||||
| } catch (e: Exception) { | |||||
| println("❌ Error in getCompletedJobOrderPickOrderRecords: ${e.message}") | |||||
| e.printStackTrace() | |||||
| emptyList() | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -30,7 +30,18 @@ import java.time.LocalDateTime | |||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import kotlin.jvm.optionals.getOrNull | import kotlin.jvm.optionals.getOrNull | ||||
| import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService | |||||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository | |||||
| import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService | |||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.StockOutRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.StockOut | |||||
| import com.ffii.fpsms.modules.stock.entity.StockOutLine | |||||
| import com.ffii.fpsms.modules.stock.web.model.StockOutStatus | |||||
| import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus | |||||
| import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPolRequest | |||||
| @Service | @Service | ||||
| open class JobOrderService( | open class JobOrderService( | ||||
| val jobOrderRepository: JobOrderRepository, | val jobOrderRepository: JobOrderRepository, | ||||
| @@ -38,6 +49,13 @@ open class JobOrderService( | |||||
| val userService: UserService, | val userService: UserService, | ||||
| val productionScheduleLineRepository: ProductionScheduleLineRepository, | val productionScheduleLineRepository: ProductionScheduleLineRepository, | ||||
| val pickOrderService: PickOrderService, | val pickOrderService: PickOrderService, | ||||
| val joPickOrderService: JoPickOrderService, | |||||
| val pickOrderRepository: PickOrderRepository, | |||||
| val pickOrderLineRepository: PickOrderLineRepository, | |||||
| val suggestedPickLotService: SuggestedPickLotService, | |||||
| val inventoryLotLineRepository: InventoryLotLineRepository, | |||||
| val stockOutRepository: StockOutRepository, | |||||
| val stockOutLineRepository: StockOutLIneRepository | |||||
| ) { | ) { | ||||
| open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | ||||
| val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10); | val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10); | ||||
| @@ -161,7 +179,81 @@ open class JobOrderService( | |||||
| pickOrderLine = pols | pickOrderLine = pols | ||||
| ) | ) | ||||
| pickOrderService.create(po) | |||||
| val createdPickOrder = pickOrderService.create(po) | |||||
| val consoCode = pickOrderService.assignConsoCode() | |||||
| val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null) | |||||
| if (pickOrderEntity != null) { | |||||
| pickOrderEntity.consoCode = consoCode | |||||
| pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED | |||||
| pickOrderRepository.saveAndFlush(pickOrderEntity) | |||||
| // ✅ 添加 suggested pick lots 创建逻辑 | |||||
| val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!) | |||||
| if (lines.isNotEmpty()) { | |||||
| val suggestions = suggestedPickLotService.suggestionForPickOrderLines( | |||||
| SuggestedPickLotForPolRequest(pickOrderLines = lines) | |||||
| ) | |||||
| val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) | |||||
| // ✅ Hold inventory quantities | |||||
| val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( | |||||
| saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } | |||||
| ) | |||||
| saveSuggestedPickLots.forEach { lot -> | |||||
| if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) { | |||||
| val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine) | |||||
| if (lineIndex >= 0) { | |||||
| inventoryLotLines[lineIndex].holdQty = | |||||
| (inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO) | |||||
| } | |||||
| } | |||||
| } | |||||
| inventoryLotLineRepository.saveAll(inventoryLotLines) | |||||
| // ✅ Create stock out record and pre-create stock out lines | |||||
| val stockOut = StockOut().apply { | |||||
| this.type = "job" | |||||
| this.consoPickOrderCode = consoCode | |||||
| this.status = StockOutStatus.PENDING.status | |||||
| this.handler = SecurityUtils.getUser().getOrNull()?.id | |||||
| } | |||||
| val savedStockOut = stockOutRepository.saveAndFlush(stockOut) | |||||
| // ✅ Pre-create stock out lines for suggested lots | |||||
| saveSuggestedPickLots.forEach { lot -> | |||||
| val polId = lot.pickOrderLine?.id | |||||
| val illId = lot.suggestedLotLine?.id | |||||
| if (polId != null && illId != null) { | |||||
| val existingLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) | |||||
| if (existingLines.isEmpty()) { | |||||
| val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null) | |||||
| val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null) | |||||
| if (pickOrderLine != null && inventoryLotLine != null) { | |||||
| val line = StockOutLine().apply { | |||||
| this.stockOut = savedStockOut | |||||
| this.pickOrderLine = pickOrderLine | |||||
| this.inventoryLotLine = inventoryLotLine | |||||
| this.item = pickOrderLine.item | |||||
| this.status = StockOutLineStatus.PENDING.status | |||||
| this.qty = 0.0 | |||||
| } | |||||
| stockOutLineRepository.save(line) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| val itemIds = pols.mapNotNull { it.itemId } | |||||
| if (itemIds.isNotEmpty()) { | |||||
| joPickOrderService.createJoPickOrdersForPickOrder(createdPickOrder.id!!, itemIds) | |||||
| joPickOrderService.createJoPickOrderRecordsForPickOrder(createdPickOrder.id!!, itemIds) | |||||
| } | |||||
| return MessageResponse( | return MessageResponse( | ||||
| id = jo.id, | id = jo.id, | ||||
| @@ -18,13 +18,14 @@ import org.springframework.web.bind.annotation.PostMapping | |||||
| import org.springframework.web.bind.annotation.RequestBody | import org.springframework.web.bind.annotation.RequestBody | ||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||
| import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/jo") | @RequestMapping("/jo") | ||||
| class JobOrderController( | class JobOrderController( | ||||
| private val jobOrderService: JobOrderService, | private val jobOrderService: JobOrderService, | ||||
| private val jobOrderBomMaterialService: JobOrderBomMaterialService, | private val jobOrderBomMaterialService: JobOrderBomMaterialService, | ||||
| private val jobOrderProcessService: JobOrderProcessService | |||||
| private val jobOrderProcessService: JobOrderProcessService, | |||||
| private val joPickOrderService: JoPickOrderService | |||||
| ) { | ) { | ||||
| @GetMapping("/getRecordByPage") | @GetMapping("/getRecordByPage") | ||||
| @@ -58,4 +59,18 @@ class JobOrderController( | |||||
| return jo | return jo | ||||
| } | } | ||||
| @GetMapping("/all-lots-hierarchical/{userId}") | |||||
| fun getAllJobOrderLotsHierarchical(@PathVariable userId: Long): Map<String, Any?> { | |||||
| return joPickOrderService.getAllJobOrderLotsWithDetailsHierarchical(userId) | |||||
| } | |||||
| @GetMapping("/completed-job-order-pick-orders/{userId}") | |||||
| fun getCompletedJobOrderPickOrders(@PathVariable userId: Long): List<Map<String, Any?>> { | |||||
| return joPickOrderService.getCompletedJobOrderPickOrders(userId) | |||||
| } | |||||
| @GetMapping("/completed-job-order-pick-order-records/{userId}") | |||||
| fun getCompletedJobOrderPickOrderRecords(@PathVariable userId: Long): List<Map<String, Any?>> { | |||||
| return joPickOrderService.getCompletedJobOrderPickOrderRecords(userId) | |||||
| } | |||||
| } | } | ||||
| @@ -2777,7 +2777,122 @@ if (existingRecords.isNotEmpty()) { | |||||
| println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") | println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") | ||||
| return emptyList() | return emptyList() | ||||
| } | } | ||||
| val allowedstatuses= listOf("assigned", "released", "picking", "completed") | |||||
| val allowedstatuses= listOf("assigned", "released", "picking" | |||||
| //, "completed" | |||||
| ) | |||||
| if (pickOrder.status?.value !in allowedstatuses) { | |||||
| println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") | |||||
| return emptyList() | |||||
| } | |||||
| val deliveryOrder = pickOrder.deliveryOrder | |||||
| val shop = deliveryOrder?.shop | |||||
| val supplier = deliveryOrder?.supplier | |||||
| println(" Delivery order: ${deliveryOrder?.code}, Shop: ${shop?.name}, Supplier: ${supplier?.code}") | |||||
| println("🔍 Shop ID: ${shop?.id}") | |||||
| // ✅ Get truck information using repository with detailed debugging | |||||
| val truck = if (shop?.id != null) { | |||||
| try { | |||||
| println("🔍 Querying truck repository for shopId: ${shop.id}") | |||||
| // Get all trucks for this shop | |||||
| val trucksForShop = truckRepository.findByShopIdAndDeletedFalse(shop.id) | |||||
| println("🔍 Trucks for shop ${shop.id}: ${trucksForShop.size}") | |||||
| trucksForShop.forEach { t -> | |||||
| println(" - Truck ID: ${t.id}, TruckNo: ${t.truckNo}, ShopId: ${t.shop?.id}, Deleted: ${t.deleted}") | |||||
| } | |||||
| // Take the first truck (or null if none found) | |||||
| val selectedTruck = trucksForShop.firstOrNull() | |||||
| if (selectedTruck != null) { | |||||
| println("✅ Selected truck: ID=${selectedTruck.id}, TruckNo=${selectedTruck.truckNo}, DepartureTime=${selectedTruck.departureTime}") | |||||
| } else { | |||||
| println("❌ No truck found for shopId ${shop.id}") | |||||
| } | |||||
| selectedTruck | |||||
| } catch (e: Exception) { | |||||
| println("⚠️ Error querying truck repository for shop ${shop.id}: ${e.message}") | |||||
| e.printStackTrace() | |||||
| null | |||||
| } | |||||
| } else { | |||||
| println("⚠️ Shop ID is null") | |||||
| null | |||||
| } | |||||
| val ticketNo = try { | |||||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||||
| val ticketNo = doPickOrders.firstOrNull()?.ticketNo ?: "" | |||||
| println("🔍 Found ticket number: $ticketNo for pick order $pickOrderId") | |||||
| ticketNo | |||||
| } catch (e: Exception) { | |||||
| println("⚠️ Error getting ticket number for pick order $pickOrderId: ${e.message}") | |||||
| "" | |||||
| } | |||||
| val dpoStoreId = try { | |||||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||||
| doPickOrders.firstOrNull()?.storeId ?: "" | |||||
| } catch (e: Exception) { | |||||
| println("⚠️ Error getting storeId for pick order $pickOrderId: ${e.message}") | |||||
| "" | |||||
| } | |||||
| val result = mapOf( | |||||
| "pickOrderId" to (pickOrder.id ?: 0L), | |||||
| "pickOrderCode" to (pickOrder.code ?: ""), | |||||
| "pickOrderConsoCode" to (pickOrder.consoCode ?: ""), | |||||
| "pickOrderTargetDate" to (pickOrder.targetDate?.toString() ?: ""), | |||||
| "pickOrderStatus" to (pickOrder.status?.value ?: ""), | |||||
| "deliveryOrderId" to (deliveryOrder?.id ?: 0L), | |||||
| "deliveryNo" to (deliveryOrder?.code ?: ""), | |||||
| "deliveryDate" to (deliveryOrder?.orderDate?.toString() ?: ""), | |||||
| "shopId" to (shop?.id ?: 0L), | |||||
| "shopCode" to (shop?.code ?: ""), | |||||
| "shopName" to (shop?.name ?: ""), | |||||
| "shopAddress" to buildShopAddress(shop), | |||||
| "shopPoNo" to (supplier?.code ?: ""), | |||||
| "numberOfCartons" to (pickOrder.pickOrderLines.size), | |||||
| "truckNo" to (truck?.truckNo ?: ""), // ✅ Use entity property | |||||
| "DepartureTime" to (truck?.departureTime?.toString() ?: ""), | |||||
| "ticketNo" to ticketNo, | |||||
| "storeId" to dpoStoreId, | |||||
| "qrCodeData" to (pickOrder.id ?: 0L) | |||||
| ) | |||||
| println("✅ FG Pick Orders by ID result count: 1") | |||||
| return listOf(result) | |||||
| } catch (e: Exception) { | |||||
| println("❌ Error in getFgPickOrdersByPickOrderId: ${e.message}") | |||||
| e.printStackTrace() | |||||
| return emptyList() | |||||
| } | |||||
| } | |||||
| open fun getnewFgPickOrdersByPickOrderId(pickOrderId: Long): List<Map<String, Any?>> { | |||||
| try { | |||||
| println("🔍 Starting getnewFgPickOrdersByPickOrderId method with pickOrderId: $pickOrderId") | |||||
| val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) | |||||
| if (pickOrder == null) { | |||||
| println("❌ Pick order not found with ID: $pickOrderId") | |||||
| return emptyList() | |||||
| } | |||||
| if (doPickOrderRepository.findByPickOrderId(pickOrderId).firstOrNull()?.hide == true) { | |||||
| println("🔍 Pick order $pickOrderId is hidden, returning empty list") | |||||
| return emptyList() | |||||
| } | |||||
| println("🔍 Found pick order: ${pickOrder.code}, type: ${pickOrder.type?.value}, status: ${pickOrder.status?.value}") | |||||
| if (pickOrder.type?.value != "do") { | |||||
| println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") | |||||
| return emptyList() | |||||
| } | |||||
| val allowedstatuses= listOf("assigned", "released", "picking","completed" | |||||
| //, "completed" | |||||
| ) | |||||
| if (pickOrder.status?.value !in allowedstatuses) { | if (pickOrder.status?.value !in allowedstatuses) { | ||||
| println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") | println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") | ||||
| return emptyList() | return emptyList() | ||||
| @@ -3169,7 +3284,9 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||||
| println("❌ User not found: $userId") | println("❌ User not found: $userId") | ||||
| return emptyMap() | return emptyMap() | ||||
| } | } | ||||
| val statusList = listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED) | |||||
| val statusList = listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, | |||||
| //PickOrderStatus.COMPLETED | |||||
| ) | |||||
| // Get all pick orders assigned to user with PENDING or RELEASED status that have doId | // Get all pick orders assigned to user with PENDING or RELEASED status that have doId | ||||
| val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( | val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( | ||||
| @@ -3749,7 +3866,7 @@ open fun getCompletedDoPickOrders( | |||||
| for (pickOrder in completedPickOrders) { | for (pickOrder in completedPickOrders) { | ||||
| // 获取该 pick order 的 FG pick orders | // 获取该 pick order 的 FG pick orders | ||||
| val fgPickOrders = getFgPickOrdersByPickOrderId(pickOrder.id!!) | |||||
| val fgPickOrders = getnewFgPickOrdersByPickOrderId(pickOrder.id!!) | |||||
| if (fgPickOrders.isNotEmpty()) { | if (fgPickOrders.isNotEmpty()) { | ||||
| val firstFgOrder = fgPickOrders[0] as Map<String, Any?> // ✅ 修复:转换为 Map | val firstFgOrder = fgPickOrders[0] as Map<String, Any?> // ✅ 修复:转换为 Map | ||||
| @@ -3801,4 +3918,168 @@ open fun getCompletedDoPickOrders( | |||||
| emptyList() | emptyList() | ||||
| } | } | ||||
| } | } | ||||
| // ... existing code ... | |||||
| open fun getLotDetailsByPickOrderId(pickOrderId: Long): List<Map<String, Any>> { | |||||
| println("=== Debug: getLotDetailsByPickOrderId ===") | |||||
| println("pickOrderId: $pickOrderId") | |||||
| // Get the pick order | |||||
| val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) | |||||
| if (pickOrder == null) { | |||||
| println("❌ Pick order not found: $pickOrderId") | |||||
| return emptyList() | |||||
| } | |||||
| // ✅ 修复:使用正确的方法名 | |||||
| val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId) | |||||
| if (pickOrderLines.isEmpty()) { | |||||
| println("❌ No pick order lines found for pick order: $pickOrderId") | |||||
| return emptyList() | |||||
| } | |||||
| // Build the SQL query similar to getAllPickOrderLotsWithDetailsWithoutAutoAssign | |||||
| // but filtered by specific pickOrderId | |||||
| 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, | |||||
| po.type as pickOrderType, | |||||
| po.status as pickOrderStatus, | |||||
| po.assignTo as pickOrderAssignTo, | |||||
| -- 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, | |||||
| -- 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, | |||||
| -- Add detailed debug fields for lotAvailability calculation | |||||
| ill.status as debug_ill_status, | |||||
| (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) as debug_is_expired, | |||||
| sol.status as debug_sol_status, | |||||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as debug_remaining_stock, | |||||
| (COALESCE(spl.qty, 0) - COALESCE(sol.qty, 0)) as debug_required_after_picked, | |||||
| -- Lot availability status | |||||
| CASE | |||||
| -- Check if lot is expired | |||||
| WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' | |||||
| -- Check if lot has rejected stock out line for this pick order | |||||
| WHEN sol.status = 'rejected' THEN 'rejected' | |||||
| -- Check if lot is unavailable due to insufficient stock | |||||
| WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' | |||||
| -- Check if lot status is unavailable | |||||
| WHEN ill.status = 'unavailable' THEN 'status_unavailable' | |||||
| -- Default to available | |||||
| 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 | |||||
| FROM fpsmsdb.pick_order po | |||||
| 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 | |||||
| WHERE po.deleted = false | |||||
| AND po.id = :pickOrderId | |||||
| AND pol.deleted = false | |||||
| AND ill.deleted = false | |||||
| AND il.deleted = false | |||||
| AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) | |||||
| ORDER BY | |||||
| CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, -- Show rejected lots first | |||||
| COALESCE(r.index, 0) ASC, | |||||
| po.code ASC, | |||||
| i.code ASC, | |||||
| il.expiryDate ASC, | |||||
| il.lotNo ASC | |||||
| """.trimIndent() | |||||
| // ✅ 使用 jdbcDao 而不是 entityManager | |||||
| println("🔍 Executing SQL for lot details by pick order: $sql") | |||||
| println("🔍 With parameters: pickOrderId = $pickOrderId") | |||||
| val results = jdbcDao.queryForList(sql, mapOf("pickOrderId" to pickOrderId)) | |||||
| println("✅ Total result count: ${results.size}") | |||||
| return results | |||||
| } | |||||
| } | } | ||||
| @@ -304,4 +304,8 @@ fun getCompletedDoPickOrders( | |||||
| ): List<Map<String, Any?>> { | ): List<Map<String, Any?>> { | ||||
| return pickOrderService.getCompletedDoPickOrders(userId, pickOrderCode, shopName, deliveryNo, ticketNo) | return pickOrderService.getCompletedDoPickOrders(userId, pickOrderCode, shopName, deliveryNo, ticketNo) | ||||
| } | } | ||||
| @GetMapping("/lot-details-by-pick-order/{pickOrderId}") | |||||
| fun getLotDetailsByPickOrderId(@PathVariable pickOrderId: Long): List<Map<String, Any>> { | |||||
| return pickOrderService.getLotDetailsByPickOrderId(pickOrderId); | |||||
| } | |||||
| } | } | ||||
| @@ -1,5 +1,5 @@ | |||||
| spring: | spring: | ||||
| datasource: | datasource: | ||||
| jdbc-url: jdbc:mysql://127.0.0.1:3306/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 | |||||
| jdbc-url: jdbc:mysql://127.0.0.1:3308/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 | |||||
| username: root | username: root | ||||
| password: secret | password: secret | ||||
| @@ -0,0 +1,45 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset enson:update | |||||
| CREATE TABLE jo_pick_order ( | |||||
| id int AUTO_INCREMENT PRIMARY KEY, | |||||
| pick_order_id int NOT NULL, | |||||
| item_id int NOT NULL, | |||||
| handled_by int, | |||||
| second_qr_scan_status enum('pending', 'scanned', 'completed') DEFAULT 'pending', | |||||
| second_qr_scan_by int NULL, | |||||
| second_qr_scan_qty int NULL, | |||||
| created datetime DEFAULT CURRENT_TIMESTAMP, | |||||
| createdBy varchar(30), | |||||
| version int DEFAULT 1, | |||||
| modified datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | |||||
| modifiedBy varchar(30), | |||||
| deleted tinyint(1) DEFAULT 0, | |||||
| FOREIGN KEY (pick_order_id) REFERENCES pick_order(id), | |||||
| FOREIGN KEY (item_id) REFERENCES items(id), | |||||
| FOREIGN KEY (handled_by) REFERENCES user(id), | |||||
| FOREIGN KEY (second_qr_scan_by) REFERENCES user(id) | |||||
| ); | |||||
| CREATE TABLE jo_pick_order_record ( | |||||
| id int AUTO_INCREMENT PRIMARY KEY, | |||||
| pick_order_id int NOT NULL, | |||||
| item_id int NOT NULL, | |||||
| handled_by int, | |||||
| second_qr_scan_status enum('pending', 'scanned', 'completed') DEFAULT 'pending', | |||||
| second_qr_scan_by int NULL, | |||||
| second_qr_scan_qty int NULL, | |||||
| created datetime DEFAULT CURRENT_TIMESTAMP, | |||||
| createdBy varchar(30), | |||||
| version int DEFAULT 1, | |||||
| modified datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | |||||
| modifiedBy varchar(30), | |||||
| deleted tinyint(1) DEFAULT 0, | |||||
| FOREIGN KEY (pick_order_id) REFERENCES pick_order(id), | |||||
| FOREIGN KEY (item_id) REFERENCES items(id), | |||||
| FOREIGN KEY (handled_by) REFERENCES user(id), | |||||
| FOREIGN KEY (second_qr_scan_by) REFERENCES user(id) | |||||
| ); | |||||