ソースを参照

Update scheduler, po, jo, prod schedule

master
cyril.tsui 2週間前
コミット
33bfdb12b4
18個のファイルの変更465行の追加48行の削除
  1. +36
    -10
      src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt
  2. +4
    -3
      src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt
  3. +5
    -0
      src/main/java/com/ffii/fpsms/modules/common/SettingNames.java
  4. +186
    -0
      src/main/java/com/ffii/fpsms/modules/common/scheduler/SchedulerService.kt
  5. +20
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderProcessService.kt
  6. +3
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt
  7. +3
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionSchedule.kt
  8. +35
    -5
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt
  9. +2
    -1
      src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
  10. +60
    -8
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  11. +21
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt
  12. +5
    -4
      src/main/java/com/ffii/fpsms/modules/master/web/models/SearchProdScheduleRequest.kt
  13. +1
    -0
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/projections/PurchaseOrderInfo.kt
  14. +64
    -16
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt
  15. +4
    -0
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/web/PurchaseOrderController.kt
  16. +4
    -0
      src/main/resources/db/changelog/changes/20250801_01_cyril/01_insert_schedule_setting.sql
  17. +5
    -0
      src/main/resources/db/changelog/changes/20250801_01_cyril/02_update_production_schedule.sql
  18. +7
    -0
      src/main/resources/db/changelog/changes/20250804_01_cyril/01_insert_schedule_setting.sql

+ 36
- 10
src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt ファイルの表示

