| @@ -12,7 +12,7 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | |||
| import org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import java.time.LocalDate | |||
| import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus | |||
| import java.time.LocalDateTime | |||
| @@ -48,6 +48,7 @@ import com.ffii.fpsms.modules.jobOrder.web.model.LotDetailResponse | |||
| import com.ffii.fpsms.modules.stock.entity.enum.StockInLineStatus | |||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | |||
| import java.time.LocalDate | |||
| @Service | |||
| open class JoPickOrderService( | |||
| private val joPickOrderRepository: JoPickOrderRepository, | |||
| @@ -1305,50 +1306,75 @@ open fun getCompletedJobOrderPickOrdersWithCompletedSecondScan(): List<Map<Strin | |||
| emptyList() | |||
| } | |||
| } | |||
| open fun getJobOrderListForPrintQrCode(): List<JobOrderListForPrintQrCodeResponse> { | |||
| println("=== getJobOrderListForPrintQrCode ===") | |||
| return try { | |||
| val jobOrders = jobOrderRepository.findAllByStatusIn(listOf(JobOrderStatus.COMPLETED)) | |||
| // Get all stockInLines and filter for "received" status and BOM type "半成品" | |||
| val allStockInLines = stockInLineRepository.findAll() | |||
| val stockInLines = allStockInLines.filter { | |||
| it.jobOrder != null && | |||
| it.status == "received" && | |||
| it.deleted == false && | |||
| it.jobOrder?.bom?.description == "半成品" | |||
| } | |||
| val result = mutableListOf<JobOrderListForPrintQrCodeResponse>() | |||
| for (stockInLine in stockInLines) { | |||
| val jobOrder = jobOrders.find { it.id == stockInLine.jobOrder?.id } | |||
| if (jobOrder != null && jobOrder.bom?.description == "半成品") { | |||
| // Check if this job order is already in the result (to avoid duplicates) | |||
| val existing = result.find { | |||
| it.stockInLineId == stockInLine.id | |||
| } | |||
| open fun getJobOrderListForPrintQrCode(date: LocalDate): List<JobOrderListForPrintQrCodeResponse> { | |||
| println("=== getJobOrderListForPrintQrCode ===") | |||
| println("Filtering by receiptDate: $date") | |||
| return try { | |||
| // 1. 根据 receiptDate 日期过滤 StockInLine(在数据库层面过滤) | |||
| val allStockInLines = stockInLineRepository.findByReceiptDateAndDeletedFalse(date) | |||
| println("📦 Found ${allStockInLines.size} StockInLines with receiptDate = $date") | |||
| println("📦 All StockInLines: ${allStockInLines.map { it.id }}, ${allStockInLines.map { it.jobOrder?.id }}") | |||
| println("📦 All StockInLines: ${allStockInLines.map { it.status }}, ${allStockInLines.map { it.jobOrder?.bom?.description }}") | |||
| // 2. 进一步过滤:status = "received" 或 "completed",且 BOM type = "WIP" 或 "FG" | |||
| val stockInLines = allStockInLines.filter { | |||
| it.jobOrder != null && | |||
| it.status == "received" && // 只显示 QC 完成但尚未 putaway 的 | |||
| it.jobOrder?.bom?.description in listOf("WIP", "FG") | |||
| } | |||
| println("✅ After filtering: ${stockInLines.size} StockInLines") | |||
| if (existing == null) { | |||
| val response = JobOrderListForPrintQrCodeResponse( | |||
| id = stockInLine.id!!, | |||
| code = jobOrder.code ?: "JO-${jobOrder.id}", | |||
| name = jobOrder.bom?.name ?: "", | |||
| reqQty = jobOrder.reqQty ?: BigDecimal.ZERO, | |||
| stockInLineId = stockInLine.id!!, | |||
| stockInLineQty = stockInLine.acceptedQty?.toDouble() ?: 0.0, | |||
| stockInLineStatus = stockInLine.status ?: "received", | |||
| finihedTime = jobOrder.planEnd ?: jobOrder.modified ?: LocalDateTime.now() | |||
| ) | |||
| result.add(response) | |||
| } | |||
| // 3. 获取相关的 JobOrder IDs | |||
| val jobOrderIds = stockInLines.mapNotNull { it.jobOrder?.id }.distinct() | |||
| println("📋 JobOrder IDs to check: $jobOrderIds") | |||
| // 4. 批量加载 JobOrders(只加载需要的,且状态为 COMPLETED) | |||
| val jobOrders = if (jobOrderIds.isNotEmpty()) { | |||
| jobOrderRepository.findAllById(jobOrderIds) | |||
| .filter { it.status == JobOrderStatus.COMPLETED } | |||
| } else { | |||
| emptyList() | |||
| } | |||
| println("✅ Completed JobOrders: ${jobOrders.map { "${it.id}(${it.status})" }}") | |||
| // 5. 构建响应 | |||
| val result = mutableListOf<JobOrderListForPrintQrCodeResponse>() | |||
| for (stockInLine in stockInLines) { | |||
| val jobOrder = jobOrders.find { it.id == stockInLine.jobOrder?.id } | |||
| if (jobOrder != null && jobOrder.bom?.description in listOf("WIP", "FG")) { | |||
| // Check if this job order is already in the result (to avoid duplicates) | |||
| val existing = result.find { | |||
| it.stockInLineId == stockInLine.id | |||
| } | |||
| if (existing == null) { | |||
| val response = JobOrderListForPrintQrCodeResponse( | |||
| id = stockInLine.id!!, | |||
| code = jobOrder.code ?: "JO-${jobOrder.id}", | |||
| name = jobOrder.bom?.name ?: "", | |||
| reqQty = jobOrder.reqQty ?: BigDecimal.ZERO, | |||
| stockInLineId = stockInLine.id!!, | |||
| stockInLineQty = stockInLine.acceptedQty?.toDouble() ?: 0.0, | |||
| stockInLineStatus = stockInLine.status ?: "received", | |||
| finihedTime = jobOrder.planEnd ?: jobOrder.modified ?: LocalDateTime.now() | |||
| ) | |||
| result.add(response) | |||
| println("✅ Added StockInLine ${stockInLine.id} (JobOrder ${jobOrder.id}, BOM: ${jobOrder.bom?.description})") | |||
| } | |||
| } else { | |||
| println("❌ StockInLine ${stockInLine.id}: JobOrder ${stockInLine.jobOrder?.id} not found or BOM type not WIP/FG") | |||
| } | |||
| result | |||
| } catch (e: Exception) { | |||
| println("❌ Error in getJobOrderListForPrintQrCode: ${e.message}") | |||
| e.printStackTrace() | |||
| emptyList() | |||
| } | |||
| println("📊 Final result: ${result.size} items") | |||
| result | |||
| } catch (e: Exception) { | |||
| println("❌ Error in getJobOrderListForPrintQrCode: ${e.message}") | |||
| e.printStackTrace() | |||
| emptyList() | |||
| } | |||
| } | |||
| open fun getCompletedJobOrderPickOrderLotDetails(pickOrderId: Long): List<Map<String, Any?>> { | |||
| println("=== getCompletedJobOrderPickOrderLotDetails ===") | |||
| println("pickOrderId: $pickOrderId") | |||
| @@ -40,7 +40,7 @@ import com.ffii.fpsms.modules.jobOrder.web.model.UpdateJoPickOrderHandledByReque | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportFGStockInLabelRequest | |||
| import java.time.LocalDate | |||
| @RestController | |||
| @RequestMapping("/jo") | |||
| @@ -231,9 +231,9 @@ fun recordSecondScanIssue( | |||
| fun getCompletedJobOrderPickOrders(): List<Map<String, Any?>> { | |||
| return joPickOrderService.getCompletedJobOrderPickOrders() | |||
| } | |||
| @GetMapping("/joForPrintQrCode") | |||
| fun getJoForPrintQrCode(): List<JobOrderListForPrintQrCodeResponse> { | |||
| return joPickOrderService.getJobOrderListForPrintQrCode() | |||
| @GetMapping("/joForPrintQrCode/{date}") | |||
| fun getJoForPrintQrCode(@PathVariable date: String): List<JobOrderListForPrintQrCodeResponse> { | |||
| return joPickOrderService.getJobOrderListForPrintQrCode(LocalDate.parse(date)) | |||
| } | |||
| @GetMapping("/completed-job-order-pick-order-lot-details-completed-pick/{pickOrderId}") | |||
| @@ -47,6 +47,7 @@ import java.time.ZoneOffset | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | |||
| import com.ffii.fpsms.modules.master.entity.UomConversionRepository | |||
| import com.ffii.fpsms.modules.master.entity.ItemUomRespository | |||
| @Service | |||
| @Transactional | |||
| open class ProductProcessService( | |||
| @@ -71,6 +72,7 @@ open class ProductProcessService( | |||
| private val pickOrderRepository: PickOrderRepository, | |||
| private val joPickOrderRepository: JoPickOrderRepository, | |||
| private val uomConversionRepository: UomConversionRepository, | |||
| private val itemUomRepository: ItemUomRespository, | |||
| ) { | |||
| open fun findAll(pageable: Pageable): Page<ProductProcess> { | |||
| @@ -782,7 +784,7 @@ open class ProductProcessService( | |||
| code = "400", | |||
| name = "Equipment Validation Failed", | |||
| type = "error", | |||
| message = "Input Equipment ID($equipmentId) and BOM Process Equipment ID(${bomProcessEquipment?.id}) not match", | |||
| message = "Input Equipment is not match with prcess", | |||
| errorPosition = "equipmentId" | |||
| ) | |||
| } | |||
| @@ -824,7 +826,7 @@ open class ProductProcessService( | |||
| code = "400", | |||
| name = "User Required", | |||
| type = "error", | |||
| message = "Staff No is required when equipment is provided", | |||
| message = "Staff No is required", | |||
| errorPosition = "staffNo" | |||
| ) | |||
| } | |||
| @@ -839,7 +841,7 @@ open class ProductProcessService( | |||
| code = "400", | |||
| name = "Equipment Validation Failed", | |||
| type = "error", | |||
| message = "Input Equipment ID(${equipmentDetail.equipmentTypeId}) and BOM Process Equipment ID(${bomProcessEquipment?.id}) not match", | |||
| message = "Input Equipment is not match with process", | |||
| errorPosition = "equipmentId" | |||
| ) | |||
| } | |||
| @@ -855,7 +857,22 @@ open class ProductProcessService( | |||
| errorPosition = "staffNo" | |||
| ) | |||
| } | |||
| if (user != null && productProcessLine.operator != null) { | |||
| val existingOperatorId = productProcessLine.operator?.id | |||
| val newOperatorId = user.id | |||
| // 如果不是同一个用户,拒绝更新 | |||
| if (existingOperatorId != null && existingOperatorId != newOperatorId) { | |||
| return MessageResponse( | |||
| id = request.productProcessLineId, | |||
| code = "409", | |||
| name = "Conflict", | |||
| type = "error", | |||
| message = "This process line is already assigned to another operator", | |||
| errorPosition = "operator" | |||
| ) | |||
| } | |||
| } | |||
| // ===== 通过校验,开始更新 ===== | |||
| // 设备有(且已通过匹配校验) → 更新 equipment / equipmentDetailId | |||
| @@ -1082,7 +1099,8 @@ open class ProductProcessService( | |||
| val stockInLineId = stockInLine?.id | |||
| val pickOrder = pickOrderRepository.findAllByJobOrder_Id(jobOrder?.id?:0L).firstOrNull() | |||
| val joPickOrdersList = joPickOrderRepository.findByPickOrderId(pickOrder?.id?:0L) | |||
| val itemUom = itemUomRepository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(productProcesses.item?.id?:0L) | |||
| val bomUom = uomConversionRepository.findById(itemUom?.uom?.id?:0L).orElse(null) | |||
| //val silHandlerId = stockInLine?.escalationLog?.firstOrNull { it.status == "pending" }?.handler?.id | |||
| AllJoborderProductProcessInfoResponse( | |||
| @@ -1100,10 +1118,12 @@ open class ProductProcessService( | |||
| "pending" | |||
| }, | |||
| RequiredQty = jobOrder?.reqQty?.toInt() ?: 0, | |||
| Uom = bomUom?.udfShortDesc, | |||
| date = productProcesses.date, | |||
| bomId = productProcesses.bom?.id, | |||
| assignedTo = pickOrder?.assignTo?.id, | |||
| itemName = productProcesses.item?.name, | |||
| itemCode = productProcesses.item?.code, | |||
| pickOrderId = pickOrder?.id, | |||
| pickOrderStatus = pickOrder?.status?.value, | |||
| jobOrderId = productProcesses.jobOrder?.id, | |||
| @@ -161,8 +161,10 @@ data class AllJoborderProductProcessInfoResponse( | |||
| val date: LocalDate?, | |||
| val bomId: Long?, | |||
| val itemName: String?, | |||
| val itemCode: String?, | |||
| val matchStatus: String?, | |||
| val RequiredQty: Int?, | |||
| val Uom: String?, | |||
| val jobOrderId: Long?, | |||
| val jobOrderCode: String?, | |||
| val assignedTo: Long?, | |||
| @@ -6,7 +6,7 @@ import com.ffii.fpsms.modules.stock.entity.projection.StockInLineInfo | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.util.Optional | |||
| import java.time.LocalDate | |||
| @Repository | |||
| interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | |||
| fun findAllStockInLineInfoByStockInIdAndDeletedFalse(stockInId: Long): List<StockInLineInfo> | |||
| @@ -23,4 +23,12 @@ interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | |||
| @Query("SELECT sil FROM StockInLine sil WHERE sil.item.id = :itemId AND sil.deleted = false") | |||
| fun findAllByItemIdAndDeletedFalse(itemId: Long): List<StockInLine> | |||
| @Query(""" | |||
| SELECT sil FROM StockInLine sil | |||
| WHERE sil.receiptDate IS NOT NULL | |||
| AND DATE(sil.receiptDate) = :date | |||
| AND sil.deleted = false | |||
| """) | |||
| fun findByReceiptDateAndDeletedFalse(date: LocalDate): List<StockInLine> | |||
| } | |||
| @@ -421,10 +421,18 @@ open class StockInLineService( | |||
| if (inventoryLotLines.sumOf { it.inQty ?: BigDecimal.ZERO } >= request.acceptQty?.times(ratio)) { | |||
| stockInLine.apply { | |||
| this.status = if (request.acceptQty?.compareTo(request.acceptedQty) == 0) | |||
| StockInLineStatus.COMPLETE.status else StockInLineStatus.PARTIALLY_COMPLETE.status | |||
| // this.inventoryLotLine = savedInventoryLotLine | |||
| } | |||
| val isWipJobOrder = stockInLine.jobOrder?.bom?.description == "WIP" | |||
| this.status = if (isWipJobOrder) { | |||
| StockInLineStatus.COMPLETE.status | |||
| } else { | |||
| // For non-WIP, use original logic | |||
| if (request.acceptQty?.compareTo(request.acceptedQty) == 0) | |||
| StockInLineStatus.COMPLETE.status | |||
| else | |||
| StockInLineStatus.PARTIALLY_COMPLETE.status | |||
| } | |||
| // this.inventoryLotLine = savedInventoryLotLine | |||
| } | |||
| // Update JO Status | |||
| if (stockInLine.jobOrder != null) { //TODO Improve | |||
| val jo = stockInLine.jobOrder | |||