diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt index 1b18c2d..60b259f 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt @@ -6,6 +6,7 @@ import com.ffii.fpsms.modules.master.entity.* import com.ffii.fpsms.modules.master.web.models.MessageResponse import org.springframework.stereotype.Service import java.time.LocalDateTime +import java.time.format.DateTimeFormatter import java.util.* import kotlin.collections.component1 import kotlin.collections.component2 @@ -19,7 +20,190 @@ open class ProductionScheduleService( private val productionScheduleLineRepository: ProductionScheduleLineRepository, ): AbstractBaseEntityService(jdbcDao, productionScheduleRepository) { // do mapping with projection + open val formatter: DateTimeFormatter? = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") + //====================細排相關 START====================// + + open fun getDailyProductionCount(assignDate: Int, selectedDate: LocalDateTime): Int { + + val args = mapOf( + "assignDate" to assignDate, + "selectedDate" to selectedDate.format(formatter) + ) + + val sql = StringBuilder( + " SELECT " + + " SUM(psl.prodQty) AS dailyProdCount " + + " FROM production_schedule ps " + + " LEFT JOIN production_schedule_line psl ON psl.prodScheduleId = ps.id " + + " WHERE ps.deleted = FALSE " + + " AND psl.deleted = FALSE " + + " AND DATE(ps.scheduleAt) LIKE DATE(:selectedDate) " + + " AND psl.assignDate = :assignDate " + + " GROUP BY ps.id " + ); + return jdbcDao.queryForInt(sql.toString(), args); + } + + open fun generateDetailScheduleByDay(assignDate: Int, selectedDate: LocalDateTime) { + val detailScheduleOutputList = ArrayList() + + //increasement available + var idleProductionCount = 22000.0 - getDailyProductionCount(assignDate, selectedDate); + + println("idleProductionCount - " + idleProductionCount); + + 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 detail scheduling (real prodQty and openBal) + for (record in scheduledList){ + 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) + } + + //sort by difference + val sortedEntries = productionPriorityMap.entries.sortedBy { it.value } + + var accProdCount = 0.0; + var fgCount = 0L; + + for ((updatedScheduleRecord, totalDifference) in sortedEntries) { + //match id with rough schedule record to create new record + val targetRecord = scheduledList.find { it.itemId == updatedScheduleRecord.itemId } + val prodDifference = updatedScheduleRecord.prodQty - (targetRecord?.prodQty ?: 0.0) + + if(idleProductionCount - prodDifference >= 0){ + //have enough quoter for adjustment + idleProductionCount -= prodDifference; + detailScheduleOutputList.add(updatedScheduleRecord) + accProdCount += updatedScheduleRecord.prodQty + fgCount++ + } + else{ + println("[INFO] item " + updatedScheduleRecord.name + " have bee skipped"); + } + } + + saveDetailScheduleOutput(detailScheduleOutputList, accProdCount, fgCount) + + //return detailScheduleOutput + } + + open fun saveDetailScheduleOutput(sortedEntries: List, accProdCount: Double, fgCount: Long) { + val tempObj = ProductionSchedule() + tempObj.id = -1; + tempObj.scheduleAt = LocalDateTime.now() + tempObj.totalFGType = fgCount; + tempObj.totalEstProdCount = accProdCount; + tempObj.id = saveProductionScheduleToDatabase(tempObj); + tempObj.type = "detailed" + + for ( detailedScheduleRecord in sortedEntries) { + saveDetailedScheduleLineToDatabase(tempObj.id ?: -1, detailedScheduleRecord) + } + } + + + private fun saveDetailedScheduleLineToDatabase(parentId: Long, detailedScheduleObj: ProductionScheduleRecord) { + try { + var savedItem = ProductionScheduleLine() + savedItem.id = -1; + savedItem.prodScheduleId = parentId; + savedItem.itemId = detailedScheduleObj.itemId ?: -1; + savedItem.lastMonthAvgSales = detailedScheduleObj.lastMonthAvgSales ?: 0.0; + savedItem.refScheduleId = detailedScheduleObj.id; + savedItem.approverId = null; + savedItem.estCloseBal = detailedScheduleObj.estCloseBal; + savedItem.prodQty = detailedScheduleObj.prodQty + savedItem.type = "detailed" + savedItem.assignDate = detailedScheduleObj.assignDate; + savedItem.itemPriority = detailedScheduleObj.itemPriority //TODO: to be updated + productionScheduleLineRepository.saveAndFlush(savedItem) + + } catch (e: Exception) { + throw RuntimeException("Error saving production schedule line: ${e.message}", e) + } + } + //====================細排相關 END====================// + + open class ProductionScheduleRecord : ProductionScheduleLine() { + //SQL record in general with item name + open var name: String = "" //item name + open var targetMinStock: Double = 0.0; + + override fun toString(): String { + return "ProductionScheduleRecord(name='$name', targetMinStock=$targetMinStock)" + } + + } + + open fun getProductionScheduleRecord(args: Map): List { + + val sql = StringBuilder( + " SELECT " + + " psl.id, " + + " psl.itemPriority, " + + " psl.itemId, " + + " i.name, " + + " psl.lastMonthAvgSales, " + + " psl.lastMonthAvgSales*2 AS targetMinStock, " + + " psl.prodQty, " + + " psl.estCloseBal, " + + " psl.`type`, " + + " psl.assignDate " + + " FROM production_schedule ps " + + " LEFT JOIN production_schedule_line psl ON psl.prodScheduleId = ps.id " + + " LEFT JOIN items i ON psl.itemId = i.id " + + " WHERE ps.deleted = FALSE " + + " AND psl.deleted = FALSE " + ); + + if(args.containsKey("selectedDate")){ + sql.append(" AND DATE(ps.scheduleAt) LIKE DATE(:selectedDate) "); + } + if(args.containsKey("name")){ + sql.append(" AND i.name LIKE :name "); + } + if(args.containsKey("assignDate")){ + sql.append(" AND psl.assignDate = :assignDate "); + } + if(args.containsKey("id")){ + sql.append(" AND i.id = :id "); + } + + sql.append(" ORDER BY psl.assignDate, psl.itemPriority ASC "); + + val resultList = jdbcDao.queryForList(sql.toString(), args, ); + + // Convert the list of maps to a list of ProductionScheduleRecord + return resultList.map { row -> + ProductionScheduleRecord().apply { + this.id = row["id"].toString().toLong() + this.itemPriority = row["itemPriority"].toString().toLong() + this.itemId = row["itemId"].toString().toLong() + this.name = row["name"] as String + this.lastMonthAvgSales = row["lastMonthAvgSales"].toString().toDouble() + this.targetMinStock = row["targetMinStock"].toString().toDouble() // Ensure correct type + this.prodQty = row["prodQty"].toString().toDouble() + this.estCloseBal = row["estCloseBal"].toString().toDouble() + this.type = row["type"] as String + this.assignDate = row["assignDate"].toString().toLong() + } + } + } + + //====================粗排相關 START====================// open fun convertToFinishedGoodList(): ArrayList { val roughScheduleList = itemService.getRoughScheduleList() // Retrieve the list from your existing method val finishedGoodList = ArrayList() @@ -193,7 +377,7 @@ open class ProductionScheduleService( tempObj.scheduleAt = LocalDateTime.now() tempObj.totalFGType = fgCount; tempObj.totalEstProdCount = accProdCount; - tempObj.id = saveRoughScheduleToDatabase(tempObj); + tempObj.id = saveProductionScheduleToDatabase(tempObj); tempObj.type = "rough" for ((roughScheduleRecord, totalDifference) in sortedEntries) { @@ -201,7 +385,7 @@ open class ProductionScheduleService( } } - private fun saveRoughScheduleToDatabase(newRecord: ProductionSchedule): Long?{ + private fun saveProductionScheduleToDatabase(newRecord: ProductionSchedule): Long?{ try { val savedItem = productionScheduleRepository.saveAndFlush(newRecord) return savedItem.id @@ -233,6 +417,7 @@ open class ProductionScheduleService( } } + //====================粗排相關 END====================// open class FinishedGood { //var fgDetail: FinishedGood? = null 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 f8295e9..055481a 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 @@ -7,6 +7,7 @@ import com.ffii.fpsms.modules.master.service.ProductionScheduleService.FinishedG import com.ffii.fpsms.modules.master.service.ProductionScheduleService.RoughScheduleObj import jakarta.servlet.http.HttpServletRequest import org.springframework.web.bind.annotation.* +import java.time.LocalDateTime import java.util.ArrayList import java.util.HashMap import kotlin.collections.component1 @@ -23,6 +24,16 @@ class ProductionScheduleController( // return uomConversionService.allItems() // } + @RequestMapping(value = ["/testDetailSchedule"], method = [RequestMethod.GET]) + fun generateDetailSchedule(request: HttpServletRequest?): Int { + try { + productionScheduleService.generateDetailScheduleByDay(1, LocalDateTime.of(2025,5,28,0,0,0)) + return 200 + } catch (e: Exception) { + throw RuntimeException("Error generate schedule: ${e.message}", e) + } + } + @RequestMapping(value = ["/testRoughSchedule"], method = [RequestMethod.GET]) fun generateRoughSchedule(request: HttpServletRequest?): List> { try {