CANCERYS\kw093 2 weeks ago
parent
commit
cc5c89f128
22 changed files with 626 additions and 52 deletions
  1. +2
    -0
      build.gradle
  2. +109
    -0
      src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt
  3. +36
    -10
      src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt
  4. +4
    -3
      src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt
  5. +5
    -0
      src/main/java/com/ffii/fpsms/modules/common/SettingNames.java
  6. +186
    -0
      src/main/java/com/ffii/fpsms/modules/common/scheduler/SchedulerService.kt
  7. +20
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderProcessService.kt
  8. +3
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt
  9. +3
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionSchedule.kt
  10. +35
    -5
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt
  11. +2
    -1
      src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
  12. +60
    -8
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  13. +21
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt
  14. +5
    -4
      src/main/java/com/ffii/fpsms/modules/master/web/models/SearchProdScheduleRequest.kt
  15. +1
    -0
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/projections/PurchaseOrderInfo.kt
  16. +64
    -16
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt
  17. +42
    -1
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/web/PurchaseOrderController.kt
  18. +1
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt
  19. +11
    -2
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  20. +4
    -0
      src/main/resources/db/changelog/changes/20250801_01_cyril/01_insert_schedule_setting.sql
  21. +5
    -0
      src/main/resources/db/changelog/changes/20250801_01_cyril/02_update_production_schedule.sql
  22. +7
    -0
      src/main/resources/db/changelog/changes/20250804_01_cyril/01_insert_schedule_setting.sql

+ 2
- 0
build.gradle View File

@@ -31,6 +31,8 @@ dependencies {
implementation 'org.springframework.security:spring-security-ldap'
implementation 'org.liquibase:liquibase-core'
implementation 'com.google.code.gson:gson:2.8.5'
implementation("org.apache.pdfbox:pdfbox:2.0.29")
implementation("org.apache.pdfbox:fontbox:2.0.34")

// // https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui
// implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8")


+ 109
- 0
src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt View File

@@ -0,0 +1,109 @@
package com.ffii.core.utils

import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.rendering.PDFRenderer
import org.apache.pdfbox.rendering.ImageType
import java.awt.image.BufferedImage
import java.io.File
import java.io.OutputStream
import java.net.Socket
import java.util.*

/**
* Utility class for generating and sending print jobs to a Zebra printer using ZPL.
* This class requires the 'org.apache.pdfbox:pdfbox' dependency to be included in your project.
*/
open class ZebraPrinterUtil {

companion object {

/**
* Converts the first page of a PDF document into a ZPL command and sends it to the specified printer.
*
* @param pdfFile The PDF file to be printed.
* @param printerIp The IP address of the Zebra printer.
* @param printerPort The port of the Zebra printer, typically 9100.
* @throws Exception if there is an error during file processing or printing.
*/
@JvmStatic
fun printPdfToZebra(pdfFile: File, printerIp: String, printerPort: Int) {

// Check if the file exists and is readable
if (!pdfFile.exists() || !pdfFile.canRead()) {
throw IllegalArgumentException("Error: File not found or not readable at path: ${pdfFile.absolutePath}")
}

try {
// 1. Load the PDF document
PDDocument.load(pdfFile).use { document ->
val renderer = PDFRenderer(document)

// 2. Render the first page of the PDF as a monochrome image
// The '300 / 72f' scales the image to 300 DPI.
val image = renderer.renderImage(0, 300 / 72f, ImageType.BINARY)

// 3. Convert the image to a ZPL format string
val zplCommand = convertImageToZpl(image)

// 4. Send the ZPL command to the printer via a network socket
val printData = zplCommand.toByteArray()
Socket(printerIp, printerPort).use { socket ->
val os: OutputStream = socket.getOutputStream()
os.write(printData)
os.flush()
}
}
} catch (e: Exception) {
// Re-throw the exception with a more descriptive message
throw Exception("Error processing print job for PDF: ${e.message}", e)
}
}

/**
* Converts a BufferedImage (monochrome) to a ZPL string using the ^GFA command.
* This function handles the conversion of pixel data to a compressed hex string.
*
* @param image The BufferedImage to convert.
* @return A ZPL-formatted string ready to be sent to the printer.
*/
private fun convertImageToZpl(image: BufferedImage): String {
val zpl = StringBuilder()
zpl.append("^XA\n")

val width = image.width
val height = image.height

// ZPL format for a graphical image is ^GFA
val bytesPerRow = (width + 7) / 8
val totalBytes = bytesPerRow * height

zpl.append("^FO0,0^GFA,").append(totalBytes).append(",").append(totalBytes).append(",").append(bytesPerRow).append(",")

// Extract pixel data and convert to ZPL hex string
for (y in 0 until height) {
val rowBits = BitSet(width)
for (x in 0 until width) {
// Check for a black pixel (0xFF000000 in ARGB)
if (image.getRGB(x, y) == 0xFF000000.toInt()) {
rowBits.set(x)
}
}

for (i in 0 until bytesPerRow) {
var byteValue = 0
for (bit in 0 until 8) {
if (rowBits.get(i * 8 + bit)) {
byteValue = byteValue or (1 shl (7 - bit))
}
}
zpl.append(String.format("%02X", byteValue))
}
}

zpl.append("^FS\n")
zpl.append("^XZ\n")

return zpl.toString()
}
}
}

+ 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)
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 View File

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

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

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

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

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

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

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

@@ -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?,


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

@@ -4,6 +4,7 @@ import com.ffii.core.response.RecordsRes
import com.ffii.core.support.JdbcDao
import com.ffii.core.utils.CriteriaArgsBuilder
import com.ffii.core.utils.PagingUtils
import com.ffii.core.utils.ZebraPrinterUtil
import com.ffii.fpsms.modules.master.entity.Items
import com.ffii.fpsms.modules.master.service.ItemsService
import com.ffii.fpsms.modules.master.web.models.MessageResponse
@@ -12,16 +13,22 @@ import com.ffii.fpsms.modules.purchaseOrder.entity.projections.PurchaseOrderData
import com.ffii.fpsms.modules.purchaseOrder.entity.projections.PurchaseOrderInfo
import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderService
import com.ffii.fpsms.modules.purchaseOrder.web.model.PagingRequest
import com.ffii.fpsms.modules.stock.service.StockInLineService
import com.ffii.fpsms.modules.stock.web.model.ExportQrCodeRequest
import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest
import jakarta.servlet.http.HttpServletRequest
import net.sf.jasperreports.engine.JasperExportManager
import net.sf.jasperreports.engine.JasperPrint
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.web.bind.annotation.*
import java.io.File

@RestController
@RequestMapping("/po")
class PurchaseOrderController(
private val purchaseOrderService: PurchaseOrderService
private val purchaseOrderService: PurchaseOrderService,
private val stockInLineService: StockInLineService
) {
@GetMapping("/list")
fun getPoList(
@@ -35,6 +42,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
@@ -42,9 +53,11 @@ class PurchaseOrderController(

val fullList = purchaseOrderService.getPoList(criteriaArgs)
val paginatedList = PagingUtils.getPaginatedList(fullList,pageSize, pageNum)

return RecordsRes(paginatedList, fullList.size)
}


@GetMapping("/testing")
fun testing(request: HttpServletRequest) {
val criteriaArgs = CriteriaArgsBuilder.withRequest(request)
@@ -58,6 +71,34 @@ class PurchaseOrderController(

@GetMapping("/detail/{id}") // purchaseOrderId
fun getDetailedPo(@PathVariable id: Long): Map<String, Any> {

/*
// tested the sample printing function by a pdf file
// just get a pdf file for demo , it can be any files? didn't try pdf with pages or multi pdf files
val request = ExportQrCodeRequest(
stockInLineIds = listOf(2L)
)
val pdf = stockInLineService.exportStockInLineQrcode(request)
val jasperPrint = pdf["report"] as JasperPrint

// 1. Create a temporary file to save the PDF.
val tempPdfFile = File.createTempFile("print_job_", ".pdf")

try {

// 2. Export the JasperPrint to the temporary PDF file.
JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath)

// 3. Call the utility function with the temporary file.
ZebraPrinterUtil.printPdfToZebra(tempPdfFile, "192.168.245.16", 9100)

} finally {
// 4. Ensure the temporary file is deleted after the print job is sent.
tempPdfFile.delete()
}
*/

return purchaseOrderService.getDetailedPo(id)
}



+ 1
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt View File

@@ -268,7 +268,7 @@ open class InventoryService(
val oneYearExpiry = 1L
val stockInLineEntries = polList.map { pol ->
val newLotNo = CodeGenerator.generateCode(
prefix = "POLOT",
prefix = "MPO",
itemId = pol.item!!.id!!,
count = pol.id!!.toInt()
)


+ 11
- 2
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt View File

@@ -1,5 +1,6 @@
package com.ffii.fpsms.modules.stock.service

import com.ffii.core.exception.BadRequestException
import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.core.utils.QrCodeUtil
@@ -74,7 +75,7 @@ open class StockInLineService(
purchaseOrderLine.apply {
status = PurchaseOrderLineStatus.RECEIVING
}
polRepository.saveAndFlush(purchaseOrderLine)
val pol = polRepository.saveAndFlush(purchaseOrderLine)
if (stockIn == null) {
stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn
// update po status to receiving
@@ -84,6 +85,10 @@ open class StockInLineService(
}
purchaseOrderRepository.save(po)
}
val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!)
if (pol.qty!! < request.acceptedQty) {
throw BadRequestException()
}
stockInLine.apply {
this.item = item
itemNo = item.code
@@ -116,7 +121,7 @@ open class StockInLineService(
"itemId" to stockInLine.item!!.id
)
val inventoryCount = jdbcDao.queryForInt(INVENTORY_COUNT.toString(), args)
val newLotNo = CodeGenerator.generateCode(prefix = "POLOT", itemId = stockInLine.item!!.id!!, count = inventoryCount)
val newLotNo = CodeGenerator.generateCode(prefix = "MPO", itemId = stockInLine.item!!.id!!, count = inventoryCount)
inventoryLot.apply {
this.item = stockInLine.item
this.stockInLine = stockInLine
@@ -247,11 +252,15 @@ open class StockInLineService(
message = "stock in line id is null",
errorPosition = null,
)
// val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockInLine!!.stockIn!!.id!!)

if (stockInLine.expiryDate != null && request.expiryDate == null) {
request.apply {
expiryDate = stockInLine.expiryDate
}
}
// TODO: check all status to prevent reverting progress due to multiple users access to the same po?

// return list of stock in line, update data grid with the list
if (request.acceptedQty.compareTo(stockInLine.acceptedQty) == 0) {
var savedInventoryLot: InventoryLot? = null


+ 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