@@ -28,10 +28,14 @@ open class M18SchedulerService(
) {
var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val defaultCronExpression = "0 0 2 * * *";

@Volatile
var scheduledM18Po: ScheduledFuture<*>? = null

@Volatile
var scheduledM18Master: ScheduledFuture<*>? = null

fun isValidCronExpression(cronExpression: String): Boolean {
return try {
CronTrigger(cronExpression)
@@ -43,35 +47,39 @@ open class M18SchedulerService(
@PostConstruct
fun init() {
scheduleM18PoTask()
scheduleM18MasterData()
}

fun scheduleM18PoTask() {
val defaultCronExpression = "0 0 2 * * *";
scheduledM18Po?.cancel(false)
fun commonSchedule(scheduled: ScheduledFuture<*>?, settingName: String, scheduleFunc: () -> Unit) {
scheduled?.cancel(false)

var cron = settingsService.findByName(SettingNames.SCHEDULE_M18_PO).getOrNull()?.value ?: defaultCronExpression;
var cron = settingsService.findByName(settingName).getOrNull()?.value ?: defaultCronExpression;

if (!isValidCronExpression(cron)) {
cron = defaultCronExpression
}
scheduledM18Po = taskScheduler.schedule(
{
// testTask();
getM18Pos()
scheduleFunc()
},
CronTrigger(cron)
)
println("Cron: $cron")
}

fun testTask() {
println("Test: ${LocalDateTime.now()}")
// Scheduler
fun scheduleM18PoTask() {
commonSchedule(scheduledM18Po, SettingNames.SCHEDULE_M18_PO, ::getM18Pos)
}

fun scheduleM18MasterData() {
commonSchedule(scheduledM18Master, SettingNames.SCHEDULE_M18_MASTER, ::getM18MasterData)
}

// Tasks
// @Async
// @Scheduled(cron = "0 0 2 * * *") // (SS/MM/HH/DD/MM/YY)
// @Scheduled(cron = "0 0 2 * * *") // (SS/MM/HH/DD/MM/YY)
open fun getM18Pos() {
logger.info("Daily Scheduler - PO")
val currentTime = LocalDateTime.now()
val today = currentTime.toLocalDate().atStartOfDay()
val yesterday = today.minusDays(1L)
@@ -84,4 +92,22 @@ open class M18SchedulerService(
logger.info("today: ${today.format(dataStringFormat)}")
logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
}

open fun getM18MasterData() {
logger.info("Daily Scheduler - Master Data")
val currentTime = LocalDateTime.now()
val today = currentTime.toLocalDate().atStartOfDay()
val yesterday = today.minusDays(1L)
val request = M18CommonRequest(
modifiedDateTo = today.format(dataStringFormat),
modifiedDateFrom = yesterday.format(dataStringFormat)
)
m18MasterDataService.saveUnits(request)
m18MasterDataService.saveProducts(request)
m18MasterDataService.saveVendors(request)
m18MasterDataService.saveBusinessUnits(request)
m18MasterDataService.saveCurrencies(request)
logger.info("today: ${today.format(dataStringFormat)}")
logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
}
}

+ 4
- 3
src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt ファイルの表示

@@ -5,6 +5,7 @@ import com.ffii.fpsms.m18.M18Config
import com.ffii.fpsms.m18.service.*
import com.ffii.fpsms.m18.web.models.M18CommonRequest
import com.ffii.fpsms.modules.common.SettingNames
import com.ffii.fpsms.modules.common.scheduler.SchedulerService
import com.ffii.fpsms.modules.master.entity.ItemUom
import com.ffii.fpsms.modules.master.entity.Items
import com.ffii.fpsms.modules.master.entity.ShopRepository
@@ -31,7 +32,7 @@ class M18TestController (
private val itemUomService: ItemUomService,
private val m18PurchaseQuotationService: M18PurchaseQuotationService,
private val m18DeliveryOrderService: M18DeliveryOrderService,
val m18SchedulerService: M18SchedulerService,
val schedulerService: SchedulerService,
private val settingsService: SettingsService,
) {
var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
@@ -60,13 +61,13 @@ class M18TestController (

@GetMapping("/test4")
fun test4(): Any {
return m18SchedulerService.getM18Pos();
return schedulerService.getM18Pos();
}
// --------------------------------------------- Scheduler --------------------------------------------- ///
@GetMapping("/schedule/po") //
fun schedulePo(@RequestParam @Valid newCron: String) {
settingsService.update(SettingNames.SCHEDULE_M18_PO, newCron);
m18SchedulerService.scheduleM18PoTask()
schedulerService.scheduleM18PoTask()
}

// --------------------------------------------- Master Data --------------------------------------------- ///


+ 5
- 0
src/main/java/com/ffii/fpsms/modules/common/SettingNames.java ファイルの表示

@@ -25,6 +25,11 @@ public abstract class SettingNames {
*/
public static final String SCHEDULE_M18_PO = "SCHEDULE.m18.po";

public static final String SCHEDULE_M18_MASTER = "SCHEDULE.m18.master";

public static final String SCHEDULE_PROD_ROUGH = "SCHEDULE.prod.rough";

public static final String SCHEDULE_PROD_DETAILED = "SCHEDULE.prod.detailed";
/*
* Mail settings
*/


+ 186
- 0
src/main/java/com/ffii/fpsms/modules/common/scheduler/SchedulerService.kt ファイルの表示

@@ -0,0 +1,186 @@
package com.ffii.fpsms.modules.common.scheduler

import com.ffii.core.utils.JwtTokenUtil
import com.ffii.fpsms.m18.service.M18DeliveryOrderService
import com.ffii.fpsms.m18.service.M18MasterDataService
import com.ffii.fpsms.m18.service.M18PurchaseOrderService
import com.ffii.fpsms.m18.web.models.M18CommonRequest
import com.ffii.fpsms.modules.common.SettingNames
import com.ffii.fpsms.modules.master.service.ProductionScheduleService
import com.ffii.fpsms.modules.settings.service.SettingsService
import jakarta.annotation.PostConstruct
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.scheduling.TaskScheduler
import org.springframework.scheduling.support.CronTrigger
import org.springframework.stereotype.Service
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.HashMap
import java.util.concurrent.ScheduledFuture
import kotlin.jvm.optionals.getOrNull

@Service
open class SchedulerService(
val settingsService: SettingsService,
val taskScheduler: TaskScheduler,
val productionScheduleService: ProductionScheduleService,
val m18PurchaseOrderService: M18PurchaseOrderService,
val m18DeliveryOrderService: M18DeliveryOrderService,
val m18MasterDataService: M18MasterDataService,
) {
var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val defaultCronExpression = "0 0 2 * * *";

@Volatile
var scheduledM18Po: ScheduledFuture<*>? = null

@Volatile
var scheduledM18Master: ScheduledFuture<*>? = null

@Volatile
var scheduledRoughProd: ScheduledFuture<*>? = null

@Volatile
var scheduledDetailedProd: ScheduledFuture<*>? = null

// Common Function
fun isValidCronExpression(cronExpression: String): Boolean {
return try {
CronTrigger(cronExpression)
true
} catch (e: IllegalArgumentException) {
false
}
}

fun commonSchedule(scheduled: ScheduledFuture<*>?, settingName: String, scheduleFunc: () -> Unit) {
scheduled?.cancel(false)

var cron = settingsService.findByName(settingName).getOrNull()?.value ?: defaultCronExpression;

if (!isValidCronExpression(cron)) {
cron = defaultCronExpression
}
scheduledM18Po = taskScheduler.schedule(
{
scheduleFunc()
},
CronTrigger(cron)
)
}

// Init Scheduler
@PostConstruct
fun init() {
scheduleM18PoTask();
scheduleM18MasterData();
scheduleRoughProd();
scheduleDetailedProd();
}

// Scheduler
// --------------------------- FP-MTMS --------------------------- //
fun scheduleRoughProd() {
commonSchedule(scheduledRoughProd, SettingNames.SCHEDULE_PROD_ROUGH, ::getRoughProdSchedule)
}

fun scheduleDetailedProd() {
commonSchedule(scheduledDetailedProd, SettingNames.SCHEDULE_PROD_DETAILED, ::getDetailedProdSchedule)
}

// --------------------------- M18 --------------------------- //
fun scheduleM18PoTask() {
commonSchedule(scheduledM18Po, SettingNames.SCHEDULE_M18_PO, ::getM18Pos)
}

fun scheduleM18MasterData() {
commonSchedule(scheduledM18Master, SettingNames.SCHEDULE_M18_MASTER, ::getM18MasterData)
}

// Function for schedule
// --------------------------- FP-MTMS --------------------------- //
open fun getRoughProdSchedule() {
try {
logger.info("Daily Scheduler - Rough Prod")
val demoFGList = productionScheduleService.convertToFinishedGoodList();

val result: HashMap<ProductionScheduleService.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);
} catch (e: Exception) {
throw RuntimeException("Error generate schedule: ${e.message}", e)
}
}

open fun getDetailedProdSchedule() {
try {
logger.info("Daily Scheduler - Detailed Prod")
// For test
val today = LocalDateTime.now()

// T, T+1, T+2
for (day in longArrayOf(0L, 1L, 2L)) {
val produceAt = today.plusDays(day)
val latestRoughScheduleAt = productionScheduleService.getScheduledAtByDate(produceAt.toLocalDate());
// assume schedule period is monday to sunday
val assignDate = produceAt.dayOfWeek.value

//TODO: update this to receive selectedDate and assignDate from schedule
println("produceAt: $produceAt | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ")
productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt.atStartOfDay(), produceAt)
}
} catch (e: Exception) {
throw RuntimeException("Error generate schedule: ${e.message}", e)
}
}

// --------------------------- M18 --------------------------- //
// @Async
// @Scheduled(cron = "0 0 2 * * *") // (SS/MM/HH/DD/MM/YY)
open fun getM18Pos() {
logger.info("Daily Scheduler - PO")
val currentTime = LocalDateTime.now()
val today = currentTime.toLocalDate().atStartOfDay()
val yesterday = today.minusDays(1L)
val request = M18CommonRequest(
modifiedDateTo = today.format(dataStringFormat),
modifiedDateFrom = yesterday.format(dataStringFormat)
)
m18PurchaseOrderService.savePurchaseOrders(request);
m18DeliveryOrderService.saveDeliveryOrders(request);
logger.info("today: ${today.format(dataStringFormat)}")
logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
}

open fun getM18MasterData() {
logger.info("Daily Scheduler - Master Data")
val currentTime = LocalDateTime.now()
val today = currentTime.toLocalDate().atStartOfDay()
val yesterday = today.minusDays(1L)
val request = M18CommonRequest(
modifiedDateTo = today.format(dataStringFormat),
modifiedDateFrom = yesterday.format(dataStringFormat)
)
m18MasterDataService.saveUnits(request)
m18MasterDataService.saveProducts(request)
// m18MasterDataService.saveBoms(request)
m18MasterDataService.saveVendors(request)
m18MasterDataService.saveBusinessUnits(request)
m18MasterDataService.saveCurrencies(request)
logger.info("today: ${today.format(dataStringFormat)}")
logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
}
}

+ 20
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderProcessService.kt ファイルの表示

@@ -39,6 +39,7 @@ import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import java.math.BigDecimal
import java.util.HashMap
import java.util.Objects
import kotlin.jvm.optionals.getOrDefault
@@ -52,6 +53,21 @@ open class JobOrderProcessService(
private val jobOrderRepository: JobOrderRepository,
) : AbstractBaseEntityService<JobOrderProcess, Long, JobOrderProcessRepository>(jdbcDao, jobOrderProcessRepository) {

open fun createJobOrderProcessRequests(joId: Long): List<CreateJobOrderProcessRequest> {
val zero = BigDecimal.ZERO
val jo = jobOrderRepository.findById(joId).getOrNull() ?: throw NoSuchElementException()

val jopRequests = jo.bom?.bomProcesses?.map { bp ->
CreateJobOrderProcessRequest(
joId = jo.id,
processId = bp.process?.id,
seqNo = bp.seqNo,
)
} ?: listOf();

return jopRequests
}

open fun createJobOrderProcesses(request: List<CreateJobOrderProcessRequest>): MessageResponse{
val joProcesses = request.map { req ->
val jo = req.joId?.let { jobOrderRepository.findById(it).getOrNull() }
@@ -110,6 +126,10 @@ open class JobOrderProcessService(
}
}

fun createJobOrderProcessesByJoId(joId: Long): MessageResponse {
return createJobOrderProcesses(createJobOrderProcessRequests(joId));
}

// open fun isCorrectMachineUsed(request: MachineRequest): MessageResponse{
// // Further development, this with check equipment for the job process
//


+ 3
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt ファイルの表示

@@ -4,6 +4,7 @@ import com.ffii.core.response.RecordsRes
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo
import com.ffii.fpsms.modules.jobOrder.service.JobOrderBomMaterialService
import com.ffii.fpsms.modules.jobOrder.service.JobOrderProcessService
import com.ffii.fpsms.modules.jobOrder.service.JobOrderService
import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest
import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderReleaseRequest
@@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.RestController
class JobOrderController(
private val jobOrderService: JobOrderService,
private val jobOrderBomMaterialService: JobOrderBomMaterialService,
private val jobOrderProcessService: JobOrderProcessService
) {

@GetMapping("/getRecordByPage")
@@ -47,6 +49,7 @@ class JobOrderController(
throw NoSuchElementException()
}
jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(jo.id)
jobOrderProcessService.createJobOrderProcessesByJoId(jo.id)

return jo
}

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionSchedule.kt ファイルの表示

@@ -11,6 +11,9 @@ open class ProductionSchedule : BaseEntity<Long>() {
@Column(name = "scheduleAt")
open var scheduleAt: LocalDateTime = LocalDateTime.now()

@Column(name = "produceAt")
open var produceAt: LocalDateTime? = null;

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



+ 35
- 5
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt ファイルの表示

@@ -18,6 +18,9 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
)
fun getLatestRoughScheduleAt(type: String?): LocalDateTime

fun findByTypeAndProduceAtAndDeletedIsFalse(type: String, produceAt: LocalDateTime): ProductionSchedule?

// Mainly for rough prod schedule page
@Query(
nativeQuery = true,
value =
@@ -27,7 +30,6 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
ps.id,
ps.deleted,
ps.scheduleAt,
-- ps.produceAt,
date_add(ps.scheduleAt, interval 7 + 1 - if(weekday(ps.scheduleAt) = 6, 0, weekday(ps.scheduleAt) + 1) day) as schedulePeriod,
date_add(ps.scheduleAt, interval 7 + 1 + 6 - if(weekday(ps.scheduleAt) = 6, 0, weekday(ps.scheduleAt) + 1) day) as schedulePeriodTo,
ps.totalEstProdCount,
@@ -38,7 +40,6 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
select * from prod
where deleted = false
and (:scheduleAt = '' or datediff(scheduleAt, coalesce(:scheduleAt, scheduleAt)) = 0)
-- and (:produceAt = '' or datediff(produceAt, coalesce(:produceAt, produceAt)) = 0)
and (:schedulePeriod = '' or datediff(schedulePeriod, coalesce(:schedulePeriod, schedulePeriod)) = 0)
and (:schedulePeriodTo = '' or datediff(schedulePeriodTo, coalesce(:schedulePeriodTo, schedulePeriodTo)) = 0)
and (:totalEstProdCount is null or :totalEstProdCount = '' or totalEstProdCount = :totalEstProdCount)
@@ -52,7 +53,6 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
ps.id,
ps.deleted,
ps.scheduleAt,
-- ps.produceAt,
date_add(ps.scheduleAt, interval 7 + 1 - if(weekday(ps.scheduleAt) = 6, 0, weekday(ps.scheduleAt) + 1) day) as schedulePeriod,
date_add(ps.scheduleAt, interval 7 + 1 + 6 - if(weekday(ps.scheduleAt) = 6, 0, weekday(ps.scheduleAt) + 1) day) as schedulePeriodTo,
ps.totalEstProdCount,
@@ -63,7 +63,6 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
select count(*) from prod
where deleted = false
and (:scheduleAt = '' or datediff(scheduleAt, coalesce(:scheduleAt, scheduleAt)) = 0)
-- and (:produceAt = '' or datediff(produceAt, coalesce(:produceAt, produceAt)) = 0)
and (:schedulePeriod = '' or datediff(schedulePeriod, coalesce(:schedulePeriod, schedulePeriod)) = 0)
and (:schedulePeriodTo = '' or datediff(schedulePeriodTo, coalesce(:schedulePeriodTo, schedulePeriodTo)) = 0)
and (:totalEstProdCount is null or :totalEstProdCount = '' or totalEstProdCount = :totalEstProdCount)
@@ -73,7 +72,6 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
)
fun findProdScheduleInfoByPage(
scheduleAt: String?,
// produceAt: String?,
schedulePeriod: String?,
schedulePeriodTo: String?,
totalEstProdCount: Double?,
@@ -81,6 +79,38 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
pageable: Pageable
): Page<ProdScheduleInfo>

// Mainly for detailed prod schedule page
@Query(
nativeQuery = true,
value =
"""
select
ps.id,
ps.deleted,
ps.scheduleAt,
ps.produceAt,
ps.totalEstProdCount,
ps.totalFGType,
ps.`type`
from production_schedule ps
where deleted = false
and produceAt is not null
and scheduleAt in (select max(ps2.scheduleAt) from production_schedule ps2 group by ps2.produceAt)
-- and (:scheduleAt = '' or datediff(scheduleAt, coalesce(:scheduleAt, scheduleAt)) = 0)
and (:produceAt = '' or datediff(produceAt, coalesce(:produceAt, produceAt)) = 0)
and (:totalEstProdCount is null or :totalEstProdCount = '' or totalEstProdCount = :totalEstProdCount)
and (coalesce(:types) is null or type in :types)
order by id desc;
"""
)
fun findProdScheduleInfoByProduceAtByPage(
// scheduleAt: String?,
produceAt: String?,
totalEstProdCount: Double?,
types: List<String>?,
pageable: Pageable
): Page<ProdScheduleInfo>

@Query(nativeQuery = true,
value =
"""


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt ファイルの表示

@@ -49,7 +49,7 @@ open class ItemsService(

open fun getRoughScheduleList(): List<Map<String, Any>> {
val now = LocalDateTime.now()
val lastMonthStart = now.minusMonths(1).withDayOfMonth(1) // Start of last month
val lastMonthStart = now.minusMonths(1).withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0) // Start of last month
val lastMonthEnd = now.minusDays(now.dayOfMonth.toLong()).withHour(23).withMinute(59).withSecond(59) // End of last month

val curMonthStart = now.withDayOfMonth(1) // Start of last month
@@ -82,6 +82,7 @@ open class ItemsService(
+ " WHERE do.deleted = false "
+ " AND do.estimatedArrivalDate >= :lastMonthStart "
+ " AND do.estimatedArrivalDate <= :lastMonthEnd "
+ " AND dol.itemId is not null "
+ " GROUP BY dol.itemId, dol.itemNo, i.name "
);
return jdbcDao.queryForList(sql.toString(), args);


+ 60
- 8
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt ファイルの表示

@@ -16,6 +16,7 @@ import com.ffii.fpsms.modules.master.entity.projections.*
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleLineRequest
import com.ffii.fpsms.modules.master.web.models.SearchProdScheduleRequest
import com.ffii.fpsms.modules.settings.service.SettingsService
import com.ffii.fpsms.modules.stock.entity.Inventory
import com.ffii.fpsms.modules.stock.entity.InventoryRepository
import com.ffii.fpsms.modules.stock.service.InventoryService
@@ -26,6 +27,7 @@ import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.reflect.TypeToken
import org.springframework.data.domain.PageRequest
import org.springframework.scheduling.support.CronTrigger
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.lang.reflect.Type
@@ -35,6 +37,7 @@ import java.time.DayOfWeek
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.ScheduledFuture
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.jvm.optionals.getOrNull
@@ -54,13 +57,15 @@ open class ProductionScheduleService(
private val jobOrderProcessService: JobOrderProcessService,
private val inventoryService: InventoryService,
private val inventoryRepository: InventoryRepository,
private val itemUomService: ItemUomService
private val itemUomService: ItemUomService,
private val settingsService: SettingsService,
) : AbstractBaseEntityService<ProductionSchedule, Long, ProductionScheduleRepository>(
jdbcDao,
productionScheduleRepository
) {
// do mapping with projection
open val formatter: DateTimeFormatter? = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
val defaultCronExpression = "0 0 2 * * *";

open fun getLatestScheduleAt(type: String?): LocalDateTime {
return productionScheduleRepository.getLatestRoughScheduleAt(type)
@@ -69,7 +74,7 @@ open class ProductionScheduleService(
open fun getScheduledAtByDate(inputDate: LocalDate): LocalDate {
val daysToSubtract = when (inputDate.dayOfWeek) {
DayOfWeek.WEDNESDAY -> 7
else -> (inputDate.dayOfWeek.value + 7 - DayOfWeek.WEDNESDAY.value) % 7
else -> inputDate.dayOfWeek.value + 7 - DayOfWeek.WEDNESDAY.value
}

val lastWednesday = inputDate.minusDays(daysToSubtract.toLong())
@@ -82,7 +87,6 @@ open class ProductionScheduleService(

val response = productionScheduleRepository.findProdScheduleInfoByPage(
scheduleAt = request.scheduleAt,
// produceAt = request.produceAt,
schedulePeriod = request.schedulePeriod,
schedulePeriodTo = request.schedulePeriodTo,
totalEstProdCount = request.totalEstProdCount,
@@ -95,6 +99,29 @@ open class ProductionScheduleService(
return RecordsRes<ProdScheduleInfo>(records, total.toInt());
}

open fun allRoughProdSchedulesByPage(request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> {
// request.apply {
// types = listOf("rough")
// }
return allProdSchedulesByPage(request);
}

open fun allDetailedProdSchedulesByPage(request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> {
val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10);

val response = productionScheduleRepository.findProdScheduleInfoByProduceAtByPage(
produceAt = request.produceAt,
totalEstProdCount = request.totalEstProdCount,
// types = listOf("detailed", "manual"),
types = request.types,
pageable = pageable
)

val records = response.content
val total = response.totalElements
return RecordsRes<ProdScheduleInfo>(records, total.toInt());
}

open fun roughProdScheduleDetail(id: Long): RoughProdScheduleWithLine {
val zero = BigDecimal.ZERO
val prodSchedule = productionScheduleRepository.findById(id).getOrNull() ?: throw NoSuchElementException()
@@ -303,10 +330,18 @@ open class ProductionScheduleService(
@Transactional(rollbackFor = [java.lang.Exception::class])
open fun saveProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse {
val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
val prodSchedule = prodScheduleLine.productionSchedule

// Update Prod Schedule Type
prodSchedule.apply {
type = "manual"
}
productionScheduleRepository.saveAndFlush(prodSchedule)

// Update Prod Schedule Line Prod qty
prodScheduleLine.apply {
prodQty = request.demandQty.toDouble()
type = "manual"
}
productionScheduleLineRepository.saveAndFlush(prodScheduleLine)

@@ -329,10 +364,21 @@ open class ProductionScheduleService(
val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) }
val approver = SecurityUtils.getUser().getOrNull()
val proportion = request.demandQty.divide(bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP)
val isSameQty = request.demandQty.equals(prodScheduleLine.prodQty)

// Update Prod Schedule Type
if (!isSameQty) {
val prodSchedule = prodScheduleLine.productionSchedule
prodSchedule.apply {
type = "manual"
}
productionScheduleRepository.save(prodSchedule)
}

// Update Prod Schedule Line Prod qty
prodScheduleLine.apply {
prodQty = request.demandQty.toDouble()
this.type = if (isSameQty) this.type else "manual"
approverId = approver?.id
}
productionScheduleLineRepository.save(prodScheduleLine)
@@ -459,9 +505,15 @@ open class ProductionScheduleService(
return jdbcDao.queryForInt(sql.toString(), args);
}

open fun generateDetailedScheduleByDay(assignDate: Int, selectedDate: LocalDateTime) {
open fun generateDetailedScheduleByDay(assignDate: Int, selectedDate: LocalDateTime, produceAt: LocalDateTime) {
val detailedScheduleOutputList = ArrayList<ProductionScheduleRecord>()

// check the produce date have manual changes.
val manualChange = productionScheduleRepository.findByTypeAndProduceAtAndDeletedIsFalse("detailed", produceAt)
if (manualChange != null) {
return;
}

//increasement available
var idleProductionCount = 22000.0 - getDailyProductionCount(assignDate, selectedDate);

@@ -518,17 +570,19 @@ open class ProductionScheduleService(
tempPriority++
}

saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount)
saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount, produceAt)
}

open fun saveDetailedScheduleOutput(
sortedEntries: List<ProductionScheduleRecord>,
accProdCount: Double,
fgCount: Long
fgCount: Long,
produceAt: LocalDateTime,
) {
val tempObj = ProductionSchedule()
tempObj.id = -1;
tempObj.scheduleAt = LocalDateTime.now()
tempObj.produceAt = produceAt;
tempObj.totalFGType = fgCount;
tempObj.totalEstProdCount = accProdCount;
tempObj.type = "detailed"
@@ -539,7 +593,6 @@ open class ProductionScheduleService(
}
}


private fun saveDetailedScheduleLineToDatabase(parentId: Long, detailedScheduleObj: ProductionScheduleRecord) {
try {
val prodSchedule = productionScheduleRepository.findById(parentId).get()
@@ -627,7 +680,6 @@ open class ProductionScheduleService(
sql.append(" ORDER BY psl.assignDate, psl.itemPriority ASC ");

print(sql)
print(args.toString())
val resultList = jdbcDao.queryForList(sql.toString(), args);
print(resultList)



+ 21
- 1
src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt ファイルの表示

@@ -2,6 +2,7 @@ package com.ffii.fpsms.modules.master.web

import com.ffii.core.response.RecordsRes
import com.ffii.core.utils.CriteriaArgsBuilder
import com.ffii.fpsms.modules.common.scheduler.SchedulerService
import com.ffii.fpsms.modules.master.entity.ProductionScheduleRepository
import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleWithLine
import com.ffii.fpsms.modules.master.entity.projections.ProdScheduleInfo
@@ -30,6 +31,7 @@ import kotlin.math.abs
class ProductionScheduleController(
private val productionScheduleService: ProductionScheduleService,
private val productionScheduleRepository: ProductionScheduleRepository,
private val schedulerService: SchedulerService,
) {
// @GetMapping
// fun allItems(): List<Items> {
@@ -48,6 +50,16 @@ class ProductionScheduleController(
return productionScheduleService.getLatestScheduleAt("rough")
}

@GetMapping("/test1")
fun test1(): Any {
return schedulerService.getRoughProdSchedule();
}

@GetMapping("/test2")
fun test2(): Any {
return schedulerService.getDetailedProdSchedule();
}

@GetMapping("/detail/rough/{id}")
fun getScheduleDetail(@PathVariable id: Long): RoughProdScheduleWithLine {
return productionScheduleService.roughProdScheduleDetail(id)
@@ -73,7 +85,15 @@ class ProductionScheduleController(
return productionScheduleService.allProdSchedulesByPage(request);
}

@GetMapping("/getRecordByPage/rough")
fun allRoughProdSchedulesByPage(@ModelAttribute request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> {
return productionScheduleService.allRoughProdSchedulesByPage(request);
}

@GetMapping("/getRecordByPage/detailed")
fun allDetailedProdSchedulesByPage(@ModelAttribute request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> {
return productionScheduleService.allDetailedProdSchedulesByPage(request);
}

@RequestMapping(value = ["/testDetailedSchedule"], method = [RequestMethod.GET])
fun generateDetailSchedule(request: HttpServletRequest?): Int {
@@ -98,7 +118,7 @@ class ProductionScheduleController(
//TODO: update this to receive selectedDate and assignDate from schedule
// productionScheduleService.generateDetailedScheduleByDay(1, LocalDateTime.of(2025,6,25,0,0,0))
println("genDate: $genDate | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ")
productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt ?: LocalDateTime.now())
productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt ?: LocalDateTime.now(), genDate ?: today)
return 200
} catch (e: Exception) {
throw RuntimeException("Error generate schedule: ${e.message}", e)


+ 5
- 4
src/main/java/com/ffii/fpsms/modules/master/web/models/SearchProdScheduleRequest.kt ファイルの表示

@@ -4,11 +4,12 @@ import java.time.LocalDate

data class SearchProdScheduleRequest (
val id: Long?,
val scheduleAt: String?,
val schedulePeriod: String?,
val schedulePeriodTo: String?,
val scheduleAt: String? = "",
val produceAt: String? = "",
val schedulePeriod: String? = "",
val schedulePeriodTo: String? = "",
val totalEstProdCount: Double?,
val types: List<String>?,
var types: List<String>?,
val pageSize: Int?,
val pageNum: Int?,
)

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/projections/PurchaseOrderInfo.kt ファイルの表示

@@ -23,6 +23,7 @@ data class PurchaseOrderDataClass(
val orderDate: LocalDateTime?,
val estimatedArrivalDate: LocalDateTime?,
val completeDate: LocalDateTime?,
val itemDetail: String,
val status: String,
val supplier: String?,
var escalated: Boolean?

+ 64
- 16
src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt ファイルの表示

@@ -65,22 +65,52 @@ open class PurchaseOrderService(
// }
open fun getPoList(args: MutableMap<String, Any>): List<PurchaseOrderDataClass> {
val sql = StringBuilder(
" select "
+ " po.*, "
+ " s.name as supplier, "
+ " CASE "
+ " WHEN sil.purchaseOrderId IS NOT NULL THEN 1 "
+ " ELSE 0 "
+ " END AS escalated "
+ " from purchase_order po "
+ " left join shop s on s.id = po.supplierId "
+ " left join ( "
+ " select "
+ " sil.purchaseOrderId "
+ " from stock_in_line sil "
+ " where sil.status like 'determine%' "
+ " ) sil on sil.purchaseOrderId = po.id "
+ " where po.deleted = false "
"select * from ( " +
"select " +
" po.*," +
" group_concat(" +
" coalesce(i.code, \"N/A\"), " +
" '-', " +
" coalesce(i.name, \"N/A\")," +
" ' ('," +
" coalesce(uc.udfudesc, \"N/A\")," +
" ')'," +
" ': ', " +
" coalesce(pol.qty, 0), " +
" ' (', " +
" coalesce(sil2.sumAcceptedQty, 0)," +
" ')' " +
" SEPARATOR ','" +
" ) as itemDetail," +
" s.name as supplier, " +
" CASE " +
" WHEN sil.purchaseOrderId IS NOT NULL THEN 1 " +
" ELSE 0 " +
" END AS escalated " +
" from purchase_order po" +
" left join purchase_order_line pol on pol.purchaseOrderId = po.id" +
" left join items i on i.id = pol.itemId" +
" left join shop s on s.id = po.supplierId " +
" left join ( " +
" select " +
" sil.purchaseOrderId " +
" from stock_in_line sil " +
" where sil.status like 'determine%'" +
" and sil.deleted = false" +
" ) sil on sil.purchaseOrderId = po.id" +
" left join ( " +
" select " +
" sil.purchaseOrderLineId, " +
" sum(coalesce(sil.acceptedQty, 0)) as sumAcceptedQty " +
" from stock_in_line sil " +
" where sil.deleted = false " +
" and sil.status = 'completed' " +
" group by sil.purchaseOrderLineId " +
" ) sil2 on sil2.purchaseOrderLineId = pol.id" +
" left join item_uom iu on iu.itemId = pol.itemId and iu.purchaseUnit = true" +
" left join uom_conversion uc on uc.id = iu.uomId" +
" where po.deleted = false " +
" and pol.deleted = false "
)
if (args.containsKey("code")){
sql.append(" AND po.code like :code ");
@@ -95,7 +125,24 @@ open class PurchaseOrderService(
sql.append(" and sil.purchaseOrderId IS NULL ");
}
}
if (args.containsKey("orderDate")){
sql.append(" AND po.orderDate >= :orderDate ");
}
if (args.containsKey("orderDateTo")){
sql.append(" AND po.orderDate <= :orderDateTo ");
}
if (args.containsKey("estimatedArrivalDate")){
sql.append(" AND po.estimatedArrivalDate >= :estimatedArrivalDate ");
}
if (args.containsKey("estimatedArrivalDateTo")){
sql.append(" AND po.estimatedArrivalDate <= :estimatedArrivalDateTo ");
}
sql.append(" group by po.id");
sql.append(" order by po.orderDate desc")
sql.append(" ) r")
if (args.containsKey("itemDetail")){
sql.append(" AND r.itemDetail like :itemDetail ");
}
val list = jdbcDao.queryForList(sql.toString(), args);
println(list)

@@ -105,6 +152,7 @@ open class PurchaseOrderService(
code = it["code"] as String,
orderDate = it["orderDate"] as LocalDateTime?,
estimatedArrivalDate = it["estimatedArrivalDate"] as LocalDateTime?,
itemDetail = it["itemDetail"] as String,
completeDate = it["completeDate"] as LocalDateTime?,
status = it["status"] as String,
supplier = it["supplier"] as String?,


+ 4
- 0
src/main/java/com/ffii/fpsms/modules/purchaseOrder/web/PurchaseOrderController.kt ファイルの表示

@@ -35,6 +35,10 @@ class PurchaseOrderController(
.addStringLike("code")
.addString("status")
.addBoolean("escalated")
.addDate("orderDate")
.addDate("orderDateTo")
.addDate("estimatedArrivalDate")
.addDate("estimatedArrivalDateTo")
.build()
println(criteriaArgs)
val pageSize = request.getParameter("pageSize")?.toIntOrNull() ?: 10 // Default to 10 if not provided


+ 4
- 0
src/main/resources/db/changelog/changes/20250801_01_cyril/01_insert_schedule_setting.sql ファイルの表示

@@ -0,0 +1,4 @@
-- liquibase formatted sql
-- changeset cyril:insert_schedule_setting

INSERT INTO `settings` (`name`, `value`, `category`, `type`) VALUES ('SCHEDULE.m18.master', '0 0 1 * * *', 'SCHEDULE', 'string');

+ 5
- 0
src/main/resources/db/changelog/changes/20250801_01_cyril/02_update_production_schedule.sql ファイルの表示

@@ -0,0 +1,5 @@
-- liquibase formatted sql
-- changeset cyril:update_production_schedule

ALTER TABLE `production_schedule`
ADD COLUMN `produceAt` DATETIME NULL AFTER `scheduleAt`;

+ 7
- 0
src/main/resources/db/changelog/changes/20250804_01_cyril/01_insert_schedule_setting.sql ファイルの表示

@@ -0,0 +1,7 @@
-- liquibase formatted sql
-- changeset cyril:insert_schedule_setting

INSERT INTO `settings` (`name`, `value`, `category`, `type`)
VALUES
('SCHEDULE.prod.rough', '0 0 4 * * Wed', 'SCHEDULE', 'string'),
('SCHEDULE.prod.detailed', '0 0 3 * * *', 'SCHEDULE', 'string');

読み込み中…
キャンセル
保存