| @@ -2,6 +2,7 @@ package com.ffii.fpsms.modules.jobOrder.entity | |||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.master.entity.Bom | import com.ffii.fpsms.modules.master.entity.Bom | ||||
| import com.ffii.fpsms.modules.master.entity.ProductionScheduleLine | |||||
| import com.ffii.fpsms.modules.user.entity.User | import com.ffii.fpsms.modules.user.entity.User | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| @@ -58,8 +59,8 @@ open class JobOrder : BaseEntity<Long>() { | |||||
| @JoinColumn(name = "approverId", referencedColumnName = "id") | @JoinColumn(name = "approverId", referencedColumnName = "id") | ||||
| open var approver: User? = null | open var approver: User? = null | ||||
| // @ManyToOne | |||||
| // @JoinColumn(name = "jobPlanningLogId") | |||||
| @Column(name = "prodScheduleLineId") | |||||
| open var prodScheduleLineId: Long? = null | |||||
| @OneToOne | |||||
| @JoinColumn(name = "prodScheduleLineId") | |||||
| // @Column(name = "prodScheduleLineId") | |||||
| open var prodScheduleLine: ProductionScheduleLine? = null | |||||
| } | } | ||||
| @@ -1,21 +1,23 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity | package com.ffii.fpsms.modules.jobOrder.entity | ||||
| import com.fasterxml.jackson.annotation.JsonBackReference | |||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.master.entity.Items | import com.ffii.fpsms.modules.master.entity.Items | ||||
| import com.ffii.fpsms.modules.master.entity.UomConversion | import com.ffii.fpsms.modules.master.entity.UomConversion | ||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLine | import com.ffii.fpsms.modules.stock.entity.InventoryLotLine | ||||
| import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot | |||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| import jakarta.validation.constraints.Size | |||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| @Entity | @Entity | ||||
| @Table(name = "job_order_material") | |||||
| open class JobOrderMaterial : BaseEntity<Long>() { | |||||
| @Table(name = "job_order_bom_material") | |||||
| open class JobOrderBomMaterial : BaseEntity<Long>() { | |||||
| @NotNull | @NotNull | ||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "jopId", nullable = false) | |||||
| open var jop: JobOrderProcess? = null | |||||
| @JoinColumn(name = "jobOrderId", nullable = false) | |||||
| open var jobOrder: JobOrder? = null | |||||
| @NotNull | @NotNull | ||||
| @ManyToOne | @ManyToOne | ||||
| @@ -23,8 +25,8 @@ open class JobOrderMaterial : BaseEntity<Long>() { | |||||
| open var item: Items? = null | open var item: Items? = null | ||||
| @NotNull | @NotNull | ||||
| @Column(name = "qty", nullable = false, precision = 14, scale = 2) | |||||
| open var qty: BigDecimal? = null | |||||
| @Column(name = "reqQty", nullable = false, precision = 14, scale = 2) | |||||
| open var reqQty: BigDecimal? = null | |||||
| @NotNull | @NotNull | ||||
| @ManyToOne | @ManyToOne | ||||
| @@ -32,11 +34,11 @@ open class JobOrderMaterial : BaseEntity<Long>() { | |||||
| open var uom: UomConversion? = null | open var uom: UomConversion? = null | ||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "inventoryLotLineId") | |||||
| open var inventoryLotLine: InventoryLotLine? = null | |||||
| @JoinColumn(name = "suggestedPickLotId") | |||||
| open var suggestedPickLot: SuggestedPickLot? = null | |||||
| @Size(max = 255) | |||||
| @NotNull | @NotNull | ||||
| @ManyToOne | |||||
| @JoinColumn(name = "joProcessDetailId", nullable = false) | |||||
| open var joProcessDetail: JobOrderProcessDetail? = null | |||||
| @Column(name = "status", nullable = false) | |||||
| open var status: String? = null | |||||
| } | } | ||||
| @@ -4,5 +4,5 @@ import com.ffii.core.support.AbstractRepository | |||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| @Repository | @Repository | ||||
| interface JobOrderMaterialRepository : AbstractRepository<JobOrderMaterial, Long> { | |||||
| interface JobOrderBomMaterialRepository : AbstractRepository<JobOrderBomMaterial, Long> { | |||||
| } | } | ||||
| @@ -11,12 +11,12 @@ import java.time.LocalDateTime | |||||
| @Table(name = "job_order_process") | @Table(name = "job_order_process") | ||||
| open class JobOrderProcess : BaseEntity<Long>() { | open class JobOrderProcess : BaseEntity<Long>() { | ||||
| @NotNull | @NotNull | ||||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "joId", nullable = false) | @JoinColumn(name = "joId", nullable = false) | ||||
| open var jo: JobOrder? = null | open var jo: JobOrder? = null | ||||
| @NotNull | @NotNull | ||||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "processId", nullable = false) | @JoinColumn(name = "processId", nullable = false) | ||||
| open var process: Process? = null | open var process: Process? = null | ||||
| @@ -1,8 +1,13 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity | package com.ffii.fpsms.modules.jobOrder.entity | ||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| import org.springframework.data.jpa.repository.Query | |||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| @Repository | @Repository | ||||
| interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | ||||
| @Query(""" | |||||
| select jo.code from JobOrder jo where jo.code like :prefix% order by jo.code desc limit 1 | |||||
| """) | |||||
| fun findLatestCodeByPrefix(prefix: String): String? | |||||
| } | } | ||||
| @@ -0,0 +1,45 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.service | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterial | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterialRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderBomMaterialRequest | |||||
| 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 | |||||
| import org.springframework.stereotype.Service | |||||
| import kotlin.jvm.optionals.getOrNull | |||||
| @Service | |||||
| open class JobOrderBomMaterialService( | |||||
| val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository, private val jobOrderRepository: JobOrderRepository, | |||||
| private val itemsRepository: ItemsRepository, | |||||
| private val uomConversionRepository: UomConversionRepository | |||||
| ) { | |||||
| fun createJobOrderBomMaterials(request: List<CreateJobOrderBomMaterialRequest>): MessageResponse { | |||||
| val joBomMaterials = request.map { req -> | |||||
| val jo = req.joId?.let { jobOrderRepository.findById(it).getOrNull() } | |||||
| val item = req.itemId?.let { itemsRepository.findById(it).getOrNull() } | |||||
| val uom = req.uomId?.let { uomConversionRepository.findById(it).getOrNull() } | |||||
| JobOrderBomMaterial().apply { | |||||
| jobOrder = jo | |||||
| this.item = item | |||||
| reqQty = req.reqQty | |||||
| this.uom = uom | |||||
| status = req.status | |||||
| } | |||||
| } | |||||
| jobOrderBomMaterialRepository.saveAll(joBomMaterials) | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = null, | |||||
| code = null, | |||||
| type = null, | |||||
| message = "Success", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -7,8 +7,11 @@ import com.ffii.core.support.JdbcDao | |||||
| import com.ffii.fpsms.m18.entity.M18DataLogRepository | import com.ffii.fpsms.m18.entity.M18DataLogRepository | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderProcess | import com.ffii.fpsms.modules.jobOrder.entity.JobOrderProcess | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderProcessRepository | import com.ffii.fpsms.modules.jobOrder.entity.JobOrderProcessRepository | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderProcessRequest | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.MachineRequest | import com.ffii.fpsms.modules.jobOrder.web.model.MachineRequest | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.OperatorResponse | import com.ffii.fpsms.modules.jobOrder.web.model.OperatorResponse | ||||
| import com.ffii.fpsms.modules.master.entity.ProcessRepository | |||||
| import com.ffii.fpsms.modules.master.entity.ShopRepository | import com.ffii.fpsms.modules.master.entity.ShopRepository | ||||
| import com.ffii.fpsms.modules.master.service.CurrencyService | import com.ffii.fpsms.modules.master.service.CurrencyService | ||||
| import com.ffii.fpsms.modules.master.service.ShopService | import com.ffii.fpsms.modules.master.service.ShopService | ||||
| @@ -45,9 +48,36 @@ import kotlin.jvm.optionals.getOrNull | |||||
| open class JobOrderProcessService( | open class JobOrderProcessService( | ||||
| private val jdbcDao: JdbcDao, | private val jdbcDao: JdbcDao, | ||||
| private val jobOrderProcessRepository: JobOrderProcessRepository, | private val jobOrderProcessRepository: JobOrderProcessRepository, | ||||
| private val userRepository: UserRepository | |||||
| private val userRepository: UserRepository, private val processRepository: ProcessRepository, | |||||
| private val jobOrderRepository: JobOrderRepository, | |||||
| ) : AbstractBaseEntityService<JobOrderProcess, Long, JobOrderProcessRepository>(jdbcDao, jobOrderProcessRepository) { | ) : AbstractBaseEntityService<JobOrderProcess, Long, JobOrderProcessRepository>(jdbcDao, jobOrderProcessRepository) { | ||||
| open fun createJobOrderProcesses(request: List<CreateJobOrderProcessRequest>): MessageResponse{ | |||||
| val joProcesses = request.map { req -> | |||||
| val jo = req.joId?.let { jobOrderRepository.findById(it).getOrNull() } | |||||
| val process = req.processId?.let { processRepository.findById(it).getOrNull() } | |||||
| JobOrderProcess().apply { | |||||
| this.jo = jo | |||||
| this.process = process | |||||
| status = req.status | |||||
| seqNo = req.seqNo | |||||
| remarks = req.remarks | |||||
| } | |||||
| } | |||||
| jobOrderProcessRepository.saveAll(joProcesses) | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = null, | |||||
| code = null, | |||||
| type = null, | |||||
| message = "Success", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| open fun isOperatorExist(request: OperatorRequest): MessageResponse{ | open fun isOperatorExist(request: OperatorRequest): MessageResponse{ | ||||
| val User = userRepository.findByUsernameAndDeletedFalse(request.username) | val User = userRepository.findByUsernameAndDeletedFalse(request.username) | ||||
| @@ -0,0 +1,75 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.service | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrder | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest | |||||
| 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.user.service.UserService | |||||
| import org.springframework.stereotype.Service | |||||
| 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, | |||||
| ) { | |||||
| open fun assignJobNo(): String { | |||||
| val suffixFormat = "%03d" | |||||
| val pattern = "yyyyMMdd" | |||||
| val formatter = DateTimeFormatter.ofPattern(pattern) | |||||
| val prefix = "JO" | |||||
| val midfix = LocalDate.now().format(formatter) | |||||
| val suffix = String.format(suffixFormat, 1) | |||||
| val latestCode = jobOrderRepository.findLatestCodeByPrefix("${prefix}-${midfix}") | |||||
| if (latestCode != null) { | |||||
| val splitLatestCode = latestCode.split("-") | |||||
| if (splitLatestCode.size > 2) { | |||||
| val latestNo = splitLatestCode[2].toInt() | |||||
| return listOf<String>(prefix, midfix, String.format(suffixFormat, latestNo + 1)).joinToString("-") | |||||
| } | |||||
| } | |||||
| return listOf<String>(prefix, midfix, suffix).joinToString("-") | |||||
| } | |||||
| open fun createJobOrder(request: CreateJobOrderRequest): MessageResponse { | |||||
| val jo = JobOrder() | |||||
| val bom = request.bomId?.let { bomService.findById(it) } | |||||
| val approver = request.approverId?.let { userService.find(it).getOrNull() } | |||||
| val prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() } | |||||
| val code = assignJobNo() | |||||
| jo.apply { | |||||
| this.code = code | |||||
| this.bom = bom | |||||
| //TODO: planStart & planEnd | |||||
| planStart = LocalDateTime.now() | |||||
| planEnd = LocalDateTime.now() | |||||
| reqQty = request.reqQty | |||||
| status = request.status | |||||
| type = request.type | |||||
| this.approver = approver | |||||
| this.prodScheduleLine = prodScheduleLine | |||||
| } | |||||
| val savedJo = jobOrderRepository.saveAndFlush(jo); | |||||
| return MessageResponse( | |||||
| id = savedJo.id, | |||||
| name = null, | |||||
| code = savedJo.code, | |||||
| type = savedJo.type, | |||||
| message = "Success", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.web.model | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDateTime | |||||
| data class CreateJobOrderRequest ( | |||||
| val bomId: Long?, | |||||
| val planStart: LocalDateTime? = null, | |||||
| val planEnd: LocalDateTime? = null, | |||||
| val reqQty: BigDecimal?, | |||||
| val type: String? = "detailed", | |||||
| val approverId: Long?, | |||||
| val prodScheduleLineId: Long?, | |||||
| val status: String = "planning", | |||||
| ) | |||||
| data class CreateJobOrderBomMaterialRequest ( | |||||
| val joId: Long?, | |||||
| val itemId: Long?, | |||||
| val reqQty: BigDecimal?, | |||||
| val uomId: Long?, | |||||
| val status: String = "pending", | |||||
| ) | |||||
| data class CreateJobOrderProcessRequest ( | |||||
| val joId: Long?, | |||||
| val processId: Long?, | |||||
| val seqNo: Long?, | |||||
| val remarks: String? = null, | |||||
| val status: String = "pending", | |||||
| ) | |||||
| @@ -61,6 +61,10 @@ open class Bom : BaseEntity<Long>() { | |||||
| @OneToMany(mappedBy = "bom", cascade = [CascadeType.ALL], orphanRemoval = true) | @OneToMany(mappedBy = "bom", cascade = [CascadeType.ALL], orphanRemoval = true) | ||||
| open var bomMaterials: MutableList<BomMaterial> = mutableListOf() | open var bomMaterials: MutableList<BomMaterial> = mutableListOf() | ||||
| @JsonManagedReference | |||||
| @OneToMany(mappedBy = "bom", cascade = [CascadeType.ALL], orphanRemoval = true) | |||||
| open var bomProcesses: MutableList<BomProcess> = mutableListOf() | |||||
| @Column(name = "m18Id") | @Column(name = "m18Id") | ||||
| open var m18Id: Long? = null | open var m18Id: Long? = null | ||||
| @@ -11,4 +11,6 @@ interface BomRepository : AbstractRepository<Bom, Long> { | |||||
| fun findByIdAndDeletedIsFalse(id: Serializable): Bom? | fun findByIdAndDeletedIsFalse(id: Serializable): Bom? | ||||
| fun findByM18IdAndDeletedIsFalse(m18Id: Long): Bom? | fun findByM18IdAndDeletedIsFalse(m18Id: Long): Bom? | ||||
| fun findByItemIdAndDeletedIsFalse(itemId: Serializable): Bom? | |||||
| } | } | ||||
| @@ -2,6 +2,7 @@ package com.ffii.fpsms.modules.master.entity | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleWithLine | import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleWithLine | ||||
| import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleWithLineWithJsonString | |||||
| import com.ffii.fpsms.modules.master.entity.projections.ProdScheduleInfo | import com.ffii.fpsms.modules.master.entity.projections.ProdScheduleInfo | ||||
| import org.springframework.data.domain.Page | import org.springframework.data.domain.Page | ||||
| import org.springframework.data.domain.Pageable | import org.springframework.data.domain.Pageable | ||||
| @@ -81,29 +82,39 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| @Query(nativeQuery = true, | @Query(nativeQuery = true, | ||||
| value = | value = | ||||
| """ | """ | ||||
| with prod_equip as ( | |||||
| with prod_prop as ( | |||||
| select | |||||
| psl.id as pslId, | |||||
| coalesce(psl.prodQty, 0) / coalesce(b.outputQty, 1) as proportion | |||||
| from production_schedule ps | |||||
| left join production_schedule_line psl on psl.prodScheduleId = ps.id | |||||
| left join bom b on b.itemId = psl.itemId | |||||
| where ps.id = :id and b.id is not null | |||||
| ), | |||||
| prod_equip as ( | |||||
| select | select | ||||
| r1.pslId, | r1.pslId, | ||||
| json_array(group_concat(json_object(r1.equipName, r1.totalMinute))) as prodTimeInMinute | |||||
| json_arrayagg(json_object('equipName', r1.equipName, 'totalMinutes', r1.totalMinutes)) as prodTimeInMinute | |||||
| from ( | from ( | ||||
| select | select | ||||
| psl.id as pslId, | psl.id as pslId, | ||||
| coalesce(e.name, 'N/A') as equipName, | coalesce(e.name, 'N/A') as equipName, | ||||
| sum(coalesce(bp.prepTimeInMinute, 0) + coalesce(bp.durationInMinute, 0) + coalesce(bp.postProdTimeInMinute, 0)) as totalMinute | |||||
| ceil(sum((coalesce(bp.prepTimeInMinute, 0) + coalesce(bp.durationInMinute, 0) + coalesce(bp.postProdTimeInMinute, 0)) * pp.proportion)) as totalMinutes | |||||
| from production_schedule ps | from production_schedule ps | ||||
| left join production_schedule_line psl on psl.prodScheduleId = ps.id | left join production_schedule_line psl on psl.prodScheduleId = ps.id | ||||
| left join prod_prop pp on pp.pslId = psl.id | |||||
| left join bom b on b.itemId = psl.itemId | left join bom b on b.itemId = psl.itemId | ||||
| left join bom_process bp on bp.bomId = b.id | left join bom_process bp on bp.bomId = b.id | ||||
| left join equipment e on bp.equipmentId = e.id | left join equipment e on bp.equipmentId = e.id | ||||
| where ps.id = :id and b.id is not null | where ps.id = :id and b.id is not null | ||||
| group by psl.id, e.id | |||||
| group by psl.id, e.id, pp.proportion | |||||
| ) r1 | ) r1 | ||||
| group by r1.pslId | group by r1.pslId | ||||
| ), | ), | ||||
| prod_material as ( | prod_material as ( | ||||
| select | select | ||||
| r2.pslId, | r2.pslId, | ||||
| json_array(group_concat(json_object('id', r2.id, 'code', r2.code, 'name', r2.name, 'type', r2.`type`, 'availableQty', r2.availableQty, 'demandQty', r2.demandQty))) as bomMaterials | |||||
| json_arrayagg(json_object('id', r2.id, 'code', r2.code, 'name', r2.name, 'type', r2.`type`, 'availableQty', r2.availableQty, 'demandQty', r2.demandQty)) as bomMaterials | |||||
| from ( | from ( | ||||
| select | select | ||||
| psl.id as pslId, | psl.id as pslId, | ||||
| @@ -112,15 +123,16 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| bmi.name, | bmi.name, | ||||
| bmi.`type`, | bmi.`type`, | ||||
| coalesce(i.onHandQty, 0) - coalesce(i.onHoldQty, 0) - coalesce(i.unavailableQty, 0) as availableQty, | coalesce(i.onHandQty, 0) - coalesce(i.onHoldQty, 0) - coalesce(i.unavailableQty, 0) as availableQty, | ||||
| coalesce(psl.prodQty, 0) as demandQty | |||||
| ceil(coalesce(bm.qty, 0) * pp.proportion) as demandQty | |||||
| from production_schedule ps | from production_schedule ps | ||||
| left join production_schedule_line psl on psl.prodScheduleId = ps.id | left join production_schedule_line psl on psl.prodScheduleId = ps.id | ||||
| left join prod_prop pp on pp.pslId = psl.id | |||||
| left join bom b on b.itemId = psl.itemId | left join bom b on b.itemId = psl.itemId | ||||
| left join bom_material bm on bm.bomId = b.id | left join bom_material bm on bm.bomId = b.id | ||||
| left join items bmi on bmi.id = bm.itemId | left join items bmi on bmi.id = bm.itemId | ||||
| left join inventory i on i.itemId = bmi.id | left join inventory i on i.itemId = bmi.id | ||||
| where ps.id = :id and bmi.id is not null | where ps.id = :id and bmi.id is not null | ||||
| group by ps.id, psl.id, bm.id, i.id | |||||
| group by ps.id, psl.id, bm.id, i.id, pp.proportion | |||||
| ) r2 | ) r2 | ||||
| group by r2.pslId | group by r2.pslId | ||||
| ) | ) | ||||
| @@ -129,9 +141,9 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| prod.scheduleAt, | prod.scheduleAt, | ||||
| prod.totalFGType, | prod.totalFGType, | ||||
| prod.totalEstProdCount, | prod.totalEstProdCount, | ||||
| json_array(group_concat( | |||||
| json_arrayagg( | |||||
| json_object('id', prod.pslId, 'bomMaterials', prod.bomMaterials, 'jobNo', prod.jobNo, 'code', prod.code, 'name', prod.name, 'type', prod.type, 'demandQty', prod.demandQty, 'prodTimeInMinute', prod.prodTimeInMinute, 'priority', prod.priority) | json_object('id', prod.pslId, 'bomMaterials', prod.bomMaterials, 'jobNo', prod.jobNo, 'code', prod.code, 'name', prod.name, 'type', prod.type, 'demandQty', prod.demandQty, 'prodTimeInMinute', prod.prodTimeInMinute, 'priority', prod.priority) | ||||
| )) | |||||
| ) as prodScheduleLines | |||||
| from ( | from ( | ||||
| select | select | ||||
| ps.id, | ps.id, | ||||
| @@ -140,7 +152,7 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| ps.totalEstProdCount, | ps.totalEstProdCount, | ||||
| psl.id as pslId, | psl.id as pslId, | ||||
| pm.bomMaterials, | pm.bomMaterials, | ||||
| jo.code as jobNo, | |||||
| coalesce(jo.code, 'N/A') as jobNo, | |||||
| psli.code, | psli.code, | ||||
| psli.name, | psli.name, | ||||
| psli.`type`, | psli.`type`, | ||||
| @@ -159,5 +171,5 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| ) prod group by prod.id limit 1 | ) prod group by prod.id limit 1 | ||||
| """ | """ | ||||
| ) | ) | ||||
| fun findDetailedProdScheduleWithLine(id: Long): List<DetailedProdScheduleWithLine> | |||||
| fun findDetailedProdScheduleWithLine(id: Long): DetailedProdScheduleWithLineWithJsonString? | |||||
| } | } | ||||
| @@ -15,7 +15,16 @@ interface ProdScheduleInfo { | |||||
| val type: String? | val type: String? | ||||
| } | } | ||||
| // Detailed Production Schedule With Line | // Detailed Production Schedule With Line | ||||
| data class DetailedProdScheduleWithLine( | |||||
| interface DetailedProdScheduleWithLineWithJsonString { | |||||
| val id: Long? | |||||
| val scheduleAt: LocalDateTime? | |||||
| val totalEstProdCount: BigDecimal? | |||||
| val totalFGType: Long? | |||||
| // val prodScheduleLines: List<DetailedProdScheduleLineInfo>? | |||||
| val prodScheduleLines: String? | |||||
| } | |||||
| data class DetailedProdScheduleWithLine ( | |||||
| val id: Long?, | val id: Long?, | ||||
| val scheduleAt: LocalDateTime?, | val scheduleAt: LocalDateTime?, | ||||
| val totalEstProdCount: BigDecimal?, | val totalEstProdCount: BigDecimal?, | ||||
| @@ -32,7 +41,7 @@ data class DetailedProdScheduleLineInfo( | |||||
| val type: String?, | val type: String?, | ||||
| val demandQty: BigDecimal?, | val demandQty: BigDecimal?, | ||||
| val prodTimeInMinute: List<DetailedProdScheduleLineProdTime>?, | val prodTimeInMinute: List<DetailedProdScheduleLineProdTime>?, | ||||
| val priority: BigDecimal?, | |||||
| val priority: BigDecimal? | |||||
| ) | ) | ||||
| data class DetailedProdScheduleLineBomMaterial ( | data class DetailedProdScheduleLineBomMaterial ( | ||||
| @@ -46,7 +55,7 @@ data class DetailedProdScheduleLineBomMaterial ( | |||||
| data class DetailedProdScheduleLineProdTime ( | data class DetailedProdScheduleLineProdTime ( | ||||
| val equipName: String?, | val equipName: String?, | ||||
| val minutes: BigDecimal?, | |||||
| val totalMinutes: BigDecimal? | |||||
| ) | ) | ||||
| // Rough Production Schedule With Line | // Rough Production Schedule With Line | ||||
| @@ -37,6 +37,10 @@ open class BomService( | |||||
| return bomRepository.findByM18IdAndDeletedIsFalse(m18Id) | return bomRepository.findByM18IdAndDeletedIsFalse(m18Id) | ||||
| } | } | ||||
| open fun findByItemId(itemId: Long): Bom? { | |||||
| return bomRepository.findByItemIdAndDeletedIsFalse(itemId) | |||||
| } | |||||
| open fun saveBom(request: SaveBomRequest): SaveBomResponse { | open fun saveBom(request: SaveBomRequest): SaveBomResponse { | ||||
| val item = request.code.let { itemsService.findByM18BomCode(it) } ?: request.itemId?.let { itemsService.findById(it) } | val item = request.code.let { itemsService.findByM18BomCode(it) } ?: request.itemId?.let { itemsService.findById(it) } | ||||
| @@ -3,13 +3,23 @@ package com.ffii.fpsms.modules.master.service | |||||
| import com.ffii.core.response.RecordsRes | import com.ffii.core.response.RecordsRes | ||||
| import com.ffii.core.support.AbstractBaseEntityService | import com.ffii.core.support.AbstractBaseEntityService | ||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.fpsms.modules.common.SecurityUtils | |||||
| 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.CreateJobOrderBomMaterialRequest | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderProcessRequest | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest | |||||
| import com.ffii.fpsms.modules.master.entity.* | import com.ffii.fpsms.modules.master.entity.* | ||||
| import com.ffii.fpsms.modules.master.entity.projections.* | 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.master.web.models.SearchProdScheduleRequest | ||||
| import com.google.gson.Gson | |||||
| import com.google.gson.reflect.TypeToken | |||||
| import org.springframework.data.domain.PageRequest | import org.springframework.data.domain.PageRequest | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.math.RoundingMode | |||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import java.util.* | import java.util.* | ||||
| @@ -25,7 +35,12 @@ open class ProductionScheduleService( | |||||
| private val itemService: ItemsService, | private val itemService: ItemsService, | ||||
| private val productionScheduleRepository: ProductionScheduleRepository, | private val productionScheduleRepository: ProductionScheduleRepository, | ||||
| private val productionScheduleLineRepository: ProductionScheduleLineRepository, | private val productionScheduleLineRepository: ProductionScheduleLineRepository, | ||||
| private val bomMaterialService: BomMaterialService, private val bomMaterialRepository: BomMaterialRepository, | |||||
| private val bomMaterialService: BomMaterialService, | |||||
| private val bomMaterialRepository: BomMaterialRepository, | |||||
| private val jobOrderService: JobOrderService, | |||||
| private val jobOrderBomMaterialService: JobOrderBomMaterialService, | |||||
| private val bomService: BomService, | |||||
| private val jobOrderProcessService: JobOrderProcessService, | |||||
| ) : AbstractBaseEntityService<ProductionSchedule, Long, ProductionScheduleRepository>( | ) : AbstractBaseEntityService<ProductionSchedule, Long, ProductionScheduleRepository>( | ||||
| jdbcDao, | jdbcDao, | ||||
| productionScheduleRepository | productionScheduleRepository | ||||
| @@ -68,12 +83,14 @@ open class ProductionScheduleService( | |||||
| val prodScheduleLineInfosByFg = prodScheduleLines.map { line -> | val prodScheduleLineInfosByFg = prodScheduleLines.map { line -> | ||||
| val bomMaterials = line.item.id?.let { bomMaterialRepository.findAllByBomItemIdAndDeletedIsFalse(it) } | val bomMaterials = line.item.id?.let { bomMaterialRepository.findAllByBomItemIdAndDeletedIsFalse(it) } | ||||
| ?.map { bm -> | ?.map { bm -> | ||||
| val proportion = | |||||
| if (line.prodQty > 0 && bm.bom?.outputQty != null && (bm.bom?.outputQty ?: zero) > zero) { | |||||
| BigDecimal(line.prodQty).divide(bm.bom!!.outputQty, 2, RoundingMode.HALF_UP) | |||||
| } else { | |||||
| zero | |||||
| } | |||||
| // val proportion = | |||||
| // if (line.prodQty > 0 && bm.bom?.outputQty != null && (bm.bom?.outputQty ?: zero) > zero) { | |||||
| // BigDecimal(line.prodQty).divide(bm.bom!!.outputQty, 2, RoundingMode.HALF_UP) | |||||
| // } else { | |||||
| // BigDecimal.ONE | |||||
| // } | |||||
| val proportion = BigDecimal(line.prodQty).divide(bm.bom?.outputQty ?: BigDecimal.ONE) | |||||
| val demandQty = bm.qty?.times(proportion) ?: zero | val demandQty = bm.qty?.times(proportion) ?: zero | ||||
| @@ -150,12 +167,14 @@ open class ProductionScheduleService( | |||||
| val bomMaterial = line.item.id?.let { bomMaterialRepository.findAllByBomItemIdAndDeletedIsFalse(it) } | val bomMaterial = line.item.id?.let { bomMaterialRepository.findAllByBomItemIdAndDeletedIsFalse(it) } | ||||
| bomMaterial?.map { bm -> | bomMaterial?.map { bm -> | ||||
| val proportion = | |||||
| if (line.prodQty > 0 && bm.bom?.outputQty != null && (bm.bom?.outputQty ?: zero) > zero) { | |||||
| BigDecimal(line.prodQty).divide(bm.bom!!.outputQty, 2, RoundingMode.HALF_UP) | |||||
| } else { | |||||
| zero | |||||
| } | |||||
| // val proportion = | |||||
| // if (line.prodQty > 0 && bm.bom?.outputQty != null && (bm.bom?.outputQty ?: zero) > zero) { | |||||
| // BigDecimal(line.prodQty).divide(bm.bom!!.outputQty, 2, RoundingMode.HALF_UP) | |||||
| // } else { | |||||
| // BigDecimal.ONE | |||||
| // } | |||||
| val proportion = BigDecimal(line.prodQty).divide(bm.bom?.outputQty ?: BigDecimal.ONE) | |||||
| val demandQty = bm.qty?.times(proportion) ?: zero | val demandQty = bm.qty?.times(proportion) ?: zero | ||||
| @@ -235,8 +254,76 @@ open class ProductionScheduleService( | |||||
| ) | ) | ||||
| } | } | ||||
| open fun detailedProdScheduleDetail(id: Long): RoughProdScheduleWithLine? { | |||||
| return null | |||||
| open fun detailedProdScheduleDetail(id: Long): DetailedProdScheduleWithLine { | |||||
| val sqlResult = productionScheduleRepository.findDetailedProdScheduleWithLine(id) ?: throw NoSuchElementException() | |||||
| val gson = Gson() | |||||
| val type = object : TypeToken<List<DetailedProdScheduleLineInfo>?>() {}.type | |||||
| val gsonResult: List<DetailedProdScheduleLineInfo>? = gson.fromJson(sqlResult.prodScheduleLines, type) | |||||
| return DetailedProdScheduleWithLine( | |||||
| id = sqlResult.id, | |||||
| scheduleAt = sqlResult.scheduleAt, | |||||
| totalEstProdCount = sqlResult.totalEstProdCount, | |||||
| totalFGType = sqlResult.totalFGType, | |||||
| prodScheduleLines = gsonResult | |||||
| ) | |||||
| } | |||||
| open fun releaseProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse { | |||||
| val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | |||||
| val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) } | |||||
| val approver = SecurityUtils.getUser().getOrNull() | |||||
| val proportion = request.demandQty.divide(bom?.outputQty ?: BigDecimal.ONE) | |||||
| // Update Prod Schedule Line Prod qty | |||||
| prodScheduleLine.apply { prodQty = request.demandQty.toDouble() } | |||||
| productionScheduleLineRepository.save(prodScheduleLine) | |||||
| // Create Job Order | |||||
| val joRequest = CreateJobOrderRequest( | |||||
| bomId = bom?.id, | |||||
| reqQty = request.demandQty, | |||||
| approverId = approver?.id, | |||||
| prodScheduleLineId = request.id | |||||
| ) | |||||
| val jo = jobOrderService.createJobOrder(joRequest) | |||||
| // Create Job Order Bom Materials | |||||
| val jobmRequests = bom?.bomMaterials?.map { bm -> | |||||
| CreateJobOrderBomMaterialRequest( | |||||
| joId = jo.id, | |||||
| itemId = bm.item?.id, | |||||
| reqQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO, | |||||
| uomId = bm.uom?.id | |||||
| ) | |||||
| } | |||||
| if (jobmRequests != null) { | |||||
| jobOrderBomMaterialService.createJobOrderBomMaterials(jobmRequests) | |||||
| } | |||||
| // Create Job Order Process | |||||
| val jopRequests = bom?.bomProcesses?.map { bp -> | |||||
| CreateJobOrderProcessRequest( | |||||
| joId = jo.id, | |||||
| processId = bp.process?.id, | |||||
| seqNo = bp.seqNo, | |||||
| ) | |||||
| } | |||||
| if (jopRequests != null) { | |||||
| jobOrderProcessService.createJobOrderProcesses(jopRequests) | |||||
| } | |||||
| return MessageResponse( | |||||
| id = request.id, | |||||
| name = null, | |||||
| code = null, | |||||
| type = null, | |||||
| message = "Success", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | } | ||||
| //====================細排相關 START====================// | //====================細排相關 START====================// | ||||
| @@ -3,11 +3,14 @@ package com.ffii.fpsms.modules.master.web | |||||
| import com.ffii.core.response.RecordsRes | import com.ffii.core.response.RecordsRes | ||||
| import com.ffii.core.utils.CriteriaArgsBuilder | import com.ffii.core.utils.CriteriaArgsBuilder | ||||
| import com.ffii.fpsms.modules.master.entity.ProductionScheduleRepository | import com.ffii.fpsms.modules.master.entity.ProductionScheduleRepository | ||||
| import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleWithLine | |||||
| import com.ffii.fpsms.modules.master.entity.projections.ProdScheduleInfo | import com.ffii.fpsms.modules.master.entity.projections.ProdScheduleInfo | ||||
| import com.ffii.fpsms.modules.master.entity.projections.RoughProdScheduleWithLine | import com.ffii.fpsms.modules.master.entity.projections.RoughProdScheduleWithLine | ||||
| import com.ffii.fpsms.modules.master.service.ProductionScheduleService | import com.ffii.fpsms.modules.master.service.ProductionScheduleService | ||||
| import com.ffii.fpsms.modules.master.service.ProductionScheduleService.FinishedGood | import com.ffii.fpsms.modules.master.service.ProductionScheduleService.FinishedGood | ||||
| import com.ffii.fpsms.modules.master.service.ProductionScheduleService.RoughScheduleObj | import com.ffii.fpsms.modules.master.service.ProductionScheduleService.RoughScheduleObj | ||||
| 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.master.web.models.SearchProdScheduleRequest | ||||
| import jakarta.servlet.http.HttpServletRequest | import jakarta.servlet.http.HttpServletRequest | ||||
| import org.springframework.web.bind.annotation.* | import org.springframework.web.bind.annotation.* | ||||
| @@ -41,16 +44,26 @@ class ProductionScheduleController( | |||||
| return productionScheduleService.getLatestScheduleAt("rough") | return productionScheduleService.getLatestScheduleAt("rough") | ||||
| } | } | ||||
| @GetMapping("/detail/{id}") | |||||
| @GetMapping("/detail/rough/{id}") | |||||
| fun getScheduleDetail(@PathVariable id: Long): RoughProdScheduleWithLine { | fun getScheduleDetail(@PathVariable id: Long): RoughProdScheduleWithLine { | ||||
| return productionScheduleService.roughProdScheduleDetail(id) | return productionScheduleService.roughProdScheduleDetail(id) | ||||
| } | } | ||||
| @GetMapping("/detail/detailed/{id}") | |||||
| fun getDetailedProdScheduleDetail(@PathVariable id: Long): DetailedProdScheduleWithLine { | |||||
| return productionScheduleService.detailedProdScheduleDetail(id) | |||||
| } | |||||
| @GetMapping("/getRecordByPage") | @GetMapping("/getRecordByPage") | ||||
| fun allProdSchedulesByPage(@ModelAttribute request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> { | fun allProdSchedulesByPage(@ModelAttribute request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> { | ||||
| return productionScheduleService.allProdSchedulesByPage(request); | return productionScheduleService.allProdSchedulesByPage(request); | ||||
| } | } | ||||
| @PostMapping("/releaseLine") | |||||
| fun releaseProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse { | |||||
| return productionScheduleService.releaseProdScheduleLine(request) | |||||
| } | |||||
| @RequestMapping(value = ["/testDetailSchedule"], method = [RequestMethod.GET]) | @RequestMapping(value = ["/testDetailSchedule"], method = [RequestMethod.GET]) | ||||
| fun generateDetailSchedule(request: HttpServletRequest?): Int { | fun generateDetailSchedule(request: HttpServletRequest?): Int { | ||||
| try { | try { | ||||
| @@ -0,0 +1,8 @@ | |||||
| package com.ffii.fpsms.modules.master.web.models | |||||
| import java.math.BigDecimal | |||||
| data class ReleaseProdScheduleLineRequest( | |||||
| val id: Long, | |||||
| val demandQty: BigDecimal, | |||||
| ) | |||||