diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrder.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrder.kt new file mode 100644 index 0000000..185d268 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrder.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRecord.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRecord.kt new file mode 100644 index 0000000..ce1cb87 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRecord.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRecordRepository.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRecordRepository.kt new file mode 100644 index 0000000..dc1e1ed --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRecordRepository.kt @@ -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 { + fun findByPickOrderId(pickOrderId: Long): List + //fun findByTicketNoStartingWith(ticketPrefix: String): List +} \ No newline at end of file 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 new file mode 100644 index 0000000..a694525 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JoPickOrderRepository.kt @@ -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 { + + fun findByPickOrderId(pickOrderId: Long): List + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JoPickOrderStatus.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JoPickOrderStatus.kt new file mode 100644 index 0000000..90bd8f9 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JoPickOrderStatus.kt @@ -0,0 +1,7 @@ +package com.ffii.fpsms.modules.jobOrder.enums + +enum class JoPickOrderStatus(val value: String) { + pending("pending"), + scanned("scanned"), + completed("completed") +} \ 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 new file mode 100644 index 0000000..7b22ee4 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt @@ -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 { + 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 { + 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 { + 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 { + 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 { + return joPickOrderRepository.findByPickOrderId(pickOrderId) + } + + // ✅ Find JoPickOrderRecord records by pick order ID + open fun findRecordsByPickOrderId(pickOrderId: Long): List { + return joPickOrderRecordRepository.findByPickOrderId(pickOrderId) + } + + // ✅ Create JoPickOrder records for a pick order + @Transactional + open fun createJoPickOrdersForPickOrder(pickOrderId: Long, itemIds: List): List { + 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): List { + 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 { + 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>() 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>() 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 + 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> { + 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() + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt index 0aff8c8..b707515 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt @@ -30,7 +30,18 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter 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 open class JobOrderService( val jobOrderRepository: JobOrderRepository, @@ -38,6 +49,13 @@ open class JobOrderService( val userService: UserService, val productionScheduleLineRepository: ProductionScheduleLineRepository, 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 { val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10); @@ -161,7 +179,81 @@ open class JobOrderService( 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( id = jo.id, diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt index 0e9c7bd..32ee623 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 @@ -18,13 +18,14 @@ import org.springframework.web.bind.annotation.PostMapping 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 @RestController @RequestMapping("/jo") class JobOrderController( private val jobOrderService: JobOrderService, private val jobOrderBomMaterialService: JobOrderBomMaterialService, - private val jobOrderProcessService: JobOrderProcessService + private val jobOrderProcessService: JobOrderProcessService, + private val joPickOrderService: JoPickOrderService ) { @GetMapping("/getRecordByPage") @@ -58,4 +59,18 @@ class JobOrderController( return jo } + @GetMapping("/all-lots-hierarchical/{userId}") + fun getAllJobOrderLotsHierarchical(@PathVariable userId: Long): Map { + return joPickOrderService.getAllJobOrderLotsWithDetailsHierarchical(userId) + } + + @GetMapping("/completed-job-order-pick-orders/{userId}") + fun getCompletedJobOrderPickOrders(@PathVariable userId: Long): List> { + return joPickOrderService.getCompletedJobOrderPickOrders(userId) + } + + @GetMapping("/completed-job-order-pick-order-records/{userId}") + fun getCompletedJobOrderPickOrderRecords(@PathVariable userId: Long): List> { + return joPickOrderService.getCompletedJobOrderPickOrderRecords(userId) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index d62a548..08c8a28 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -2777,7 +2777,122 @@ if (existingRecords.isNotEmpty()) { println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") 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> { + 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) { println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") return emptyList() @@ -3169,7 +3284,9 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map // ✅ 修复:转换为 Map @@ -3801,4 +3918,168 @@ open fun getCompletedDoPickOrders( emptyList() } } +// ... existing code ... + +open fun getLotDetailsByPickOrderId(pickOrderId: Long): List> { + 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 +} + + + } diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt index fc7624d..504d1d4 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt @@ -304,4 +304,8 @@ fun getCompletedDoPickOrders( ): List> { return pickOrderService.getCompletedDoPickOrders(userId, pickOrderCode, shopName, deliveryNo, ticketNo) } +@GetMapping("/lot-details-by-pick-order/{pickOrderId}") +fun getLotDetailsByPickOrderId(@PathVariable pickOrderId: Long): List> { + return pickOrderService.getLotDetailsByPickOrderId(pickOrderId); +} } \ No newline at end of file diff --git a/src/main/resources/application-db-local.yml b/src/main/resources/application-db-local.yml index 4fa8584..a9c01bf 100644 --- a/src/main/resources/application-db-local.yml +++ b/src/main/resources/application-db-local.yml @@ -1,5 +1,5 @@ spring: 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 password: secret \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/202510927_01_enson/01_altertable_enson.sql b/src/main/resources/db/changelog/changes/202510927_01_enson/01_altertable_enson.sql new file mode 100644 index 0000000..66eb887 --- /dev/null +++ b/src/main/resources/db/changelog/changes/202510927_01_enson/01_altertable_enson.sql @@ -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) +); \ No newline at end of file