|
@@ -6,6 +6,7 @@ import com.ffii.fpsms.modules.master.entity.* |
|
|
import com.ffii.fpsms.modules.master.web.models.MessageResponse |
|
|
import com.ffii.fpsms.modules.master.web.models.MessageResponse |
|
|
import org.springframework.stereotype.Service |
|
|
import org.springframework.stereotype.Service |
|
|
import java.time.LocalDateTime |
|
|
import java.time.LocalDateTime |
|
|
|
|
|
import java.time.format.DateTimeFormatter |
|
|
import java.util.* |
|
|
import java.util.* |
|
|
import kotlin.collections.component1 |
|
|
import kotlin.collections.component1 |
|
|
import kotlin.collections.component2 |
|
|
import kotlin.collections.component2 |
|
@@ -19,7 +20,190 @@ open class ProductionScheduleService( |
|
|
private val productionScheduleLineRepository: ProductionScheduleLineRepository, |
|
|
private val productionScheduleLineRepository: ProductionScheduleLineRepository, |
|
|
): AbstractBaseEntityService<ProductionSchedule, Long, ProductionScheduleRepository>(jdbcDao, productionScheduleRepository) { |
|
|
): AbstractBaseEntityService<ProductionSchedule, Long, ProductionScheduleRepository>(jdbcDao, productionScheduleRepository) { |
|
|
// do mapping with projection |
|
|
// 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<ProductionScheduleRecord>() |
|
|
|
|
|
|
|
|
|
|
|
//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<ProductionScheduleRecord> = getProductionScheduleRecord(args) |
|
|
|
|
|
|
|
|
|
|
|
//用缺口決定生產順序 |
|
|
|
|
|
val productionPriorityMap: HashMap<ProductionScheduleRecord, Double> = 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<ProductionScheduleRecord>, 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<String, Any>): List<ProductionScheduleRecord> { |
|
|
|
|
|
|
|
|
|
|
|
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<FinishedGood> { |
|
|
open fun convertToFinishedGoodList(): ArrayList<FinishedGood> { |
|
|
val roughScheduleList = itemService.getRoughScheduleList() // Retrieve the list from your existing method |
|
|
val roughScheduleList = itemService.getRoughScheduleList() // Retrieve the list from your existing method |
|
|
val finishedGoodList = ArrayList<FinishedGood>() |
|
|
val finishedGoodList = ArrayList<FinishedGood>() |
|
@@ -193,7 +377,7 @@ open class ProductionScheduleService( |
|
|
tempObj.scheduleAt = LocalDateTime.now() |
|
|
tempObj.scheduleAt = LocalDateTime.now() |
|
|
tempObj.totalFGType = fgCount; |
|
|
tempObj.totalFGType = fgCount; |
|
|
tempObj.totalEstProdCount = accProdCount; |
|
|
tempObj.totalEstProdCount = accProdCount; |
|
|
tempObj.id = saveRoughScheduleToDatabase(tempObj); |
|
|
|
|
|
|
|
|
tempObj.id = saveProductionScheduleToDatabase(tempObj); |
|
|
tempObj.type = "rough" |
|
|
tempObj.type = "rough" |
|
|
|
|
|
|
|
|
for ((roughScheduleRecord, totalDifference) in sortedEntries) { |
|
|
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 { |
|
|
try { |
|
|
val savedItem = productionScheduleRepository.saveAndFlush(newRecord) |
|
|
val savedItem = productionScheduleRepository.saveAndFlush(newRecord) |
|
|
return savedItem.id |
|
|
return savedItem.id |
|
@@ -233,6 +417,7 @@ open class ProductionScheduleService( |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
//====================粗排相關 END====================// |
|
|
|
|
|
|
|
|
open class FinishedGood { |
|
|
open class FinishedGood { |
|
|
//var fgDetail: FinishedGood? = null |
|
|
//var fgDetail: FinishedGood? = null |
|
|