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