diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt index 2b382d8..954eeb3 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt @@ -13,7 +13,7 @@ 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 java.time.LocalDateTime import com.ffii.core.support.JdbcDao import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest @@ -24,7 +24,11 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository import java.math.BigDecimal import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory - +import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository +import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository +import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository +import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository +import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository @Service open class JoPickOrderService( private val joPickOrderRepository: JoPickOrderRepository, @@ -33,7 +37,12 @@ open class JoPickOrderService( private val userService: UserService, private val pickOrderRepository: PickOrderRepository, private val jdbcDao: JdbcDao, - private val pickExecutionIssueRepository: PickExecutionIssueRepository + private val pickExecutionIssueRepository: PickExecutionIssueRepository, + private val pickOrderLineRepository: PickOrderLineRepository, + private val suggestPickLotRepository: SuggestPickLotRepository, + private val inventoryLotLineRepository: InventoryLotLineRepository, + private val inventoryLotRepository: InventoryLotRepository, + private val stockOutLineRepository: StockOutLIneRepository ) { open fun save(record: JoPickOrder): JoPickOrder { @@ -209,10 +218,8 @@ open class JoPickOrderService( println("🔍 DEBUG: Found ${allAssignedPickOrders.size} job order pick orders assigned to user $userId") - val visiblePickOrders = allAssignedPickOrders - // Filter based on assignment and status - val filteredPickOrders = if (visiblePickOrders.isNotEmpty()) { + val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) { // Check if there are any RELEASED orders assigned to this user (active work) val assignedReleasedOrders = allAssignedPickOrders.filter { it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId @@ -248,223 +255,201 @@ open class JoPickOrderService( ) } - // 修复:简化 SQL 查询,移除不存在的字段 - val pickOrderIdsStr = pickOrderIds.joinToString(",") - - val sql = """ - SELECT - -- Pick Order Information - po.id as pickOrderId, - po.code as pickOrderCode, - po.consoCode as pickOrderConsoCode, - DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, - po.type as pickOrderType, - po.status as pickOrderStatus, - po.assignTo as pickOrderAssignTo, - - -- Job Order Information - jo.id as jobOrderId, - jo.code as jobOrderCode, - - -- Pick Order Line Information - pol.id as pickOrderLineId, - pol.qty as pickOrderLineRequiredQty, - pol.status as pickOrderLineStatus, - - -- Item Information - i.id as itemId, - i.code as itemCode, - i.name as itemName, - uc.code as uomCode, - uc.udfudesc as uomDesc, - uc.udfShortDesc as uomShortDesc, - - -- Lot Information - ill.id as lotId, - il.lotNo, - DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, - w.name as location, - COALESCE(uc.udfudesc, 'N/A') as stockUnit, - - -- Simplified Router Information using warehouse - w.`order` as routerIndex, - w.code as routerRoute, - w.code as routerArea, - - -- Set quantities to NULL for rejected lots - CASE - WHEN sol.status = 'rejected' THEN NULL - ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) - END as availableQty, - - -- Required quantity for this lot - COALESCE(spl.qty, 0) as requiredQty, - - -- Actual picked quantity - COALESCE(sol.qty, 0) as actualPickQty, - - -- Suggested pick lot information - spl.id as suggestedPickLotId, - ill.status as lotStatus, + // 使用 Repository 获取数据 + try { + val pickOrders = pickOrderRepository.findAllByIdIn(pickOrderIds) + .filter { it.deleted == false && it.assignTo?.id == userId } - -- Stock out line information - sol.id as stockOutLineId, - sol.status as stockOutLineStatus, - COALESCE(sol.qty, 0) as stockOutLineQty, + if (pickOrders.isEmpty()) { + return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) + } - -- Additional detailed fields - COALESCE(ill.inQty, 0) as inQty, - COALESCE(ill.outQty, 0) as outQty, - COALESCE(ill.holdQty, 0) as holdQty, - COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId, - ill.inventoryLotId as debugInventoryLotId, + val pickOrder = pickOrders.first() // 取第一个(应该只有一个) + val jobOrder = pickOrder.jobOrder ?: return mapOf( + "pickOrder" to null as Any?, + "pickOrderLines" to emptyList>() as Any? + ) - -- Calculate total picked quantity by ALL pick orders for this lot - COALESCE(( - SELECT SUM(sol_all.qty) - FROM fpsmsdb.stock_out_line sol_all - WHERE sol_all.inventoryLotLineId = ill.id - AND sol_all.deleted = false - AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed') - ), 0) as totalPickedByAllPickOrders, + // 获取 pick order lines + val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id!!) + .filter { it.deleted == false } - -- Calculate remaining available quantity correctly - (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders, + // 获取所有 pick order line IDs + val pickOrderLineIds = pickOrderLines.map { it.id!! } - -- Lot availability status - CASE - WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' - WHEN sol.status = 'rejected' THEN 'rejected' - WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' - WHEN ill.status = 'unavailable' THEN 'status_unavailable' - ELSE 'available' - END as lotAvailability, + // 获取 suggested pick lots + val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) { + suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) + .filter { it.deleted == false } + } else { + emptyList() + } - -- Processing status - CASE - WHEN sol.status = 'completed' THEN 'completed' - WHEN sol.status = 'rejected' THEN 'rejected' - WHEN sol.status = 'created' THEN 'pending' - ELSE 'pending' - END as processingStatus, + // 获取所有 inventory lot line IDs + val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id } - -- JoPickOrder second scan status - jpo.match_status as match_status, - jpo.match_by as match_by, - jpo.match_qty as match_qty, - jpo.ticketReleaseTime as ticketReleaseTime, - jpo.ticketCompleteTime as ticketCompleteTime + // 获取 inventory lot lines + val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) { + inventoryLotLineRepository.findAllByIdIn(inventoryLotLineIds) + .filter { it.deleted == false } + } else { + emptyList() + } - FROM fpsmsdb.pick_order po - JOIN fpsmsdb.job_order jo ON jo.id = po.joId - JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id - JOIN fpsmsdb.items i ON i.id = pol.itemId - LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId - LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId - LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id - LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId - 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 - AND po.id IN ($pickOrderIdsStr) - AND pol.deleted = false - AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED') - AND po.assignTo = :userId - AND ill.deleted = false - AND il.deleted = false - AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) - ORDER BY - CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, - COALESCE(w.`order`, 999999) ASC, -- 使用 warehouse.order 排序 - po.code ASC, - i.code ASC, - il.expiryDate ASC, - il.lotNo ASC - """.trimIndent() - - println("🔍 Executing SQL for job order hierarchical structure: $sql") - println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") - - // 修复:直接执行 SQL 查询并构建分层结构 - try { - val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) + // 获取 inventory lots + val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct() + val inventoryLots = if (inventoryLotIds.isNotEmpty()) { + inventoryLotRepository.findAllByIdIn(inventoryLotIds) + .filter { it.deleted == false } + } else { + emptyList() + } - if (results.isEmpty()) { - return mapOf( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? - ) + // 获取 stock out lines + val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) { + pickOrderLineIds.flatMap { polId -> + inventoryLotLineIds.flatMap { illId -> + stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) + } + } + } else { + emptyList() } + // 取得所有 stock out line(含無 lot 情況) + val stockOutLinesByPickOrderLine = pickOrderLineIds.associateWith { polId -> + stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId) + } + // 获取 jo_pick_order 记录 + val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!) - // 构建分层结构 - val pickOrderInfo = results.firstOrNull()?.let { firstRow: Map -> - mapOf( - "id" to firstRow["pickOrderId"], - "code" to firstRow["pickOrderCode"], - "consoCode" to firstRow["pickOrderConsoCode"], - "targetDate" to firstRow["pickOrderTargetDate"], - "type" to firstRow["pickOrderType"], - "status" to firstRow["pickOrderStatus"], - "assignTo" to firstRow["pickOrderAssignTo"], - "jobOrder" to mapOf( - "id" to firstRow["jobOrderId"], - "code" to firstRow["jobOrderCode"], - "name" to "Job Order ${firstRow["jobOrderCode"]}" // 使用生成的名称 - ) + // 构建 pick order info + val pickOrderInfo = mapOf( + "id" to pickOrder.id, + "code" to pickOrder.code, + "consoCode" to pickOrder.consoCode, + "targetDate" to pickOrder.targetDate?.let { + "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" + }, + "type" to pickOrder.type?.value, + "status" to pickOrder.status?.value, + "assignTo" to pickOrder.assignTo?.id, + "jobOrder" to mapOf( + "id" to jobOrder.id, + "code" to jobOrder.code, + "name" to "Job Order ${jobOrder.code}" ) - } + ) - // 按 pick order line 分组 - val groupedByLine = results.groupBy { row: Map -> row["pickOrderLineId"] } - val pickOrderLines = groupedByLine.map { (lineId: Any?, lineRows: List>) -> - val firstLineRow = lineRows.first() - mapOf( - "id" to lineId, - "itemId" to firstLineRow["itemId"], - "itemCode" to firstLineRow["itemCode"], - "itemName" to firstLineRow["itemName"], - "requiredQty" to firstLineRow["pickOrderLineRequiredQty"], - "uomCode" to firstLineRow["uomCode"], - "uomDesc" to firstLineRow["uomDesc"], - "lots" to lineRows.map { row: Map -> - mapOf( - "lotId" to row["lotId"], - "lotNo" to row["lotNo"], - "expiryDate" to row["expiryDate"], - "location" to row["location"], - "availableQty" to row["availableQty"], - "requiredQty" to row["requiredQty"], - "actualPickQty" to row["actualPickQty"], - "processingStatus" to row["processingStatus"], - "lotAvailability" to row["lotAvailability"], - "pickOrderId" to row["pickOrderId"], - "pickOrderCode" to row["pickOrderCode"], - "pickOrderConsoCode" to row["pickOrderConsoCode"], - "pickOrderLineId" to row["pickOrderLineId"], - "stockOutLineId" to row["stockOutLineId"], - "suggestedPickLotId" to row["suggestedPickLotId"], - "stockOutLineQty" to row["stockOutLineQty"], - "stockOutLineStatus" to row["stockOutLineStatus"], - "routerIndex" to row["routerIndex"], - "routerArea" to row["routerArea"], - "routerRoute" to row["routerRoute"], - "uomShortDesc" to row["uomShortDesc"], - "matchStatus" to row["match_status"], - "matchBy" to row["match_by"], - "matchQty" to row["match_qty"] - ) + // 构建 pick order lines with lots + val pickOrderLinesResult = pickOrderLines.map { pol -> + val item = pol.item + val uom = pol.uom + val lineId = pol.id!! + val suggestions = suggestedPickLots.filter { it.pickOrderLine?.id == lineId } + val stockoutsForLine = stockOutLinesByPickOrderLine[lineId].orEmpty() + // 获取该 line 的 suggested pick lots + val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id } + + // 构建 lots 数据 + val lots = lineSuggestedLots.mapNotNull { spl -> + val ill = spl.suggestedLotLine + if (ill == null || ill.deleted == true) return@mapNotNull null + + val il = ill.inventoryLot + if (il == null || il.deleted == true) return@mapNotNull null + + val warehouse = ill.warehouse + + // 获取对应的 stock out line + val sol = stockOutLines.firstOrNull { + it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id + } + + // 获取对应的 jo_pick_order + val jpo = joPickOrders.firstOrNull { it.itemId == item?.id } + + // 计算 available quantity + val availableQty = if (sol?.status == "rejected") { + null + } else { + (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty ?: BigDecimal.ZERO) + } + + // 计算 total picked by all pick orders + val totalPickedByAllPickOrders = stockOutLines + .filter { it.inventoryLotLine?.id == ill.id && it.deleted == false } + .filter { it.status in listOf("pending", "checked", "partially_completed", "completed") } + .sumOf { it.qty?.toBigDecimal() ?: BigDecimal.ZERO } + + // 计算 lot availability + val lotAvailability = when { + il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired" + sol?.status == "rejected" -> "rejected" + availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" + ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable" + else -> "available" + } + + // 计算 processing status + val processingStatus = when (sol?.status) { + "completed" -> "completed" + "rejected" -> "rejected" + "created" -> "pending" + else -> "pending" } + + mapOf( + "lotId" to ill.id, + "lotNo" to il.lotNo, + "expiryDate" to il.expiryDate?.let { + "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" + }, + "location" to warehouse?.name, + "availableQty" to availableQty?.toDouble(), + "requiredQty" to (spl.qty?.toDouble() ?: 0.0), + "actualPickQty" to (sol?.qty ?: 0.0), + "processingStatus" to processingStatus, + "lotAvailability" to lotAvailability, + "pickOrderId" to pickOrder.id, + "pickOrderCode" to pickOrder.code, + "pickOrderConsoCode" to pickOrder.consoCode, + "pickOrderLineId" to pol.id, + "stockOutLineId" to sol?.id, + "suggestedPickLotId" to spl.id, + "stockOutLineQty" to (sol?.qty ?: 0.0), + "stockOutLineStatus" to sol?.status, + "routerIndex" to warehouse?.order, + "routerArea" to warehouse?.code, + "routerRoute" to warehouse?.code, + "uomShortDesc" to uom?.udfShortDesc, + "matchStatus" to jpo?.matchStatus?.value, + "matchBy" to jpo?.matchBy, + "matchQty" to jpo?.matchQty + ) + } + + mapOf( + "id" to pol.id, + "itemId" to item?.id, + "itemCode" to item?.code, + "itemName" to item?.name, + "requiredQty" to pol.qty?.toDouble(), + "uomCode" to uom?.code, + "uomDesc" to uom?.udfudesc, + "lots" to lots ) } return mapOf( "pickOrder" to pickOrderInfo as Any?, - "pickOrderLines" to pickOrderLines as Any? + "pickOrderLines" to pickOrderLinesResult as Any? ) } catch (e: Exception) { - println("❌ Error executing Job Order SQL: ${e.message}") + println("❌ Error executing Job Order hierarchical query: ${e.message}") e.printStackTrace() return mapOf( "pickOrder" to null as Any?, diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt index 9536c92..d68a8a4 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService +import com.ffii.fpsms.modules.productProcess.service.ProductProcessService import com.ffii.fpsms.modules.jobOrder.web.model.* import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest @@ -40,7 +41,8 @@ class JobOrderController( private val jobOrderService: JobOrderService, private val jobOrderBomMaterialService: JobOrderBomMaterialService, private val jobOrderProcessService: JobOrderProcessService, - private val joPickOrderService: JoPickOrderService + private val joPickOrderService: JoPickOrderService, + private val productProcessService: ProductProcessService ) { @GetMapping("/getRecordByPage") @@ -87,7 +89,7 @@ class JobOrderController( } jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(jo.id) jobOrderProcessService.createJobOrderProcessesByJoId(jo.id) - + productProcessService.createProductProcessByJobOrderId(jo.id) return jo } @GetMapping("/all-lots-hierarchical/{userId}") diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt index 018878b..f0328d8 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt @@ -39,4 +39,18 @@ interface ProductionScheduleLineRepository : AbstractRepository? + @Query(""" + SELECT psl FROM ProductionScheduleLine psl + JOIN psl.productionSchedule ps + WHERE psl.item.id = :itemId + AND ps.produceAt = :produceAt + AND psl.type = :type + AND ps.deleted = false + ORDER BY psl.itemPriority ASC + """) + fun findByItemIdAndProduceAtAndType( + itemId: Long, + produceAt: LocalDateTime, + type: String + ): ProductionScheduleLine? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt index 332559f..759288e 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt @@ -24,16 +24,24 @@ data class ProductProcessInfo( val isDark: String?, val isDense: Int?, val isFloat: String?, + val itemId: Long?, + val itemCode: String?, + val itemName: String?, val outputQtyUom: String?, val outputQty: Int?, - - val productProcessLines: List? + val productionPriority: String?, + val productProcessLines: List?, + val totalStockQty: Int?, + val insufficientStockQty: Int?, + val sufficientStockQty: Int?, + val jobOrderLines: List? ) data class ProductProcessLineInfo( val id:Long?, val bomprocessId: Long?, val operatorId: Long?, + val operatorName: String?, val equipmentId: Long?, val handlerId: Long?, val seqNo: Long?, @@ -50,8 +58,22 @@ data class ProductProcessLineInfo( val defectUom: String?, val outputFromProcessQty: Int?, val outputFromProcessUom: String?, + val durationInMinutes: Int?, + val prepTimeInMinutes: Int?, + val postProdTimeInMinutes: Int?, @JsonFormat(pattern = "yyyy-MM-dd") val startTime: LocalDateTime?, @JsonFormat(pattern = "yyyy-MM-dd") val endTime: LocalDateTime? +) +data class jobOrderLineInfo( + val id: Long?, + val itemId: Long?, + val itemCode: String?, + val itemName: String?, + val reqQty: Int?, + val stockQty: Int?, + val uom: String?, + val shortUom: String?, + val availableStatus: String? ) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt index c4ed5f2..58e5349 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt @@ -7,6 +7,7 @@ import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus import com.ffii.fpsms.modules.productProcess.web.model.* import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable +import com.ffii.fpsms.modules.productProcess.entity.projections.jobOrderLineInfo import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDate @@ -27,6 +28,14 @@ import com.ffii.fpsms.modules.master.entity.Equipment import com.ffii.fpsms.modules.master.entity.BomProcess import com.ffii.fpsms.modules.master.entity.Bom import com.ffii.fpsms.modules.master.web.models.MessageResponse +import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterialRepository +import com.ffii.fpsms.modules.master.service.ProductionScheduleService +import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository +import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository +import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus +import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus +import com.ffii.fpsms.modules.stock.service.StockInLineService +import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest @Service @Transactional open class ProductProcessService( @@ -40,6 +49,10 @@ open class ProductProcessService( private val userRepository: UserRepository, private val processRepository: ProcessRepository, private val equipmentRepository: EquipmentRepository, + private val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository, + private val inventoryLotLineRepository: InventoryLotLineRepository, + private val productionScheduleLineRepository: ProductionScheduleLineRepository, + private val stockInLineService: StockInLineService, ) { open fun findAll(pageable: Pageable): Page { @@ -457,6 +470,62 @@ open class ProductProcessService( val productProcesses = productProcessRepository.findByJobOrder_Id(joid) val jobOrder = jobOrderRepository.findById(joid).orElse(null) val bom = bomRepository.findById(jobOrder?.bom?.id?:0L).orElse(null) + val bomProcess = bomProcessRepository.findByBomId(jobOrder?.bom?.id?:0L) + // get bom materials + val bomMaterials = jobOrderBomMaterialRepository.findAllByJobOrderId(jobOrder?.id?:0) + val itemIds = bomMaterials.mapNotNull { it.item?.id } + + // calculate each item's available stock + val stockQtyMap = bomMaterials.mapNotNull { material -> + val itemId = material.item?.id ?: return@mapNotNull null + val availableLots = inventoryLotLineRepository + .findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE) + val stockQty = availableLots.sumOf { lot -> + (lot.inQty ?: BigDecimal.ZERO) + .minus(lot.outQty ?: BigDecimal.ZERO) + .minus(lot.holdQty ?: BigDecimal.ZERO) + } + itemId to stockQty + }.toMap() + + // calculate statistics + val totalStockQty = stockQtyMap.values.sumOf { it.toInt() } + val insufficientStockQty = bomMaterials + .filter { material -> + val itemId = material.item?.id ?: 0L + val stockQty = stockQtyMap[itemId] ?: BigDecimal.ZERO + stockQty < (material.reqQty ?: BigDecimal.ZERO) + } + .sumOf { material -> + val itemId = material.item?.id ?: 0L + stockQtyMap[itemId]?.toInt() ?: 0 + } + val sufficientStockQty = bomMaterials + .filter { material -> + val itemId = material.item?.id ?: 0L + val stockQty = stockQtyMap[itemId] ?: BigDecimal.ZERO + stockQty >= (material.reqQty ?: BigDecimal.ZERO) + } + .sumOf { material -> + val itemId = material.item?.id ?: 0L + stockQtyMap[itemId]?.toInt() ?: 0 + } + + // 获取 productionPriority + val itemId = jobOrder?.bom?.item?.id + val planEndDate = jobOrder?.planEnd?.toLocalDate() + val productionPriority = if (itemId != null && planEndDate != null) { + val scheduleLine = productionScheduleLineRepository + .findByItemIdAndProduceAtAndType( + itemId, + planEndDate.atStartOfDay(), + "detailed" + ) + scheduleLine?.itemPriority?.toString() ?: "0" + } else { + "0" + } + fun calculateColourScore(value: Int?): String { return when (value) { 0 -> "淺" @@ -471,13 +540,16 @@ open class ProductProcessService( else -> "" } } - //val processId = productProcess.id ?: 0 + return productProcesses.map { process -> ProductProcessInfo( id = process.id?:0, bomId = process.bom?.id?:0, jobOrderId = process.jobOrder?.id?:0, jobOrderCode = jobOrder?.code?:"", + itemId = bom?.item?.id?:0, + itemCode = bom?.item?.code?:"", + itemName = bom?.item?.name?:"", isDark = calculateColourScore(bom?.isDark?:0), isDense = bom?.isDense?:0, isFloat = calculateFloatScore(bom?.isFloat?:0), @@ -488,11 +560,16 @@ open class ProductProcessService( startTime = process.startTime?:LocalDateTime.now(), endTime = process.endTime?:LocalDateTime.now(), date = process.date?:LocalDate.now(), + productionPriority = productionPriority, // 已经是 String,不需要 ?:0 + totalStockQty = totalStockQty, + insufficientStockQty = insufficientStockQty, + sufficientStockQty = sufficientStockQty, productProcessLines = productProcessLineRepository.findByProductProcess_Id(process.id?:0).map { line -> ProductProcessLineInfo( id = line.id?:0, bomprocessId = line.bomProcess?.id?:0, operatorId = line.operator?.id?:0, + operatorName = line.operator?.name?:"", equipmentId = line.equipment?.id?:0, handlerId = line.handler?.id?:0, seqNo = line.seqNo?:0, @@ -500,6 +577,9 @@ open class ProductProcessService( description = line.description?:"", equipment_name = line.equipmentType?:"", status = line.status?:"", + durationInMinutes = line.bomProcess?.durationInMinute?:0, + prepTimeInMinutes = line.bomProcess?.prepTimeInMinute?:0, + postProdTimeInMinutes = line.bomProcess?.postProdTimeInMinute?:0, byproductId = line.byproduct?.id?:0, byproductName = line.byproduct?.name?:"", byproductQty = line.byproductQty?:0, @@ -512,54 +592,36 @@ open class ProductProcessService( startTime = line.startTime, endTime = line.endTime ) + }, + jobOrderLines = bomMaterials.map { line -> + val itemId = line.item?.id ?: 0L + val stockQty = stockQtyMap[itemId]?.toInt() ?: 0 + val uom = line.uom + val shortUom = uom?.udfShortDesc ?: uom?.udfudesc ?: "" + + // 计算 availableStatus + val availableStatus = if (stockQty >= (line.reqQty?.toInt() ?: 0)) { + "available" + } else { + "insufficient" + } + + jobOrderLineInfo( + id = line.id?:0, + itemId = itemId, + itemCode = line.item?.code?:"", + itemName = line.item?.name?:"", + reqQty = line.reqQty?.toInt() ?: 0, + stockQty = stockQty, + uom = uom?.udfudesc ?: "", + shortUom = shortUom, + availableStatus = availableStatus + ) } ) } } - /* - open fun productProcessDetailfindbyidtest(id: Long): ProductProcessInfo { - val zero = BigDecimal.ZERO - val productProcess = productProcessRepository.findById(id).orElse(null) - val processId = productProcess.id ?: 0 - val productProcessLines = productProcessLineRepository.findByProductProcess_Id(processId) - return ProductProcessInfo( - id = productProcess.id?:0, - bomId = productProcess.bom?.id?:0, - jobOrderId = productProcess.jobOrder?.id?:0, - productProcessCode = productProcess.productProcessCode?:"", - status = productProcess.status?:ProductProcessStatus.PENDING, - startTime = productProcess.startTime?:LocalDateTime.now(), - endTime = productProcess.endTime?:LocalDateTime.now(), - date = productProcess.date?:LocalDate.now(), - productProcessLines = productProcessLines.map { line -> - ProductProcessLineInfo( - id = line.id?:0, - bomprocessId = line.bomProcess?.id?:0, - operatorId = line.operator?.id?:0, - equipmentId = line.equipment?.id?:0, - handlerId = line.handler?.id?:0, - seqNo = line.seqNo?:0, - name = line.name?:"", - description = line.description?:"", - equipment_name = line.equipment?.name?:"", - status = line.status?:"", - byproductId = line.byproduct?.id?:0, - byproductName = line.byproduct?.name?:"", - byproductQty = line.byproductQty?:0, - byproductUom = line.byproductUom?:"", - scrapQty = line.scrapQty?:0, - defectQty = line.defectQty?:0, - defectUom = line.defectUom?:"", - outputFromProcessQty = line.outputFromProcessQty?:0, - outputFromProcessUom = line.outputFromProcessUom?:"", - startTime = line.startTime?:LocalDateTime.now(), - endTime = line.endTime?:LocalDateTime.now() - ) - } - ) - } - */ open fun createProductProcessByJobOrderId(jobOrderId: Long): MessageResponse { val jobOrder = jobOrderRepository.findById(jobOrderId).orElse(null) val bom = bomRepository.findById(jobOrder?.bom?.id ?: 0L).orElse(null) @@ -792,6 +854,7 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest productProcessLine.byproductUom = byproductUom } productProcessLineRepository.save(productProcessLine) + CompleteProductProcessLine(request.productProcessLineId) return MessageResponse( id = request.productProcessLineId, code = "200", @@ -806,11 +869,14 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest 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" } + val stockInLine = jobOrder?.stockInLines?.firstOrNull() + val stockInLineId = stockInLine?.id + //val silHandlerId = stockInLine?.escalationLog?.firstOrNull { it.status == "pending" }?.handler?.id + AllJoborderProductProcessInfoResponse( id = productProcesses.id ?: 0L, productProcessCode = productProcesses.productProcessCode, @@ -821,6 +887,7 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest bomId = productProcesses.bom?.id, itemName = productProcesses.item?.name, jobOrderId = productProcesses.jobOrder?.id, + stockInLineId = stockInLineId, jobOrderCode = jobOrder?.code, productProcessLineCount = productProcessLines.size, FinishedProductProcessLineCount = FinishedProductProcessLineCount, @@ -936,10 +1003,30 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest updateProductProcessLineEndTime(productProcessLineId) updateProductProcessLineStatus(productProcessLineId, "Completed") val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) - val allproductProcessLines=productProcessLineRepository.findByProductProcess_Id(productProcessLine.productProcess?.id?:0L) + val productProcessId = productProcessLine?.productProcess?.id ?: 0L + val allproductProcessLines=productProcessLineRepository.findByProductProcess_Id(productProcessId) + if(allproductProcessLines.all { it.status == "Completed" }) { - updateProductProcessEndTime(productProcessLine.id) - updateProductProcessStatus(productProcessLine.id, ProductProcessStatus.COMPLETED) + updateProductProcessEndTime(productProcessId) + updateProductProcessStatus(productProcessId, ProductProcessStatus.COMPLETED) + val productProcess = productProcessRepository.findById(productProcessId).orElse(null) + val jobOrder = jobOrderRepository.findById(productProcess?.jobOrder?.id?:0L).orElse(null) + if(jobOrder != null) { + jobOrder.status = JobOrderStatus.STORING + jobOrderRepository.save(jobOrder) + stockInLineService.create( + SaveStockInLineRequest( + itemId = productProcess?.item?.id?:0L, + acceptedQty = jobOrder?.reqQty?:BigDecimal.ZERO, + productLotNo = jobOrder?.code, + productionDate = LocalDate.now(), + jobOrderId = jobOrder.id, + acceptQty =jobOrder?.reqQty?:BigDecimal.ZERO , + expiryDate=null, + status="", + ) + ) + } } return MessageResponse( id = productProcessLine.id, diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt index 6a2c43a..3b361fc 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt @@ -147,7 +147,7 @@ data class AllJoborderProductProcessInfoResponse( val jobOrderCode: String?, val productProcessLineCount: Int, val FinishedProductProcessLineCount: Int, - + val stockInLineId: Long?, val lines: List ) data class ProductProcessInfoResponse( diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt index 7a951e6..12e5927 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt @@ -15,4 +15,5 @@ interface InventoryLotRepository: AbstractRepository { select il.lotNo from InventoryLot il where il.lotNo like :prefix% order by il.lotNo desc limit 1 """) fun findLatestLotNoByPrefix(prefix: String): String? + fun findAllByIdIn(ids: List): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt index db197f4..aca6f31 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt @@ -21,4 +21,5 @@ interface StockOutLIneRepository: AbstractRepository { inventoryLotLineId: Long ): List fun existsByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(pickOrderLineId: Long, inventoryLotLineId: Long): Boolean + //fun findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId: Long): List }