|
@@ -0,0 +1,294 @@ |
|
|
|
|
|
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.stereotype.Service |
|
|
|
|
|
import java.time.LocalDateTime |
|
|
|
|
|
import java.util.* |
|
|
|
|
|
import kotlin.collections.component1 |
|
|
|
|
|
import kotlin.collections.component2 |
|
|
|
|
|
import kotlin.math.ceil |
|
|
|
|
|
|
|
|
|
|
|
@Service |
|
|
|
|
|
open class ProductionScheduleService( |
|
|
|
|
|
private val jdbcDao: JdbcDao, |
|
|
|
|
|
private val itemService: ItemsService, |
|
|
|
|
|
private val productionScheduleRepository: ProductionScheduleRepository, |
|
|
|
|
|
private val productionScheduleLineRepository: ProductionScheduleLineRepository, |
|
|
|
|
|
): AbstractBaseEntityService<ProductionSchedule, Long, ProductionScheduleRepository>(jdbcDao, productionScheduleRepository) { |
|
|
|
|
|
// do mapping with projection |
|
|
|
|
|
|
|
|
|
|
|
open fun convertToFinishedGoodList(): ArrayList<FinishedGood> { |
|
|
|
|
|
val roughScheduleList = itemService.getRoughScheduleList() // Retrieve the list from your existing method |
|
|
|
|
|
val finishedGoodList = ArrayList<FinishedGood>() |
|
|
|
|
|
|
|
|
|
|
|
for (item in roughScheduleList) { |
|
|
|
|
|
val finishedGood = FinishedGood( |
|
|
|
|
|
id = item["itemId"].toString().toLong(), // Cast to Long |
|
|
|
|
|
name = item["itemName"] as String?, // Cast to String |
|
|
|
|
|
receipeId = 0, // Assuming itemNo is a Long |
|
|
|
|
|
openBalance = 0.0, // Set default value or calculate if needed |
|
|
|
|
|
lastMonthAvgSalesCount = item["lastMonthAvgSalesCount"].toString().toDouble(), // Cast to Double |
|
|
|
|
|
fgProductionLimit = item["fgProductionLimit"].toString().toDouble() // Default production limit |
|
|
|
|
|
) |
|
|
|
|
|
finishedGoodList.add(finishedGood) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return finishedGoodList |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
open fun generateRoughScheduleByWeek(fgList : ArrayList<FinishedGood>): HashMap<RoughScheduleObj, Double> { |
|
|
|
|
|
val roughScheduleOutput: HashMap<RoughScheduleObj, Double> = HashMap(); |
|
|
|
|
|
|
|
|
|
|
|
var itemCount = 1 |
|
|
|
|
|
for(fg:FinishedGood in fgList) { |
|
|
|
|
|
val roughScheduleRecord: RoughScheduleObj = roughScheduleByItem(fg); |
|
|
|
|
|
itemCount++ |
|
|
|
|
|
roughScheduleOutput.put(roughScheduleRecord, roughScheduleRecord.totalDifference); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//完成初步fg粗排後檢查調整 |
|
|
|
|
|
for (i in 0 until 12) { |
|
|
|
|
|
var accProductionCount = 0L; |
|
|
|
|
|
|
|
|
|
|
|
// Sort by totalDifference before processing |
|
|
|
|
|
val sortedEntries = roughScheduleOutput.entries.sortedBy { it.value } |
|
|
|
|
|
|
|
|
|
|
|
//原排產 |
|
|
|
|
|
var itemCount = 1L; |
|
|
|
|
|
for ((roughScheduleRecord, totalDifference) in sortedEntries) { |
|
|
|
|
|
//記錄粗排優先順序(以最少庫存缺口最大優先) |
|
|
|
|
|
roughScheduleRecord.itemPriority[i] = itemCount; |
|
|
|
|
|
itemCount++; |
|
|
|
|
|
|
|
|
|
|
|
if (accProductionCount + ceil(roughScheduleRecord.productionSchedule[i]).toLong() <= 22000) { //沒有超過每日產量 |
|
|
|
|
|
accProductionCount += ceil(roughScheduleRecord.productionSchedule[i]).toLong() //分開日子算 |
|
|
|
|
|
|
|
|
|
|
|
roughScheduleRecord.totalDifference += roughScheduleRecord.productionSchedule[i] |
|
|
|
|
|
roughScheduleOutput[roughScheduleRecord] = roughScheduleRecord.totalDifference |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
else{//超過每日產量 |
|
|
|
|
|
var maxExtraProduction = Math.max((22000L - accProductionCount),0L); |
|
|
|
|
|
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 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//解決拖欠的數量 |
|
|
|
|
|
for ((roughScheduleRecord, totalDifference) in sortedEntries){ |
|
|
|
|
|
if (accProductionCount + ceil(roughScheduleRecord.productionSchedule[i]).toLong() <= 22000) { //沒有超過每日產量 |
|
|
|
|
|
if(roughScheduleRecord.totalForgoneCount > 0.0){ |
|
|
|
|
|
if(accProductionCount + ceil(roughScheduleRecord.totalForgoneCount).toLong() <=22000){ |
|
|
|
|
|
//可以全做 |
|
|
|
|
|
roughScheduleRecord.productionSchedule[i] = roughScheduleRecord.productionSchedule[i] + ceil(roughScheduleRecord.totalForgoneCount).toLong(); |
|
|
|
|
|
|
|
|
|
|
|
accProductionCount += ceil(roughScheduleRecord.totalForgoneCount).toLong(); |
|
|
|
|
|
|
|
|
|
|
|
roughScheduleRecord.totalDifference += roughScheduleRecord.totalForgoneCount; |
|
|
|
|
|
roughScheduleOutput[roughScheduleRecord] = roughScheduleRecord.totalDifference |
|
|
|
|
|
|
|
|
|
|
|
//update close balance |
|
|
|
|
|
for (j in i until 12){ |
|
|
|
|
|
roughScheduleRecord.closeBalance[j] = roughScheduleRecord.closeBalance[j] + roughScheduleRecord.totalForgoneCount; |
|
|
|
|
|
} |
|
|
|
|
|
roughScheduleRecord.totalForgoneCount = 0.0; |
|
|
|
|
|
} |
|
|
|
|
|
else{ |
|
|
|
|
|
//只能做部分 |
|
|
|
|
|
var maxExtraProduction = Math.max((22000L - accProductionCount), 0L); |
|
|
|
|
|
roughScheduleRecord.totalForgoneCount = Math.max((roughScheduleRecord.totalForgoneCount - maxExtraProduction), 0.0); |
|
|
|
|
|
|
|
|
|
|
|
roughScheduleRecord.productionSchedule[i] = roughScheduleRecord.productionSchedule[i] + maxExtraProduction; |
|
|
|
|
|
accProductionCount += maxExtraProduction; |
|
|
|
|
|
roughScheduleRecord.totalDifference += maxExtraProduction; |
|
|
|
|
|
|
|
|
|
|
|
//update close balance |
|
|
|
|
|
for (j in i until 12){ |
|
|
|
|
|
roughScheduleRecord.closeBalance[j] = roughScheduleRecord.closeBalance[j] + maxExtraProduction; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//debug use |
|
|
|
|
|
for ((roughScheduleRecord, totalDifference) in sortedEntries){ |
|
|
|
|
|
println("[RUN" + i + "][index:" + totalDifference + "] - " + roughScheduleRecord.fgDetail?.name + " - " + roughScheduleRecord.productionSchedule[i]); |
|
|
|
|
|
} |
|
|
|
|
|
println("Total Production Count $accProductionCount"); |
|
|
|
|
|
println("========================================"); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return roughScheduleOutput |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
open fun roughScheduleByItem(fg: FinishedGood): RoughScheduleObj{ |
|
|
|
|
|
val minStockCount: Double = fg.lastMonthAvgSalesCount * 2 |
|
|
|
|
|
val stockDifference: Double = minStockCount - fg.openBalance |
|
|
|
|
|
val productionSchedule: Array<Double> = Array<Double>(12, { 0.0 }) |
|
|
|
|
|
|
|
|
|
|
|
//首日粗排產量 —> 取結果,或是fg本身生產上限,並不可小於0 |
|
|
|
|
|
productionSchedule[0] = Math.max( |
|
|
|
|
|
ceil(Math.min( |
|
|
|
|
|
(stockDifference + fg.lastMonthAvgSalesCount), |
|
|
|
|
|
fg.fgProductionLimit |
|
|
|
|
|
)), |
|
|
|
|
|
0.0 |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
val closeBalance: Array<Double> = Array<Double>(12, { 0.0 }) |
|
|
|
|
|
closeBalance[0] = /*Math.floor(*/fg.openBalance + productionSchedule[0] - fg.lastMonthAvgSalesCount/*)*/; |
|
|
|
|
|
|
|
|
|
|
|
for (i in 1 until 12){ |
|
|
|
|
|
//最少庫存-closeBalance 有可能小於0,所以必須要確保輸出>=0 |
|
|
|
|
|
productionSchedule[i] = Math.max( |
|
|
|
|
|
ceil(Math.min( |
|
|
|
|
|
(minStockCount - closeBalance[i-1] + fg.lastMonthAvgSalesCount), |
|
|
|
|
|
fg.fgProductionLimit |
|
|
|
|
|
)), |
|
|
|
|
|
0.0 |
|
|
|
|
|
) |
|
|
|
|
|
closeBalance[i] = /*Math.floor(*/closeBalance[i-1] + productionSchedule[i] - fg.lastMonthAvgSalesCount/*)*/; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var totalProductionCount: Double = 0.0 |
|
|
|
|
|
|
|
|
|
|
|
for (i in 5 until 12) { |
|
|
|
|
|
totalProductionCount = totalProductionCount + productionSchedule[i]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val totalDifference: Double = -(totalProductionCount - fg.openBalance); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return RoughScheduleObj( |
|
|
|
|
|
fg, |
|
|
|
|
|
productionSchedule, |
|
|
|
|
|
closeBalance, |
|
|
|
|
|
totalDifference, |
|
|
|
|
|
totalProductionCount, |
|
|
|
|
|
0.0 |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
open fun saveRoughScheduleOutput(sortedEntries: List<Map.Entry<RoughScheduleObj, Double>>, accProdCount: Double, fgCount: Long) { |
|
|
|
|
|
val tempObj = ProductionSchedule() |
|
|
|
|
|
tempObj.id = -1; |
|
|
|
|
|
tempObj.scheduleAt = LocalDateTime.now() |
|
|
|
|
|
tempObj.totalFGType = fgCount; |
|
|
|
|
|
tempObj.totalEstProdCount = accProdCount; |
|
|
|
|
|
tempObj.id = saveRoughScheduleToDatabase(tempObj); |
|
|
|
|
|
|
|
|
|
|
|
for ((roughScheduleRecord, totalDifference) in sortedEntries) { |
|
|
|
|
|
saveRoughScheduleLineToDatabase(tempObj.id ?: -1, roughScheduleRecord) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun saveRoughScheduleToDatabase(newRecord: ProductionSchedule): Long?{ |
|
|
|
|
|
try { |
|
|
|
|
|
val savedItem = productionScheduleRepository.saveAndFlush(newRecord) |
|
|
|
|
|
return savedItem.id |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
throw RuntimeException("Error saving production schedule: ${e.message}", e) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun saveRoughScheduleLineToDatabase(parentId: Long, roughScheduleObj: RoughScheduleObj) { |
|
|
|
|
|
for (i in 5 until 12){ |
|
|
|
|
|
try { |
|
|
|
|
|
var savedItem = ProductionScheduleLine() |
|
|
|
|
|
savedItem.id = -1; |
|
|
|
|
|
savedItem.prodScheduleId = parentId; |
|
|
|
|
|
savedItem.itemId = roughScheduleObj.fgDetail?.id ?: -1; |
|
|
|
|
|
savedItem.lastMonthAvgSales = roughScheduleObj.fgDetail?.lastMonthAvgSalesCount ?: 0.0; |
|
|
|
|
|
savedItem.refScheduleId = null; |
|
|
|
|
|
savedItem.approverId = null; |
|
|
|
|
|
savedItem.estCloseBal = roughScheduleObj.closeBalance[i] |
|
|
|
|
|
savedItem.prodQty = roughScheduleObj.productionSchedule[i] |
|
|
|
|
|
savedItem.type = "rough" |
|
|
|
|
|
savedItem.assignDate = i-4L; |
|
|
|
|
|
savedItem.itemPriority = roughScheduleObj.itemPriority[i] |
|
|
|
|
|
productionScheduleLineRepository.saveAndFlush(savedItem) |
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
throw RuntimeException("Error saving production schedule line: ${e.message}", e) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
open class RoughScheduleObj { |
|
|
|
|
|
var fgDetail: FinishedGood? = null |
|
|
|
|
|
var productionSchedule: Array<Double> = Array<Double>(12, { 0.0 }) |
|
|
|
|
|
var closeBalance: Array<Double> = Array(12) { 0.0 }; |
|
|
|
|
|
var itemPriority: Array<Long> = Array(12) { 0 }; |
|
|
|
|
|
var totalDifference: Double = 0.0 |
|
|
|
|
|
var totalProductionCount: Double = 0.0 |
|
|
|
|
|
var totalForgoneCount: Double = 0.0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor( |
|
|
|
|
|
fgDetail: FinishedGood?, |
|
|
|
|
|
productionSchedule: Array<Double>, |
|
|
|
|
|
closeBalance: Array<Double>, |
|
|
|
|
|
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)" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |