From 6369797c234cf07cfe868d224d305882f3e5730f Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Fri, 18 Jul 2025 18:11:43 +0800 Subject: [PATCH] [Job Order & m18 scheduler] JO can gen Pick Order now & m18 po scheduler --- .../java/com/ffii/core/utils/GsonUtils.kt | 42 ++++++++++++ .../fpsms/m18/service/M18SchedulerService.kt | 32 ++++++++++ .../fpsms/modules/jobOrder/entity/JobOrder.kt | 5 ++ .../jobOrder/entity/JobOrderBomMaterial.kt | 10 +-- .../jobOrder/entity/JobOrderRepository.kt | 37 +++++++++++ .../entity/projections/JobOrderInfo.kt | 36 ++++++++++- .../jobOrder/service/JobOrderService.kt | 64 +++++++++++++++++++ .../jobOrder/web/JobOrderController.kt | 17 +++++ .../web/model/JobOrderActionRequest.kt | 5 ++ .../service/ProductionScheduleService.kt | 15 ++--- .../pickOrder/service/PickOrderService.kt | 12 +++- .../web/models/SavePickOrderRequest.kt | 6 +- 12 files changed, 260 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/ffii/core/utils/GsonUtils.kt create mode 100644 src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/JobOrderActionRequest.kt diff --git a/src/main/java/com/ffii/core/utils/GsonUtils.kt b/src/main/java/com/ffii/core/utils/GsonUtils.kt new file mode 100644 index 0000000..a170234 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/GsonUtils.kt @@ -0,0 +1,42 @@ +package com.ffii.core.utils + +import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleLineInfo +import com.ffii.fpsms.modules.master.service.ProductionScheduleService +import com.google.gson.GsonBuilder +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.reflect.TypeToken +import java.lang.reflect.Type + +open class GsonUtils { + + class BooleanTypeAdapter : JsonDeserializer { + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Boolean { + return json.asInt == 1 + } + } + + companion object { + val gson = GsonBuilder() + .registerTypeAdapter(Boolean::class.javaObjectType, BooleanTypeAdapter()) + .create() + + /** + * Read from JSON String. + * + * @param content + * JSON String content + * + * @param type + * Type + * + */ + fun stringToJson(content: String?, type: Type): T { +// val type = object : TypeToken() {}.type + val gsonResult: T = gson.fromJson(content, type) + + return gsonResult; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt new file mode 100644 index 0000000..2eb83fd --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt @@ -0,0 +1,32 @@ +package com.ffii.fpsms.m18.service + +import com.ffii.core.utils.JwtTokenUtil +import com.ffii.fpsms.m18.web.models.M18TestPoRequest +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +@Service +open class M18SchedulerService( + val m18PurchaseOrderService: M18PurchaseOrderService, + val m18MasterDataService: M18MasterDataService, +) { + var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) + val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd") + + @Scheduled(cron = "0 0 0 * * *") + open fun getPos() { + val today = LocalDateTime.now() + val yesterday = today.minusDays(1L) + val request = M18TestPoRequest( + modifiedDateTo = today.format(dataStringFormat), + modifiedDateFrom = yesterday.format(dataStringFormat) + ) + m18PurchaseOrderService.savePurchaseOrders(request); + logger.info("today: ${today.format(dataStringFormat)}") + logger.info("yesterday: ${yesterday.format(dataStringFormat)}") + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt index ae1e574..9f1907b 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt @@ -1,5 +1,6 @@ package com.ffii.fpsms.modules.jobOrder.entity +import com.fasterxml.jackson.annotation.JsonManagedReference import com.ffii.core.entity.BaseEntity import com.ffii.fpsms.modules.master.entity.Bom import com.ffii.fpsms.modules.master.entity.ProductionScheduleLine @@ -63,4 +64,8 @@ open class JobOrder : BaseEntity() { @JoinColumn(name = "prodScheduleLineId") // @Column(name = "prodScheduleLineId") open var prodScheduleLine: ProductionScheduleLine? = null + + @JsonManagedReference + @OneToMany(mappedBy = "jobOrder", cascade = [CascadeType.ALL], orphanRemoval = true) + open var jobms: MutableList = mutableListOf() } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterial.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterial.kt index 50932a5..ed3c6f1 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterial.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterial.kt @@ -5,6 +5,7 @@ import com.ffii.core.entity.BaseEntity import com.ffii.fpsms.modules.master.entity.Items import com.ffii.fpsms.modules.master.entity.UomConversion import com.ffii.fpsms.modules.stock.entity.InventoryLotLine +import com.ffii.fpsms.modules.stock.entity.StockOutLine import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot import jakarta.persistence.* import jakarta.validation.constraints.NotNull @@ -14,6 +15,7 @@ import java.math.BigDecimal @Entity @Table(name = "job_order_bom_material") open class JobOrderBomMaterial : BaseEntity() { + @JsonBackReference @NotNull @ManyToOne @JoinColumn(name = "jobOrderId", nullable = false) @@ -33,12 +35,12 @@ open class JobOrderBomMaterial : BaseEntity() { @JoinColumn(name = "uomId", nullable = false) open var uom: UomConversion? = null - @ManyToOne - @JoinColumn(name = "suggestedPickLotId") - open var suggestedPickLot: SuggestedPickLot? = null - @Size(max = 255) @NotNull @Column(name = "status", nullable = false) open var status: String? = null + + @ManyToOne + @JoinColumn(name = "stockOutLineId") + open var stockOutLine: StockOutLine? = null } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt index f3c2e48..a3f2c99 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt @@ -1,6 +1,7 @@ package com.ffii.fpsms.modules.jobOrder.entity import com.ffii.core.support.AbstractRepository +import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailWithJsonString import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable @@ -15,4 +16,40 @@ interface JobOrderRepository : AbstractRepository { fun findLatestCodeByPrefix(prefix: String): String? fun findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc(code: String, bomName: String, pageable: Pageable): Page + + @Query( + nativeQuery = true, + value = """ + select + jo.id, + jo.code, + b.name, + jo.reqQty, + b.outputQtyUom, + json_arrayagg( + json_object( + 'id', jobm.id, + 'code', i.code, + 'name', i.name, + 'lotNo', il.lotNo, + 'reqQty', jobm.reqQty, + 'uom', uc.udfudesc, + 'status', jobm.status + ) + ) as pickLines, + jo.status + from job_order jo + left join bom b on b.id = jo.bomId + left join job_order_bom_material jobm on jo.id = jobm.jobOrderId + left join items i on i.id = jobm.itemId + left join uom_conversion uc on uc.id = jobm.uomId + left join stock_out_line sol on sol.id = jobm.stockOutLineId + left join inventory_lot_line ill on ill.id = sol.inventoryLotLineId + left join inventory_lot il on il.id = ill.inventoryLotId + where jo.id = :id + group by jo.id + limit 1 + """ + ) + fun findJobOrderDetailById(id: Long): JobOrderDetailWithJsonString?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt index 27d46c6..e05b09a 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt @@ -3,13 +3,47 @@ package com.ffii.fpsms.modules.jobOrder.entity.projections import org.springframework.beans.factory.annotation.Value import java.math.BigDecimal +// Job Orders interface JobOrderInfo { val id: Long; val code: String; + @get:Value("#{target.bom.name}") val name: String; val reqQty: BigDecimal; + @get:Value("#{target.bom.outputQtyUom}") val uom: String; val status: String; -} \ No newline at end of file +} + +// Job Order +interface JobOrderDetailWithJsonString { + val id: Long?; + val code: String?; + val name: String?; + val reqQty: BigDecimal?; + val outputQtyUom: String?; + val pickLines: String?; + val status: String?; +} + +data class JobOrderDetail( + val id: Long?, + val code: String?, + val name: String?, + val reqQty: BigDecimal?, + val outputQtyUom: String?, + val pickLines: List?, + val status: String? +) + +data class JobOrderDetailPickLine( + val id: Long?, + val code: String?, + val name: String?, + val lotNo: String?, + val reqQty: BigDecimal?, + val uom: String?, + val status: String? +) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt index 2f6f17b..2d83482 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt @@ -1,28 +1,41 @@ package com.ffii.fpsms.modules.jobOrder.service import com.ffii.core.response.RecordsRes +import com.ffii.core.utils.GsonUtils import com.ffii.fpsms.modules.jobOrder.entity.JobOrder import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository +import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail +import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest +import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderReleaseRequest import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository import com.ffii.fpsms.modules.master.service.BomService import com.ffii.fpsms.modules.master.web.models.MessageResponse +import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType +import com.ffii.fpsms.modules.pickOrder.service.PickOrderService +import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderLineRequest +import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderRequest import com.ffii.fpsms.modules.user.service.UserService +import com.google.gson.reflect.TypeToken import org.springframework.data.domain.PageRequest import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.math.BigDecimal import java.time.LocalDate import java.time.LocalDateTime import java.time.format.DateTimeFormatter import kotlin.jvm.optionals.getOrNull + @Service open class JobOrderService( val jobOrderRepository: JobOrderRepository, val bomService: BomService, val userService: UserService, val productionScheduleLineRepository: ProductionScheduleLineRepository, + val pickOrderService: PickOrderService, ) { open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes { val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10); @@ -37,6 +50,22 @@ open class JobOrderService( val total = response.totalElements return RecordsRes(records, total.toInt()); } + + open fun jobOrderDetail(id: Long): JobOrderDetail { + val sqlResult = jobOrderRepository.findJobOrderDetailById(id) ?: throw NoSuchElementException(); + + val type = object : TypeToken>() {}.type + val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson>(it, type) } + return JobOrderDetail( + id = sqlResult.id, + code = sqlResult.code, + name = sqlResult.name, + reqQty = sqlResult.reqQty, + outputQtyUom = sqlResult.outputQtyUom, + pickLines = jsonResult, + status = sqlResult.status + ) + } open fun assignJobNo(): String { val suffixFormat = "%03d" val pattern = "yyyyMMdd" @@ -89,4 +118,39 @@ open class JobOrderService( errorPosition = null ) } + + @Transactional(rollbackFor = [Exception::class]) + open fun releaseJobOrder(request: JobOrderReleaseRequest): MessageResponse { + val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() + jo.apply { + status = "pending" + } + jobOrderRepository.save(jo) + + val pols = jo.jobms.map { + SavePickOrderLineRequest( + itemId = it.item?.id, + qty = it.reqQty ?: BigDecimal.ZERO, + uomId = it.uom?.id, + ) + } + val po = SavePickOrderRequest( + joId = jo.id, + type = PickOrderType.JOB_ORDER, + targetDate = jo.planStart?.toLocalDate() ?: LocalDate.now(), + pickOrderLine = pols + ) + + pickOrderService.create(po) + + return MessageResponse( + id = jo.id, + code = jo.code, + name = jo.bom?.name, + type = null, + message = null, + errorPosition = null, + entity = mapOf("status" to jo.status) + ) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt index c28dde5..52caea5 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt @@ -1,11 +1,18 @@ package com.ffii.fpsms.modules.jobOrder.web 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.JobOrderService +import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderReleaseRequest import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest +import com.ffii.fpsms.modules.master.web.models.MessageResponse +import jakarta.validation.Valid import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.ModelAttribute +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -19,4 +26,14 @@ class JobOrderController( fun allJobOrdersByPage(@ModelAttribute request: SearchJobOrderInfoRequest): RecordsRes { return jobOrderService.allJobOrdersByPage(request); } + + @GetMapping("/detail/{id}") + fun jobOrderDetail(@PathVariable id: Long): JobOrderDetail { + return jobOrderService.jobOrderDetail(id); + } + + @PostMapping("/release") + fun releaseJobOrder(@Valid @RequestBody request: JobOrderReleaseRequest): MessageResponse { + return jobOrderService.releaseJobOrder(request) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/JobOrderActionRequest.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/JobOrderActionRequest.kt new file mode 100644 index 0000000..d8e3bcc --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/JobOrderActionRequest.kt @@ -0,0 +1,5 @@ +package com.ffii.fpsms.modules.jobOrder.web.model + +data class JobOrderReleaseRequest( + val id: Long, +) diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt index 34af086..5ccfe23 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt @@ -3,6 +3,7 @@ package com.ffii.fpsms.modules.master.service import com.ffii.core.response.RecordsRes import com.ffii.core.support.AbstractBaseEntityService import com.ffii.core.support.JdbcDao +import com.ffii.core.utils.GsonUtils import com.ffii.fpsms.modules.common.SecurityUtils import com.ffii.fpsms.modules.jobOrder.service.JobOrderBomMaterialService import com.ffii.fpsms.modules.jobOrder.service.JobOrderProcessService @@ -265,21 +266,13 @@ open class ProductionScheduleService( ) } - class BooleanTypeAdapter: JsonDeserializer { - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Boolean { -// println(json) - return json.asInt == 1 - } - } - open fun detailedProdScheduleDetail(id: Long): DetailedProdScheduleWithLine { val sqlResult = productionScheduleRepository.findDetailedProdScheduleWithLine(id) ?: throw NoSuchElementException() - val gson = GsonBuilder() - .registerTypeAdapter(Boolean::class.javaObjectType, BooleanTypeAdapter()) - .create() val type = object : TypeToken?>() {}.type - val gsonResult: List? = gson.fromJson(sqlResult.prodScheduleLines, type) +// val gsonResult: List? = gson.fromJson(sqlResult.prodScheduleLines, type) + val gsonResult: List? = GsonUtils.stringToJson(sqlResult.prodScheduleLines, type) + return DetailedProdScheduleWithLine( id = sqlResult.id, diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index b2e2353..9c8db6d 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -4,6 +4,7 @@ import com.ffii.core.response.RecordsRes import com.ffii.core.support.AbstractBaseEntityService import com.ffii.core.support.JdbcDao import com.ffii.fpsms.modules.common.SecurityUtils +import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository import com.ffii.fpsms.modules.master.entity.ItemsRepository import com.ffii.fpsms.modules.master.entity.UomConversionRepository import com.ffii.fpsms.modules.master.web.models.MessageResponse @@ -53,20 +54,25 @@ open class PickOrderService( private val inventoryService: InventoryService, private val stockOutRepository: StockOutRepository, private val itemsRepository: ItemsRepository, - private val uomConversionRepository: UomConversionRepository + private val uomConversionRepository: UomConversionRepository, + private val jobOrderRepository: JobOrderRepository ): AbstractBaseEntityService(jdbcDao, pickOrderRepository) { open fun create(request: SavePickOrderRequest): MessageResponse { val code = assignPickCode() + val jo = request.joId?.let { jobOrderRepository.findById(it).getOrNull() } val pickOrder = PickOrder().apply { this.code = code + this.jobOrder = jo this.targetDate = request.targetDate.atStartOfDay() this.type = request.type this.status = PickOrderStatus.PENDING } val savedPickOrder = saveAndFlush(pickOrder) val polEntries = request.pickOrderLine.map { - val item = itemsRepository.findById(it.itemId).orElseThrow() - val uom = uomConversionRepository.findByCodeAndDeletedFalse(it.uom) +// val item = itemsRepository.findById(it.itemId).orElseThrow() + val uom = it.uom?.let { uomCode -> uomConversionRepository.findByCodeAndDeletedFalse(uomCode) } + ?: it.uomId?.let { uomId -> uomConversionRepository.findById(uomId).orElseThrow() } + val item = it.itemId?.let { itemId -> itemsRepository.findById(itemId).orElseThrow() } PickOrderLine().apply { this.pickOrder = savedPickOrder this.item = item diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SavePickOrderRequest.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SavePickOrderRequest.kt index 6ba0373..f812237 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SavePickOrderRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SavePickOrderRequest.kt @@ -6,12 +6,14 @@ import java.time.LocalDate import java.time.LocalDateTime data class SavePickOrderLineRequest ( - val itemId: Long, + val itemId: Long?, val qty: BigDecimal, // val uomId: Long - val uom: String, + val uom: String? = null, + val uomId: Long? = null ) data class SavePickOrderRequest ( + val joId: Long? = null, val type: PickOrderType, var targetDate: LocalDate, val pickOrderLine: List