diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/SchedulingService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/SchedulingService.kt new file mode 100644 index 0000000..34038c6 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/service/SchedulingService.kt @@ -0,0 +1,204 @@ +package com.ffii.fpsms.modules.master.service + +import com.ffii.core.support.AbstractBaseEntityService +import com.ffii.core.support.JdbcDao +import com.ffii.fpsms.modules.master.entity.* +import com.ffii.fpsms.modules.master.web.models.MessageResponse +import org.springframework.core.io.ClassPathResource +import org.springframework.stereotype.Service +import com.google.gson.Gson +import org.springframework.transaction.annotation.Transactional +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.nio.charset.StandardCharsets +import java.sql.DriverManager.println +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.* +import java.util.regex.Pattern +import kotlin.math.roundToLong +import kotlin.math.ceil + +@Service +open class SchedulingService( + private val jdbcDao: JdbcDao, + private val uomConversionRepository: UomConversionRepository, +): AbstractBaseEntityService(jdbcDao, uomConversionRepository) { + // do mapping with projection + + open fun generateRoughScheduleByWeek(fgList : ArrayList): HashMap { + val roughScheduleOutput: HashMap = HashMap(); + + for(fg:FinishedGood in fgList) { + val roughScheduleRecord: RoughScheduleObj = roughScheduleByItem(fg); + roughScheduleOutput.put(roughScheduleRecord, roughScheduleRecord.totalDifference); + } + + //完成初步fg粗排後檢查調整 + for (i in 5 until 12) { + var accProductionCount = 0L; + + // Sort by totalDifference before processing + val sortedEntries = roughScheduleOutput.entries.sortedBy { it.value } + + for ((roughScheduleRecord, totalDifference) in sortedEntries) { + kotlin.io.println("[RUN" + i + "][index:" + totalDifference + "] - " + roughScheduleRecord.fgDetail?.name); + + if (accProductionCount + ceil(totalDifference).toLong() < 22000) { //沒有超過每日產量 + accProductionCount += ceil(roughScheduleRecord.productionSchedule[i]).toLong() //分開日子算 + + if(roughScheduleRecord.totalForgoneCount > 0.0){ + //優先解決拖欠的數量 + if(accProductionCount + ceil(roughScheduleRecord.totalForgoneCount).toLong() <22000){ + //可以全做 + accProductionCount += ceil(roughScheduleRecord.totalForgoneCount).toLong(); + roughScheduleRecord.totalForgoneCount = 0.0; + } + else{ + //只能做部分 + var maxExtraProduction = 22000L - accProductionCount; + roughScheduleRecord.totalForgoneCount = roughScheduleRecord.totalForgoneCount - maxExtraProduction; + roughScheduleRecord.productionSchedule[i] = roughScheduleRecord.productionSchedule[i] + maxExtraProduction; + accProductionCount += maxExtraProduction; + + //update close balance + for (j in i until 12){ + roughScheduleRecord.closeBalance[j] = roughScheduleRecord.closeBalance[j] + maxExtraProduction; + } + } + } + + roughScheduleRecord.totalDifference += roughScheduleRecord.productionSchedule[i] + roughScheduleOutput[roughScheduleRecord] = roughScheduleRecord.totalDifference + } + else{//超過每日產量 + var maxExtraProduction = 22000L - accProductionCount; + roughScheduleRecord.totalForgoneCount = roughScheduleRecord.totalForgoneCount + roughScheduleRecord.productionSchedule[i] - maxExtraProduction; + roughScheduleRecord.productionSchedule[i] = maxExtraProduction.toDouble(); + accProductionCount += maxExtraProduction; + + //update close balance + roughScheduleRecord.closeBalance[i] = roughScheduleRecord.closeBalance[i] - roughScheduleRecord.productionSchedule[i] + maxExtraProduction.toDouble(); + for (j in i+1 until 12){ + roughScheduleRecord.closeBalance[j] = roughScheduleRecord.closeBalance[j] - roughScheduleRecord.productionSchedule[i] + maxExtraProduction.toDouble(); + } + + // Update totalDifference in roughScheduleRecord + roughScheduleRecord.totalDifference += roughScheduleRecord.productionSchedule[i] + maxExtraProduction.toDouble(); + roughScheduleOutput[roughScheduleRecord] = roughScheduleRecord.totalDifference + } + + kotlin.io.println("========================================"); + } + } + + return roughScheduleOutput + } + + open fun roughScheduleByItem(fg: FinishedGood): RoughScheduleObj{ + val minStockCount: Double = fg.lastMonthAvgSalesCount * 1.5 + val stockDifference: Double = minStockCount - fg.openBalance + val productionSchedule: Array = Array(12, { 0.0 }) + + //首日粗排產量 —> 取結果,或是fg本身生產上限,並不可小於0 + productionSchedule[0] = Math.max( + Math.min( + (stockDifference + fg.lastMonthAvgSalesCount), + fg.fgProductionLimit + ), + 0.0 + ) + + val closeBalance: Array = Array(12, { 0.0 }) + closeBalance[0] = fg.openBalance + productionSchedule[0] - fg.lastMonthAvgSalesCount; + + for (i in 1 until 12){ + //最少庫存-closeBalance 有可能小於0,所以必須要確保輸出>=0 + productionSchedule[i] = Math.max( + Math.min( + (minStockCount - closeBalance[i-1] + fg.lastMonthAvgSalesCount), + fg.fgProductionLimit + ), + 0.0 + ) + closeBalance[i] = closeBalance[i-1] + productionSchedule[i] - fg.lastMonthAvgSalesCount; + } + + var totalProductionCount: Double = 0.0 + + for (i in 0 until 12) { + totalProductionCount = totalProductionCount + productionSchedule[i]; + } + + val totalDifference: Double = -(totalProductionCount - fg.openBalance); + + + return RoughScheduleObj( + fg, + productionSchedule, + closeBalance, + totalDifference, + totalProductionCount, + 0.0 + ); + } + + open class FinishedGood { + //var fgDetail: FinishedGood? = null + var id: Long? = null + var name: String? = null + var receipeId: Long? = null + var openBalance: Double = 0.0 + var lastMonthAvgSalesCount: Double = 0.0 + var fgProductionLimit: Double = 22000.0 + + constructor( + id: Long?, + name: String?, + receipeId: Long?, + openBalance: Double, + lastMonthAvgSalesCount: Double, + fgProductionLimit: Double + ) { + this.id = id + this.name = name + this.receipeId = receipeId + this.openBalance = openBalance + this.lastMonthAvgSalesCount = lastMonthAvgSalesCount + this.fgProductionLimit = fgProductionLimit + } + } + + class RoughScheduleObj { + var fgDetail: FinishedGood? = null + var productionSchedule: Array = Array(12, { 0.0 }) + var closeBalance: Array = Array(12) { 0.0 }; + var totalDifference: Double = 0.0 + var totalProductionCount: Double = 0.0 + var totalForgoneCount: Double = 0.0 + + constructor( + fgDetail: FinishedGood?, + productionSchedule: Array, + closeBalance: Array, + totalDifference: Double, + totalProductionCount: Double, + totalForgoneCount: Double + ) { + this.fgDetail = fgDetail + this.productionSchedule = productionSchedule + this.closeBalance = closeBalance + this.totalDifference = totalDifference + this.totalProductionCount = totalProductionCount + this.totalForgoneCount = totalForgoneCount + } + + override fun toString(): String { + return "RoughScheduleObj(fgDetail=$fgDetail, productionSchedule=${productionSchedule.contentToString()}, closeBalance=${closeBalance.contentToString()}, totalDifference=$totalDifference, totalProductionCount=$totalProductionCount, totalForgoneCount=$totalForgoneCount)" + } + + + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/UomConversionController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/UomConversionController.kt index 64d9912..3087b02 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/UomConversionController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/UomConversionController.kt @@ -1,16 +1,23 @@ package com.ffii.fpsms.modules.master.web import com.ffii.fpsms.modules.master.entity.UomConversion +import com.ffii.fpsms.modules.master.service.SchedulingService +import com.ffii.fpsms.modules.master.service.SchedulingService.FinishedGood +import com.ffii.fpsms.modules.master.service.SchedulingService.RoughScheduleObj import com.ffii.fpsms.modules.master.service.UomConversionService import jakarta.servlet.http.HttpServletRequest import org.springframework.web.bind.annotation.* import java.util.ArrayList +import java.util.HashMap +import kotlin.collections.component1 +import kotlin.collections.component2 @RestController @RequestMapping("/uomConversion") class UomConversionController( - private val uomConversionService: UomConversionService + private val uomConversionService: UomConversionService, + private val schedulingService: SchedulingService ) { // @GetMapping // fun allItems(): List { @@ -29,4 +36,46 @@ class UomConversionController( throw RuntimeException("Error converting BOM: ${e.message}", e) } } + + @RequestMapping(value = ["/testRoughSchedule"], method = [RequestMethod.GET]) + fun generateRoughSchedule(request: HttpServletRequest?): HashMap { + try { + val demoFGList = arrayListOf( + FinishedGood( + 1, + "豆腐花", + 3, + 91.0, + 31.0, + 3000.0 + ), + FinishedGood( + 2, + "中湯包", + 4, + 100.0, + 72.5, + 3000.0 + ), + FinishedGood( + 3, + "苦瓜炒蛋", + 3, + 700.0, + 300.5, + 22000.0 + ), + ); + + val result: HashMap = schedulingService.generateRoughScheduleByWeek(demoFGList); + val sortedEntries = result.entries.sortedBy { it.value } + + for ((roughScheduleRecord, totalDifference) in result) { + println("[totalDifference:" + sortedEntries + "] - " + sortedEntries.toString()) + } + return result; + } catch (e: Exception) { + throw RuntimeException("Error genereate schedule: ${e.message}", e) + } + } } \ No newline at end of file