| @@ -12,7 +12,7 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||
| import java.time.LocalDate | |||||
| import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | ||||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus | import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus | ||||
| import java.time.LocalDateTime | 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.enum.StockInLineStatus | ||||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | ||||
| import java.time.LocalDate | |||||
| @Service | @Service | ||||
| open class JoPickOrderService( | open class JoPickOrderService( | ||||
| private val joPickOrderRepository: JoPickOrderRepository, | private val joPickOrderRepository: JoPickOrderRepository, | ||||
| @@ -1305,50 +1306,75 @@ open fun getCompletedJobOrderPickOrdersWithCompletedSecondScan(): List<Map<Strin | |||||
| emptyList() | 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?>> { | open fun getCompletedJobOrderPickOrderLotDetails(pickOrderId: Long): List<Map<String, Any?>> { | ||||
| println("=== getCompletedJobOrderPickOrderLotDetails ===") | println("=== getCompletedJobOrderPickOrderLotDetails ===") | ||||
| println("pickOrderId: $pickOrderId") | 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.JobOrderInfo | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName | import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportFGStockInLabelRequest | import com.ffii.fpsms.modules.jobOrder.web.model.ExportFGStockInLabelRequest | ||||
| import java.time.LocalDate | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/jo") | @RequestMapping("/jo") | ||||
| @@ -231,9 +231,9 @@ fun recordSecondScanIssue( | |||||
| fun getCompletedJobOrderPickOrders(): List<Map<String, Any?>> { | fun getCompletedJobOrderPickOrders(): List<Map<String, Any?>> { | ||||
| return joPickOrderService.getCompletedJobOrderPickOrders() | 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}") | @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.entity.JoPickOrderRepository | ||||
| import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | ||||
| import com.ffii.fpsms.modules.master.entity.UomConversionRepository | import com.ffii.fpsms.modules.master.entity.UomConversionRepository | ||||
| import com.ffii.fpsms.modules.master.entity.ItemUomRespository | |||||
| @Service | @Service | ||||
| @Transactional | @Transactional | ||||
| open class ProductProcessService( | open class ProductProcessService( | ||||
| @@ -71,6 +72,7 @@ open class ProductProcessService( | |||||
| private val pickOrderRepository: PickOrderRepository, | private val pickOrderRepository: PickOrderRepository, | ||||
| private val joPickOrderRepository: JoPickOrderRepository, | private val joPickOrderRepository: JoPickOrderRepository, | ||||
| private val uomConversionRepository: UomConversionRepository, | private val uomConversionRepository: UomConversionRepository, | ||||
| private val itemUomRepository: ItemUomRespository, | |||||
| ) { | ) { | ||||
| open fun findAll(pageable: Pageable): Page<ProductProcess> { | open fun findAll(pageable: Pageable): Page<ProductProcess> { | ||||
| @@ -782,7 +784,7 @@ open class ProductProcessService( | |||||
| code = "400", | code = "400", | ||||
| name = "Equipment Validation Failed", | name = "Equipment Validation Failed", | ||||
| type = "error", | 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" | errorPosition = "equipmentId" | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -824,7 +826,7 @@ open class ProductProcessService( | |||||
| code = "400", | code = "400", | ||||
| name = "User Required", | name = "User Required", | ||||
| type = "error", | type = "error", | ||||
| message = "Staff No is required when equipment is provided", | |||||
| message = "Staff No is required", | |||||
| errorPosition = "staffNo" | errorPosition = "staffNo" | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -839,7 +841,7 @@ open class ProductProcessService( | |||||
| code = "400", | code = "400", | ||||
| name = "Equipment Validation Failed", | name = "Equipment Validation Failed", | ||||
| type = "error", | 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" | errorPosition = "equipmentId" | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -855,7 +857,22 @@ open class ProductProcessService( | |||||
| errorPosition = "staffNo" | 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 | // 设备有(且已通过匹配校验) → 更新 equipment / equipmentDetailId | ||||
| @@ -1082,7 +1099,8 @@ open class ProductProcessService( | |||||
| val stockInLineId = stockInLine?.id | val stockInLineId = stockInLine?.id | ||||
| val pickOrder = pickOrderRepository.findAllByJobOrder_Id(jobOrder?.id?:0L).firstOrNull() | val pickOrder = pickOrderRepository.findAllByJobOrder_Id(jobOrder?.id?:0L).firstOrNull() | ||||
| val joPickOrdersList = joPickOrderRepository.findByPickOrderId(pickOrder?.id?:0L) | 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 | //val silHandlerId = stockInLine?.escalationLog?.firstOrNull { it.status == "pending" }?.handler?.id | ||||
| AllJoborderProductProcessInfoResponse( | AllJoborderProductProcessInfoResponse( | ||||
| @@ -1100,10 +1118,12 @@ open class ProductProcessService( | |||||
| "pending" | "pending" | ||||
| }, | }, | ||||
| RequiredQty = jobOrder?.reqQty?.toInt() ?: 0, | RequiredQty = jobOrder?.reqQty?.toInt() ?: 0, | ||||
| Uom = bomUom?.udfShortDesc, | |||||
| date = productProcesses.date, | date = productProcesses.date, | ||||
| bomId = productProcesses.bom?.id, | bomId = productProcesses.bom?.id, | ||||
| assignedTo = pickOrder?.assignTo?.id, | assignedTo = pickOrder?.assignTo?.id, | ||||
| itemName = productProcesses.item?.name, | itemName = productProcesses.item?.name, | ||||
| itemCode = productProcesses.item?.code, | |||||
| pickOrderId = pickOrder?.id, | pickOrderId = pickOrder?.id, | ||||
| pickOrderStatus = pickOrder?.status?.value, | pickOrderStatus = pickOrder?.status?.value, | ||||
| jobOrderId = productProcesses.jobOrder?.id, | jobOrderId = productProcesses.jobOrder?.id, | ||||
| @@ -161,8 +161,10 @@ data class AllJoborderProductProcessInfoResponse( | |||||
| val date: LocalDate?, | val date: LocalDate?, | ||||
| val bomId: Long?, | val bomId: Long?, | ||||
| val itemName: String?, | val itemName: String?, | ||||
| val itemCode: String?, | |||||
| val matchStatus: String?, | val matchStatus: String?, | ||||
| val RequiredQty: Int?, | val RequiredQty: Int?, | ||||
| val Uom: String?, | |||||
| val jobOrderId: Long?, | val jobOrderId: Long?, | ||||
| val jobOrderCode: String?, | val jobOrderCode: String?, | ||||
| val assignedTo: Long?, | 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.data.jpa.repository.Query | ||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.util.Optional | import java.util.Optional | ||||
| import java.time.LocalDate | |||||
| @Repository | @Repository | ||||
| interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | ||||
| fun findAllStockInLineInfoByStockInIdAndDeletedFalse(stockInId: Long): List<StockInLineInfo> | 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") | @Query("SELECT sil FROM StockInLine sil WHERE sil.item.id = :itemId AND sil.deleted = false") | ||||
| fun findAllByItemIdAndDeletedFalse(itemId: Long): List<StockInLine> | 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)) { | if (inventoryLotLines.sumOf { it.inQty ?: BigDecimal.ZERO } >= request.acceptQty?.times(ratio)) { | ||||
| stockInLine.apply { | 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 | // Update JO Status | ||||
| if (stockInLine.jobOrder != null) { //TODO Improve | if (stockInLine.jobOrder != null) { //TODO Improve | ||||
| val jo = stockInLine.jobOrder | val jo = stockInLine.jobOrder | ||||