diff --git a/deployLocal.bat b/deployLocal.bat new file mode 100644 index 0000000..8b91772 --- /dev/null +++ b/deployLocal.bat @@ -0,0 +1,34 @@ +@echo off + +set "PROJECT_PATH=C:\dev\FPSMS-backend" +set "SERVICE_NAME=FP-backend" + +echo. +echo [1/4] Stopping service %SERVICE_NAME% ... +net stop "%SERVICE_NAME%" >nul 2>&1 + +echo. +echo [2/4] Waiting 5 seconds... +timeout /t 5 >nul + +echo. +echo [3/4] Building new JAR... +cd /d "%PROJECT_PATH%" +call gradlew clean bootJar --no-daemon + +if %errorlevel% neq 0 ( + echo. + echo Build FAILED! Service not restarted. + timeout /t 10 + exit /b 1 +) + +echo. +echo [4/4] Starting service %SERVICE_NAME% ... +net start "%SERVICE_NAME%" + +echo. +echo SUCCESS! %SERVICE_NAME% is running with the new JAR! +echo Deployment completed: %date% %time% +echo Closing in 5 seconds... +timeout /t 5 >nul \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt index c2d7afe..e8059b9 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt @@ -133,4 +133,43 @@ interface JobOrderRepository : AbstractRepository { planStartTo: LocalDateTime?, pageable: Pageable ): Page + + @Query( + nativeQuery = true, + value = """ + select + jo.id, + jo.code, + b.name, + jo.reqQty, + b.outputQtyUom as unit, + uc2.udfudesc as uom, + json_arrayagg( + json_object( + 'id', jobm.id, + 'code', i.code, + 'name', i.name, + 'lotNo', il.lotNo, + 'reqQty', jobm.reqQty, + 'uom', uc.udfudesc, + 'status', jobm.status + ) + ) as pickLines, + jo.status + from job_order jo + left join bom b on b.id = jo.bomId + left join item_uom iu on b.itemId = iu.itemId and iu.salesUnit = true + left join uom_conversion uc2 on uc2.id = iu.uomId + left join job_order_bom_material jobm on jo.id = jobm.jobOrderId + left join items i on i.id = jobm.itemId + left join uom_conversion uc on uc.id = jobm.uomId + left join stock_out_line sol on sol.id = jobm.stockOutLineId + left join inventory_lot_line ill on ill.id = sol.inventoryLotLineId + left join inventory_lot il on il.id = ill.inventoryLotId + where jo.prodScheduleLineId = :prodScheduleLineId + group by jo.id, uc2.udfudesc + limit 1 + """ + ) + fun findJobOrderByProdScheduleLineId(prodScheduleLineId: Long): JobOrderDetailWithJsonString?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt index 49826e0..5d8c50d 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt @@ -264,6 +264,24 @@ open class JobOrderService( ) } + open fun jobOrderDetailByPsId(prodScheduleLineId: Long): JobOrderDetail { + val sqlResult = jobOrderRepository.findJobOrderByProdScheduleLineId(prodScheduleLineId) ?: throw NoSuchElementException("Job Order not found: $prodScheduleLineId"); + + val type = object : TypeToken>() {}.type + val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson>(it, type) } + return JobOrderDetail( + id = sqlResult.id, + code = sqlResult.code, + itemCode = sqlResult.itemCode, + name = sqlResult.name, + reqQty = sqlResult.reqQty, + uom = sqlResult.uom, + pickLines = jsonResult, + status = sqlResult.status, + shortUom = sqlResult.shortUom + ) + } + open fun jobOrderDetailByCode(code: String): JobOrderDetail { val sqlResult = jobOrderRepository.findJobOrderDetailByCode(code) ?: throw NoSuchElementException("Job Order not found: $code"); diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLine.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLine.kt index 12da0b1..afa5180 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLine.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLine.kt @@ -45,6 +45,24 @@ open class ProductionScheduleLine : BaseEntity() { @Column(name = "weightingRef") open var weightingRef: Double = 0.0 + @Column(name = "outputQty") + open var outputQty: Double = 0.0 + + @Column(name = "stockQty") + open var stockQty: Double = 0.0 + + @Column(name = "daysLeft") + open var daysLeft: Double = 0.0 + + @Column(name = "batchNeed") + open var batchNeed: Int = 0 + + @Column(name = "needNoOfJobOrder") + open var needNoOfJobOrder: Int = 0 + + @Column(name = "avgQtyLastMonth") + open var avgQtyLastMonth: Double = 0.0 + @JsonBackReference @ManyToOne @JoinColumn(name = "prodScheduleId", nullable = false) diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt index 1532cc4..47f54cb 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt @@ -202,8 +202,15 @@ interface ProductionScheduleRepository : AbstractRepository> + val data: List> = searchProdScheduleLine(request.id) + + if (data.isEmpty()) { + logger.warn("No production schedule lines found for ID: ${request.id}") + return MessageResponse( + id = request.id.toLong(), + message = "No lines to release. Parent ID: ${request.id}", + errorPosition = "data", + name = "", + code = "", + type = "" + ) + } + + val approver = SecurityUtils.getUser().getOrNull() // Get approver once + + for (d in data) { + // 2. Safely cast the ID (Int) and convert it to Long for repository lookup + val prodScheduleLineIdInt = d["id"] as? Int + ?: throw IllegalStateException("Production Schedule Line ID is missing or not an Int in search result map.") + + val prodScheduleLineId = prodScheduleLineIdInt.toLong() + + val prodScheduleLine = productionScheduleLineRepository.findById(prodScheduleLineId).getOrNull() + ?: throw NoSuchElementException("Production Schedule Line with ID $prodScheduleLineId not found.") + + try { + jobOrderService.jobOrderDetailByPsId(prodScheduleLineId) + } catch (e: NoSuchElementException) { + + // 3. Fetch BOM, handling nullability safely + val item = prodScheduleLine.item + ?: throw IllegalStateException("Item object is missing for Production Schedule Line $prodScheduleLineId.") + + val itemId = item.id + ?: throw IllegalStateException("Item ID is missing for Production Schedule Line $prodScheduleLineId.") + + val bom = bomService.findByItemId(itemId) + ?: throw NoSuchElementException("BOM not found for Item ID $itemId.") + + // 4. Update Prod Schedule Line fields + prodScheduleLine.apply { + // Use bom.outputQty, ensuring it's treated as Double for prodQty + prodQty = bom.outputQty?.toDouble() + ?: throw IllegalStateException("BOM output quantity is null for Item ID $itemId.") + approverId = approver?.id + } + + productionScheduleLineRepository.save(prodScheduleLine) + + // 5. Logging (optional but kept) + logger.info("prodScheduleLine.prodQty: ${prodScheduleLine.prodQty}") + logger.info("bom?.outputQty: ${bom.outputQty} ${bom.outputQtyUom}") + + logger.info("[releaseProdSchedule] prodScheduleLine.needNoOfJobOrder:" + prodScheduleLine.needNoOfJobOrder) + repeat(prodScheduleLine.needNoOfJobOrder) { + // 6. Create Job Order + val joRequest = CreateJobOrderRequest( + bomId = bom.id, // bom is guaranteed non-null here + reqQty = bom.outputQty, // bom is guaranteed non-null here + approverId = approver?.id, + + // CRUCIAL FIX: Use the line ID, not the parent schedule ID + prodScheduleLineId = prodScheduleLine.id!! + ) + + // Assuming createJobOrder returns the created Job Order (jo) + val jo = jobOrderService.createJobOrder(joRequest) + + val createdJobOrderId = jo.id + ?: throw IllegalStateException("Job Order creation failed: returned object ID is null.") + + // 7. Create related job order data + jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(createdJobOrderId) + jobOrderProcessService.createJobOrderProcessesByJoId(createdJobOrderId) + productProcessService.createProductProcessByJobOrderId(createdJobOrderId) + } + + } + + // No need to fetch latest detail inside the loop + } + + return MessageResponse( + id = request.id.toLong(), + message = "Successfully created Job Orders for all production schedule lines (Parent ID: ${request.id}).", + name = "", + code = "", + type = "", + errorPosition = null + // entity = mapOf("prodScheduleLines" to latestDetail?.prodScheduleLines) + ) + } + @Transactional(rollbackFor = [java.lang.Exception::class]) open fun releaseProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse { val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) } val approver = SecurityUtils.getUser().getOrNull() - val proportion = BigDecimal.ONE // request.demandQty.divide(bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP) val isSameQty = request.demandQty.equals(prodScheduleLine.prodQty) // Update Prod Schedule Type @@ -385,107 +485,71 @@ open class ProductionScheduleService( } productionScheduleLineRepository.save(prodScheduleLine) - // Create Job Order - val joRequest = CreateJobOrderRequest( - bomId = bom?.id, - reqQty = request.demandQty, - approverId = approver?.id, - prodScheduleLineId = request.id, - //jobType = null, - ) - - val jo = jobOrderService.createJobOrder(joRequest) - - // Create Job Order Bom Materials - if (bom?.bomMaterials != null) { - // Job Order Bom Material - val jobmRequests = bom.bomMaterials.map { bm -> - val demandQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO - val saleUnit = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } - println("itemId: ${bm.item?.id} | itemNo: ${bm.item?.code} | itemName: ${bm.item?.name} | saleUnit: ${saleUnit?.uom?.udfudesc}") - - val jobm = CreateJobOrderBomMaterialRequest( - joId = jo.id, - itemId = bm.item?.id, - reqQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO, - uomId = saleUnit?.uom?.id - ) - - jobm - } - - if (jobmRequests != null) { - jobOrderBomMaterialService.createJobOrderBomMaterials(jobmRequests) - } - - // Inventory - /* val inventories = bom.bomMaterials.map { bm -> - val demandQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO - - var inventory = bm.item?.id?.let { inventoryRepository.findByItemId(it).getOrNull() } - if (inventory != null) { - inventory.apply { - this.onHoldQty = (this.onHoldQty ?: BigDecimal.ZERO).plus(demandQty) - } - } else { - if (bm.item != null) { - val itemUom = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } - inventory = Inventory().apply { - item = bm.item - onHandQty = BigDecimal.ZERO - unavailableQty = BigDecimal.ZERO - this.onHoldQty = demandQty - uom = itemUom?.uom - status = "unavailable" - } - } - } - - inventory - }.groupBy { it?.item } // Group by item - .mapNotNull { (item, invList) -> - if (invList.isNotEmpty()) { - invList[0]?.apply { - onHoldQty = invList.sumOf { it?.onHoldQty ?: BigDecimal.ZERO } - } - } else { - null - } - } - - inventoryRepository.saveAllAndFlush(inventories) */ - } - - // Create Job Order Process - val jopRequests = bom?.bomProcesses?.map { bp -> - CreateJobOrderProcessRequest( - joId = jo.id, - processId = bp.process?.id, - seqNo = bp.seqNo, + //compare prodQty to bom outputQty + logger.info("request.demandQty:" + request.demandQty); + logger.info("prodScheduleLine.prodQty:" + prodScheduleLine.prodQty); + logger.info("bom?.outputQty:" + bom?.outputQty + "" + bom?.outputQtyUom); + + logger.info("prodScheduleLine.needNoOfJobOrder:" + prodScheduleLine.needNoOfJobOrder) + repeat(prodScheduleLine.needNoOfJobOrder) { + // Create Job Order + val joRequest = CreateJobOrderRequest( + bomId = bom?.id, + reqQty = bom?.outputQty, + approverId = approver?.id, + prodScheduleLineId = request.id ) - } - if (jopRequests != null) { - jobOrderProcessService.createJobOrderProcesses(jopRequests) - } + val jo = jobOrderService.createJobOrder(joRequest) - productProcessService.createProductProcessByJobOrderId(jo.id!!) + logger.info("jo created:" + jo.id!!) + + jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(jo.id!!) + jobOrderProcessService.createJobOrderProcessesByJoId(jo.id!!) + productProcessService.createProductProcessByJobOrderId(jo.id!!) + } + // Get Latest Data // val bomMaterials = prodScheduleLine.id?.let { productionScheduleLineRepository.getBomMaterials(it) } val latestDetail = prodScheduleLine.productionSchedule.id?.let { detailedProdScheduleDetail(it) } return MessageResponse( + id = request.id, name = null, - code = jo.code, + //code = "", + //entity = null, type = null, message = "Success", + code = "", entity = mapOf("prodScheduleLines" to latestDetail?.prodScheduleLines), errorPosition = null + ) + } //====================細排相關 START====================// + open fun searchProdScheduleLine(prodScheduleId: Int): List> { + + val args = mapOf( + "prodScheduleId" to prodScheduleId, + ) + + val sql = """ + SELECT + psl.* + FROM production_schedule ps + LEFT JOIN production_schedule_line psl ON psl.prodScheduleId = ps.id + WHERE ps.deleted = FALSE + AND psl.deleted = FALSE + AND psl.prodScheduleId = :prodScheduleId + """; + + + return jdbcDao.queryForList(sql, args); + + } open fun getDailyProductionCount(assignDate: Int, selectedDate: LocalDateTime): Int { @@ -507,65 +571,164 @@ open class ProductionScheduleService( + " Limit 1" ); return jdbcDao.queryForInt(sql.toString(), args); + } - open fun generateDetailedScheduleByDay(assignDate: Int, selectedDate: LocalDateTime, produceAt: LocalDateTime) { - val detailedScheduleOutputList = ArrayList() + open fun getNeedQty(): List { + val sql = """ + SELECT + i.outputQty, + i.avgQtyLastMonth, + (i.onHandQty -500), + (i.onHandQty -500) * 1.0 / i.avgQtyLastMonth as daysLeft, + i.avgQtyLastMonth * 2 - stockQty as needQty, + i.stockQty, + CASE + WHEN stockQty * 1.0 / i.avgQtyLastMonth <= 1.9 THEN + CEIL((i.avgQtyLastMonth * 1.9 - stockQty) / i.outputQty) + ELSE 0 + END AS needNoOfJobOrder, + CASE + WHEN stockQty * 1.0 / i.avgQtyLastMonth <= 1.9 THEN + CEIL((i.avgQtyLastMonth * 1.9 - stockQty) / i.outputQty) + ELSE 0 + END AS batchNeed, + markDark + markFloat + markDense + markAS as priority, + i.* + FROM + (SELECT + (SELECT + ROUND(AVG(d.dailyQty) * 1.5) + FROM + (SELECT + SUM(dol.qty) AS dailyQty + FROM + delivery_order_line dol + LEFT JOIN delivery_order do ON dol.deliveryOrderId = do.id + WHERE + dol.itemId = items.id + -- AND MONTH(do.estimatedArrivalDate) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH)) + AND do.estimatedArrivalDate >= '2025-12-01' AND do.estimatedArrivalDate < '2025-12-11' + GROUP BY do.estimatedArrivalDate) AS d) AS avgQtyLastMonth, + inventory.onHandQty - 500 AS stockQty, + bom.outputQty, + bom.outputQtyUom, + (SELECT + udfudesc + FROM + delivery_order_line + LEFT JOIN uom_conversion ON delivery_order_line.uomId = uom_conversion.id + WHERE + delivery_order_line.itemId = bom.itemId + LIMIT 1) AS doUom, + items.code, + items.name, + bom.description, + inventory.onHandQty, + bom.itemId, + bom.id AS bomId, + CASE WHEN bom.isDark = 5 THEN 11 + ELSE 0 END as markDark, + CASE WHEN bom.isFloat = 3 THEN 11 + ELSE 0 END as markFloat, + CASE WHEN bom.isDense = 5 THEN 11 + ELSE 0 END as markDense, + CASE WHEN bom.allergicSubstances = 5 THEN 11 + ELSE 0 END as markAS, + inventory.id AS inventoryId + FROM + bom + LEFT JOIN items ON bom.itemId = items.id + LEFT JOIN inventory ON items.id = inventory.itemId + WHERE + bom.itemId != 16771) AS i + WHERE 1 + and i.avgQtyLastMonth is not null + and i.onHandQty is not null + and (i.onHandQty -500) * 1.0 / i.avgQtyLastMonth <= 1.9 + -- and avgQtyLastMonth - (onHandQty - 500) > 0 + + """.trimIndent() + + val rows: List> = jdbcDao.queryForList(sql) + + return rows.map { row -> + NeedQtyRecord().apply { + name = row["name"] as String + id = (row["itemId"] as Number).toLong() + needQty = (row["needQty"] as Number).toDouble() + outputQty = (row["outputQty"] as Number).toDouble() + stockQty = (row["stockQty"] as Number).toDouble() + avgQtyLastMonth = (row["avgQtyLastMonth"] as Number).toDouble() + needNoOfJobOrder = (row["needNoOfJobOrder"] as Number).toLong() + daysLeft = (row["daysLeft"] as Number).toDouble() + batchNeed = row["batchNeed"] as Number - // check the produce date have manual changes. - val manualChange = productionScheduleRepository.findByTypeAndProduceAtAndDeletedIsFalse("detailed", produceAt) - if (manualChange != null) { - return; + } } + } - //increasement available - var idleProductionCount = 22000.0 - getDailyProductionCount(assignDate, selectedDate); - - println("idleProductionCount - " + idleProductionCount); + open fun generateDetailedScheduleByDay(assignDate: Int, selectedDate: LocalDateTime, produceAt: LocalDateTime) { + val detailedScheduleOutputList = ArrayList() + //increasement available + //var warehouseCap = 22000.0 + var machineCap = 10000.0 + var needQtyList = getNeedQty() + + println("needQtyList - " + needQtyList); + //##### The 22000, 10000 machine cap just means the max warehouse storage qty, not production qty cap + //##### The total production qty of the date is 10000 due to machine cap + //##### search all items with bom to consider need or no need production val args = mapOf( "assignDate" to assignDate, "selectedDate" to selectedDate.format(formatter) ) - - val scheduledList: List = getProductionScheduleRecord(args) - - //用缺口決定生產順序 - val productionPriorityMap: HashMap = HashMap(); - //TODO: update to use SQL get shop order record for detailed scheduling (real prodQty and openBal) - for (record in scheduledList) { + val productionPriorityMap: HashMap = HashMap(); + for (record in needQtyList) { println("Object - " + record.toString()); - val tempRecord = record; - tempRecord.prodQty = tempRecord.prodQty * 2; - val difference = - -(tempRecord.targetMinStock + tempRecord.prodQty - tempRecord.estCloseBal) /*TODO: this should be real close bal*/; - productionPriorityMap.put(tempRecord, difference) + //val tempRecord = record; + //val difference = + // -tempRecord.outputQty + val isDark = maxOf(record.isDark.toDouble(), 0.0) + val isFloat = maxOf(record.isFloat.toDouble(), 0.0) + val isDense = maxOf(record.isDense.toDouble(), 0.0) + val allergicSubstances = maxOf(record.allergicSubstances.toDouble(), 0.0) + + val priority = isDark + isFloat + isDense + allergicSubstances + record.itemPriority = priority + productionPriorityMap.put(record, priority) } + //##### all qty should be 包, FG qty/bom outputQty + //sort by difference val sortedEntries = productionPriorityMap.entries.sortedBy { it.value } var accProdCount = 0.0; var fgCount = 0L; - for ((updatedScheduleRecord, totalDifference) in sortedEntries) { + for ((updatedScheduleRecord, priority) in sortedEntries) { //match id with rough schedule record to create new record - val targetRecord = scheduledList.find { it.item.id == updatedScheduleRecord.item.id } - val prodDifference = updatedScheduleRecord.prodQty - (targetRecord?.prodQty ?: 0.0) - - if (idleProductionCount - prodDifference >= 0) { + val targetRecord = needQtyList.find { it.id == updatedScheduleRecord.id } + //??? this should be the bom output Qty * no. of job order needed + val prodDifference = updatedScheduleRecord.outputQty - (targetRecord?.outputQty ?: 0.0) + updatedScheduleRecord.itemPriority = priority; + + + if (machineCap - prodDifference >= 0) { //have enough quoter for adjustment - idleProductionCount -= prodDifference; + machineCap -= prodDifference; detailedScheduleOutputList.add(updatedScheduleRecord) - accProdCount += updatedScheduleRecord.prodQty + accProdCount += updatedScheduleRecord.outputQty fgCount++ } else { println("[INFO] item " + updatedScheduleRecord.name + " have bee skipped"); } } - // Sort detailedScheduleOutputList by item priority - val sortedOutputList = detailedScheduleOutputList.sortedBy { it.weightingRef }.toMutableList() + // Sort detailedScheduleOutputList by item priority: needQty + val sortedOutputList = detailedScheduleOutputList.sortedBy { it.needQty }.toMutableList() // Update itemPriority in the sorted list var tempPriority = 1L @@ -576,9 +739,9 @@ open class ProductionScheduleService( saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount, produceAt) } - + open fun saveDetailedScheduleOutput( - sortedEntries: List, + sortedEntries: List, accProdCount: Double, fgCount: Long, produceAt: LocalDateTime, @@ -593,28 +756,37 @@ open class ProductionScheduleService( tempObj.id = saveProductionScheduleToDatabase(tempObj); for (detailedScheduleRecord in sortedEntries) { - saveDetailedScheduleLineToDatabase(tempObj.id ?: -1, detailedScheduleRecord) + if(detailedScheduleRecord.batchNeed.toInt() > 0) + saveDetailedScheduleLineToDatabase(tempObj.id ?: -1, detailedScheduleRecord) } } - private fun saveDetailedScheduleLineToDatabase(parentId: Long, detailedScheduleObj: ProductionScheduleRecord) { + private fun saveDetailedScheduleLineToDatabase(parentId: Long, detailedScheduleObj: NeedQtyRecord) { try { val prodSchedule = productionScheduleRepository.findById(parentId).get() - val item = detailedScheduleObj.item.id?.let { itemService.findById(it) } ?: Items() + val item = detailedScheduleObj.id?.let { itemService.findById(it) } ?: Items() var savedItem = ProductionScheduleLine() + print("###detailedScheduleObj.stockQty:" + detailedScheduleObj.stockQty) savedItem.id = -1; // savedItem.prodScheduleId = parentId; savedItem.productionSchedule = prodSchedule; savedItem.item = item; - savedItem.lastMonthAvgSales = detailedScheduleObj.lastMonthAvgSales ?: 0.0; + savedItem.lastMonthAvgSales = detailedScheduleObj.avgQtyLastMonth ?: 0.0; savedItem.refScheduleId = detailedScheduleObj.id; savedItem.approverId = null; - savedItem.estCloseBal = detailedScheduleObj.estCloseBal; - savedItem.prodQty = detailedScheduleObj.prodQty + savedItem.estCloseBal = detailedScheduleObj.outputQty; + savedItem.prodQty = detailedScheduleObj.outputQty savedItem.type = "detailed" - savedItem.assignDate = detailedScheduleObj.assignDate; - savedItem.weightingRef = detailedScheduleObj.weightingRef - savedItem.itemPriority = detailedScheduleObj.itemPriority + savedItem.assignDate = LocalDateTime.now().dayOfMonth.toLong(); + savedItem.weightingRef = detailedScheduleObj.needQty + savedItem.itemPriority = detailedScheduleObj.needQty.toLong() + savedItem.outputQty = detailedScheduleObj.outputQty + savedItem.avgQtyLastMonth = detailedScheduleObj.avgQtyLastMonth + savedItem.stockQty = detailedScheduleObj.stockQty + savedItem.daysLeft = detailedScheduleObj.daysLeft + savedItem.batchNeed = detailedScheduleObj.batchNeed.toInt() + savedItem.needNoOfJobOrder = detailedScheduleObj.needNoOfJobOrder.toInt() + productionScheduleLineRepository.saveAndFlush(savedItem) } catch (e: Exception) { @@ -622,6 +794,38 @@ open class ProductionScheduleService( } } //====================細排相關 END====================// + open class NeedQtyRecord { + //SQL record in general with item name + open var name: String = "" //item name + open var avgQtyLastMonth: Double = 0.0 + open var needNoOfJobOrder: Number = 0 + open var id: Long = 0 + open var needQty: Double = 0.0 + open var outputQty: Double = 0.0 + open var itemPriority: Number = 0 + open var isDark: Number = 0 + open var isFloat: Number = 0 + open var isDense: Number = 0 + open var allergicSubstances: Number = 0 + open var stockQty: Double = 0.0 + open var daysLeft: Double = 0.0 + open var batchNeed: Number = 0 + + override fun toString(): String { + return "NeedQtyRecord(name=${name}," + + " avgQtyLastMonth=$avgQtyLastMonth" + + " stockQty=$stockQty" + + " itemId=$id" + + " needNoOfJobOrder=$needNoOfJobOrder" + + " outputQty=$outputQty" + + " needQty=$needQty" + + " itemPriority=$itemPriority"+ + " isDark=$isDark" + + " isFloat=$isFloat" + + " isDense=$isDense" + + " allergicSubstances=$allergicSubstances" + } + } open class ProductionScheduleRecord : ProductionScheduleLine() { //SQL record in general with item name diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt index cebd7ad..42ac896 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt @@ -13,6 +13,7 @@ import com.ffii.fpsms.modules.master.service.ProductionScheduleService.RoughSche import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleLineRequest import com.ffii.fpsms.modules.master.web.models.SearchProdScheduleRequest +import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleRequest import jakarta.servlet.http.HttpServletRequest import jakarta.validation.Valid import org.springframework.web.bind.annotation.* @@ -65,6 +66,7 @@ class ProductionScheduleController( @GetMapping("/detail/detailed/{id}") fun getDetailedProdScheduleDetail(@PathVariable id: Long): DetailedProdScheduleWithLine { + print("getDetailedProdScheduleDetail################") return productionScheduleService.detailedProdScheduleDetail(id) } @@ -78,8 +80,14 @@ class ProductionScheduleController( return productionScheduleService.releaseProdScheduleLine(request) } + @PostMapping("/detail/detailed/release") + fun releaseProdSchedule(@Valid @RequestBody request: ReleaseProdScheduleRequest): MessageResponse { + return productionScheduleService.releaseProdSchedule(request) + } + @GetMapping("/getRecordByPage") fun allProdSchedulesByPage(@ModelAttribute request: SearchProdScheduleRequest): RecordsRes { + print("allProdSchedulesByPage############") return productionScheduleService.allProdSchedulesByPage(request); } @@ -97,11 +105,12 @@ class ProductionScheduleController( fun generateDetailSchedule(request: HttpServletRequest?): Int { try { // For test - val genDate = request?.getParameter("genDate")?.let { LocalDate.parse(it).atStartOfDay() } + //val genDate = request?.getParameter("genDate")?.let { LocalDate.parse(it).atStartOfDay() } + val genDate = LocalDateTime.now() val today = LocalDateTime.now() - val latestRoughScheduleAt = productionScheduleService.getLatestScheduleAt("rough"); + //val latestRoughScheduleAt = productionScheduleService.getLatestScheduleAt("rough"); // val latestRoughScheduleAt = productionScheduleService.getSecondLatestRoughScheduleAt("rough"); // val latestRoughScheduleAt = productionScheduleService.getScheduledAtByDate(genDate?.toLocalDate() ?: today.toLocalDate()); // assume schedule period is monday to sunday @@ -115,8 +124,11 @@ class ProductionScheduleController( // } else assignDate.toInt() //TODO: update this to receive selectedDate and assignDate from schedule // productionScheduleService.generateDetailedScheduleByDay(1, LocalDateTime.of(2025,6,25,0,0,0)) - println("genDate: $genDate | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ") - productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt ?: LocalDateTime.now(), genDate ?: today) + //println("genDate: $genDate | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ") + //productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt ?: LocalDateTime.now(), genDate ?: today) + println("Start generate detailed Schedule") + productionScheduleService.generateDetailedScheduleByDay(assignDate, today, today) + return 200 } catch (e: Exception) { throw RuntimeException("Error generate schedule: ${e.message}", e) diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/models/ReleaseProdScheduleRequest.kt b/src/main/java/com/ffii/fpsms/modules/master/web/models/ReleaseProdScheduleRequest.kt new file mode 100644 index 0000000..c244f9e --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/web/models/ReleaseProdScheduleRequest.kt @@ -0,0 +1,9 @@ +package com.ffii.fpsms.modules.master.web.models + +import jakarta.validation.constraints.NotNull +import java.math.BigDecimal + +data class ReleaseProdScheduleRequest( + @field:NotNull(message = "Id cannot be null") + val id: Int +) diff --git a/src/main/resources/db/changelog/changes/20251211_01_Fai/01_add_fields_to_psl.sql b/src/main/resources/db/changelog/changes/20251211_01_Fai/01_add_fields_to_psl.sql new file mode 100644 index 0000000..a2ae920 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251211_01_Fai/01_add_fields_to_psl.sql @@ -0,0 +1,9 @@ +-- liquibase formatted sql +-- changeset Fai:add_fields_to_psl + +ALTER TABLE `fpsmsdb`.`production_schedule_line` +ADD COLUMN `outputQty` DECIMAL(16,2) NULL AFTER `weightingRef`, +ADD COLUMN `stockQty` DECIMAL(16,2) NULL AFTER `outputQty`, +ADD COLUMN `daysLeft` DECIMAL(16,2) NULL AFTER `stockQty`, +ADD COLUMN `batchNeed` INT NULL AFTER `daysLeft`, +ADD COLUMN `needNoOfJobOrder` INT NULL AFTER `batchNeed`; diff --git a/src/main/resources/db/changelog/changes/20251211_01_Fai/02_add_fields_to_psl.sql b/src/main/resources/db/changelog/changes/20251211_01_Fai/02_add_fields_to_psl.sql new file mode 100644 index 0000000..1803c36 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251211_01_Fai/02_add_fields_to_psl.sql @@ -0,0 +1,5 @@ +-- liquibase formatted sql +-- changeset Fai:add_avgQtyLastMonth_to_psl + +ALTER TABLE `fpsmsdb`.`production_schedule_line` +ADD COLUMN `avgQtyLastMonth` DECIMAL(16,2) NULL;