| @@ -46,7 +46,7 @@ class DoPickOrder { | |||
| @Column(name = "ticket_release_time") | |||
| var ticketReleaseTime: LocalDateTime? = null | |||
| // ✅ 新增字段声明 | |||
| // 新增字段声明 | |||
| @Column(name = "ticketCompleteDateTime") | |||
| var ticketCompleteDateTime: LocalDateTime? = null | |||
| @@ -23,13 +23,13 @@ open class DoPickOrderLineRecord: BaseEntity<Long>() { | |||
| @Column(name = "do_pick_order_id", length = 100) | |||
| open var doPickOrderId: Long? = null | |||
| @Column(name = "pick_order_id") // ✅ 正确:普通列 | |||
| @Column(name = "pick_order_id") // 正确:普通列 | |||
| open var pickOrderId: Long? = null | |||
| @Column(name = "do_order_id") // ✅ 正确:普通列 | |||
| @Column(name = "do_order_id") // 正确:普通列 | |||
| open var doOrderId: Long? = null | |||
| @Column(name = "pick_order_code") // ✅ 正确:普通列 | |||
| @Column(name = "pick_order_code") // 正确:普通列 | |||
| open var pickOrderCode: String? = null | |||
| @Column(name = "delivery_order_code") | |||
| @@ -54,7 +54,7 @@ class DoPickOrderRecord { | |||
| @Column(name = "handled_by") | |||
| var handledBy: Long? = null | |||
| // ✅ 新增字段声明 | |||
| // 新增字段声明 | |||
| @Column(name = "ticketCompleteDateTime") | |||
| var ticketCompleteDateTime: LocalDateTime? = null | |||
| @@ -10,9 +10,11 @@ import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| import java.time.LocalDateTime | |||
| import java.time.LocalDate | |||
| @Repository | |||
| interface DoPickOrderRecordRepository : JpaRepository<DoPickOrderRecord, Long> { | |||
| fun findByPickOrderId(pickOrderId: Long): List<DoPickOrderRecord> | |||
| fun findByTicketNoStartingWith(ticketPrefix: String): List<DoPickOrderRecord> | |||
| fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn(storeId: String, requiredDeliveryDate: LocalDate, ticketStatus: List<DoPickOrderStatus>): List<DoPickOrderRecord> | |||
| } | |||
| @@ -71,11 +71,11 @@ import com.ffii.fpsms.modules.stock.service.InventoryLotService | |||
| import net.sf.jasperreports.engine.JasperPrintManager | |||
| import net.sf.jasperreports.engine.JRPrintPage | |||
| import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository | |||
| import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService // ✅ 添加这行 | |||
| import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService // 添加这行 | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.* | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue // ✅ 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository // ✅ 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory // ✅ 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue // 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository // 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory // 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus | |||
| @Service | |||
| open class DeliveryOrderService( | |||
| @@ -310,7 +310,7 @@ open class DeliveryOrderService( | |||
| return null | |||
| } | |||
| // ✅ 新增方法2:获取 warehouse 的 code 字段(用于显示路由) | |||
| // 新增方法2:获取 warehouse 的 code 字段(用于显示路由) | |||
| open fun getWarehouseCodeByItemId(itemId: Long): String? { | |||
| val inventoryLots = inventoryLotService.findByItemId(itemId) | |||
| if (inventoryLots.isNotEmpty()) { | |||
| @@ -451,7 +451,7 @@ open class DeliveryOrderService( | |||
| pickOrderRepository.saveAndFlush(pickOrderEntity) | |||
| println("�� DEBUG: Assigned consoCode $consoCode to pick order ${createdPickOrder.id}") | |||
| // ✅ Debug: Check pick order lines | |||
| // Debug: Check pick order lines | |||
| println("�� DEBUG: Pick order has ${pickOrderEntity.pickOrderLines?.size ?: 0} pick order lines") | |||
| pickOrderEntity.pickOrderLines?.forEach { line -> | |||
| println("🔍 DEBUG: Pick order line - Item ID: ${line.item?.id}, Qty: ${line.qty}") | |||
| @@ -470,7 +470,7 @@ open class DeliveryOrderService( | |||
| println("⚠️ WARNING: $insufficientCount items have insufficient stock (issues auto-created)") | |||
| } | |||
| // ✅ Hold inventory quantities | |||
| // Hold inventory quantities | |||
| val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( | |||
| saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } | |||
| ) | |||
| @@ -486,7 +486,7 @@ open class DeliveryOrderService( | |||
| } | |||
| inventoryLotLineRepository.saveAll(inventoryLotLines) | |||
| // ✅ Create stock out record and pre-create stock out lines | |||
| // Create stock out record and pre-create stock out lines | |||
| val stockOut = StockOut().apply { | |||
| this.type = "do" | |||
| this.consoPickOrderCode = consoCode | |||
| @@ -495,7 +495,7 @@ open class DeliveryOrderService( | |||
| } | |||
| val savedStockOut = stockOutRepository.saveAndFlush(stockOut) | |||
| // ✅ Pre-create stock out lines for suggested lots | |||
| // Pre-create stock out lines for suggested lots | |||
| saveSuggestedPickLots.forEach { lot -> | |||
| val polId = lot.pickOrderLine?.id | |||
| val illId = lot.suggestedLotLine?.id | |||
| @@ -522,10 +522,10 @@ open class DeliveryOrderService( | |||
| } | |||
| } | |||
| // ✅ CREATE do_pick_order_record entries | |||
| // CREATE do_pick_order_record entries | |||
| // 第 471-555 行附近 - 修复创建逻辑 | |||
| // ✅ CREATE do_pick_order_record entries | |||
| // CREATE do_pick_order_record entries | |||
| val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() | |||
| val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) | |||
| @@ -535,9 +535,9 @@ open class DeliveryOrderService( | |||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId") | |||
| // ✅ 移除提前返回,总是分析 items 分布 | |||
| // 移除提前返回,总是分析 items 分布 | |||
| // 分析 DO order lines 中的 items 分布 | |||
| // ✅ 分析 items 来确定 storeId | |||
| // 分析 items 来确定 storeId | |||
| val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct() | |||
| val inventoryQuery = """ | |||
| SELECT | |||
| @@ -560,7 +560,7 @@ open class DeliveryOrderService( | |||
| println("🔍 DEBUG: Floor item count distribution: $floorItemCount") | |||
| println("🔍 DEBUG: Total items: ${itemIds.size}, Items on 4F: ${floorItemCount["4F"] ?: 0}") | |||
| // ✅ 新逻辑:只有所有 items 都在 4F,才算 4F,否则算 2F | |||
| // 新逻辑:只有所有 items 都在 4F,才算 4F,否则算 2F | |||
| val preferredFloor = if ((floorItemCount["4F"] ?: 0) == itemIds.size && (floorItemCount["2F"] ?: 0) == 0) { | |||
| "4F" // 所有 items 都在 4F | |||
| } else { | |||
| @@ -569,7 +569,7 @@ open class DeliveryOrderService( | |||
| println("🔍 DEBUG: Preferred floor: $preferredFloor (All items on 4F: ${preferredFloor == "4F"})") | |||
| // ✅ 查找 truck | |||
| // 查找 truck | |||
| val truck = deliveryOrder.shop?.id?.let { shopId -> | |||
| println("🔍 DEBUG: Looking for truck with shop ID: $shopId") | |||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| @@ -593,7 +593,7 @@ open class DeliveryOrderService( | |||
| selectedTruck | |||
| } | |||
| // ✅ 检查 truck 和 preferredFloor 是否匹配 | |||
| // 检查 truck 和 preferredFloor 是否匹配 | |||
| val truckStoreId = truck?.storeId | |||
| val expectedStoreId = when (preferredFloor) { | |||
| "2F" -> 2 | |||
| @@ -608,9 +608,9 @@ open class DeliveryOrderService( | |||
| throw IllegalStateException(errorMsg) // 或返回错误响应 | |||
| } | |||
| println("✅ DEBUG: Truck matches preferred floor - Truck Store: $truckStoreId, Preferred: $preferredFloor") | |||
| println(" DEBUG: Truck matches preferred floor - Truck Store: $truckStoreId, Preferred: $preferredFloor") | |||
| // ✅ storeId 基于 items 的 preferredFloor | |||
| // storeId 基于 items 的 preferredFloor | |||
| val storeId = "$preferredFloor/F" | |||
| val loadingSequence = truck.loadingSequence ?: 999 | |||
| @@ -922,12 +922,12 @@ open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDo | |||
| val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id) | |||
| ?: throw NoSuchElementException("Delivery Order not found") | |||
| // ✅ 检查状态,跳过已完成或已发布的DO | |||
| // 检查状态,跳过已完成或已发布的DO | |||
| if (deliveryOrder.status == DeliveryOrderStatus.COMPLETED || deliveryOrder.status == DeliveryOrderStatus.RECEIVING) { | |||
| throw IllegalStateException("Delivery Order ${deliveryOrder.id} is already ${deliveryOrder.status?.value}, skipping release") | |||
| } | |||
| // ✅ 更新状态为released (使用RECEIVING表示已发布) | |||
| // 更新状态为released (使用RECEIVING表示已发布) | |||
| deliveryOrder.apply { | |||
| status = DeliveryOrderStatus.RECEIVING // 使用RECEIVING表示已发布状态 | |||
| } | |||
| @@ -1004,7 +1004,7 @@ open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDo | |||
| this.pickOrderLine = pickOrderLine | |||
| this.inventoryLotLine = inventoryLotLine // 可能为 null | |||
| this.item = pickOrderLine.item | |||
| // ✅ 修复:根据是否有 inventoryLotLine 设置状态 | |||
| // 修复:根据是否有 inventoryLotLine 设置状态 | |||
| this.status = if (inventoryLotLine == null) { | |||
| StockOutLineStatus.PARTIALLY_COMPLETE.status // 没有库存批次时使用 PARTIALLY_COMPLETE | |||
| } else { | |||
| @@ -1046,7 +1046,7 @@ open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDo | |||
| "2F" | |||
| } | |||
| // ✅ 查找匹配 preferred floor 的 truck | |||
| // 查找匹配 preferred floor 的 truck | |||
| val truck = deliveryOrder.shop?.id?.let { shopId -> | |||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| val preferredStoreId = when (preferredFloor) { | |||
| @@ -1065,14 +1065,14 @@ open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDo | |||
| } | |||
| } | |||
| // ✅ 如果没有匹配的 truck,抛出异常跳过 | |||
| // 如果没有匹配的 truck,抛出异常跳过 | |||
| if (truck == null) { | |||
| val errorMsg = "No matching truck found for preferredFloor ($preferredFloor). Skipping DO ${deliveryOrder.id}." | |||
| println("⚠️ $errorMsg") | |||
| throw IllegalStateException(errorMsg) | |||
| } | |||
| println("✅ DEBUG: Matched truck - ID=${truck.id}, Store=${truck.storeId}, Floor=$preferredFloor") | |||
| println(" DEBUG: Matched truck - ID=${truck.id}, Store=${truck.storeId}, Floor=$preferredFloor") | |||
| return ReleaseDoResult( | |||
| deliveryOrderId = deliveryOrder.id!!, | |||
| @@ -1087,7 +1087,7 @@ open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDo | |||
| truckId = truck.id, | |||
| truckDepartureTime = truck.departureTime, | |||
| truckLanceCode = truck.truckLanceCode, | |||
| loadingSequence = truck.loadingSequence // ✅ 直接使用 truck 的值 | |||
| loadingSequence = truck.loadingSequence // 直接使用 truck 的值 | |||
| ) | |||
| } | |||
| @@ -20,7 +20,7 @@ class DoPickOrderAssignmentService( | |||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | |||
| private val pickOrderRepository: PickOrderRepository, | |||
| private val userRepository: UserRepository, | |||
| private val jdbcDao: JdbcDao // ✅ 添加 JdbcDao | |||
| private val jdbcDao: JdbcDao // 添加 JdbcDao | |||
| ) { | |||
| fun assignByLane(request: AssignByLaneRequest): MessageResponse { | |||
| @@ -30,7 +30,7 @@ class DoPickOrderAssignmentService( | |||
| message = "User not found", errorPosition = null, entity = null | |||
| ) | |||
| // ✅ 转换 storeId 格式 | |||
| // 转换 storeId 格式 | |||
| val actualStoreId = when (request.storeId) { | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| @@ -38,11 +38,11 @@ class DoPickOrderAssignmentService( | |||
| } | |||
| println("🔍 DEBUG: assignByLane - Converting storeId from '${request.storeId}' to '$actualStoreId'") | |||
| // ✅ 获取日期(如果提供) | |||
| // 获取日期(如果提供) | |||
| val requiredDate = request.requiredDate | |||
| println("🔍 DEBUG: assignByLane - Requested date: $requiredDate") | |||
| // ✅ 根据是否有日期参数选择不同的查询方法 | |||
| // 根据是否有日期参数选择不同的查询方法 | |||
| val allCandidates = if (requiredDate != null) { | |||
| println("🔍 DEBUG: Filtering by date: $requiredDate") | |||
| doPickOrderRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( | |||
| @@ -62,7 +62,7 @@ class DoPickOrderAssignmentService( | |||
| println("🔍 DEBUG: Found ${allCandidates.size} candidate do_pick_orders for lane ${request.truckLanceCode}") | |||
| // ✅ 过滤掉所有 pick orders 都是 issue 的记录 | |||
| // 过滤掉所有 pick orders 都是 issue 的记录 | |||
| val filteredCandidates = allCandidates.filter { doPickOrder -> | |||
| val hasNonIssueLines = checkDoPickOrderHasNonIssueLines(doPickOrder.id!!) | |||
| if (!hasNonIssueLines) { | |||
| @@ -83,14 +83,14 @@ class DoPickOrderAssignmentService( | |||
| val firstOrder = filteredCandidates.first() | |||
| // ✅ 更新 do_pick_order | |||
| // 更新 do_pick_order | |||
| firstOrder.handledBy = request.userId | |||
| firstOrder.handlerName = user.name | |||
| firstOrder.ticketStatus = DoPickOrderStatus.released | |||
| firstOrder.ticketReleaseTime = LocalDateTime.now() | |||
| doPickOrderRepository.save(firstOrder) | |||
| // ✅ 获取这个 do_pick_order 下的所有 pick orders 并分配给用户 | |||
| // 获取这个 do_pick_order 下的所有 pick orders 并分配给用户 | |||
| val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(firstOrder.id!!) | |||
| println("🔍 DEBUG: Found ${doPickOrderLines.size} pick orders in do_pick_order ${firstOrder.id}") | |||
| @@ -108,7 +108,7 @@ class DoPickOrderAssignmentService( | |||
| } | |||
| } | |||
| // ✅ 同步更新 do_pick_order_record(如果有的话) | |||
| // 同步更新 do_pick_order_record(如果有的话) | |||
| doPickOrderLines.forEach { line -> | |||
| if (line.pickOrderId != null) { | |||
| val records = doPickOrderRecordRepository.findByPickOrderId(line.pickOrderId!!) | |||
| @@ -141,7 +141,7 @@ class DoPickOrderAssignmentService( | |||
| ) | |||
| } | |||
| // ✅ 添加过滤方法(和 DoPickOrderQueryService 中相同的逻辑) | |||
| // 添加过滤方法(和 DoPickOrderQueryService 中相同的逻辑) | |||
| private fun checkDoPickOrderHasNonIssueLines(doPickOrderId: Long): Boolean { | |||
| return try { | |||
| val totalLinesSql = """ | |||
| @@ -25,14 +25,14 @@ open class DoPickOrderCompletionService( | |||
| } | |||
| val savedDoPickOrders = doPickOrderRepository.saveAll(doPickOrders) | |||
| // ✅ 同步更新相关的delivery_order状态 | |||
| // 同步更新相关的delivery_order状态 | |||
| savedDoPickOrders.forEach { doPickOrder -> | |||
| if (doPickOrder.doOrderId != null) { | |||
| val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doPickOrder.doOrderId!!) | |||
| if (deliveryOrder != null && deliveryOrder.status != DeliveryOrderStatus.COMPLETED) { | |||
| deliveryOrder.status = DeliveryOrderStatus.COMPLETED | |||
| deliveryOrderRepository.save(deliveryOrder) | |||
| println("✅ Updated delivery order ${doPickOrder.doOrderId} status to completed") | |||
| println(" Updated delivery order ${doPickOrder.doOrderId} status to completed") | |||
| } | |||
| } | |||
| } | |||
| @@ -46,7 +46,7 @@ open class DoPickOrderCompletionService( | |||
| var deletedCount = 0 | |||
| doPickOrders.forEach { doPickOrder -> | |||
| // ✅ 第一步:复制 do_pick_order 到 do_pick_order_record | |||
| // 第一步:复制 do_pick_order 到 do_pick_order_record | |||
| val doPickOrderRecord = DoPickOrderRecord( | |||
| storeId = doPickOrder.storeId ?: "", | |||
| @@ -70,9 +70,9 @@ open class DoPickOrderCompletionService( | |||
| requiredDeliveryDate = doPickOrder.requiredDeliveryDate | |||
| ) | |||
| val savedRecord = doPickOrderRecordRepository.save(doPickOrderRecord) | |||
| println("✅ Copied do_pick_order ${doPickOrder.id} to do_pick_order_record") | |||
| println(" Copied do_pick_order ${doPickOrder.id} to do_pick_order_record") | |||
| // ✅ 第二步:复制 do_pick_order_line 到 do_pick_order_line_record | |||
| // 第二步:复制 do_pick_order_line 到 do_pick_order_line_record | |||
| val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrder.id!!) | |||
| doPickOrderLines.forEach { line -> | |||
| val doPickOrderLineRecord = DoPickOrderLineRecord() | |||
| @@ -83,19 +83,19 @@ open class DoPickOrderCompletionService( | |||
| doPickOrderLineRecord.deliveryOrderCode = line.deliveryOrderCode | |||
| doPickOrderLineRecord.status = line.status | |||
| doPickOrderLineRecordRepository.save(doPickOrderLineRecord) | |||
| println("✅ Copied do_pick_order_line ${line.id} to do_pick_order_line_record") | |||
| println(" Copied do_pick_order_line ${line.id} to do_pick_order_line_record") | |||
| } | |||
| // ✅ 第三步:删除原始的 do_pick_order_line 记录 | |||
| // 第三步:删除原始的 do_pick_order_line 记录 | |||
| if (doPickOrderLines.isNotEmpty()) { | |||
| doPickOrderLineRepository.deleteAll(doPickOrderLines) | |||
| println("✅ Deleted ${doPickOrderLines.size} do_pick_order_line records") | |||
| println(" Deleted ${doPickOrderLines.size} do_pick_order_line records") | |||
| } | |||
| // ✅ 第四步:删除原始的 do_pick_order 记录 | |||
| // 第四步:删除原始的 do_pick_order 记录 | |||
| doPickOrderRepository.delete(doPickOrder) | |||
| deletedCount++ | |||
| println("✅ Deleted do_pick_order ${doPickOrder.id}") | |||
| println(" Deleted do_pick_order ${doPickOrder.id}") | |||
| } | |||
| return deletedCount | |||
| @@ -33,7 +33,7 @@ import java.time.LocalDateTime | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByStoreRequest | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.* // ✅ 导入 | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.* // 导入 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | |||
| import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository | |||
| @@ -52,7 +52,7 @@ open class DoPickOrderService( | |||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | |||
| private val userRepository: UserRepository, | |||
| private val pickOrderRepository: PickOrderRepository, | |||
| private val jdbcDao: JdbcDao, // ✅ 添加这行 | |||
| private val jdbcDao: JdbcDao, // 添加这行 | |||
| private val truckRepository: TruckRepository, | |||
| private val doPickOrderLineRepository: DoPickOrderLineRepository, | |||
| @Lazy private val deliveryOrderRepository: DeliveryOrderRepository, | |||
| @@ -73,7 +73,7 @@ open class DoPickOrderService( | |||
| } else { | |||
| datePrefix | |||
| } | |||
| // ✅ 修改搜索模式为新格式 | |||
| // 修改搜索模式为新格式 | |||
| val searchPattern = "TI-${shortDatePrefix}-${sanitizedStoreId}-" // T-20250915-4F- | |||
| val todayTickets = doPickOrderRepository.findByTicketNoStartingWith(searchPattern) | |||
| println("🔍 DEBUG: Found ${todayTickets.size} existing tickets with prefix $searchPattern") | |||
| @@ -81,7 +81,7 @@ open class DoPickOrderService( | |||
| println("🔍 DEBUG: Existing ticket: ${ticket.ticketNo}, Status: ${ticket.ticketStatus}") | |||
| } | |||
| val nextNumber = (todayTickets.size + 1).toString().padStart(3, '0') | |||
| // ✅ 修改生成格式 | |||
| // 修改生成格式 | |||
| val ticketNumber = "TI-${datePrefix}-${sanitizedStoreId}-${nextNumber}" // T-20250915-4F-001 | |||
| println("🔍 DEBUG: Generated ticket number: $ticketNumber") | |||
| return ticketNumber | |||
| @@ -129,10 +129,10 @@ open class DoPickOrderService( | |||
| ) | |||
| } | |||
| // ✅ Updated method to set ticketReleaseTime when assigning order to user | |||
| // Updated method to set ticketReleaseTime when assigning order to user | |||
| open fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long): List<DoPickOrder> { | |||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||
| val user = userRepository.findById(userId).orElse(null) // ✅ 改用 orElse(null) | |||
| val user = userRepository.findById(userId).orElse(null) // 改用 orElse(null) | |||
| val handlerName = user?.name ?: "Unknown" | |||
| doPickOrders.forEach { | |||
| it.handledBy = userId | |||
| @@ -151,14 +151,14 @@ open class DoPickOrderService( | |||
| } | |||
| val savedDoPickOrders = doPickOrderRepository.saveAll(doPickOrders) | |||
| // ✅ 同步更新相关的delivery_order状态 | |||
| // 同步更新相关的delivery_order状态 | |||
| savedDoPickOrders.forEach { doPickOrder -> | |||
| if (doPickOrder.doOrderId != null) { | |||
| val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doPickOrder.doOrderId!!) | |||
| if (deliveryOrder != null && deliveryOrder.status != DeliveryOrderStatus.COMPLETED) { | |||
| deliveryOrder.status = DeliveryOrderStatus.COMPLETED | |||
| deliveryOrderRepository.save(deliveryOrder) | |||
| println("✅ Updated delivery order ${doPickOrder.doOrderId} status to completed") | |||
| println(" Updated delivery order ${doPickOrder.doOrderId} status to completed") | |||
| } | |||
| } | |||
| } | |||
| @@ -166,15 +166,15 @@ open class DoPickOrderService( | |||
| return savedDoPickOrders | |||
| } | |||
| // ✅ New method to remove do_pick_order records when auto-assigning by store | |||
| // ✅ 修改方法:先复制记录到record表,再删除原记录 | |||
| // New method to remove do_pick_order records when auto-assigning by store | |||
| // 修改方法:先复制记录到record表,再删除原记录 | |||
| @Transactional | |||
| open fun removeDoPickOrdersForPickOrder(pickOrderId: Long): Int { | |||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||
| var deletedCount = 0 | |||
| doPickOrders.forEach { doPickOrder -> | |||
| // ✅ 第一步:复制 do_pick_order 到 do_pick_order_record | |||
| // 第一步:复制 do_pick_order 到 do_pick_order_record | |||
| val doPickOrderRecord = DoPickOrderRecord( | |||
| recordId = doPickOrder.id, | |||
| storeId = doPickOrder.storeId?: "", | |||
| @@ -198,34 +198,34 @@ open class DoPickOrderService( | |||
| requiredDeliveryDate = doPickOrder.requiredDeliveryDate | |||
| ) | |||
| val savedRecord = doPickOrderRecordRepository.save(doPickOrderRecord) | |||
| println("✅ Copied do_pick_order ${doPickOrder.id} to do_pick_order_record") | |||
| println(" Copied do_pick_order ${doPickOrder.id} to do_pick_order_record") | |||
| // ✅ 第二步:复制 do_pick_order_line 到 do_pick_order_line_record | |||
| // ✅ 第二步:复制 do_pick_order_line 到 do_pick_order_line_record | |||
| // 第二步:复制 do_pick_order_line 到 do_pick_order_line_record | |||
| // 第二步:复制 do_pick_order_line 到 do_pick_order_line_record | |||
| val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrder.id!!) | |||
| doPickOrderLines.forEach { line -> | |||
| val doPickOrderLineRecord = DoPickOrderLineRecord() | |||
| doPickOrderLineRecord.recordId = line.id // ✅ 使用默认构造函数 | |||
| doPickOrderLineRecord.doPickOrderId = savedRecord.id // ✅ 设置属性 | |||
| doPickOrderLineRecord.recordId = line.id // 使用默认构造函数 | |||
| doPickOrderLineRecord.doPickOrderId = savedRecord.id // 设置属性 | |||
| doPickOrderLineRecord.pickOrderId = line.pickOrderId | |||
| doPickOrderLineRecord.doOrderId = line.doOrderId | |||
| doPickOrderLineRecord.pickOrderCode = line.pickOrderCode | |||
| doPickOrderLineRecord.deliveryOrderCode = line.deliveryOrderCode | |||
| doPickOrderLineRecord.status = line.status | |||
| doPickOrderLineRecordRepository.save(doPickOrderLineRecord) | |||
| println("✅ Copied do_pick_order_line ${line.id} to do_pick_order_line_record") | |||
| println(" Copied do_pick_order_line ${line.id} to do_pick_order_line_record") | |||
| } | |||
| // ✅ 第三步:删除原始的 do_pick_order_line 记录 | |||
| // 第三步:删除原始的 do_pick_order_line 记录 | |||
| if (doPickOrderLines.isNotEmpty()) { | |||
| doPickOrderLineRepository.deleteAll(doPickOrderLines) | |||
| println("✅ Deleted ${doPickOrderLines.size} do_pick_order_line records") | |||
| println(" Deleted ${doPickOrderLines.size} do_pick_order_line records") | |||
| } | |||
| // ✅ 第四步:删除原始的 do_pick_order 记录 | |||
| // 第四步:删除原始的 do_pick_order 记录 | |||
| doPickOrderRepository.delete(doPickOrder) | |||
| deletedCount++ | |||
| println("✅ Deleted do_pick_order ${doPickOrder.id}") | |||
| println(" Deleted do_pick_order ${doPickOrder.id}") | |||
| } | |||
| return deletedCount | |||
| @@ -235,10 +235,10 @@ open class DoPickOrderService( | |||
| return doPickOrderRecordRepository.save(record) | |||
| } | |||
| // ✅ Add method to update DoPickOrderRecord status | |||
| // Add method to update DoPickOrderRecord status | |||
| open fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long): List<DoPickOrderRecord> { | |||
| val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||
| val user = userRepository.findById(userId).orElse(null) // ✅ 改用 orElse(null) | |||
| val user = userRepository.findById(userId).orElse(null) // 改用 orElse(null) | |||
| val handlerName = user?.name ?: "Unknown" | |||
| doPickOrderRecords.forEach { | |||
| it.handledBy = userId | |||
| @@ -249,12 +249,12 @@ open class DoPickOrderService( | |||
| return doPickOrderRecordRepository.saveAll(doPickOrderRecords) | |||
| } | |||
| // ✅ Add method to complete DoPickOrderRecord | |||
| // Add method to complete DoPickOrderRecord | |||
| open fun completeDoPickOrderRecordsForPickOrder(pickOrderId: Long): List<DoPickOrderRecord> { | |||
| val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||
| doPickOrderRecords.forEach { | |||
| it.ticketStatus = DoPickOrderStatus.completed | |||
| it.ticketCompleteDateTime = LocalDateTime.now() // ✅ 设置完成时间 | |||
| it.ticketCompleteDateTime = LocalDateTime.now() // 设置完成时间 | |||
| } | |||
| return doPickOrderRecordRepository.saveAll(doPickOrderRecords) | |||
| } | |||
| @@ -271,29 +271,32 @@ open class DoPickOrderService( | |||
| } | |||
| return doPickOrderRepository.saveAll(doPickOrders) | |||
| } | |||
| open fun getSummaryByStore(storeId: String, requiredDate: LocalDate?): StoreLaneSummary { | |||
| fun getSummaryByStore(storeId: String, requiredDate: LocalDate?): StoreLaneSummary { | |||
| val targetDate = requiredDate ?: LocalDate.now() | |||
| println("🔍 DEBUG: Getting summary for store=$storeId, date=$targetDate") | |||
| // ✅ 修复格式转换:保持原始格式 | |||
| val actualStoreId = when (storeId) { | |||
| "2/F" -> "2/F" // ✅ 保持原格式 | |||
| "4/F" -> "4/F" // ✅ 保持原格式 | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| else -> storeId | |||
| } | |||
| println("🔍 DEBUG: Using storeId: '$actualStoreId'") | |||
| // ✅ 直接查询 do_pick_order 表 | |||
| val allRecords = doPickOrderRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( | |||
| actualStoreId, | |||
| targetDate, | |||
| listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed) | |||
| ) | |||
| // 添加 finishedRecords 查询 | |||
| val finishedRecords = doPickOrderRecordRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( | |||
| actualStoreId, | |||
| targetDate, | |||
| listOf(DoPickOrderStatus.completed) | |||
| ) | |||
| println("🔍 DEBUG: Found ${allRecords.size} records for date $targetDate") | |||
| println("🔍 DEBUG: Found ${finishedRecords.size} finished records for date $targetDate") | |||
| // ✅ 过滤掉所有 do_pick_order_line 都是 "issue" 状态的 shop | |||
| /* | |||
| val filteredRecords = allRecords.filter { doPickOrder -> | |||
| val hasNonIssueLines = checkDoPickOrderHasNonIssueLines(doPickOrder.id!!) | |||
| if (!hasNonIssueLines) { | |||
| @@ -301,19 +304,27 @@ open class DoPickOrderService( | |||
| } | |||
| hasNonIssueLines | |||
| } | |||
| */ | |||
| // println("🔍 DEBUG: After filtering, ${filteredRecords.size} records remain") | |||
| // ✅ 使用过滤后的记录 | |||
| val grouped = allRecords.groupBy { it.truckDepartureTime to it.truckLanceCode } | |||
| .mapValues { (_, list) -> | |||
| println("🔍 DEBUG: After filtering, ${filteredRecords.size} records remain") | |||
| val grouped = filteredRecords.groupBy { it.truckDepartureTime to it.truckLanceCode } | |||
| .mapValues { (key, list) -> | |||
| val (truckDepartureTime, truckLanceCode) = key | |||
| // 计算匹配的 finishedRecords 数量 | |||
| val matchingFinishedCount = finishedRecords.count { record -> | |||
| (record.truckDepartureTime == truckDepartureTime) && | |||
| (record.truckLanceCode == truckLanceCode) | |||
| } | |||
| println("🔍 DEBUG: Group key - truckDepartureTime: $truckDepartureTime, truckLanceCode: $truckLanceCode") | |||
| println("🔍 DEBUG: Found ${list.size} active records in this group") | |||
| println("🔍 DEBUG: Found $matchingFinishedCount finished records matching this group") | |||
| LaneBtn( | |||
| truckLanceCode = list.first().truckLanceCode ?: "", | |||
| unassigned = list.count { it.handledBy == null }, | |||
| total = list.size | |||
| total = list.size + matchingFinishedCount // 添加 finishedRecords 计数 | |||
| ) | |||
| } | |||
| val timeGroups = grouped.entries | |||
| .groupBy { it.key.first } | |||
| .mapValues { (_, entries) -> | |||
| @@ -330,7 +341,7 @@ open class DoPickOrderService( | |||
| lanes = lanes | |||
| ) | |||
| } | |||
| return StoreLaneSummary(storeId = storeId, rows = timeGroups) | |||
| } | |||
| private fun checkDoPickOrderHasNonIssueLines(doPickOrderId: Long): Boolean { | |||
| @@ -374,7 +385,7 @@ open class DoPickOrderService( | |||
| return true // 出错时不过滤 | |||
| } | |||
| } | |||
| // ✅ 修复:把 assignByLane 移到类里面 | |||
| // 修复:把 assignByLane 移到类里面 | |||
| open fun assignByLane(request: AssignByLaneRequest): MessageResponse { | |||
| val user = userRepository.findById(request.userId).orElse(null) | |||
| ?: return MessageResponse( | |||
| @@ -382,10 +393,10 @@ open class DoPickOrderService( | |||
| message = "User not found", errorPosition = null, entity = null | |||
| ) | |||
| // ✅ 转换 storeId 格式:'2/F' -> '2F/F', '4/F' -> '4F/F' | |||
| // 转换 storeId 格式:'2/F' -> '2F/F', '4/F' -> '4F/F' | |||
| val actualStoreId = when (request.storeId) { | |||
| "2/F" -> "2/F" // ✅ 保持原格式 | |||
| "4/F" -> "4/F" // ✅ 保持原格式 | |||
| "2/F" -> "2/F" // 保持原格式 | |||
| "4/F" -> "4/F" // 保持原格式 | |||
| else -> request.storeId | |||
| } | |||
| println("🔍 DEBUG: assignByLane - Converting storeId from '${request.storeId}' to '$actualStoreId'") | |||
| @@ -406,14 +417,14 @@ open class DoPickOrderService( | |||
| val firstOrder = candidates.first() | |||
| // ✅ 更新 do_pick_order | |||
| // 更新 do_pick_order | |||
| firstOrder.handledBy = request.userId | |||
| firstOrder.handlerName = user.name | |||
| firstOrder.ticketStatus = DoPickOrderStatus.released | |||
| firstOrder.ticketReleaseTime = LocalDateTime.now() | |||
| doPickOrderRepository.save(firstOrder) | |||
| // ✅ 关键修改:获取这个 do_pick_order 下的所有 pick orders 并分配给用户 | |||
| // 关键修改:获取这个 do_pick_order 下的所有 pick orders 并分配给用户 | |||
| val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(firstOrder.id!!) | |||
| println("🔍 DEBUG: Found ${doPickOrderLines.size} pick orders in do_pick_order ${firstOrder.id}") | |||
| @@ -431,7 +442,7 @@ open class DoPickOrderService( | |||
| } | |||
| } | |||
| // ✅ 同步更新 do_pick_order_record(如果有的话) | |||
| // 同步更新 do_pick_order_record(如果有的话) | |||
| doPickOrderLines.forEach { line -> | |||
| if (line.pickOrderId != null) { | |||
| val records = doPickOrderRecordRepository.findByPickOrderId(line.pickOrderId!!) | |||
| @@ -588,4 +599,4 @@ open class DoPickOrderService( | |||
| open fun getTicketReleaseTable(): List<DoPickOrder>{ | |||
| return doPickOrderRepository.findAllByDeletedFalseOrderByTicketReleaseTimeDesc() | |||
| } | |||
| }// ✅ 类结束 | |||
| }// 类结束 | |||
| @@ -183,7 +183,7 @@ class DoReleaseCoordinatorService( | |||
| """.trimIndent() | |||
| val rowsUpdated = jdbcDao.executeUpdate(updateSql, emptyMap<String, Any>()) | |||
| println("✅ Updated $rowsUpdated ticket numbers") | |||
| println(" Updated $rowsUpdated ticket numbers") | |||
| } catch (e: Exception) { | |||
| println("❌ Error updating ticket numbers: ${e.message}") | |||
| e.printStackTrace() | |||
| @@ -332,16 +332,16 @@ class DoReleaseCoordinatorService( | |||
| """.trimIndent() | |||
| println("🔍 DEBUG: SQL length: ${sql.length} characters") // ✅ 添加这行 | |||
| println("🔍 DEBUG: SQL first 500 chars: ${sql.take(500)}") // ✅ 添加这行 | |||
| println("🔍 DEBUG: SQL length: ${sql.length} characters") // 添加这行 | |||
| println("🔍 DEBUG: SQL first 500 chars: ${sql.take(500)}") // 添加这行 | |||
| val results = jdbcDao.queryForList(sql) | |||
| println("🔍 DEBUG: Results type: ${results.javaClass.name}") // ✅ 添加这行 | |||
| println("🔍 DEBUG: Results size: ${results.size}") // ✅ 添加这行 | |||
| println("🔍 DEBUG: Results type: ${results.javaClass.name}") // 添加这行 | |||
| println("🔍 DEBUG: Results size: ${results.size}") // 添加这行 | |||
| if (results.isNotEmpty()) { | |||
| println("🔍 DEBUG: First result keys: ${results[0].keys}") // ✅ 添加这行 | |||
| println("🔍 DEBUG: First result: ${results[0]}") // ✅ 添加这行 | |||
| println("🔍 DEBUG: First result keys: ${results[0].keys}") // 添加这行 | |||
| println("🔍 DEBUG: First result: ${results[0]}") // 添加这行 | |||
| } | |||
| val sortedIds = results.mapNotNull { row -> | |||
| @@ -359,7 +359,7 @@ class DoReleaseCoordinatorService( | |||
| } | |||
| } catch (e: Exception) { | |||
| println("❌ ERROR: ${e.message}") | |||
| println("❌ ERROR Stack Trace:") // ✅ 添加这行 | |||
| println("❌ ERROR Stack Trace:") // 添加这行 | |||
| e.printStackTrace() | |||
| return ids | |||
| } | |||
| @@ -407,7 +407,7 @@ class DoReleaseCoordinatorService( | |||
| } | |||
| println("❌ DO $id skipped: ${e.message}") | |||
| // ✅ 调用 PickExecutionIssueService 创建 issue 记录 | |||
| // 调用 PickExecutionIssueService 创建 issue 记录 | |||
| try { | |||
| val issueCategory = when { | |||
| e.message?.contains("Unable to find") == true && | |||
| @@ -430,9 +430,9 @@ class DoReleaseCoordinatorService( | |||
| println("⚠️ Failed to create issue for DO $id: ${issueException.message}") | |||
| } | |||
| } | |||
| } // ✅ forEach 循环在这里结束 | |||
| } // forEach 循环在这里结束 | |||
| // ✅ 第二步:按日期、楼层、店铺分组(在 forEach 之后) | |||
| // 第二步:按日期、楼层、店铺分组(在 forEach 之后) | |||
| val sortedResults = releaseResults.sortedWith( | |||
| compareBy( | |||
| { it.estimatedArrivalDate }, | |||
| @@ -444,7 +444,7 @@ class DoReleaseCoordinatorService( | |||
| ) | |||
| ) | |||
| // ✅ 然后按正确的顺序分组(保持排序后的顺序) | |||
| // 然后按正确的顺序分组(保持排序后的顺序) | |||
| val grouped = sortedResults.groupBy { | |||
| Triple(it.estimatedArrivalDate, it.preferredFloor, it.shopId) | |||
| } | |||
| @@ -468,7 +468,7 @@ class DoReleaseCoordinatorService( | |||
| updateBatchTicketNumbers() | |||
| } | |||
| println("✅ Batch completed: ${status.success.get()} success, ${status.failed.size} failed") | |||
| println(" Batch completed: ${status.success.get()} success, ${status.failed.size} failed") | |||
| } catch (e: Exception) { | |||
| println("❌ Batch release exception: ${e.javaClass.simpleName} - ${e.message}") | |||
| @@ -511,7 +511,7 @@ class DoReleaseCoordinatorService( | |||
| requiredDeliveryDate = first.estimatedArrivalDate | |||
| ) | |||
| // ✅ 直接使用 doPickOrderRepository.save() 而不是 doPickOrderService.save() | |||
| // 直接使用 doPickOrderRepository.save() 而不是 doPickOrderService.save() | |||
| val saved = doPickOrderRepository.save(doPickOrder) | |||
| println("🔍 DEBUG: Saved DoPickOrder - ID: ${saved.id}, Ticket: ${saved.ticketNo}") | |||
| @@ -524,7 +524,7 @@ class DoReleaseCoordinatorService( | |||
| return@forEach // 跳过这个 | |||
| } | |||
| // ✅ 先创建 DoPickOrderLine,然后检查库存问题 | |||
| // 先创建 DoPickOrderLine,然后检查库存问题 | |||
| val line = DoPickOrderLine().apply { | |||
| doPickOrderId = saved.id | |||
| pickOrderId = result.pickOrderId | |||
| @@ -537,7 +537,7 @@ class DoReleaseCoordinatorService( | |||
| println("🔍 DEBUG: Created DoPickOrderLine for pick order ${result.pickOrderId}") | |||
| } | |||
| // ✅ 现在检查整个 DoPickOrder 是否有库存问题 | |||
| // 现在检查整个 DoPickOrder 是否有库存问题 | |||
| val hasStockIssues = checkPickOrderHasStockIssues(saved.id!!) | |||
| if (hasStockIssues) { | |||
| // 更新所有相关的 DoPickOrderLine 状态为 "issue" | |||
| @@ -180,7 +180,7 @@ class DeliveryOrderController( | |||
| @PostMapping("/release") | |||
| fun releaseDeliveryOrder(@RequestBody request: ReleaseDoRequest): MessageResponse { | |||
| // ✅ Simply pass the request directly - userId comes from frontend session | |||
| // Simply pass the request directly - userId comes from frontend session | |||
| return deliveryOrderService.releaseDeliveryOrder(request) | |||
| } | |||
| @@ -75,7 +75,7 @@ class DoPickOrderController( | |||
| } | |||
| @PostMapping("/assign-by-lane") | |||
| fun assignByLane(@RequestBody request: AssignByLaneRequest): MessageResponse { | |||
| return doPickOrderAssignmentService.assignByLane(request) // ✅ 使用新的 Service | |||
| return doPickOrderAssignmentService.assignByLane(request) // 使用新的 Service | |||
| } | |||
| @PostMapping("/batch-release/async") | |||
| @@ -44,7 +44,7 @@ open class JoPickOrderService( | |||
| return joPickOrderRecordRepository.save(record) | |||
| } | |||
| // ✅ Update JoPickOrder status to released | |||
| // Update JoPickOrder status to released | |||
| open fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long): List<JoPickOrder> { | |||
| val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId) | |||
| @@ -68,7 +68,7 @@ open class JoPickOrderService( | |||
| } | |||
| return joPickOrderRepository.saveAll(joPickOrders) | |||
| } | |||
| // ✅ Complete JoPickOrder | |||
| // Complete JoPickOrder | |||
| open fun completeJoPickOrdersForPickOrder(pickOrderId: Long): List<JoPickOrder> { | |||
| val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId) | |||
| joPickOrders.forEach { | |||
| @@ -78,7 +78,7 @@ open class JoPickOrderService( | |||
| return joPickOrderRepository.saveAll(joPickOrders) | |||
| } | |||
| // ✅ Update JoPickOrderRecord status to released | |||
| // Update JoPickOrderRecord status to released | |||
| open fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long): List<JoPickOrderRecord> { | |||
| val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||
| @@ -103,7 +103,7 @@ open class JoPickOrderService( | |||
| return joPickOrderRecordRepository.saveAll(joPickOrderRecords) | |||
| } | |||
| // ✅ Complete JoPickOrderRecord | |||
| // Complete JoPickOrderRecord | |||
| open fun completeJoPickOrderRecordsForPickOrder(pickOrderId: Long): List<JoPickOrderRecord> { | |||
| val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||
| joPickOrderRecords.forEach { | |||
| @@ -113,17 +113,17 @@ open class JoPickOrderService( | |||
| return joPickOrderRecordRepository.saveAll(joPickOrderRecords) | |||
| } | |||
| // ✅ Find JoPickOrder records by pick order ID | |||
| // 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 | |||
| // 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 | |||
| // Create JoPickOrder records for a pick order | |||
| @Transactional | |||
| open fun createJoPickOrdersForPickOrder(pickOrderId: Long, itemIds: List<Long>): List<JoPickOrder> { | |||
| // Get pick order and item details | |||
| @@ -155,7 +155,7 @@ open class JoPickOrderService( | |||
| } | |||
| return joPickOrderRepository.saveAll(joPickOrders) | |||
| } | |||
| // ✅ Create JoPickOrderRecord records for a pick order | |||
| // Create JoPickOrderRecord records for a pick order | |||
| @Transactional | |||
| open fun createJoPickOrderRecordsForPickOrder(pickOrderId: Long, itemIds: List<Long>): List<JoPickOrderRecord> { | |||
| // Get pick order and item details | |||
| @@ -248,7 +248,7 @@ open class JoPickOrderService( | |||
| ) | |||
| } | |||
| // ✅ 修复:简化 SQL 查询,移除不存在的字段 | |||
| // 修复:简化 SQL 查询,移除不存在的字段 | |||
| val pickOrderIdsStr = pickOrderIds.joinToString(",") | |||
| val sql = """ | |||
| @@ -286,7 +286,7 @@ open class JoPickOrderService( | |||
| w.name as location, | |||
| COALESCE(uc.udfudesc, 'N/A') as stockUnit, | |||
| -- ✅ Simplified Router Information using warehouse | |||
| -- Simplified Router Information using warehouse | |||
| w.`order` as routerIndex, | |||
| w.code as routerRoute, | |||
| w.code as routerArea, | |||
| @@ -363,7 +363,7 @@ open class JoPickOrderService( | |||
| 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.inventory_lot il ON il.id = ill.inventoryLotId | |||
| LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId -- ✅ 直接关联 warehouse | |||
| LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId -- 直接关联 warehouse | |||
| 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 | |||
| @@ -376,7 +376,7 @@ open class JoPickOrderService( | |||
| 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(w.`order`, 999999) ASC, -- ✅ 使用 warehouse.order 排序 | |||
| COALESCE(w.`order`, 999999) ASC, -- 使用 warehouse.order 排序 | |||
| po.code ASC, | |||
| i.code ASC, | |||
| il.expiryDate ASC, | |||
| @@ -386,7 +386,7 @@ open class JoPickOrderService( | |||
| println("🔍 Executing SQL for job order hierarchical structure: $sql") | |||
| println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") | |||
| // ✅ 修复:直接执行 SQL 查询并构建分层结构 | |||
| // 修复:直接执行 SQL 查询并构建分层结构 | |||
| try { | |||
| val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) | |||
| @@ -410,7 +410,7 @@ open class JoPickOrderService( | |||
| "jobOrder" to mapOf( | |||
| "id" to firstRow["jobOrderId"], | |||
| "code" to firstRow["jobOrderCode"], | |||
| "name" to "Job Order ${firstRow["jobOrderCode"]}" // ✅ 使用生成的名称 | |||
| "name" to "Job Order ${firstRow["jobOrderCode"]}" // 使用生成的名称 | |||
| ) | |||
| ) | |||
| } | |||
| @@ -474,7 +474,7 @@ open class JoPickOrderService( | |||
| } | |||
| // Get completed job order pick orders (for second tab) | |||
| // ✅ Fix the getCompletedJobOrderLotsHierarchical method | |||
| // Fix the getCompletedJobOrderLotsHierarchical method | |||
| open fun getCompletedJobOrderLotsHierarchical(userId: Long): Map<String, Any?> { | |||
| println("=== Debug: getCompletedJobOrderLotsHierarchical ===") | |||
| println("today: ${LocalDate.now()}") | |||
| @@ -520,7 +520,7 @@ open fun getCompletedJobOrderLotsHierarchical(userId: Long): Map<String, Any?> { | |||
| println("🔍 DEBUG: Using latest completed order: ${latestCompletedOrder.code}") | |||
| // ✅ Use the same SQL query approach as getAllJobOrderLotsWithDetailsHierarchical | |||
| // Use the same SQL query approach as getAllJobOrderLotsWithDetailsHierarchical | |||
| val pickOrderIds = listOf(latestCompletedOrder.id!!) | |||
| val pickOrderIdsStr = pickOrderIds.joinToString(",") | |||
| @@ -657,7 +657,7 @@ open fun getCompletedJobOrderLotsHierarchical(userId: Long): Map<String, Any?> { | |||
| println("🔍 Executing SQL for completed job order hierarchical structure: $sql") | |||
| println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") | |||
| // ✅ Execute SQL query and build hierarchical structure | |||
| // Execute SQL query and build hierarchical structure | |||
| try { | |||
| val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) | |||
| @@ -799,7 +799,7 @@ open fun getUnassignedJobOrderPickOrders(): List<Map<String, Any?>> { | |||
| println("=== getUnassignedJobOrderPickOrders ===") | |||
| return try { | |||
| // ✅ 修复:使用正确的 repository 方法 | |||
| // 修复:使用正确的 repository 方法 | |||
| val unassignedPickOrders = pickOrderRepository.findAllByStatusAndAssignToIsNullAndDeletedFalse( | |||
| com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED | |||
| ).filter { pickOrder -> | |||
| @@ -840,7 +840,7 @@ open fun getUnassignedJobOrderPickOrders(): List<Map<String, Any?>> { | |||
| } | |||
| } | |||
| // ✅ 修复:assignJobOrderPickOrderToUser 方法中的自引用问题 | |||
| // 修复:assignJobOrderPickOrderToUser 方法中的自引用问题 | |||
| @Transactional | |||
| open fun assignJobOrderPickOrderToUser(pickOrderId: Long, userId: Long): MessageResponse { | |||
| println("=== assignJobOrderPickOrderToUser ===") | |||
| @@ -874,7 +874,7 @@ open fun assignJobOrderPickOrderToUser(pickOrderId: Long, userId: Long): Message | |||
| pickOrder.assignTo = userService.find(userId).orElse(null) | |||
| pickOrderRepository.save(pickOrder) | |||
| // ✅ 修复:使用 this 而不是 joPickOrderService | |||
| // 修复:使用 this 而不是 joPickOrderService | |||
| this.updateHandledByForPickOrder(pickOrderId, userId) | |||
| this.updateRecordHandledByForPickOrder(pickOrderId, userId) | |||
| MessageResponse( | |||
| @@ -898,7 +898,7 @@ open fun assignJobOrderPickOrderToUser(pickOrderId: Long, userId: Long): Message | |||
| ) | |||
| } | |||
| } | |||
| // ✅ Fix the updateMatchStatus method | |||
| // Fix the updateMatchStatus method | |||
| open fun updateMatchStatus(pickOrderId: Long, itemId: Long, userId: Long, qty: Int): MessageResponse { | |||
| try { | |||
| println("=== Debug: updateMatchStatus ===") | |||
| @@ -919,23 +919,23 @@ open fun assignJobOrderPickOrderToUser(pickOrderId: Long, userId: Long): Message | |||
| val joPickOrderEntity = joPickOrder.get() | |||
| // ✅ 设置扫描状态和相关字段 | |||
| // 设置扫描状态和相关字段 | |||
| joPickOrderEntity.matchStatus = JoPickOrderStatus.scanned | |||
| joPickOrderEntity.matchBy = userId | |||
| joPickOrderEntity.matchQty = qty // ✅ 使用传递的 qty | |||
| joPickOrderEntity.matchQty = qty // 使用传递的 qty | |||
| joPickOrderRepository.save(joPickOrderEntity) | |||
| // ✅ 同时更新 jo_pick_order_record | |||
| // 同时更新 jo_pick_order_record | |||
| val joPickOrderRecord = joPickOrderRecordRepository.findByPickOrderIdAndItemId(pickOrderId, itemId) | |||
| if (joPickOrderRecord.isPresent) { | |||
| val recordEntity = joPickOrderRecord.get() | |||
| recordEntity.matchStatus = JoPickOrderStatus.scanned | |||
| recordEntity.matchBy = userId | |||
| recordEntity.matchQty = qty // ✅ 使用相同的 qty | |||
| recordEntity.matchQty = qty // 使用相同的 qty | |||
| joPickOrderRecordRepository.save(recordEntity) | |||
| } | |||
| println("✅ Updated match status: pickOrderId=$pickOrderId, itemId=$itemId, matchQty=$qty") | |||
| println(" Updated match status: pickOrderId=$pickOrderId, itemId=$itemId, matchQty=$qty") | |||
| return MessageResponse( | |||
| id = null, | |||
| @@ -979,17 +979,17 @@ open fun submitSecondScanQty(request: SecondScanSubmitRequest): MessageResponse | |||
| val joPickOrderEntity = joPickOrder.get() | |||
| joPickOrderEntity.matchQty = request.qty.toInt() | |||
| // ✅ Set status to completed when submitting quantity | |||
| // Set status to completed when submitting quantity | |||
| joPickOrderEntity.matchStatus = JoPickOrderStatus.completed | |||
| // ✅ 添加:如果 ticketCompleteTime 还没设置,现在设置(通常已经在拣货完成时设置了) | |||
| // 添加:如果 ticketCompleteTime 还没设置,现在设置(通常已经在拣货完成时设置了) | |||
| if (joPickOrderEntity.ticketCompleteTime == null) { | |||
| joPickOrderEntity.ticketCompleteTime = LocalDateTime.now() | |||
| } | |||
| joPickOrderRepository.save(joPickOrderEntity) | |||
| println("✅ Updated jo_pick_order: status=${joPickOrderEntity.matchStatus}, qty=${joPickOrderEntity.matchQty}, completeTime=${joPickOrderEntity.ticketCompleteTime}") | |||
| println(" Updated jo_pick_order: status=${joPickOrderEntity.matchStatus}, qty=${joPickOrderEntity.matchQty}, completeTime=${joPickOrderEntity.ticketCompleteTime}") | |||
| return MessageResponse( | |||
| id = null, | |||
| @@ -1014,7 +1014,7 @@ open fun submitSecondScanQty(request: SecondScanSubmitRequest): MessageResponse | |||
| } | |||
| // ✅ Fix the recordSecondScanIssue method | |||
| // Fix the recordSecondScanIssue method | |||
| open fun recordSecondScanIssue(request: SecondScanIssueRequest): MessageResponse { | |||
| try { | |||
| println("=== Debug: recordSecondScanIssue ===") | |||
| @@ -1033,7 +1033,7 @@ open fun recordSecondScanIssue(request: SecondScanIssueRequest): MessageResponse | |||
| ) | |||
| } | |||
| // ✅ Get pick order and pick order line details with item and lot information | |||
| // Get pick order and pick order line details with item and lot information | |||
| val pickOrderDetails = jdbcDao.queryForList( | |||
| """ | |||
| SELECT | |||
| @@ -1060,7 +1060,7 @@ open fun recordSecondScanIssue(request: SecondScanIssueRequest): MessageResponse | |||
| ) | |||
| ) | |||
| // ✅ 修复:使用正确的变量名 pickOrderDetails | |||
| // 修复:使用正确的变量名 pickOrderDetails | |||
| if (pickOrderDetails.isEmpty()) { | |||
| return MessageResponse( | |||
| id = null, | |||
| @@ -1072,7 +1072,7 @@ open fun recordSecondScanIssue(request: SecondScanIssueRequest): MessageResponse | |||
| ) | |||
| } | |||
| // ✅ 修复:使用正确的变量名 pickOrderDetails | |||
| // 修复:使用正确的变量名 pickOrderDetails | |||
| val orderData = pickOrderDetails.first() | |||
| val pickOrderCode = orderData["pickOrderCode"] as String? | |||
| val pickOrderCreateDate = orderData["pickOrderCreateDate"]?.let { | |||
| @@ -1086,7 +1086,7 @@ open fun recordSecondScanIssue(request: SecondScanIssueRequest): MessageResponse | |||
| BigDecimal(it.toString()) | |||
| } | |||
| // ✅ 获取 suggested lot 信息(用于 lot_id, lot_no, store_location) | |||
| // 获取 suggested lot 信息(用于 lot_id, lot_no, store_location) | |||
| val lotResults = jdbcDao.queryForList( | |||
| """ | |||
| SELECT | |||
| @@ -1127,41 +1127,41 @@ open fun recordSecondScanIssue(request: SecondScanIssueRequest): MessageResponse | |||
| joPickOrderRepository.save(joPickOrderEntity) | |||
| // ✅ 生成 issueNo | |||
| // 生成 issueNo | |||
| val issueNo = generateIssueNoForJo() | |||
| // ✅ Create pick execution issue with complete data | |||
| // ✅ Create pick execution issue with complete data | |||
| // Create pick execution issue with complete data | |||
| // Create pick execution issue with complete data | |||
| val pickExecutionIssue = PickExecutionIssue( | |||
| id = null, // ✅ 1 | |||
| pickOrderId = request.pickOrderId, // ✅ 2 | |||
| pickOrderCode = pickOrderCode ?: "PI-${request.pickOrderId}", // ✅ 3 | |||
| pickOrderCreateDate = pickOrderCreateDate, // ✅ 4 | |||
| pickExecutionDate = LocalDate.now(), // ✅ 5 | |||
| pickOrderLineId = pickOrderLineId, // ✅ 6 | |||
| issueNo = issueNo, // ✅ 7 | |||
| issueCategory = IssueCategory.match_issue, // ✅ 8 - 因为这是 JO 的 match 问题 | |||
| itemId = request.itemId, // ✅ 9 | |||
| itemCode = itemCode, // ✅ 10 | |||
| itemDescription = itemName, // ✅ 11 | |||
| lotId = lotId, // ✅ 12 | |||
| lotNo = lotNo, // ✅ 13 | |||
| storeLocation = storeLocation, // ✅ 14 | |||
| requiredQty = requiredQty, // ✅ 15 | |||
| actualPickQty = request.qty.toBigDecimal(), // ✅ 16 | |||
| missQty = request.missQty.toBigDecimal(), // ✅ 17 | |||
| badItemQty = request.badItemQty.toBigDecimal(), // ✅ 18 | |||
| issueRemark = request.reason, // ✅ 19 | |||
| pickerName = pickerName, // ✅ 20 | |||
| handleStatus = HandleStatus.pending, // ✅ 21 | |||
| handleDate = null, // ✅ 22 | |||
| handledBy = null, // ✅ 23 | |||
| created = LocalDateTime.now(), // ✅ 24 | |||
| createdBy = request.createdBy.toString(), // ✅ 25 | |||
| version = 0, // ✅ 26 | |||
| modified = LocalDateTime.now(), // ✅ 27 | |||
| modifiedBy = null, // ✅ 28 | |||
| deleted = false // ✅ 29 | |||
| id = null, // 1 | |||
| pickOrderId = request.pickOrderId, // 2 | |||
| pickOrderCode = pickOrderCode ?: "PI-${request.pickOrderId}", // 3 | |||
| pickOrderCreateDate = pickOrderCreateDate, // 4 | |||
| pickExecutionDate = LocalDate.now(), // 5 | |||
| pickOrderLineId = pickOrderLineId, // 6 | |||
| issueNo = issueNo, // 7 | |||
| issueCategory = IssueCategory.match_issue, // 8 - 因为这是 JO 的 match 问题 | |||
| itemId = request.itemId, // 9 | |||
| itemCode = itemCode, // 10 | |||
| itemDescription = itemName, // 11 | |||
| lotId = lotId, // 12 | |||
| lotNo = lotNo, // 13 | |||
| storeLocation = storeLocation, // 14 | |||
| requiredQty = requiredQty, // 15 | |||
| actualPickQty = request.qty.toBigDecimal(), // 16 | |||
| missQty = request.missQty.toBigDecimal(), // 17 | |||
| badItemQty = request.badItemQty.toBigDecimal(), // 18 | |||
| issueRemark = request.reason, // 19 | |||
| pickerName = pickerName, // 20 | |||
| handleStatus = HandleStatus.pending, // 21 | |||
| handleDate = null, // 22 | |||
| handledBy = null, // 23 | |||
| created = LocalDateTime.now(), // 24 | |||
| createdBy = request.createdBy.toString(), // 25 | |||
| version = 0, // 26 | |||
| modified = LocalDateTime.now(), // 27 | |||
| modifiedBy = null, // 28 | |||
| deleted = false // 29 | |||
| ) | |||
| pickExecutionIssueRepository.save(pickExecutionIssue) | |||
| @@ -244,7 +244,7 @@ open class JobOrderService( | |||
| pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED | |||
| pickOrderRepository.saveAndFlush(pickOrderEntity) | |||
| // ✅ 添加 suggested pick lots 创建逻辑 | |||
| // 添加 suggested pick lots 创建逻辑 | |||
| val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!) | |||
| if (lines.isNotEmpty()) { | |||
| val suggestions = suggestedPickLotService.suggestionForPickOrderLines( | |||
| @@ -253,7 +253,7 @@ open class JobOrderService( | |||
| val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) | |||
| // ✅ Hold inventory quantities | |||
| // Hold inventory quantities | |||
| val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( | |||
| saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } | |||
| ) | |||
| @@ -269,7 +269,7 @@ open class JobOrderService( | |||
| } | |||
| inventoryLotLineRepository.saveAll(inventoryLotLines) | |||
| // ✅ Create stock out record and pre-create stock out lines | |||
| // Create stock out record and pre-create stock out lines | |||
| val stockOut = StockOut().apply { | |||
| this.type = "job" | |||
| this.consoPickOrderCode = consoCode | |||
| @@ -278,7 +278,7 @@ open class JobOrderService( | |||
| } | |||
| val savedStockOut = stockOutRepository.saveAndFlush(stockOut) | |||
| // ✅ Pre-create stock out lines for suggested lots | |||
| // Pre-create stock out lines for suggested lots | |||
| saveSuggestedPickLots.forEach { lot -> | |||
| val polId = lot.pickOrderLine?.id | |||
| val illId = lot.suggestedLotLine?.id | |||
| @@ -134,8 +134,8 @@ class JobOrderController( | |||
| @RequestBody data: Map<String, Any> | |||
| ): MessageResponse { | |||
| val request = SecondScanSubmitRequest( | |||
| pickOrderId = pickOrderId, // ✅ Use path variable | |||
| itemId = itemId, // ✅ Use path variable | |||
| pickOrderId = pickOrderId, // Use path variable | |||
| itemId = itemId, // Use path variable | |||
| qty = (data["qty"] as Number).toDouble(), | |||
| isMissing = data["isMissing"] as? Boolean ?: false, | |||
| isBad = data["isBad"] as? Boolean ?: false | |||
| @@ -150,10 +150,10 @@ fun recordSecondScanIssue( | |||
| @RequestBody data: Map<String, Any> | |||
| ): MessageResponse { | |||
| val request = SecondScanIssueRequest( | |||
| pickOrderId = pickOrderId, // ✅ path 变量 | |||
| itemId = itemId, // ✅ path 变量 | |||
| pickOrderId = pickOrderId, // path 变量 | |||
| itemId = itemId, // path 变量 | |||
| qty = (data["qty"] as Number).toDouble(), | |||
| // ✅ 新增:安全读取 missQty/badItemQty/type,默认 0/"match" | |||
| // 新增:安全读取 missQty/badItemQty/type,默认 0/"match" | |||
| missQty = (data["missQty"] as? Number)?.toDouble() ?: 0.0, | |||
| badItemQty = (data["badItemQty"] as? Number)?.toDouble() ?: 0.0, | |||
| isMissing = data["isMissing"] as? Boolean ?: false, | |||
| @@ -4,8 +4,8 @@ data class SecondScanIssueRequest( | |||
| val pickOrderId: Long, | |||
| val itemId: Long, | |||
| val qty: Double, // 这是 actual pick qty (verified qty) | |||
| val missQty: Double = 0.0, // ✅ 添加:单独的 miss qty | |||
| val badItemQty: Double = 0.0, // ✅ 添加:单独的 bad item qty | |||
| val missQty: Double = 0.0, // 添加:单独的 miss qty | |||
| val badItemQty: Double = 0.0, // 添加:单独的 bad item qty | |||
| val isMissing: Boolean, | |||
| val isBad: Boolean, | |||
| val reason: String, | |||
| @@ -4,9 +4,9 @@ import jakarta.validation.constraints.NotNull | |||
| data class SecondScanSubmitRequest( | |||
| @NotNull | |||
| val pickOrderId: Long, // ✅ Add missing property | |||
| val pickOrderId: Long, // Add missing property | |||
| @NotNull | |||
| val itemId: Long, // ✅ Add missing property | |||
| val itemId: Long, // Add missing property | |||
| @NotNull | |||
| val qty: Double, | |||
| val isMissing: Boolean = false, | |||
| @@ -103,7 +103,7 @@ val doPickOrderId: Long? = null, | |||
| @Column(name = "deleted", nullable = false) | |||
| val deleted: Boolean = false | |||
| ) { | |||
| // ✅ 添加默认构造函数 | |||
| // 添加默认构造函数 | |||
| constructor() : this( | |||
| pickOrderId = null, | |||
| pickOrderCode = "", | |||
| @@ -13,15 +13,15 @@ interface TruckRepository : AbstractRepository<Truck, Long> { | |||
| @Query("SELECT t FROM Truck t WHERE t.shop.id = :shopId AND t.deleted = false ORDER BY t.id ASC") | |||
| fun findFirstByShopIdAndDeletedFalse(@Param("shopId") shopId: Long): List<Truck> | |||
| // ✅ 使用新的 TruckLanceCode 字段名 | |||
| // 使用新的 TruckLanceCode 字段名 | |||
| @Query("SELECT t FROM Truck t WHERE t.truckLanceCode = :truckLanceCode AND t.deleted = false") | |||
| fun findByTruckLanceCodeAndDeletedFalse(@Param("truckLanceCode") truckLanceCode: String): Truck? | |||
| // ✅ 按 ShopCode 查询 | |||
| // 按 ShopCode 查询 | |||
| @Query("SELECT t FROM Truck t WHERE t.shopCode = :shopCode AND t.deleted = false") | |||
| fun findByShopCodeAndDeletedFalse(@Param("shopCode") shopCode: String): List<Truck> | |||
| // ✅ 按 Store_id 查询 | |||
| // 按 Store_id 查询 | |||
| fun findByStoreIdAndDeletedFalse(storeId: Int): List<Truck> | |||
| } | |||
| @@ -5,7 +5,7 @@ import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockOutLine | |||
| import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory // ✅ 添加这行 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory // 添加这行 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus | |||
| import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLine | |||
| @@ -70,15 +70,15 @@ open class PickExecutionIssueService( | |||
| val pickOrder = pickOrderRepository.findById(request.pickOrderId).orElse(null) | |||
| // 2. 创建 pick execution issue 记录 | |||
| val pickExecutionIssue = PickExecutionIssue( | |||
| id = null, // ✅ 添加 id | |||
| id = null, // 添加 id | |||
| pickOrderId = request.pickOrderId, | |||
| pickOrderCode = request.pickOrderCode, | |||
| pickOrderCreateDate = request.pickOrderCreateDate, | |||
| pickExecutionDate = request.pickExecutionDate ?: LocalDate.now(), | |||
| pickOrderLineId = request.pickOrderLineId, | |||
| issueNo = generateIssueNo(), | |||
| joPickOrderId = pickOrder?.jobOrder?.id, // ✅ 添加 | |||
| doPickOrderId = if (pickOrder?.type?.value == "do") pickOrder.id else null, // ✅ 添加 | |||
| joPickOrderId = pickOrder?.jobOrder?.id, // 添加 | |||
| doPickOrderId = if (pickOrder?.type?.value == "do") pickOrder.id else null, // 添加 | |||
| issueCategory = IssueCategory.valueOf( | |||
| request.issueCategory ?: "lot_issue" | |||
| ), | |||
| @@ -93,16 +93,16 @@ open class PickExecutionIssueService( | |||
| missQty = request.missQty, | |||
| badItemQty = request.badItemQty, | |||
| issueRemark = request.issueRemark, | |||
| pickerName = request.pickerName, // ✅ 确保从 request 中获取 | |||
| handleStatus = HandleStatus.pending, // ✅ 添加 | |||
| handleDate = null, // ✅ 添加 | |||
| pickerName = request.pickerName, // 确保从 request 中获取 | |||
| handleStatus = HandleStatus.pending, // 添加 | |||
| handleDate = null, // 添加 | |||
| handledBy = request.handledBy, | |||
| created = LocalDateTime.now(), | |||
| createdBy = "system", | |||
| version = 0, // ✅ 添加 | |||
| version = 0, // 添加 | |||
| modified = LocalDateTime.now(), | |||
| modifiedBy = "system", | |||
| deleted = false // ✅ 添加 | |||
| deleted = false // 添加 | |||
| ) | |||
| val savedIssue = pickExecutionIssueRepository.save(pickExecutionIssue) | |||
| @@ -139,7 +139,7 @@ open class PickExecutionIssueService( | |||
| handleBothMissAndBadItem(request, missQty, badItemQty) | |||
| } | |||
| // ✅ 修复:情况4: 有 miss item 的情况(无论 actualPickQty 是多少) | |||
| // 修复:情况4: 有 miss item 的情况(无论 actualPickQty 是多少) | |||
| missQty > BigDecimal.ZERO -> { | |||
| handleMissItemWithPartialPick(request, actualPickQty, missQty) | |||
| } | |||
| @@ -264,7 +264,7 @@ private fun checkAndCompletePickOrder(consoCode: String) { | |||
| // 4. 如果所有行都完成或被拒绝,则完成 pick order | |||
| if (unfinishedLines.isEmpty()) { | |||
| println("✅ All stock out lines completed or rejected, completing pick order...") | |||
| println(" All stock out lines completed or rejected, completing pick order...") | |||
| // 4.1 更新 StockOut 状态 | |||
| stockOut.status = StockOutStatus.COMPLETE.status | |||
| @@ -281,7 +281,7 @@ private fun checkAndCompletePickOrder(consoCode: String) { | |||
| println("Updated pick order line ${line.id} to COMPLETED") | |||
| } | |||
| pickOrderLineRepository.saveAll(pickOrderLines) | |||
| println("✅ Updated ${pickOrderLines.size} pick order lines to COMPLETED status") | |||
| println(" Updated ${pickOrderLines.size} pick order lines to COMPLETED status") | |||
| // 4.3 获取所有受影响的 pick orders | |||
| val pickOrderIds = pickOrderLines.mapNotNull { it.pickOrder?.id }.distinct() | |||
| @@ -302,15 +302,15 @@ private fun checkAndCompletePickOrder(consoCode: String) { | |||
| pickOrder.status = PickOrderStatus.COMPLETED | |||
| pickOrder.completeDate = LocalDateTime.now() | |||
| pickOrderRepository.save(pickOrder) | |||
| println("✅ Updated pick order ${pickOrder.code} to COMPLETED status") | |||
| println(" Updated pick order ${pickOrder.code} to COMPLETED status") | |||
| // 4.5 处理 DO pick order 相关记录 | |||
| try { | |||
| val removedCount = doPickOrderService.removeDoPickOrdersForPickOrder(pickOrderId) | |||
| println("✅ Removed $removedCount do_pick_order records for completed pick order ${pickOrderId}") | |||
| println(" Removed $removedCount do_pick_order records for completed pick order ${pickOrderId}") | |||
| doPickOrderService.completeDoPickOrderRecordsForPickOrder(pickOrderId) | |||
| println("✅ Updated do_pick_order_record status to COMPLETED for pick order ${pickOrderId}") | |||
| println(" Updated do_pick_order_record status to COMPLETED for pick order ${pickOrderId}") | |||
| } catch (e: Exception) { | |||
| println("⚠️ Error updating DO pick order records: ${e.message}") | |||
| } | |||
| @@ -330,7 +330,7 @@ private fun checkAndCompletePickOrder(consoCode: String) { | |||
| } | |||
| joPickOrderRecordRepository.saveAll(joPickOrderRecords) | |||
| println("✅ Set jo_pick_order ticketCompleteTime for pick order ${pickOrderId}") | |||
| println(" Set jo_pick_order ticketCompleteTime for pick order ${pickOrderId}") | |||
| } catch (e: Exception) { | |||
| println("⚠️ Error updating JO pick order records: ${e.message}") | |||
| } | |||
| @@ -346,7 +346,7 @@ private fun checkAndCompletePickOrder(consoCode: String) { | |||
| } | |||
| } | |||
| // FPSMS-backend/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt | |||
| // ✅ 修复:处理有部分拣货但有 miss item 的情况 | |||
| // 修复:处理有部分拣货但有 miss item 的情况 | |||
| @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) | |||
| private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, actualPickQty: BigDecimal, missQty: BigDecimal) { | |||
| println("=== HANDLING MISS ITEM WITH PARTIAL PICK (FIXED LOGIC) ===") | |||
| @@ -358,16 +358,16 @@ private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, ac | |||
| val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null) | |||
| if (inventoryLotLine != null) { | |||
| // ✅ 修复1:只处理已拣货的部分:更新 outQty | |||
| // 修复1:只处理已拣货的部分:更新 outQty | |||
| val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO | |||
| val newOutQty = currentOutQty.add(actualPickQty) | |||
| inventoryLotLine.outQty = newOutQty | |||
| // ✅ 修复2:Miss item 不减少 inQty,而是标记为 unavailable | |||
| // 修复2:Miss item 不减少 inQty,而是标记为 unavailable | |||
| // 因为 miss item 意味着这些物品实际上不存在或找不到 | |||
| // 所以应该标记整个批次为 unavailable,而不是减少 inQty | |||
| // ✅ 修复3:如果 missQty > 0,标记批次为 unavailable | |||
| // 修复3:如果 missQty > 0,标记批次为 unavailable | |||
| if (missQty > BigDecimal.ZERO) { | |||
| inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE | |||
| } | |||
| @@ -381,11 +381,11 @@ private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, ac | |||
| println(" - Set status to UNAVAILABLE due to missQty: ${missQty}") | |||
| } | |||
| // ✅ 修复4:更新 inventory 表的 unavailableQty | |||
| // 修复4:更新 inventory 表的 unavailableQty | |||
| // 对于 miss item,应该将 missQty 计入 unavailableQty | |||
| updateInventoryUnavailableQty(itemId, missQty) | |||
| // ✅ 修复5:更新 stock_out_line 状态为 rejected(因为还有 miss item) | |||
| // 修复5:更新 stock_out_line 状态为 rejected(因为还有 miss item) | |||
| updateStockOutLineStatus(request, "rejected") | |||
| // 重新建议拣货批次(针对 miss 的数量) | |||
| @@ -397,7 +397,7 @@ private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, ac | |||
| } | |||
| } | |||
| // ✅ 修复:Miss item 处理逻辑 | |||
| // 修复:Miss item 处理逻辑 | |||
| @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) | |||
| private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigDecimal) { | |||
| println("=== HANDLING MISS ITEM ONLY (FIXED LOGIC) ===") | |||
| @@ -408,7 +408,7 @@ private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigD | |||
| val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null) | |||
| if (inventoryLotLine != null) { | |||
| // ✅ 修复:Miss item 意味着剩余的所有物品都找不到 | |||
| // 修复:Miss item 意味着剩余的所有物品都找不到 | |||
| val currentInQty = inventoryLotLine.inQty ?: BigDecimal.ZERO | |||
| val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO | |||
| val remainingQty = currentInQty.minus(currentOutQty) | |||
| @@ -425,16 +425,16 @@ private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigD | |||
| println(" - Unavailable qty (should be remaining qty): ${remainingQty}") | |||
| } | |||
| // ✅ 修复:更新 inventory 表的 unavailableQty | |||
| // 修复:更新 inventory 表的 unavailableQty | |||
| // 应该是剩余的数量,而不是 missQty | |||
| val currentInQty = inventoryLotLine?.inQty ?: BigDecimal.ZERO | |||
| val currentOutQty = inventoryLotLine?.outQty ?: BigDecimal.ZERO | |||
| val remainingQty = currentInQty.minus(currentOutQty) | |||
| // ✅ 修复:只增加剩余数量,不要重复计算 missQty | |||
| // 修复:只增加剩余数量,不要重复计算 missQty | |||
| updateInventoryUnavailableQty(itemId, remainingQty) | |||
| // ✅ 修复:更新 stock_out_line 状态为 rejected | |||
| // 修复:更新 stock_out_line 状态为 rejected | |||
| updateStockOutLineStatus(request, "rejected") | |||
| // 重新建议拣货批次 | |||
| @@ -446,7 +446,7 @@ private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigD | |||
| } | |||
| } | |||
| // ✅ 修复:Bad item 处理逻辑 | |||
| // 修复:Bad item 处理逻辑 | |||
| @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) | |||
| private fun handleBadItemOnly(request: PickExecutionIssueRequest, badItemQty: BigDecimal) { | |||
| println("=== HANDLING BAD ITEM ONLY (FIXED LOGIC) ===") | |||
| @@ -457,7 +457,7 @@ private fun handleBadItemOnly(request: PickExecutionIssueRequest, badItemQty: Bi | |||
| val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null) | |||
| if (inventoryLotLine != null) { | |||
| // ✅ 修复:Bad item 不减少 inQty,而是标记为 unavailable | |||
| // 修复:Bad item 不减少 inQty,而是标记为 unavailable | |||
| // 因为 bad item 意味着这些物品质量有问题,不能使用 | |||
| inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE | |||
| inventoryLotLine.modified = LocalDateTime.now() | |||
| @@ -467,10 +467,10 @@ private fun handleBadItemOnly(request: PickExecutionIssueRequest, badItemQty: Bi | |||
| println("Bad item only: Set lot ${lotId} status to UNAVAILABLE") | |||
| } | |||
| // ✅ 修复:更新 inventory 表的 unavailableQty | |||
| // 修复:更新 inventory 表的 unavailableQty | |||
| updateInventoryUnavailableQty(itemId, badItemQty) | |||
| // ✅ 修复:更新 stock_out_line 状态为 rejected | |||
| // 修复:更新 stock_out_line 状态为 rejected | |||
| updateStockOutLineStatus(request, "rejected") | |||
| // 重新建议拣货批次 | |||
| @@ -482,7 +482,7 @@ private fun handleBadItemOnly(request: PickExecutionIssueRequest, badItemQty: Bi | |||
| } | |||
| } | |||
| // ✅ 修复:Both miss and bad item 处理逻辑 | |||
| // 修复:Both miss and bad item 处理逻辑 | |||
| @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) | |||
| private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty: BigDecimal, badItemQty: BigDecimal) { | |||
| println("=== HANDLING BOTH MISS AND BAD ITEM (FIXED LOGIC) ===") | |||
| @@ -494,7 +494,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| val totalUnavailableQty = missQty.add(badItemQty) | |||
| if (inventoryLotLine != null) { | |||
| // ✅ 修复:Miss + Bad item 不减少 inQty,而是标记为 unavailable | |||
| // 修复:Miss + Bad item 不减少 inQty,而是标记为 unavailable | |||
| inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE | |||
| inventoryLotLine.modified = LocalDateTime.now() | |||
| inventoryLotLine.modifiedBy = "system" | |||
| @@ -506,10 +506,10 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| println(" - Total Unavailable Qty: ${totalUnavailableQty}") | |||
| } | |||
| // ✅ 修复:更新 inventory 表的 unavailableQty | |||
| // 修复:更新 inventory 表的 unavailableQty | |||
| updateInventoryUnavailableQty(itemId, totalUnavailableQty) | |||
| // ✅ 修复:更新 stock_out_line 状态为 rejected | |||
| // 修复:更新 stock_out_line 状态为 rejected | |||
| updateStockOutLineStatus(request, "rejected") | |||
| // 重新建议拣货批次 | |||
| @@ -521,19 +521,19 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| } | |||
| } | |||
| // ✅ 修复:正常拣货处理逻辑 | |||
| // 修复:正常拣货处理逻辑 | |||
| @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) | |||
| private fun handleNormalPick(request: PickExecutionIssueRequest, actualPickQty: BigDecimal) { | |||
| println("=== HANDLING NORMAL PICK ===") | |||
| // ✅ 修复:更新 stock_out_line,但不要累积 qty | |||
| // 修复:更新 stock_out_line,但不要累积 qty | |||
| val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( | |||
| request.pickOrderLineId, | |||
| request.lotId ?: 0L | |||
| ) | |||
| stockOutLines.forEach { stockOutLine -> | |||
| // ✅ 修复:直接设置 qty 为 actualPickQty,不要累积 | |||
| // 修复:直接设置 qty 为 actualPickQty,不要累积 | |||
| val requiredQty = request.requiredQty?.toDouble() ?: 0.0 | |||
| val actualPickQtyDouble = actualPickQty.toDouble() | |||
| val newStatus = if (actualPickQtyDouble >= requiredQty) { | |||
| @@ -543,7 +543,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| } | |||
| stockOutLine.status = newStatus | |||
| stockOutLine.qty = actualPickQtyDouble // ✅ 直接设置,不累积 | |||
| stockOutLine.qty = actualPickQtyDouble // 直接设置,不累积 | |||
| stockOutLine.modified = LocalDateTime.now() | |||
| stockOutLine.modifiedBy = "system" | |||
| stockOutLineRepository.save(stockOutLine) | |||
| @@ -551,11 +551,11 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| println("Updated stock out line ${stockOutLine.id}: status=${newStatus}, qty=${actualPickQtyDouble}") | |||
| } | |||
| // ✅ 修复:更新 inventory_lot_line 的 outQty | |||
| // 修复:更新 inventory_lot_line 的 outQty | |||
| val lotId = request.lotId ?: return | |||
| val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null) | |||
| if (inventoryLotLine != null) { | |||
| // ✅ 修复:计算新的 outQty,考虑之前的 outQty | |||
| // 修复:计算新的 outQty,考虑之前的 outQty | |||
| val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO | |||
| val previousPickedQty = currentOutQty.minus(actualPickQty) // 计算之前已拣的数量 | |||
| val newOutQty = previousPickedQty.add(actualPickQty) // 更新为新的总拣货数量 | |||
| @@ -569,7 +569,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| } | |||
| } | |||
| // ✅ 新方法:统一更新 inventory 表的 unavailableQty | |||
| // 新方法:统一更新 inventory 表的 unavailableQty | |||
| private fun updateInventoryUnavailableQty(itemId: Long, unavailableQty: BigDecimal) { | |||
| try { | |||
| println("=== INVENTORY UNAVAILABLE QTY UPDATE (TRIGGER HANDLED) ===") | |||
| @@ -582,7 +582,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| } | |||
| } | |||
| // ✅ 修复:更新 stock_out_line 状态 | |||
| // 修复:更新 stock_out_line 状态 | |||
| private fun updateStockOutLineStatus(request: PickExecutionIssueRequest, status: String) { | |||
| val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( | |||
| request.pickOrderLineId, | |||
| @@ -592,7 +592,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| stockOutLines.forEach { stockOutLine -> | |||
| stockOutLine.status = status | |||
| // ✅ FIX: Update qty to actualPickQty before setting status to rejected | |||
| // FIX: Update qty to actualPickQty before setting status to rejected | |||
| if (status == "rejected" && request.actualPickQty != null) { | |||
| stockOutLine.qty = request.actualPickQty.toDouble() | |||
| println("Updated stock out line ${stockOutLine.id} qty to: ${request.actualPickQty}") | |||
| @@ -605,7 +605,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| println("Updated stock out line ${stockOutLine.id} status to: ${status}") | |||
| } | |||
| } | |||
| // ✅ 修复:使用 REQUIRES_NEW 传播级别,避免事务冲突 | |||
| // 修复:使用 REQUIRES_NEW 传播级别,避免事务冲突 | |||
| @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) | |||
| private fun resuggestPickOrder(pickOrderId: Long?) { | |||
| if (pickOrderId != null) { | |||
| @@ -639,7 +639,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| "jo" -> { | |||
| // Case 1: 只返回 Job Order 的 issues | |||
| val joIssues = pickExecutionIssueRepository.findJoIssues() | |||
| println("✅ Finding JO issues: ${joIssues.size} records") | |||
| println(" Finding JO issues: ${joIssues.size} records") | |||
| joIssues.forEach { | |||
| println(" - ID=${it.id}, joPickOrderId=${it.joPickOrderId}, doPickOrderId=${it.doPickOrderId}") | |||
| } | |||
| @@ -648,7 +648,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| "do" -> { | |||
| // Case 2: 只返回 Delivery Order 的 issues | |||
| val doIssues = pickExecutionIssueRepository.findDoIssues() | |||
| println("✅ Finding DO issues: ${doIssues.size} records") | |||
| println(" Finding DO issues: ${doIssues.size} records") | |||
| doIssues.forEach { | |||
| println(" - ID=${it.id}, joPickOrderId=${it.joPickOrderId}, doPickOrderId=${it.doPickOrderId}") | |||
| } | |||
| @@ -657,7 +657,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| "material" -> { | |||
| // Case 3: 只返回 Material 的 issues (普通 pick order) | |||
| val materialIssues = pickExecutionIssueRepository.findMaterialIssues() | |||
| println("✅ Finding Material issues: ${materialIssues.size} records") | |||
| println(" Finding Material issues: ${materialIssues.size} records") | |||
| materialIssues.forEach { | |||
| println(" - ID=${it.id}, joPickOrderId=${it.joPickOrderId}, doPickOrderId=${it.doPickOrderId}") | |||
| } | |||
| @@ -666,7 +666,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty | |||
| else -> { | |||
| // Case 4: 返回所有 issues | |||
| val allIssues = pickExecutionIssueRepository.findByDeletedFalse() | |||
| println("✅ Finding ALL issues: ${allIssues.size} records") | |||
| println(" Finding ALL issues: ${allIssues.size} records") | |||
| allIssues | |||
| } | |||
| } | |||
| @@ -772,7 +772,7 @@ open fun createBatchReleaseIssue( | |||
| createdCount++ | |||
| } | |||
| println("✅ Created $createdCount ${issueCategory.name} issues for DO $deliveryOrderId") | |||
| println(" Created $createdCount ${issueCategory.name} issues for DO $deliveryOrderId") | |||
| return createdCount | |||
| } catch (e: Exception) { | |||
| @@ -3,7 +3,7 @@ package com.ffii.fpsms.modules.pickOrder.web | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue | |||
| import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService // ✅ 修复导入路径 | |||
| import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService // 修复导入路径 | |||
| import com.ffii.fpsms.modules.stock.web.model.PickExecutionIssueRequest | |||
| import org.springframework.web.bind.annotation.* | |||
| @@ -14,7 +14,7 @@ import com.ffii.fpsms.modules.master.entity.Bom | |||
| @Entity | |||
| @Table(name = "productprocess") | |||
| open class ProductProcess : BaseEntity<Long>() { | |||
| @Size(max = 50) | |||
| @NotNull | |||
| @Column(name = "productprocesscode", nullable = false, unique = true, length = 50) | |||
| @@ -14,12 +14,12 @@ import com.ffii.fpsms.modules.master.entity.BomProcess | |||
| open class ProductProcessLine : BaseEntity<Long>() { | |||
| // ✅ 添加 @ManyToOne | |||
| // 添加 @ManyToOne | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "operatorId") | |||
| open var operator: User? = null | |||
| // ✅ 添加 @ManyToOne | |||
| // 添加 @ManyToOne | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "equipmentId") | |||
| open var equipment: Equipment? = null | |||
| @@ -35,7 +35,7 @@ open class ProductProcessLine : BaseEntity<Long>() { | |||
| @Column(name = "equipment_name", length = 100) | |||
| open var equipmentType: String? = null | |||
| // ✅ 添加 @ManyToOne | |||
| // 添加 @ManyToOne | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "byproductId") | |||
| open var byproduct: Items? = null | |||
| @@ -8,4 +8,6 @@ interface ProductProcessLineRepository : JpaRepository<ProductProcessLine, Long> | |||
| fun findByProductProcess_Id(productProcessId: Long): List<ProductProcessLine> | |||
| fun findByProductProcess_IdAndHandler_Id(productProcessId: Long, handlerId: Long): List<ProductProcessLine> | |||
| fun findByHandler_IdAndStartTimeIsNotNullAndEndTimeIsNull(handlerId: Long): List<ProductProcessLine> | |||
| fun findByProductProcess_IdIn(ids: List<Long>): List<ProductProcessLine> | |||
| } | |||
| @@ -8,8 +8,8 @@ import jakarta.validation.constraints.Size | |||
| import java.time.LocalDateTime | |||
| @Entity | |||
| @Table(name = "productionprocessissue") // ✅ 修复:改为正确的表名 | |||
| open class ProductionProcessIssue : BaseEntity<Long>() { // ✅ 修复:改为正确的类名 | |||
| @Table(name = "productionprocessissue") // 修复:改为正确的表名 | |||
| open class ProductionProcessIssue : BaseEntity<Long>() { // 修复:改为正确的类名 | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "productprocessid", nullable = false) | |||
| @@ -45,7 +45,7 @@ open class ProductProcessService( | |||
| open fun findAll(pageable: Pageable): Page<ProductProcess> { | |||
| println("📋 Service: Finding all ProductProcess with page: ${pageable.pageNumber}, size: ${pageable.pageSize}") | |||
| val result = productProcessRepository.findAll(pageable) | |||
| println("✅ Service: Found ${result.totalElements} records") | |||
| println(" Service: Found ${result.totalElements} records") | |||
| return result | |||
| } | |||
| @@ -89,14 +89,14 @@ open class ProductProcessService( | |||
| } | |||
| val savedProcess = productProcessRepository.save(productProcess) | |||
| println("✅ Service: ProductProcess created with ID: ${savedProcess.id}") | |||
| println(" Service: ProductProcess created with ID: ${savedProcess.id}") | |||
| // 4. 查询 BOM 的所有工序步骤 | |||
| val bomProcesses = bomProcessRepository.findByBomIdOrderBySeqNo(request.bomId) | |||
| println("🔍 Service: Found ${bomProcesses.size} BOM processes") | |||
| // 5. 为每个 BOM Process 创建 ProductProcessLine | |||
| bomProcesses.forEachIndexed { index, bomProcess -> // ✅ 修复 forEach | |||
| bomProcesses.forEachIndexed { index, bomProcess -> // 修复 forEach | |||
| val line = ProductProcessLine().apply { | |||
| this.productProcess = savedProcess | |||
| this.bomProcess = bomProcess | |||
| @@ -109,7 +109,7 @@ open class ProductProcessService( | |||
| println("➕ Service: Created line ${index + 1} - seq: ${line.seqNo}, name: ${line.name}") | |||
| } | |||
| println("✅ Service: All ${bomProcesses.size} lines created automatically") | |||
| println(" Service: All ${bomProcesses.size} lines created automatically") | |||
| return SaveProductProcessResponse( | |||
| id = savedProcess.id!!, | |||
| @@ -126,11 +126,11 @@ open class ProductProcessService( | |||
| } | |||
| // ✅ 添加:查询工序的所有步骤 | |||
| // 添加:查询工序的所有步骤 | |||
| open fun getLines(productProcessId: Long): List<ProductProcessLine> { | |||
| println("📋 Service: Getting lines for process ID: $productProcessId") | |||
| val lines = productProcessLineRepository.findByProductProcess_Id(productProcessId) | |||
| println("✅ Service: Found ${lines.size} lines") | |||
| println(" Service: Found ${lines.size} lines") | |||
| return lines | |||
| } | |||
| @@ -140,7 +140,7 @@ open class ProductProcessService( | |||
| productProcess.status = ProductProcessStatus.IN_PROGRESS | |||
| productProcess.startTime = LocalDateTime.now() | |||
| val saved = productProcessRepository.save(productProcess) | |||
| println("✅ Service: Process started, status: ${saved.status}") | |||
| println(" Service: Process started, status: ${saved.status}") | |||
| return saved | |||
| } | |||
| @@ -160,7 +160,7 @@ open class ProductProcessService( | |||
| productProcessRepository.save(productProcess) | |||
| val savedIssue = productionProcessIssueRepository.save(issue) | |||
| println("✅ Service: Process stopped, issue ID: ${savedIssue.id}") | |||
| println(" Service: Process stopped, issue ID: ${savedIssue.id}") | |||
| return savedIssue | |||
| } | |||
| @@ -178,7 +178,7 @@ open class ProductProcessService( | |||
| productProcessRepository.save(productProcess) | |||
| val savedIssue = productionProcessIssueRepository.save(issue) | |||
| println("✅ Service: Process resumed, total stop time: ${savedIssue.totalTime} minutes") | |||
| println(" Service: Process resumed, total stop time: ${savedIssue.totalTime} minutes") | |||
| return savedIssue | |||
| } | |||
| @@ -188,7 +188,7 @@ open class ProductProcessService( | |||
| productProcess.status = ProductProcessStatus.COMPLETED | |||
| productProcess.endTime = LocalDateTime.now() | |||
| val saved = productProcessRepository.save(productProcess) | |||
| println("✅ Service: Process completed") | |||
| println(" Service: Process completed") | |||
| return saved | |||
| } | |||
| @@ -206,7 +206,7 @@ open class ProductProcessService( | |||
| } | |||
| val saved = productProcessLineRepository.save(line) | |||
| println("✅ Service: Line added with ID: ${saved.id}") | |||
| println(" Service: Line added with ID: ${saved.id}") | |||
| return SaveProductProcessLineResponse(saved.id!!) | |||
| } | |||
| @@ -230,20 +230,20 @@ open class ProductProcessService( | |||
| } | |||
| val saved = productProcessLineRepository.save(line) | |||
| println("✅ Service: Line output updated") | |||
| println(" Service: Line output updated") | |||
| return saved | |||
| } | |||
| open fun getIssues(productProcessId: Long): List<ProductionProcessIssue> { | |||
| println("📋 Service: Getting issues for ProductProcess ID: $productProcessId") | |||
| val issues = productionProcessIssueRepository.findByProductProcess_Id(productProcessId) | |||
| println("✅ Service: Found ${issues.size} issues") | |||
| println(" Service: Found ${issues.size} issues") | |||
| return issues | |||
| } | |||
| open fun findByJobOrderId(jobOrderId: Long): List<ProductProcess> { | |||
| println("🔍 Service: Finding ProductProcess by jobOrderId: $jobOrderId") | |||
| val result = productProcessRepository.findByJobOrder_Id(jobOrderId) | |||
| println("✅ Service: Found ${result.size} processes") | |||
| println(" Service: Found ${result.size} processes") | |||
| return result | |||
| } | |||
| @@ -280,7 +280,7 @@ open class ProductProcessService( | |||
| ) | |||
| } | |||
| println("✅ Service: Returning ${result.size} processes with ${result.sumOf { it.lines.size }} total lines") | |||
| println(" Service: Returning ${result.size} processes with ${result.sumOf { it.lines.size }} total lines") | |||
| return result | |||
| } | |||
| open fun findAllAsDto(pageable: Pageable): Page<ProductProcessSimpleResponse> { | |||
| @@ -300,7 +300,7 @@ open class ProductProcessService( | |||
| ) | |||
| } | |||
| println("✅ Service: Converted ${dtoList.size} entities to DTOs") | |||
| println(" Service: Converted ${dtoList.size} entities to DTOs") | |||
| return org.springframework.data.domain.PageImpl(dtoList, pageable, entityPage.totalElements) | |||
| } | |||
| @@ -435,7 +435,7 @@ open class ProductProcessService( | |||
| val line = productProcessLineRepository.findById(lineId) | |||
| .orElseThrow { IllegalArgumentException("Line not found") } | |||
| // ✅ 修复:使用 UserRepository 获取 User 对象 | |||
| // 修复:使用 UserRepository 获取 User 对象 | |||
| val user = userRepository.findById(userId) | |||
| .orElseThrow { IllegalArgumentException("User not found with id: $userId") } | |||
| @@ -443,13 +443,13 @@ open class ProductProcessService( | |||
| line.startTime = LocalDateTime.now() | |||
| val saved = productProcessLineRepository.save(line) | |||
| println("✅ Service: Line started, handlerId: ${saved.handler?.id}") | |||
| println(" Service: Line started, handlerId: ${saved.handler?.id}") | |||
| return saved | |||
| } | |||
| open fun findPendingLinesByHandlerId(handlerId: Long): List<ProductProcessLine> { | |||
| println("🔍 Service: Finding pending lines for handlerId: $handlerId") | |||
| val lines = productProcessLineRepository.findByHandler_IdAndStartTimeIsNotNullAndEndTimeIsNull(handlerId) | |||
| println("✅ Service: Found ${lines.size} pending lines") | |||
| println(" Service: Found ${lines.size} pending lines") | |||
| return lines | |||
| } | |||
| @@ -753,42 +753,45 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest | |||
| errorPosition = null, | |||
| ) | |||
| } | |||
| /* | |||
| open fun getAllJoborderProductProcessInfo(): List<AllJoborderProductProcessInfoResponse> { | |||
| val productProcesses = productProcessRepository.findAllByDeletedIsFalse() | |||
| val productProcessId=productProcesses.map { it.id } | |||
| val productProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcessId.map { it.id }) | |||
| return productProcesses.map { process -> | |||
| AllJoborderProductProcessInfoResponse( | |||
| id = productProcesses.id, | |||
| productProcessCode = productProcesses.productProcessCode, | |||
| status = productProcesses.status, | |||
| startTime = productProcesses.startTime, | |||
| endTime = productProcesses.endTime, | |||
| date = productProcesses.date, | |||
| bomId = productProcesses.bom.id, | |||
| bomName = productProcesses.bom.name, | |||
| jobOrderId = productProcesses.jobOrder.id, | |||
| lines = productProcessLines.map { line -> | |||
| ProductProcessInfoResponse( | |||
| id = line.id?:0, | |||
| productProcessCode = process.productProcessCode?:"", | |||
| operatorId = process.operator?.id?:0, | |||
| operatorName = process.operator?.name?:"", | |||
| equipmentId = line.equipment?.id?:0, | |||
| equipmentName = process.equipment?.name?:"", | |||
| startTime = line.startTime?:LocalDateTime.now(), | |||
| endTime = line.endTime?:LocalDateTime.now(), | |||
| date = line.date?:LocalDate.now(), | |||
| status = line.status?:"", | |||
| bomId = process.bom?.id?:0, | |||
| bomName = process.bom?.name?:"", | |||
| jobOrderId = process.jobOrder?.id?:0 | |||
| ) | |||
| } | |||
| ) | |||
| open fun getAllJoborderProductProcessInfo(): List<AllJoborderProductProcessInfoResponse> { | |||
| val productProcesses = productProcessRepository.findAllByDeletedIsFalse() | |||
| val productProcessIds = productProcesses.map { it.id } | |||
| return productProcesses.map { productProcesses -> | |||
| val productProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcesses.id?:0L) | |||
| val jobOrder = jobOrderRepository.findById(productProcesses.jobOrder?.id?:0L).orElse(null) | |||
| val FinishedProductProcessLineCount = productProcessLineRepository.findByProductProcess_Id(productProcesses.id?:0L).count { it.status == "Completed" } | |||
| AllJoborderProductProcessInfoResponse( | |||
| id = productProcesses.id ?: 0L, | |||
| productProcessCode = productProcesses.productProcessCode, | |||
| status = productProcesses.status.value, | |||
| startTime = productProcesses.startTime, | |||
| endTime = productProcesses.endTime, | |||
| date = productProcesses.date, | |||
| bomId = productProcesses.bom?.id, | |||
| itemName = productProcesses.item?.name, | |||
| jobOrderId = productProcesses.jobOrder?.id, | |||
| jobOrderCode = jobOrder?.code, | |||
| productProcessLineCount = productProcessLines.size, | |||
| FinishedProductProcessLineCount = FinishedProductProcessLineCount, | |||
| lines = productProcessLines | |||
| .map { line -> | |||
| ProductProcessInfoResponse( | |||
| id = line.id ?: 0, | |||
| operatorId = line.operator?.id, | |||
| operatorName = line.operator?.name ?: "", | |||
| equipmentId = line.equipment?.id, | |||
| equipmentName = line.equipmentType?: "", | |||
| startTime = line.startTime, | |||
| endTime = line.endTime, | |||
| status = line.status ?: "" | |||
| ) | |||
| } | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| */ | |||
| } | |||
| @@ -16,18 +16,18 @@ class ProductProcessController( | |||
| ) { | |||
| @GetMapping | |||
| fun findAll(pageable: Pageable): Page<ProductProcessSimpleResponse> { // ✅ 改为返回 DTO | |||
| fun findAll(pageable: Pageable): Page<ProductProcessSimpleResponse> { // 改为返回 DTO | |||
| println("📋 Controller: GET /product-process - Finding all") | |||
| val result = productProcessService.findAllAsDto(pageable) // ✅ 使用新方法 | |||
| println("✅ Controller: Returning ${result.totalElements} records") | |||
| val result = productProcessService.findAllAsDto(pageable) // 使用新方法 | |||
| println(" Controller: Returning ${result.totalElements} records") | |||
| return result | |||
| } | |||
| @GetMapping("/{id}") | |||
| fun findById(@PathVariable id: Long): ProductProcessSimpleResponse { // ✅ 改为返回 DTO | |||
| fun findById(@PathVariable id: Long): ProductProcessSimpleResponse { // 改为返回 DTO | |||
| println("🔍 Controller: GET /product-process/$id") | |||
| val result = productProcessService.findByIdAsDto(id) // ✅ 使用新方法 | |||
| println("✅ Controller: Found: ${result.productProcessCode}") | |||
| val result = productProcessService.findByIdAsDto(id) // 使用新方法 | |||
| println(" Controller: Found: ${result.productProcessCode}") | |||
| return result | |||
| } | |||
| @@ -35,24 +35,24 @@ class ProductProcessController( | |||
| fun findByCode(@PathVariable code: String): ProductProcess { | |||
| println("🔍 Controller: GET /product-process/code/$code") | |||
| val result = productProcessService.findByCode(code) | |||
| println("✅ Controller: Found ID: ${result.id}") | |||
| println(" Controller: Found ID: ${result.id}") | |||
| return result | |||
| } | |||
| @PostMapping | |||
| fun create(@RequestBody request: SaveProductProcessRequest): SaveProductProcessResponse { | |||
| println("💾 Controller: POST /product-process - Creating ProductProcess") | |||
| //println("📦 Controller: Request - bomId: ${request.bomId}, jobOrderId: ${request.jobOrderId}, date: ${request.date}") // ✅ 修复 | |||
| //println("📦 Controller: Request - bomId: ${request.bomId}, jobOrderId: ${request.jobOrderId}, date: ${request.date}") // 修复 | |||
| val result = productProcessService.create(request) | |||
| println("✅ Controller: Created ProductProcess - ID: ${result.id}, Code: ${result.productProcessCode}, Lines: ${result.linesCreated}") // ✅ 改进日志 | |||
| println(" Controller: Created ProductProcess - ID: ${result.id}, Code: ${result.productProcessCode}, Lines: ${result.linesCreated}") // 改进日志 | |||
| return result | |||
| } | |||
| @PostMapping("/{id}/start") | |||
| fun startProcess(@PathVariable id: Long, @RequestParam operatorId: Long): ProductProcessSimpleResponse { | |||
| println("▶️ Controller: POST /product-process/$id/start - operatorId: $operatorId") | |||
| val result = productProcessService.startProcessAsDto(id, operatorId) // ✅ 改用 AsDto 方法 | |||
| println("✅ Controller: Process started") | |||
| val result = productProcessService.startProcessAsDto(id, operatorId) // 改用 AsDto 方法 | |||
| println(" Controller: Process started") | |||
| return result | |||
| } | |||
| @@ -60,11 +60,11 @@ class ProductProcessController( | |||
| fun stopProcess( | |||
| @PathVariable id: Long, | |||
| @RequestBody request: StopProcessRequest | |||
| ): ProductionProcessIssueResponse { // ✅ 改为 Issue Response | |||
| ): ProductionProcessIssueResponse { // 改为 Issue Response | |||
| println("⏸️ Controller: POST /product-process/$id/stop") | |||
| println("📦 Controller: Reason: ${request.reason}") | |||
| val result = productProcessService.stopProcessAsDto(id, request) // ✅ 使用返回 DTO 的方法 | |||
| println("✅ Controller: Process stopped, issue ID: ${result.id}") | |||
| val result = productProcessService.stopProcessAsDto(id, request) // 使用返回 DTO 的方法 | |||
| println(" Controller: Process stopped, issue ID: ${result.id}") | |||
| return result | |||
| } | |||
| @@ -72,18 +72,18 @@ class ProductProcessController( | |||
| fun resumeProcess( | |||
| @PathVariable id: Long, | |||
| @PathVariable issueId: Long | |||
| ): ProductionProcessIssueResponse { // ✅ 改为 Issue Response | |||
| ): ProductionProcessIssueResponse { // 改为 Issue Response | |||
| println("▶️ Controller: POST /product-process/$id/resume/$issueId") | |||
| val result = productProcessService.resumeProcessAsDto(id, issueId) // ✅ 使用返回 DTO 的方法 | |||
| println("✅ Controller: Process resumed, stop duration: ${result.totalTime} min") | |||
| val result = productProcessService.resumeProcessAsDto(id, issueId) // 使用返回 DTO 的方法 | |||
| println(" Controller: Process resumed, stop duration: ${result.totalTime} min") | |||
| return result | |||
| } | |||
| @PostMapping("/{id}/complete") | |||
| fun completeProcess(@PathVariable id: Long): ProductProcessSimpleResponse { | |||
| println("✔️ Controller: POST /product-process/$id/complete") | |||
| val result = productProcessService.completeProcessAsDto(id) // ✅ 改用 AsDto 方法 | |||
| println("✅ Controller: Process completed") | |||
| val result = productProcessService.completeProcessAsDto(id) // 改用 AsDto 方法 | |||
| println(" Controller: Process completed") | |||
| return result | |||
| } | |||
| @@ -95,7 +95,7 @@ class ProductProcessController( | |||
| println("➕ Controller: POST /product-process/$id/lines") | |||
| println("📦 Controller: Line - name: ${request.name}, equipment: ${request.equipmentType}") | |||
| val result = productProcessService.addLine(id, request) | |||
| println("✅ Controller: Line added with ID: ${result.id}") | |||
| println(" Controller: Line added with ID: ${result.id}") | |||
| return result | |||
| } | |||
| @@ -103,10 +103,10 @@ class ProductProcessController( | |||
| fun updateLineOutput( | |||
| @PathVariable lineId: Long, | |||
| @RequestBody request: UpdateLineOutputRequest | |||
| ): ProductProcessLineResponse { // ✅ 改为 ProductProcessLineResponse | |||
| ): ProductProcessLineResponse { // 改为 ProductProcessLineResponse | |||
| println("📊 Controller: PUT /lines/$lineId/output") | |||
| val result = productProcessService.updateLineOutputAsDto(lineId, request) | |||
| println("✅ Controller: Line output updated") | |||
| println(" Controller: Line output updated") | |||
| return result | |||
| } | |||
| @@ -114,32 +114,32 @@ class ProductProcessController( | |||
| fun getLines(@PathVariable id: Long): List<ProductProcessLineResponse> { | |||
| println("📋 Controller: GET /product-process/$id/lines") | |||
| val result = productProcessService.getLinesAsDto(id) | |||
| println("✅ Controller: Found ${result.size} lines") | |||
| println(" Controller: Found ${result.size} lines") | |||
| return result | |||
| } | |||
| @GetMapping("/{id}/issues") | |||
| fun getIssues(@PathVariable id: Long): List<ProductionProcessIssueResponse> { // ✅ 改为 Issue Response List | |||
| fun getIssues(@PathVariable id: Long): List<ProductionProcessIssueResponse> { // 改为 Issue Response List | |||
| println("📋 Controller: GET /product-process/$id/issues") | |||
| val result = productProcessService.getIssuesAsDto(id) // ✅ 使用返回 DTO 的方法 | |||
| println("✅ Controller: Found ${result.size} issues") | |||
| val result = productProcessService.getIssuesAsDto(id) // 使用返回 DTO 的方法 | |||
| println(" Controller: Found ${result.size} issues") | |||
| return result | |||
| } | |||
| @GetMapping("/by-job-order/{jobOrderId}") | |||
| fun findByJobOrderId(@PathVariable jobOrderId: Long): List<ProductProcessWithLinesResponse> { // ✅ 改为返回 DTO | |||
| fun findByJobOrderId(@PathVariable jobOrderId: Long): List<ProductProcessWithLinesResponse> { // 改为返回 DTO | |||
| println("🔍 Controller: GET /product-process/by-job-order/$jobOrderId") | |||
| val result = productProcessService.findByJobOrderIdWithLines(jobOrderId) // ✅ 直接使用 with-lines 方法 | |||
| println("✅ Controller: Found ${result.size} processes for Job Order $jobOrderId") | |||
| val result = productProcessService.findByJobOrderIdWithLines(jobOrderId) // 直接使用 with-lines 方法 | |||
| println(" Controller: Found ${result.size} processes for Job Order $jobOrderId") | |||
| return result | |||
| } | |||
| // ✅ 额外添加:获取带 lines 的完整数据 | |||
| // 额外添加:获取带 lines 的完整数据 | |||
| @GetMapping("/by-job-order/{jobOrderId}/with-lines") | |||
| fun findByJobOrderIdWithLines(@PathVariable jobOrderId: Long): List<ProductProcessWithLinesResponse> { | |||
| println("🔍 Controller: GET /product-process/by-job-order/$jobOrderId/with-lines") | |||
| val result = productProcessService.findByJobOrderIdWithLines(jobOrderId) | |||
| println("✅ Controller: Found ${result.size} processes with lines for Job Order $jobOrderId") | |||
| println(" Controller: Found ${result.size} processes with lines for Job Order $jobOrderId") | |||
| return result | |||
| } | |||
| @@ -147,7 +147,7 @@ class ProductProcessController( | |||
| fun startLine(@PathVariable lineId: Long, @RequestParam userId: Long): ProductProcessLineResponse { | |||
| println("▶️ Controller: POST /product-process/lines/$lineId/start - userId: $userId") | |||
| val entity = productProcessService.startLine(lineId, userId) | |||
| // ✅ 修复:返回正确的 DTO | |||
| // 修复:返回正确的 DTO | |||
| return productProcessService.getLinesAsDto(entity.productProcess?.id!!).find { it.id == entity.id!! }!! | |||
| } | |||
| @@ -183,10 +183,10 @@ class ProductProcessController( | |||
| fun demoupdateqty(@PathVariable lineId: Long, @RequestBody request: UpdateProductProcessLineQtyRequest): MessageResponse { | |||
| return productProcessService.updateProductProcessLineQty(request) | |||
| } | |||
| /* | |||
| @GetMapping("/Demo/Process/all") | |||
| fun demoprocessall(): List<AllJoborderProductProcessInfoResponse> { | |||
| return productProcessService.getAllJoborderProductProcessInfo() | |||
| } | |||
| */ | |||
| } | |||
| @@ -27,8 +27,8 @@ data class ProductionProcessIssueResponse( | |||
| ) | |||
| data class SaveProductProcessResponse( | |||
| val id: Long, | |||
| val productProcessCode: String? = null, // ✅ 添加 | |||
| val linesCreated: Int = 0 // ✅ 添加 | |||
| val productProcessCode: String? = null, // 添加 | |||
| val linesCreated: Int = 0 // 添加 | |||
| ) | |||
| data class ProductProcessWithLinesResponse( | |||
| val id: Long, | |||
| @@ -142,22 +142,22 @@ data class AllJoborderProductProcessInfoResponse( | |||
| val endTime: LocalDateTime?, | |||
| val date: LocalDate?, | |||
| val bomId: Long?, | |||
| val bomName: String?, | |||
| val itemName: String?, | |||
| val jobOrderId: Long?, | |||
| val jobOrderCode: String?, | |||
| val productProcessLineCount: Int, | |||
| val FinishedProductProcessLineCount: Int, | |||
| val lines: List<ProductProcessInfoResponse> | |||
| ) | |||
| data class ProductProcessInfoResponse( | |||
| val id: Long, | |||
| val productProcessCode: String?, | |||
| val operatorId: Long?, | |||
| val operatorName: String?, | |||
| val equipmentId: Long?, | |||
| val equipmentName: String?, | |||
| val startTime: LocalDateTime?, | |||
| val endTime: LocalDateTime?, | |||
| val date: LocalDate?, | |||
| val status: String, | |||
| val bomId: Long?, | |||
| val bomName: String?, | |||
| val jobOrderId: Long? | |||
| ) | |||
| @@ -121,7 +121,7 @@ open class InventoryLotLineService( | |||
| val updatedLotLine = saveInventoryLotLine(updateRequest) | |||
| // ✅ ADD THIS: Update inventory table after lot line status change | |||
| // ADD THIS: Update inventory table after lot line status change | |||
| updateInventoryTable(updatedLotLine) | |||
| return MessageResponse( | |||
| @@ -134,7 +134,7 @@ open class InventoryLotLineService( | |||
| ) | |||
| } | |||
| // ✅ ADD THIS: New method to update inventory table | |||
| // ADD THIS: New method to update inventory table | |||
| private fun updateInventoryTable(inventoryLotLine: InventoryLotLine) { | |||
| try { | |||
| // Get the item ID from the inventory lot | |||
| @@ -97,7 +97,7 @@ val existingStockOutLine = stockOutLineRepository.findByPickOrderLineIdAndInvent | |||
| ) | |||
| if (existingStockOutLine.isNotEmpty()) { | |||
| // ✅ 如果已存在,返回 null 表示不需要创建 | |||
| // 如果已存在,返回 null 表示不需要创建 | |||
| return MessageResponse( | |||
| id = null, | |||
| name = "Stock out line already exists", | |||
| @@ -189,7 +189,7 @@ fun handleQc(stockOutLine: StockOutLine, request: UpdateStockOutLineRequest): Li | |||
| @Transactional | |||
| open fun createWithoutConso(request: CreateStockOutLineWithoutConsoRequest): MessageResponse { | |||
| try { | |||
| // ✅ Get stockOutId from pickOrderLineId with detailed error | |||
| // Get stockOutId from pickOrderLineId with detailed error | |||
| val stockOutId = getStockOutIdFromPickOrderLine(request.pickOrderLineId) | |||
| println("Found stockOutId: $stockOutId for pickOrderLineId: ${request.pickOrderLineId}") | |||
| @@ -253,16 +253,16 @@ open fun createWithoutConso(request: CreateStockOutLineWithoutConsoRequest): Mes | |||
| } | |||
| } | |||
| // ✅ Update helper method with detailed error messages | |||
| // Update helper method with detailed error messages | |||
| private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||
| println("Getting stockOutId for pickOrderLineId: $pickOrderLineId") | |||
| // ✅ Fixed: Use poId instead of pick_order_id | |||
| // Fixed: Use poId instead of pick_order_id | |||
| val sql = """ | |||
| SELECT so.id as stockOutId, so.consoPickOrderCode, po.consoCode | |||
| FROM stock_out so | |||
| JOIN pick_order po ON po.consoCode = so.consoPickOrderCode | |||
| JOIN pick_order_line pol ON pol.poId = po.id -- ✅ Fixed: Use poId | |||
| JOIN pick_order_line pol ON pol.poId = po.id -- Fixed: Use poId | |||
| WHERE pol.id = :pickOrderLineId | |||
| """.trimIndent() | |||
| @@ -569,7 +569,7 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||
| throw e | |||
| } | |||
| } | |||
| // ✅ ADD THIS: Handle lot rejection when stock out line is rejected | |||
| // ADD THIS: Handle lot rejection when stock out line is rejected | |||
| private fun handleLotRejectionFromStockOutLine(stockOutLine: StockOutLine) { | |||
| try { | |||
| println("=== HANDLING LOT REJECTION FROM STOCK OUT LINE ===") | |||
| @@ -612,7 +612,7 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||
| } | |||
| } | |||
| // ✅ ADD THIS: Update inventory table after lot rejection | |||
| // ADD THIS: Update inventory table after lot rejection | |||
| private fun updateInventoryTableAfterLotRejection(inventoryLotLine: InventoryLotLine) { | |||
| try { | |||
| println("=== UPDATING INVENTORY TABLE ===") | |||
| @@ -5,9 +5,9 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickOrder | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue // ✅ 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue // 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory | |||
| import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus // ✅ 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus // 添加 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository | |||
| import com.ffii.fpsms.modules.stock.entity.* | |||
| import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
| @@ -20,8 +20,8 @@ import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotResponse | |||
| import org.springframework.stereotype.Service | |||
| import java.math.BigDecimal | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime // ✅ 添加 | |||
| import java.time.format.DateTimeFormatter // ✅ 添加 | |||
| import java.time.LocalDateTime // 添加 | |||
| import java.time.format.DateTimeFormatter // 添加 | |||
| import kotlin.jvm.optionals.getOrDefault | |||
| import kotlin.jvm.optionals.getOrNull | |||
| import org.springframework.transaction.annotation.Transactional | |||
| @@ -46,7 +46,7 @@ open class SuggestedPickLotService( | |||
| val pickOrderLineRepository: PickOrderLineRepository, | |||
| val inventoryLotLineService: InventoryLotLineService, | |||
| val itemUomService: ItemUomService, | |||
| val pickExecutionIssueRepository: PickExecutionIssueRepository, // ✅ 添加逗号 | |||
| val pickExecutionIssueRepository: PickExecutionIssueRepository, // 添加逗号 | |||
| val pickOrderRepository: PickOrderRepository, | |||
| val inventoryRepository: InventoryRepository, | |||
| val failInventoryLotLineRepository: FailInventoryLotLineRepository, | |||
| @@ -114,13 +114,13 @@ open class SuggestedPickLotService( | |||
| val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() | |||
| val ratio = one // (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) | |||
| // ✅ FIX: Calculate remaining quantity needed (not the full required quantity) | |||
| // FIX: Calculate remaining quantity needed (not the full required quantity) | |||
| val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(line.id!!) | |||
| val totalPickedQty = stockOutLines | |||
| .filter { | |||
| it.status == "completed" || | |||
| it.status == "partially_completed" || | |||
| (it.status == "rejected" && (it.qty ?: zero) > zero) // ✅ 包含已 picked 的 rejected | |||
| (it.status == "rejected" && (it.qty ?: zero) > zero) // 包含已 picked 的 rejected | |||
| } | |||
| .sumOf { it.qty ?: zero } | |||
| val requiredQty = line.qty ?: zero | |||
| @@ -132,7 +132,7 @@ open class SuggestedPickLotService( | |||
| println("Remaining qty needed: $remainingQty") | |||
| println("Stock out lines: ${stockOutLines.map { "${it.id}(status=${it.status}, qty=${it.qty})" }}") | |||
| // ✅ FIX: Use remainingQty instead of line.qty | |||
| // FIX: Use remainingQty instead of line.qty | |||
| var remainingQtyToAllocate = remainingQty | |||
| println("remaining1 $remainingQtyToAllocate (sales units)") | |||
| val updatedLotLines = mutableListOf<InventoryLotLineInfo>() | |||
| @@ -142,7 +142,7 @@ open class SuggestedPickLotService( | |||
| println("calculateRemainingQtyForInfo(lotLine) ${calculateRemainingQtyForInfo(lotLine)}") | |||
| // ✅ 修复:计算可用数量,转换为销售单位 | |||
| // 修复:计算可用数量,转换为销售单位 | |||
| val availableQtyInBaseUnits = calculateRemainingQtyForInfo(lotLine) | |||
| val holdQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero | |||
| val availableQtyInSalesUnits = availableQtyInBaseUnits | |||
| @@ -159,11 +159,11 @@ open class SuggestedPickLotService( | |||
| val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } | |||
| val originalHoldQty = inventoryLotLine?.holdQty | |||
| // ✅ 修复:在销售单位中计算分配数量 | |||
| // 修复:在销售单位中计算分配数量 | |||
| val assignQtyInSalesUnits = minOf(availableQtyInSalesUnits, remainingQtyToAllocate) | |||
| remainingQtyToAllocate = remainingQtyToAllocate.minus(assignQtyInSalesUnits) | |||
| val newHoldQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero | |||
| // ✅ 修复:将销售单位转换为基础单位来更新 holdQty | |||
| // 修复:将销售单位转换为基础单位来更新 holdQty | |||
| val assignQtyInBaseUnits = assignQtyInSalesUnits.multiply(ratio) | |||
| holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQtyInBaseUnits) | |||
| @@ -171,10 +171,10 @@ open class SuggestedPickLotService( | |||
| type = SuggestedPickLotType.PICK_ORDER | |||
| suggestedLotLine = inventoryLotLine | |||
| pickOrderLine = line | |||
| qty = assignQtyInSalesUnits // ✅ 保存销售单位 | |||
| qty = assignQtyInSalesUnits // 保存销售单位 | |||
| } | |||
| } | |||
| // ✅ 修复:计算现有 suggestions 中 pending/checked 状态满足的数量 | |||
| // 修复:计算现有 suggestions 中 pending/checked 状态满足的数量 | |||
| var existingSatisfiedQty = BigDecimal.ZERO | |||
| // 查询现有的 suggestions 用于这个 pick order line | |||
| @@ -194,7 +194,7 @@ open class SuggestedPickLotService( | |||
| } | |||
| } | |||
| // ✅ 调整 remainingQtyToAllocate,减去已经通过现有 suggestions 满足的数量 | |||
| // 调整 remainingQtyToAllocate,减去已经通过现有 suggestions 满足的数量 | |||
| remainingQtyToAllocate = remainingQtyToAllocate.minus(existingSatisfiedQty) | |||
| println("Existing satisfied qty: $existingSatisfiedQty") | |||
| println("Adjusted remaining qty: $remainingQtyToAllocate") | |||
| @@ -209,7 +209,7 @@ open class SuggestedPickLotService( | |||
| type = SuggestedPickLotType.PICK_ORDER | |||
| suggestedLotLine = null | |||
| pickOrderLine = line | |||
| qty = remainingQtyToAllocate // ✅ 保存销售单位 | |||
| qty = remainingQtyToAllocate // 保存销售单位 | |||
| } | |||
| try { | |||
| /* | |||
| @@ -337,7 +337,7 @@ open class SuggestedPickLotService( | |||
| } | |||
| val savedStockOutLine = stockOutLIneRepository.saveAndFlush(stockOutLine) | |||
| println("✅ Created stock out line ID: ${savedStockOutLine.id} for suggestion ID: ${suggestion.id}") | |||
| println(" Created stock out line ID: ${savedStockOutLine.id} for suggestion ID: ${suggestion.id}") | |||
| return savedStockOutLine | |||
| @@ -357,7 +357,7 @@ open class SuggestedPickLotService( | |||
| println("Pick Order Code: ${pickOrder.code}") | |||
| println("Pick Order Status: ${pickOrder.status}") | |||
| // ✅ NEW: Get ALL pick orders for the same items | |||
| // NEW: Get ALL pick orders for the same items | |||
| val itemIds = pickOrder.pickOrderLines.mapNotNull { it.item?.id }.distinct() | |||
| println("Item IDs in current pick order: $itemIds") | |||
| @@ -373,7 +373,7 @@ open class SuggestedPickLotService( | |||
| allCompetingPickOrders.addAll(competingOrders) | |||
| } | |||
| // ✅ FIX: Only resuggest pick orders that have rejected stock out lines | |||
| // FIX: Only resuggest pick orders that have rejected stock out lines | |||
| val allPickOrdersToResuggest = (listOf(pickOrder) + allCompetingPickOrders) | |||
| .filter { pickOrderToCheck -> | |||
| // Only resuggest if the pick order has rejected stock out lines | |||
| @@ -397,7 +397,7 @@ open class SuggestedPickLotService( | |||
| println("Filtered pick orders to resuggest: ${allPickOrdersToResuggest.size}") | |||
| println("Pick orders being resuggested: ${allPickOrdersToResuggest.map { "${it.code}(${it.status})" }}") | |||
| // ✅ FIX: Only resuggest if there are actually pick orders with rejected lots | |||
| // FIX: Only resuggest if there are actually pick orders with rejected lots | |||
| if (allPickOrdersToResuggest.isEmpty()) { | |||
| println("No pick orders need resuggesting - no rejected lots found") | |||
| return MessageResponse( | |||
| @@ -410,21 +410,21 @@ open class SuggestedPickLotService( | |||
| ) | |||
| } | |||
| // ✅ FIX: Get all pick order line IDs for the orders to resuggest | |||
| // FIX: Get all pick order line IDs for the orders to resuggest | |||
| val allPickOrderLineIds = allPickOrdersToResuggest | |||
| .flatMap { it.pickOrderLines } | |||
| .mapNotNull { it.id } | |||
| println("All pick order line IDs to resuggest: $allPickOrderLineIds") | |||
| // ✅ FIX: Get all existing suggestions for these pick order lines | |||
| // FIX: Get all existing suggestions for these pick order lines | |||
| val allSuggestions = suggestedPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIds) | |||
| println("Found ${allSuggestions.size} existing suggestions") | |||
| // ✅ 删除第 376-395 行的旧代码,替换为: | |||
| // ✅ FIX: Separate suggestions to keep (those WITHOUT rejected stock out lines) and delete | |||
| // 删除第 376-395 行的旧代码,替换为: | |||
| // FIX: Separate suggestions to keep (those WITHOUT rejected stock out lines) and delete | |||
| val suggestionsToKeep = allSuggestions.filter { suggestion -> | |||
| val pickOrderLineId = suggestion.pickOrderLine?.id | |||
| val suggestedLotLineId = suggestion.suggestedLotLine?.id | |||
| @@ -434,14 +434,14 @@ val suggestionsToKeep = allSuggestions.filter { suggestion -> | |||
| pickOrderLineId, | |||
| suggestedLotLineId | |||
| ) | |||
| // ✅ 保留没有 rejected stock out lines 的 suggestions | |||
| // 保留没有 rejected stock out lines 的 suggestions | |||
| !stockOutLines.any { it.status == "rejected" } | |||
| } else { | |||
| true // 保留有问题的 suggestions 用于调试 | |||
| } | |||
| } | |||
| // ✅ 只删除有 rejected stock out lines 的 suggestions | |||
| // 只删除有 rejected stock out lines 的 suggestions | |||
| val suggestionsToDelete = allSuggestions.filter { suggestion -> | |||
| val pickOrderLineId = suggestion.pickOrderLine?.id | |||
| val suggestedLotLineId = suggestion.suggestedLotLine?.id | |||
| @@ -451,7 +451,7 @@ val suggestionsToDelete = allSuggestions.filter { suggestion -> | |||
| pickOrderLineId, | |||
| suggestedLotLineId | |||
| ) | |||
| stockOutLines.any { it.status == "rejected" } // ✅ 只删除 rejected 的 | |||
| stockOutLines.any { it.status == "rejected" } // 只删除 rejected 的 | |||
| } else { | |||
| suggestedLotLineId == null | |||
| } | |||
| @@ -461,7 +461,7 @@ val suggestionsToDelete = allSuggestions.filter { suggestion -> | |||
| println("Suggestions to keep (with rejected stock out lines): ${suggestionsToKeep.size}") | |||
| println("Suggestions to delete: ${suggestionsToDelete.size}") | |||
| // ✅ FIX: Clear holdQty ONLY for lots that have rejected stock out lines | |||
| // FIX: Clear holdQty ONLY for lots that have rejected stock out lines | |||
| val rejectedLotIds = suggestionsToDelete.mapNotNull { it.suggestedLotLine?.id }.distinct() | |||
| println("Rejected lot IDs (clearing holdQty only): $rejectedLotIds") | |||
| @@ -477,7 +477,7 @@ val suggestionsToDelete = allSuggestions.filter { suggestion -> | |||
| println("Keeping all suggestions (including rejected ones for display)") | |||
| // ✅ NEW: Build holdQtyMap with existing holdQty from other pick orders | |||
| // NEW: Build holdQtyMap with existing holdQty from other pick orders | |||
| val existingHoldQtyMap = mutableMapOf<Long?, BigDecimal?>() | |||
| // Get all lots that are being used by other pick orders (including those NOT being resuggested) | |||
| @@ -507,9 +507,9 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| println("Final existing holdQtyMap: $existingHoldQtyMap") | |||
| // ✅ FIX: Create new suggestions for all pick orders to resuggest | |||
| // FIX: Create new suggestions for all pick orders to resuggest | |||
| allPickOrdersToResuggest.forEach { pickOrderToResuggest -> | |||
| // ✅ 只获取有 rejected stock out lines 的 pick order lines | |||
| // 只获取有 rejected stock out lines 的 pick order lines | |||
| val problematicPickOrderLines = pickOrderToResuggest.pickOrderLines.filter { pol -> | |||
| val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pol.id!!) | |||
| stockOutLines.any { it.status == "rejected" } | |||
| @@ -518,7 +518,7 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| if (problematicPickOrderLines.isNotEmpty()) { | |||
| println("=== Creating new suggestions for pick order: ${pickOrderToResuggest.code} ===") | |||
| // ✅ 调用 suggestionForPickOrderLines 生成新的 suggestions | |||
| // 调用 suggestionForPickOrderLines 生成新的 suggestions | |||
| val request = SuggestedPickLotForPolRequest( | |||
| pickOrderLines = problematicPickOrderLines, | |||
| holdQtyMap = existingHoldQtyMap.toMutableMap() | |||
| @@ -533,24 +533,24 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| if (response.suggestedList.isNotEmpty()) { | |||
| println("Saving ${response.suggestedList.size} new suggestions") | |||
| // ✅ 获取现有的 pending/checked 状态的 suggestions(可以更新的) | |||
| // 获取现有的 pending/checked 状态的 suggestions(可以更新的) | |||
| val existingUpdatableSuggestions = suggestionsToKeep | |||
| .filter { it.suggestedLotLine?.id != null } | |||
| .groupBy { it.pickOrderLine?.id to it.suggestedLotLine?.id } | |||
| .mapValues { it.value.first() } // 每个 (lineId, lotId) 只取第一个 | |||
| // ✅ 处理新的 suggestions:更新现有的或创建新的 | |||
| // 处理新的 suggestions:更新现有的或创建新的 | |||
| val suggestionsToSave = response.suggestedList.mapNotNull { newSugg -> | |||
| val key = newSugg.pickOrderLine?.id to newSugg.suggestedLotLine?.id | |||
| val lineId = newSugg.pickOrderLine?.id | |||
| val lotId = newSugg.suggestedLotLine?.id | |||
| if (lineId != null && lotId != null) { | |||
| // ✅ 检查这个 lot 是否已有 suggestion | |||
| // 检查这个 lot 是否已有 suggestion | |||
| val existingSugg = existingUpdatableSuggestions[key] | |||
| if (existingSugg != null) { | |||
| // ✅ 检查现有 suggestion 的 stock_out_line 状态 | |||
| // 检查现有 suggestion 的 stock_out_line 状态 | |||
| val stockOutLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( | |||
| lineId, lotId | |||
| ) | |||
| @@ -560,23 +560,23 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| } | |||
| if (canUpdate) { | |||
| // ✅ Case 1: 更新现有的 suggestion | |||
| // Case 1: 更新现有的 suggestion | |||
| existingSugg.qty = newSugg.qty | |||
| existingSugg.modified = LocalDateTime.now() | |||
| existingSugg.modifiedBy = "system" | |||
| println("⚠️ Updated existing suggestion ${existingSugg.id} for lot $lotId: new qty=${newSugg.qty}") | |||
| existingSugg | |||
| } else { | |||
| // ✅ Case 2: 已完成/拒绝,跳过(不更新,也不创建新的) | |||
| // Case 2: 已完成/拒绝,跳过(不更新,也不创建新的) | |||
| println("⏭️ Skipping lot $lotId - already ${stockOutLines.first().status}") | |||
| null | |||
| } | |||
| } else { | |||
| // ✅ 没有现有的 suggestion,创建新的 | |||
| // 没有现有的 suggestion,创建新的 | |||
| newSugg | |||
| } | |||
| } else if (lotId == null) { | |||
| // ✅ lotId=null:检查是否已有 resuggest_issue | |||
| // lotId=null:检查是否已有 resuggest_issue | |||
| val existingResuggestIssues = pickExecutionIssueRepository | |||
| .findByPickOrderLineIdAndDeletedFalse(lineId ?: 0L) | |||
| .filter { it.issueCategory.name == "resuggest_issue" } | |||
| @@ -601,20 +601,20 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| if (updatedSuggestions.isNotEmpty()) { | |||
| val savedUpdated = suggestedPickLotRepository.saveAllAndFlush(updatedSuggestions) | |||
| allSavedSuggestions.addAll(savedUpdated) | |||
| println("✅ Updated ${savedUpdated.size} existing suggestions") | |||
| println(" Updated ${savedUpdated.size} existing suggestions") | |||
| } | |||
| // 保存新的 suggestions | |||
| if (newSuggestions.isNotEmpty()) { | |||
| val savedNew = suggestedPickLotRepository.saveAllAndFlush(newSuggestions) | |||
| allSavedSuggestions.addAll(savedNew) | |||
| println("✅ Created ${savedNew.size} new suggestions") | |||
| println(" Created ${savedNew.size} new suggestions") | |||
| } | |||
| val savedSuggestions = allSavedSuggestions | |||
| println("Saved/Updated ${savedSuggestions.size} suggestions") | |||
| // ✅ 为每个新 suggestion 创建 stock out line 或 issue | |||
| // 为每个新 suggestion 创建 stock out line 或 issue | |||
| savedSuggestions.forEach { suggestion -> | |||
| if (suggestion.suggestedLotLine != null) { | |||
| val isNewSuggestion = response.suggestedList.any { | |||
| @@ -624,10 +624,10 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| } | |||
| val stockOutLine = createStockOutLineForSuggestion(suggestion, pickOrderToResuggest) | |||
| if (stockOutLine != null) { | |||
| println("✅ Created stock out line ${stockOutLine.id} for suggestion ${suggestion.id}") | |||
| println(" Created stock out line ${stockOutLine.id} for suggestion ${suggestion.id}") | |||
| } | |||
| } else { | |||
| // ✅ 如果 lot 是 null,表示没有可用的 lot,创建 resuggest_issue | |||
| // 如果 lot 是 null,表示没有可用的 lot,创建 resuggest_issue | |||
| println("❌ No available lot for pick order line ${suggestion.pickOrderLine?.id}, creating resuggest_issue") | |||
| val pickOrderLine = suggestion.pickOrderLine | |||
| @@ -637,7 +637,7 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| .findAllByPickOrderLineIdAndDeletedFalse(pickOrderLine.id!!) | |||
| .filter { it.status == "rejected" } | |||
| println("Rejected stock out lines: ${rejectedStockOutLines.size}") | |||
| // ✅ 修复:只创建一个 resuggest_issue(如果有 rejected lines) | |||
| // 修复:只创建一个 resuggest_issue(如果有 rejected lines) | |||
| if (rejectedStockOutLines.isNotEmpty()) { | |||
| println("Creating resuggest failure issue") | |||
| createResuggestFailureIssue( | |||
| @@ -653,7 +653,7 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| } | |||
| } | |||
| // ✅ 更新 holdQty | |||
| // 更新 holdQty | |||
| response.holdQtyMap.forEach { (lotId, newHoldQty) -> | |||
| if (lotId != null && newHoldQty != null && newHoldQty > BigDecimal.ZERO) { | |||
| val lot = inventoryLotLineRepository.findById(lotId).orElse(null) | |||
| @@ -670,7 +670,7 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| } | |||
| } | |||
| } else { | |||
| // ✅ 如果完全没有生成任何 suggestions | |||
| // 如果完全没有生成任何 suggestions | |||
| println("No suggestions generated at all for pick order: ${pickOrderToResuggest.code}") | |||
| problematicPickOrderLines.forEach { pickOrderLine -> | |||
| @@ -693,7 +693,7 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| } | |||
| } | |||
| } | |||
| // ✅ FIX: Update inventory table for each pick order | |||
| // FIX: Update inventory table for each pick order | |||
| allPickOrdersToResuggest.forEach { pickOrderToUpdate -> | |||
| println("=== Updating inventory table for pick order: ${pickOrderToUpdate.code} ===") | |||
| updateInventoryTableAfterResuggest(pickOrderToUpdate) | |||
| @@ -727,12 +727,12 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| private fun createResuggestFailureIssue( | |||
| pickOrder: PickOrder, | |||
| pickOrderLine: PickOrderLine, | |||
| rejectedStockOutLine: StockOutLineInfo // ✅ 使用 StockOutLineInfo | |||
| rejectedStockOutLine: StockOutLineInfo // 使用 StockOutLineInfo | |||
| ) { | |||
| try { | |||
| val item = pickOrderLine.item | |||
| // ✅ 从 StockOutLineInfo 获取 inventoryLotLineId | |||
| // 从 StockOutLineInfo 获取 inventoryLotLineId | |||
| val inventoryLotLineId = rejectedStockOutLine.inventoryLotLineId | |||
| val inventoryLotLine = if (inventoryLotLineId != null) { | |||
| inventoryLotLineRepository.findById(inventoryLotLineId).orElse(null) | |||
| @@ -758,8 +758,8 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| lotNo = inventoryLotLine?.inventoryLot?.lotNo, | |||
| storeLocation = inventoryLotLine?.warehouse?.name, | |||
| requiredQty = pickOrderLine.qty, | |||
| actualPickQty = rejectedStockOutLine.qty ?: BigDecimal.ZERO, // ✅ 直接使用,不需要 toBigDecimal() | |||
| missQty = (pickOrderLine.qty ?: BigDecimal.ZERO).minus(rejectedStockOutLine.qty ?: BigDecimal.ZERO), // ✅ 直接使用 | |||
| actualPickQty = rejectedStockOutLine.qty ?: BigDecimal.ZERO, // 直接使用,不需要 toBigDecimal() | |||
| missQty = (pickOrderLine.qty ?: BigDecimal.ZERO).minus(rejectedStockOutLine.qty ?: BigDecimal.ZERO), // 直接使用 | |||
| badItemQty = BigDecimal.ZERO, | |||
| issueRemark = "Resuggest failed: No alternative lots available for rejected lot ${inventoryLotLine?.inventoryLot?.lotNo}", | |||
| pickerName = null, | |||
| @@ -775,7 +775,7 @@ println("Keeping all suggestions (including rejected ones for display)") | |||
| ) | |||
| pickExecutionIssueRepository.save(issue) | |||
| println("✅ Created resuggest_issue: ${issue.issueNo} for pick order ${pickOrder.code}") | |||
| println(" Created resuggest_issue: ${issue.issueNo} for pick order ${pickOrder.code}") | |||
| } catch (e: Exception) { | |||
| println("❌ Error creating resuggest_issue: ${e.message}") | |||
| @@ -849,16 +849,16 @@ private fun generateOptimalSuggestionsForAllPickOrders( | |||
| .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } | |||
| .sortedBy { it.expiryDate } | |||
| // ✅ FIX: Get fresh lot data and reset holdQty to 0 for calculation | |||
| // FIX: Get fresh lot data and reset holdQty to 0 for calculation | |||
| val lotEntities = inventoryLotLineRepository.findAllByIdIn(availableLots.mapNotNull { it.id }) | |||
| lotEntities.forEach { lot -> lot.holdQty = BigDecimal.ZERO } | |||
| // ✅ FIX: Calculate remaining quantity for each pick order line | |||
| // ✅ FIX: Calculate remaining quantity for each pick order line | |||
| // FIX: Calculate remaining quantity for each pick order line | |||
| // FIX: Calculate remaining quantity for each pick order line | |||
| val remainingQtyPerLine = pickOrderLines.associate { pol -> | |||
| val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pol.id!!) | |||
| // ✅ FIX: Count picked qty from ALL statuses except 'rejected' | |||
| // FIX: Count picked qty from ALL statuses except 'rejected' | |||
| // This includes 'completed', 'pending', 'checked', 'partially_completed' | |||
| val totalPickedQty = stockOutLines | |||
| .sumOf { it.qty ?: BigDecimal.ZERO } | |||
| @@ -872,7 +872,7 @@ private fun generateOptimalSuggestionsForAllPickOrders( | |||
| pol.id to remainingQty | |||
| }.toMutableMap() | |||
| // ✅ FIX: Filter out pick order lines that don't need more qty | |||
| // FIX: Filter out pick order lines that don't need more qty | |||
| val remainingPickOrderLines = pickOrderLines.filter { pol -> | |||
| val remainingQty = remainingQtyPerLine[pol.id] ?: zero | |||
| remainingQty > zero | |||
| @@ -915,7 +915,7 @@ private fun generateOptimalSuggestionsForAllPickOrders( | |||
| } | |||
| } | |||
| // ✅ FIX: Create insufficient stock suggestions for remaining quantities | |||
| // FIX: Create insufficient stock suggestions for remaining quantities | |||
| remainingQtyPerLine.forEach { (lineId, remainingQty) -> | |||
| if (remainingQty > zero) { | |||
| val pickOrderLine = pickOrderLines.find { it.id == lineId } | |||
| @@ -939,11 +939,11 @@ private fun updateInventoryTableAfterResuggest(pickOrder: PickOrder) { | |||
| val itemIds = pickOrder.pickOrderLines.mapNotNull { it.item?.id }.distinct() | |||
| itemIds.forEach { itemId -> | |||
| // ✅ FIX: Calculate onHoldQty for ALL pick orders that use this item, not just the current one | |||
| // FIX: Calculate onHoldQty for ALL pick orders that use this item, not just the current one | |||
| val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE) | |||
| .sumOf { it.holdQty ?: BigDecimal.ZERO } | |||
| // ✅ FIX: Use enum method instead of string method | |||
| // FIX: Use enum method instead of string method | |||
| val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.UNAVAILABLE) | |||
| .sumOf { | |||
| val inQty = it.inQty ?: BigDecimal.ZERO | |||
| @@ -1110,17 +1110,17 @@ private fun generateCorrectSuggestionsWithExistingHolds(pickOrder: PickOrder): L | |||
| val salesUnit = itemUomService.findSalesUnitByItemId(itemId) | |||
| val ratio = one | |||
| // ✅ FIX: Get ALL inventory lots (both available and unavailable) | |||
| // FIX: Get ALL inventory lots (both available and unavailable) | |||
| val allLots = inventoryLotLineService | |||
| .allInventoryLotLinesByItemIdIn(listOf(itemId)) | |||
| .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } | |||
| .sortedBy { it.expiryDate } | |||
| // ✅ FIX: Separate available and unavailable lots | |||
| // FIX: Separate available and unavailable lots | |||
| val availableLots = allLots.filter { it.status == InventoryLotLineStatus.AVAILABLE.value } | |||
| val unavailableLots = allLots.filter { it.status == InventoryLotLineStatus.UNAVAILABLE.value } | |||
| // ✅ FIX: Calculate total quantity that was previously held by unavailable lots | |||
| // FIX: Calculate total quantity that was previously held by unavailable lots | |||
| var totalUnavailableHoldQty = BigDecimal.ZERO | |||
| val modifiedUnavailableLots = mutableListOf<InventoryLotLine>() | |||
| @@ -1128,19 +1128,19 @@ private fun generateCorrectSuggestionsWithExistingHolds(pickOrder: PickOrder): L | |||
| val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) } | |||
| lot?.let { | |||
| totalUnavailableHoldQty = totalUnavailableHoldQty.plus(it.holdQty ?: zero) | |||
| // ✅ Reset holdQty for unavailable lots | |||
| // Reset holdQty for unavailable lots | |||
| it.holdQty = BigDecimal.ZERO | |||
| modifiedUnavailableLots.add(it) // ✅ Keep reference to modified entity | |||
| modifiedUnavailableLots.add(it) // Keep reference to modified entity | |||
| } | |||
| } | |||
| // ✅ FIX: Save the modified entities (not fresh ones from database) | |||
| // FIX: Save the modified entities (not fresh ones from database) | |||
| if (modifiedUnavailableLots.isNotEmpty()) { | |||
| inventoryLotLineRepository.saveAll(modifiedUnavailableLots) | |||
| println("Reset holdQty for ${modifiedUnavailableLots.size} unavailable lots") | |||
| } | |||
| // ✅ FIX: Add the unavailable hold quantity to the required quantity | |||
| // FIX: Add the unavailable hold quantity to the required quantity | |||
| //val totalRequiredQty = requiredQty.plus(totalUnavailableHoldQty.divide(ratio, 2, RoundingMode.HALF_UP)) | |||
| val totalRequiredQty = requiredQty | |||
| var remainingQtyInSalesUnits = totalRequiredQty | |||
| @@ -1222,7 +1222,7 @@ private fun generateCorrectSuggestionsWithOriginalHolds( | |||
| .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } | |||
| .sortedBy { it.expiryDate } | |||
| // ✅ Calculate total quantity that needs to be redistributed | |||
| // Calculate total quantity that needs to be redistributed | |||
| var totalRedistributeQty = requiredQty | |||
| originalHoldQtyMap.forEach { (lotId, originalHoldQty) -> | |||
| if (originalHoldQty > zero) { | |||
| @@ -1478,7 +1478,7 @@ open fun updateSuggestedLotLineId(suggestedPickLotId: Long, newLotLineId: Long): | |||
| suggestedPickLot.suggestedLotLine = newInventoryLotLine | |||
| val savedSuggestedPickLot = suggestedPickLotRepository.save(suggestedPickLot) | |||
| println("✅ Successfully updated suggested pick lot ${suggestedPickLotId} to use lot line ${newLotLineId}") | |||
| println(" Successfully updated suggested pick lot ${suggestedPickLotId} to use lot line ${newLotLineId}") | |||
| return MessageResponse( | |||
| id = savedSuggestedPickLot.id, | |||
| @@ -1509,7 +1509,7 @@ private fun createInsufficientStockIssue( | |||
| insufficientQty: BigDecimal | |||
| ) { | |||
| try { | |||
| // ✅ 检查是否已存在相同的 issue(避免重复创建) | |||
| // 检查是否已存在相同的 issue(避免重复创建) | |||
| val existingIssues = pickExecutionIssueRepository | |||
| .findByPickOrderLineIdAndDeletedFalse(pickOrderLine.id ?: 0L) | |||
| .filter { | |||
| @@ -1557,7 +1557,7 @@ private fun createInsufficientStockIssue( | |||
| ) | |||
| pickExecutionIssueRepository.save(issue) | |||
| println("✅ Auto-created issue ${issue.issueNo} for insufficient stock (line ${pickOrderLine.id}, qty ${insufficientQty})") | |||
| println(" Auto-created issue ${issue.issueNo} for insufficient stock (line ${pickOrderLine.id}, qty ${insufficientQty})") | |||
| } catch (e: Exception) { | |||
| println("❌ Error creating insufficient stock issue: ${e.message}") | |||
| @@ -89,7 +89,7 @@ class InventoryLotLineController ( | |||
| try { | |||
| val result = inventoryLotLineService.updateInventoryLotLineStatus(request) | |||
| println("✅ Controller: Update successful - $result") | |||
| println(" Controller: Update successful - $result") | |||
| return result | |||
| } catch (e: Exception) { | |||
| println("❌ Controller: Update failed - ${e.message}") | |||