Browse Source

Update scheduler, po, jo, prod schedule

master
cyril.tsui 2 weeks ago
parent
commit
33bfdb12b4
18 changed files with 465 additions and 48 deletions
  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 View File

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


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


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

fun isValidCronExpression(cronExpression: String): Boolean { fun isValidCronExpression(cronExpression: String): Boolean {
return try { return try {
CronTrigger(cronExpression) CronTrigger(cronExpression)
@@ -43,35 +47,39 @@ open class M18SchedulerService(
@PostConstruct @PostConstruct
fun init() { fun init() {
scheduleM18PoTask() 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)) { if (!isValidCronExpression(cron)) {
cron = defaultCronExpression cron = defaultCronExpression
} }
scheduledM18Po = taskScheduler.schedule( scheduledM18Po = taskScheduler.schedule(
{ {
// testTask();
getM18Pos()
scheduleFunc()
}, },
CronTrigger(cron) 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 // @Async
// @Scheduled(cron = "0 0 2 * * *") // (SS/MM/HH/DD/MM/YY)
// @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() { open fun getM18Pos() {
logger.info("Daily Scheduler - PO")
val currentTime = LocalDateTime.now() val currentTime = LocalDateTime.now()
val today = currentTime.toLocalDate().atStartOfDay() val today = currentTime.toLocalDate().atStartOfDay()
val yesterday = today.minusDays(1L) val yesterday = today.minusDays(1L)
@@ -84,4 +92,22 @@ open class M18SchedulerService(
logger.info("today: ${today.format(dataStringFormat)}") logger.info("today: ${today.format(dataStringFormat)}")
logger.info("yesterday: ${yesterday.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 View File

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


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


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


+ 5
- 0
src/main/java/com/ffii/fpsms/modules/common/SettingNames.java View File

@@ -25,6 +25,11 @@ public abstract class SettingNames {
*/ */
public static final String SCHEDULE_M18_PO = "SCHEDULE.m18.po"; 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 * Mail settings
*/ */


+ 186
- 0
src/main/java/com/ffii/fpsms/modules/common/scheduler/SchedulerService.kt View File

@@ -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 View File

@@ -39,6 +39,7 @@ import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import java.io.IOException import java.io.IOException
import java.math.BigDecimal
import java.util.HashMap import java.util.HashMap
import java.util.Objects import java.util.Objects
import kotlin.jvm.optionals.getOrDefault import kotlin.jvm.optionals.getOrDefault
@@ -52,6 +53,21 @@ open class JobOrderProcessService(
private val jobOrderRepository: JobOrderRepository, private val jobOrderRepository: JobOrderRepository,
) : AbstractBaseEntityService<JobOrderProcess, Long, JobOrderProcessRepository>(jdbcDao, jobOrderProcessRepository) { ) : 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{ open fun createJobOrderProcesses(request: List<CreateJobOrderProcessRequest>): MessageResponse{
val joProcesses = request.map { req -> val joProcesses = request.map { req ->
val jo = req.joId?.let { jobOrderRepository.findById(it).getOrNull() } 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{ // open fun isCorrectMachineUsed(request: MachineRequest): MessageResponse{
// // Further development, this with check equipment for the job process // // Further development, this with check equipment for the job process
// //


+ 3
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt View File

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


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


return jo return jo
} }

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionSchedule.kt View File

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


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

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




+ 35
- 5
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt View File

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


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

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


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt View File

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


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


+ 60
- 8
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt View File

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


open fun getLatestScheduleAt(type: String?): LocalDateTime { open fun getLatestScheduleAt(type: String?): LocalDateTime {
return productionScheduleRepository.getLatestRoughScheduleAt(type) return productionScheduleRepository.getLatestRoughScheduleAt(type)
@@ -69,7 +74,7 @@ open class ProductionScheduleService(
open fun getScheduledAtByDate(inputDate: LocalDate): LocalDate { open fun getScheduledAtByDate(inputDate: LocalDate): LocalDate {
val daysToSubtract = when (inputDate.dayOfWeek) { val daysToSubtract = when (inputDate.dayOfWeek) {
DayOfWeek.WEDNESDAY -> 7 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()) val lastWednesday = inputDate.minusDays(daysToSubtract.toLong())
@@ -82,7 +87,6 @@ open class ProductionScheduleService(


val response = productionScheduleRepository.findProdScheduleInfoByPage( val response = productionScheduleRepository.findProdScheduleInfoByPage(
scheduleAt = request.scheduleAt, scheduleAt = request.scheduleAt,
// produceAt = request.produceAt,
schedulePeriod = request.schedulePeriod, schedulePeriod = request.schedulePeriod,
schedulePeriodTo = request.schedulePeriodTo, schedulePeriodTo = request.schedulePeriodTo,
totalEstProdCount = request.totalEstProdCount, totalEstProdCount = request.totalEstProdCount,
@@ -95,6 +99,29 @@ open class ProductionScheduleService(
return RecordsRes<ProdScheduleInfo>(records, total.toInt()); 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 { open fun roughProdScheduleDetail(id: Long): RoughProdScheduleWithLine {
val zero = BigDecimal.ZERO val zero = BigDecimal.ZERO
val prodSchedule = productionScheduleRepository.findById(id).getOrNull() ?: throw NoSuchElementException() val prodSchedule = productionScheduleRepository.findById(id).getOrNull() ?: throw NoSuchElementException()
@@ -303,10 +330,18 @@ open class ProductionScheduleService(
@Transactional(rollbackFor = [java.lang.Exception::class]) @Transactional(rollbackFor = [java.lang.Exception::class])
open fun saveProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse { open fun saveProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse {
val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() 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 // Update Prod Schedule Line Prod qty
prodScheduleLine.apply { prodScheduleLine.apply {
prodQty = request.demandQty.toDouble() prodQty = request.demandQty.toDouble()
type = "manual"
} }
productionScheduleLineRepository.saveAndFlush(prodScheduleLine) productionScheduleLineRepository.saveAndFlush(prodScheduleLine)


@@ -329,10 +364,21 @@ open class ProductionScheduleService(
val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) } val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) }
val approver = SecurityUtils.getUser().getOrNull() val approver = SecurityUtils.getUser().getOrNull()
val proportion = request.demandQty.divide(bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP) 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 // Update Prod Schedule Line Prod qty
prodScheduleLine.apply { prodScheduleLine.apply {
prodQty = request.demandQty.toDouble() prodQty = request.demandQty.toDouble()
this.type = if (isSameQty) this.type else "manual"
approverId = approver?.id approverId = approver?.id
} }
productionScheduleLineRepository.save(prodScheduleLine) productionScheduleLineRepository.save(prodScheduleLine)
@@ -459,9 +505,15 @@ open class ProductionScheduleService(
return jdbcDao.queryForInt(sql.toString(), args); 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>() val detailedScheduleOutputList = ArrayList<ProductionScheduleRecord>()


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

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


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


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


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



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


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




+ 21
- 1
src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt View File

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


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


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

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

@GetMapping("/detail/rough/{id}") @GetMapping("/detail/rough/{id}")
fun getScheduleDetail(@PathVariable id: Long): RoughProdScheduleWithLine { fun getScheduleDetail(@PathVariable id: Long): RoughProdScheduleWithLine {
return productionScheduleService.roughProdScheduleDetail(id) return productionScheduleService.roughProdScheduleDetail(id)
@@ -73,7 +85,15 @@ class ProductionScheduleController(
return productionScheduleService.allProdSchedulesByPage(request); 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]) @RequestMapping(value = ["/testDetailedSchedule"], method = [RequestMethod.GET])
fun generateDetailSchedule(request: HttpServletRequest?): Int { fun generateDetailSchedule(request: HttpServletRequest?): Int {
@@ -98,7 +118,7 @@ class ProductionScheduleController(
//TODO: update this to receive selectedDate and assignDate from schedule //TODO: update this to receive selectedDate and assignDate from schedule
// productionScheduleService.generateDetailedScheduleByDay(1, LocalDateTime.of(2025,6,25,0,0,0)) // productionScheduleService.generateDetailedScheduleByDay(1, LocalDateTime.of(2025,6,25,0,0,0))
println("genDate: $genDate | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ") 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 return 200
} catch (e: Exception) { } catch (e: Exception) {
throw RuntimeException("Error generate schedule: ${e.message}", e) throw RuntimeException("Error generate schedule: ${e.message}", e)


+ 5
- 4
src/main/java/com/ffii/fpsms/modules/master/web/models/SearchProdScheduleRequest.kt View File

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


data class SearchProdScheduleRequest ( data class SearchProdScheduleRequest (
val id: Long?, 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 totalEstProdCount: Double?,
val types: List<String>?,
var types: List<String>?,
val pageSize: Int?, val pageSize: Int?,
val pageNum: Int?, val pageNum: Int?,
) )

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/projections/PurchaseOrderInfo.kt View File

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

+ 64
- 16
src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt View File

@@ -65,22 +65,52 @@ open class PurchaseOrderService(
// } // }
open fun getPoList(args: MutableMap<String, Any>): List<PurchaseOrderDataClass> { open fun getPoList(args: MutableMap<String, Any>): List<PurchaseOrderDataClass> {
val sql = StringBuilder( 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")){ if (args.containsKey("code")){
sql.append(" AND po.code like :code "); sql.append(" AND po.code like :code ");
@@ -95,7 +125,24 @@ open class PurchaseOrderService(
sql.append(" and sil.purchaseOrderId IS NULL "); 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(" 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); val list = jdbcDao.queryForList(sql.toString(), args);
println(list) println(list)


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


+ 4
- 0
src/main/java/com/ffii/fpsms/modules/purchaseOrder/web/PurchaseOrderController.kt View File

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

@@ -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 View File

@@ -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 View File

@@ -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');

Loading…
Cancel
Save