Browse Source

[Prod Schedule] Update Prod Schedule (e.g. release function)

master
cyril.tsui 1 month ago
parent
commit
7add3fd6a7
17 changed files with 379 additions and 51 deletions
  1. +5
    -4
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt
  2. +14
    -12
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterial.kt
  3. +1
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt
  4. +2
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderProcess.kt
  5. +5
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
  6. +45
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt
  7. +31
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderProcessService.kt
  8. +75
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  9. +31
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt
  10. +4
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/Bom.kt
  11. +2
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt
  12. +23
    -11
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt
  13. +12
    -3
      src/main/java/com/ffii/fpsms/modules/master/entity/projections/ProdScheduleInfo.kt
  14. +4
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt
  15. +103
    -16
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  16. +14
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt
  17. +8
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/models/ReleaseProdScheduleLineRequest.kt

+ 5
- 4
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt View File

@@ -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
} }

src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderMaterial.kt → src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterial.kt View File

@@ -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
} }

src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderMaterialRepository.kt → src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt View File

@@ -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> {
} }

+ 2
- 2
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderProcess.kt View File

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




+ 5
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt View File

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

+ 45
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt View File

@@ -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
)
}
}

+ 31
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderProcessService.kt View File

@@ -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)




+ 75
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt View File

@@ -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
)
}
}

+ 31
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt View File

@@ -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",
)

+ 4
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/Bom.kt View File

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




+ 2
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt View File

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

+ 23
- 11
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt View File

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

+ 12
- 3
src/main/java/com/ffii/fpsms/modules/master/entity/projections/ProdScheduleInfo.kt View File

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


+ 4
- 0
src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt View File

@@ -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) }


+ 103
- 16
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt View File

@@ -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====================//


+ 14
- 1
src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt View File

@@ -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 {


+ 8
- 0
src/main/java/com/ffii/fpsms/modules/master/web/models/ReleaseProdScheduleLineRequest.kt View File

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

Loading…
Cancel
Save