Преглед изворни кода

update save rough production schedule logic

create_edit_user
jason.lam пре 3 месеци
родитељ
комит
2074a4fa0c
7 измењених фајлова са 492 додато и 1 уклоњено
  1. +20
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionSchedule.kt
  2. +40
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLine.kt
  3. +8
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt
  4. +8
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt
  5. +294
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  6. +119
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt
  7. +3
    -1
      src/main/resources/db/changelog/changes/20250528_01_jason_lam/01_create_rough_schedule_table.sql

+ 20
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionSchedule.kt Прегледај датотеку

@@ -0,0 +1,20 @@
package com.ffii.fpsms.modules.master.entity;

import com.ffii.core.entity.BaseEntity
import jakarta.persistence.*
import java.time.LocalDateTime

@Entity
@Table(name = "production_schedule")
open class ProductionSchedule : BaseEntity<Long>() {
@Column(name = "scheduleAt")
open var scheduleAt: LocalDateTime = LocalDateTime.now()

@Column(name = "totalEstProdCount")
open var totalEstProdCount: Double = 0.0

@Column(name = "totalFGType")
open var totalFGType: Long = 0L


}

+ 40
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLine.kt Прегледај датотеку

@@ -0,0 +1,40 @@
package com.ffii.fpsms.modules.master.entity;

import com.ffii.core.entity.BaseEntity
import jakarta.persistence.*
import java.time.LocalDateTime

@Entity
@Table(name = "production_schedule_line")
open class ProductionScheduleLine : BaseEntity<Long>() {
@Column(name = "prodScheduleId")
open var prodScheduleId: Long = 0L

@Column(name = "itemId")
open var itemId: Long = 0L

@Column(name = "lastMonthAvgSales")
open var lastMonthAvgSales: Double = 0.0

@Column(name = "prodQty")
open var prodQty: Double = 0.0

@Column(name = "estCloseBal")
open var estCloseBal: Double = 0.0

@Column(name = "type")
open var type: String = "rough"

@Column(name = "approverId")
open var approverId: Long? = 0

@Column(name = "refScheduleId")
open var refScheduleId: Long? = 0

//in Integer (i.e.: Day1, Day2, Day3)
@Column(name = "assignDate")
open var assignDate: Long = 0L

@Column(name = "itemPriority")
open var itemPriority: Long = 0L
}

+ 8
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt Прегледај датотеку

@@ -0,0 +1,8 @@
package com.ffii.fpsms.modules.master.entity

import com.ffii.core.support.AbstractRepository
import org.springframework.stereotype.Repository

@Repository
interface ProductionScheduleLineRepository : AbstractRepository<ProductionScheduleLine, Long> {
}

+ 8
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt Прегледај датотеку

@@ -0,0 +1,8 @@
package com.ffii.fpsms.modules.master.entity

import com.ffii.core.support.AbstractRepository
import org.springframework.stereotype.Repository

@Repository
interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, Long> {
}

+ 294
- 0
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt Прегледај датотеку

@@ -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)"
}

}

}

+ 119
- 0
src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt Прегледај датотеку

@@ -0,0 +1,119 @@
package com.ffii.fpsms.modules.master.web

import com.ffii.fpsms.modules.master.entity.ProductionSchedule
import com.ffii.fpsms.modules.master.entity.UomConversion
import com.ffii.fpsms.modules.master.service.ProductionScheduleService
import com.ffii.fpsms.modules.master.service.ProductionScheduleService.FinishedGood
import com.ffii.fpsms.modules.master.service.ProductionScheduleService.RoughScheduleObj
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("/productionSchedule")
class ProductionScheduleController(
private val productionScheduleService: ProductionScheduleService,
) {
// @GetMapping
// fun allItems(): List<Items> {
// return uomConversionService.allItems()
// }

@RequestMapping(value = ["/testRoughSchedule"], method = [RequestMethod.GET])
fun generateRoughSchedule(request: HttpServletRequest?): List<HashMap<RoughScheduleObj, Double>> {
try {
val demoFGList_old = arrayListOf<FinishedGood>(
FinishedGood(
1,
"豆腐花 - Tofu pudding",
3,
91.0,
31.0,
3000.0
),
FinishedGood(
2,
"中湯包 - Chinese Soup",
4,
100.0,
72.5,
3000.0
),
FinishedGood(
3,
"苦瓜炒蛋 - Fried Egg",
3,
700.0,
300.5,
22000.0
),
FinishedGood(
4,
"酸菜魚湯包 - Pickled fish soup",
4,
3000.0,
600.5,
22000.0
),
FinishedGood(
5,
"咖哩雞球 - Curry chicken balls",
5,
3000.0,
2150.5,
22000.0
),
FinishedGood(
6,
"水煮牛 - Spicy Beef",
6,
1000.0,
2000.5,
22000.0
),
FinishedGood(
6,
"海鮮濃湯 - Sea Food Soup",
6,
0.0,
3500.5,
22000.0
),
FinishedGood(
7,
"薯條 - Fries",
7,
0.0,
3000.5,
22000.0
),
);

val demoFGList = productionScheduleService.convertToFinishedGoodList();

val result: HashMap<RoughScheduleObj, Double> = productionScheduleService.generateRoughScheduleByWeek(demoFGList)
val sortedEntries = result.entries.sortedBy { it.value }


var accProdCount = 0.0;
var fgCount = 0L;
for ((roughScheduleRecord, totalDifference) in sortedEntries) {
accProdCount += roughScheduleRecord.totalProductionCount;
fgCount ++;
println("[totalDifference:" + totalDifference + "] - " + roughScheduleRecord.toString())
}

// Convert to List<HashMap<RoughScheduleObj, Double>>
productionScheduleService.saveRoughScheduleOutput(sortedEntries, accProdCount, fgCount);
return sortedEntries.map { entry ->
hashMapOf(entry.key to entry.value)
}
} catch (e: Exception) {
throw RuntimeException("Error generate schedule: ${e.message}", e)
}
}
}

+ 3
- 1
src/main/resources/db/changelog/changes/20250528_01_jason_lam/01_create_rough_schedule_table.sql Прегледај датотеку

@@ -31,8 +31,10 @@ CREATE TABLE production_schedule_line
lastMonthAvgSales DECIMAL(16, 2) NULL DEFAULT 0.0,
prodQty DECIMAL(16, 2) NOT NULL,
estCloseBal DECIMAL(16, 2) NULL DEFAULT 0.0,
type VARCHAR(30) NOT NULL DEFAULT "rough",
type VARCHAR(10) NOT NULL DEFAULT "rough",
approverId INT(11) NULL,
refScheduleId INT(11) NULL,
assignDate INT(11) NULL,
itemPriority INT(11) NULL,
CONSTRAINT pk_production_schedule_line PRIMARY KEY (id)
);

Loading…
Откажи
Сачувај