@@ -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<Boolean> { | |||
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 <T> stringToJson(content: String?, type: Type): T { | |||
// val type = object : TypeToken<T>() {}.type | |||
val gsonResult: T = gson.fromJson(content, type) | |||
return gsonResult; | |||
} | |||
} | |||
} |
@@ -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)}") | |||
} | |||
} |
@@ -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<Long>() { | |||
@JoinColumn(name = "prodScheduleLineId") | |||
// @Column(name = "prodScheduleLineId") | |||
open var prodScheduleLine: ProductionScheduleLine? = null | |||
@JsonManagedReference | |||
@OneToMany(mappedBy = "jobOrder", cascade = [CascadeType.ALL], orphanRemoval = true) | |||
open var jobms: MutableList<JobOrderBomMaterial> = mutableListOf() | |||
} |
@@ -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<Long>() { | |||
@JsonBackReference | |||
@NotNull | |||
@ManyToOne | |||
@JoinColumn(name = "jobOrderId", nullable = false) | |||
@@ -33,12 +35,12 @@ open class JobOrderBomMaterial : BaseEntity<Long>() { | |||
@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 | |||
} |
@@ -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<JobOrder, Long> { | |||
fun findLatestCodeByPrefix(prefix: String): String? | |||
fun findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc(code: String, bomName: String, pageable: Pageable): Page<JobOrderInfo> | |||
@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?; | |||
} |
@@ -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; | |||
} | |||
} | |||
// 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<JobOrderDetailPickLine>?, | |||
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? | |||
) |
@@ -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<JobOrderInfo> { | |||
val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10); | |||
@@ -37,6 +50,22 @@ open class JobOrderService( | |||
val total = response.totalElements | |||
return RecordsRes<JobOrderInfo>(records, total.toInt()); | |||
} | |||
open fun jobOrderDetail(id: Long): JobOrderDetail { | |||
val sqlResult = jobOrderRepository.findJobOrderDetailById(id) ?: throw NoSuchElementException(); | |||
val type = object : TypeToken<List<JobOrderDetailPickLine>>() {}.type | |||
val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson<List<JobOrderDetailPickLine>>(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) | |||
) | |||
} | |||
} |
@@ -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<JobOrderInfo> { | |||
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) | |||
} | |||
} |
@@ -0,0 +1,5 @@ | |||
package com.ffii.fpsms.modules.jobOrder.web.model | |||
data class JobOrderReleaseRequest( | |||
val id: Long, | |||
) |
@@ -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<Boolean> { | |||
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<List<DetailedProdScheduleLineInfo>?>() {}.type | |||
val gsonResult: List<DetailedProdScheduleLineInfo>? = gson.fromJson(sqlResult.prodScheduleLines, type) | |||
// val gsonResult: List<DetailedProdScheduleLineInfo>? = gson.fromJson(sqlResult.prodScheduleLines, type) | |||
val gsonResult: List<DetailedProdScheduleLineInfo>? = GsonUtils.stringToJson(sqlResult.prodScheduleLines, type) | |||
return DetailedProdScheduleWithLine( | |||
id = sqlResult.id, | |||
@@ -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<PickOrder, Long, PickOrderRepository>(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 | |||
@@ -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<SavePickOrderLineRequest> | |||