| @@ -28,7 +28,7 @@ open class JobOrderBomMaterial : BaseEntity<Long>() { | |||||
| @Column(name = "reqQty", nullable = false, precision = 14, scale = 2) | @Column(name = "reqQty", nullable = false, precision = 14, scale = 2) | ||||
| open var reqQty: BigDecimal? = null | open var reqQty: BigDecimal? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "uomId", nullable = false) | @JoinColumn(name = "uomId", nullable = false) | ||||
| open var uom: UomConversion? = null | open var uom: UomConversion? = null | ||||
| @@ -1,10 +1,41 @@ | |||||
| package com.ffii.fpsms.modules.master.entity | 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.DetailedProdScheduleLineBomMaterial | |||||
| import com.ffii.fpsms.modules.master.entity.projections.DetailedProdScheduleLineBomMaterialInterface | |||||
| import org.springframework.data.jpa.repository.Query | import org.springframework.data.jpa.repository.Query | ||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| @Repository | @Repository | ||||
| interface ProductionScheduleLineRepository : AbstractRepository<ProductionScheduleLine, Long> { | interface ProductionScheduleLineRepository : AbstractRepository<ProductionScheduleLine, Long> { | ||||
| @Query(nativeQuery = true, | |||||
| value = """ | |||||
| with prod_prop as ( | |||||
| select | |||||
| psl.id as pslId, | |||||
| round(coalesce(psl.prodQty, 0) / coalesce(b.outputQty, 1), 2) as proportion | |||||
| from production_schedule_line psl | |||||
| left join bom b on b.itemId = psl.itemId | |||||
| where psl.id = :id and b.id is not null | |||||
| ) | |||||
| select | |||||
| psl.id as pslId, | |||||
| bm.id, | |||||
| b.outputQty as bomOutputQty, | |||||
| bmi.code, | |||||
| bmi.name, | |||||
| bmi.`type`, | |||||
| coalesce(i.onHandQty, 0) - coalesce(i.onHoldQty, 0) - coalesce(i.unavailableQty, 0) as availableQty, | |||||
| ceil(coalesce(bm.qty, 0) * pp.proportion) as demandQty | |||||
| from production_schedule_line psl | |||||
| left join prod_prop pp on pp.pslId = psl.id | |||||
| left join bom b on b.itemId = psl.itemId | |||||
| left join bom_material bm on bm.bomId = b.id | |||||
| left join items bmi on bmi.id = bm.itemId | |||||
| left join inventory i on i.itemId = bmi.id | |||||
| where psl.id = :id and bmi.id is not null | |||||
| group by psl.id, bm.id, i.id, pp.proportion | |||||
| """) | |||||
| fun getBomMaterials(id: Long): List<DetailedProdScheduleLineBomMaterialInterface>? | |||||
| } | } | ||||
| @@ -1,9 +1,7 @@ | |||||
| package com.ffii.fpsms.modules.master.entity | 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.DetailedProdScheduleWithLineWithJsonString | |||||
| import com.ffii.fpsms.modules.master.entity.projections.ProdScheduleInfo | |||||
| import com.ffii.fpsms.modules.master.entity.projections.* | |||||
| import org.springframework.data.domain.Page | import org.springframework.data.domain.Page | ||||
| import org.springframework.data.domain.Pageable | import org.springframework.data.domain.Pageable | ||||
| import org.springframework.data.jpa.repository.Query | import org.springframework.data.jpa.repository.Query | ||||
| @@ -85,7 +83,7 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| with prod_prop as ( | with prod_prop as ( | ||||
| select | select | ||||
| psl.id as pslId, | psl.id as pslId, | ||||
| coalesce(psl.prodQty, 0) / coalesce(b.outputQty, 1) as proportion | |||||
| round(coalesce(psl.prodQty, 0) / coalesce(b.outputQty, 1), 2) as proportion | |||||
| 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 bom b on b.itemId = psl.itemId | left join bom b on b.itemId = psl.itemId | ||||
| @@ -99,7 +97,8 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| select | select | ||||
| psl.id as pslId, | psl.id as pslId, | ||||
| coalesce(e.name, 'N/A') as equipName, | coalesce(e.name, 'N/A') as equipName, | ||||
| ceil(sum((coalesce(bp.prepTimeInMinute, 0) + coalesce(bp.durationInMinute, 0) + coalesce(bp.postProdTimeInMinute, 0)) * pp.proportion)) as totalMinutes | |||||
| -- ceil(sum((coalesce(bp.prepTimeInMinute, 0) + coalesce(bp.durationInMinute, 0) + coalesce(bp.postProdTimeInMinute, 0)) * pp.proportion)) as totalMinutes | |||||
| sum(coalesce(bp.prepTimeInMinute, 0) + coalesce(bp.durationInMinute, 0) + coalesce(bp.postProdTimeInMinute, 0)) 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 prod_prop pp on pp.pslId = psl.id | ||||
| @@ -114,16 +113,19 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| prod_material as ( | prod_material as ( | ||||
| select | select | ||||
| r2.pslId, | r2.pslId, | ||||
| r2.bomOutputQty, | |||||
| json_arrayagg(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, | ||||
| bm.id, | bm.id, | ||||
| b.outputQty as bomOutputQty, | |||||
| bmi.code, | bmi.code, | ||||
| 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, | ||||
| ceil(coalesce(bm.qty, 0) * pp.proportion) as demandQty | |||||
| -- ceil(coalesce(bm.qty, 0) * pp.proportion) as demandQty | |||||
| 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 prod_prop pp on pp.pslId = psl.id | ||||
| @@ -134,7 +136,7 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| 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, pp.proportion | group by ps.id, psl.id, bm.id, i.id, pp.proportion | ||||
| ) r2 | ) r2 | ||||
| group by r2.pslId | |||||
| group by r2.pslId, r2.bomOutputQty | |||||
| ) | ) | ||||
| select | select | ||||
| prod.id, | prod.id, | ||||
| @@ -142,7 +144,7 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| prod.totalFGType, | prod.totalFGType, | ||||
| prod.totalEstProdCount, | prod.totalEstProdCount, | ||||
| json_arrayagg( | 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, 'bomOutputQty', prod.bomOutputQty, 'prodTimeInMinute', prod.prodTimeInMinute, 'priority', prod.priority, 'approved', prod.approved, 'proportion', prod.proportion) | |||||
| ) as prodScheduleLines | ) as prodScheduleLines | ||||
| from ( | from ( | ||||
| select | select | ||||
| @@ -150,24 +152,28 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||||
| ps.scheduleAt, | ps.scheduleAt, | ||||
| ps.totalFGType, | ps.totalFGType, | ||||
| ps.totalEstProdCount, | ps.totalEstProdCount, | ||||
| psl.approverId is not null as approved, | |||||
| psl.id as pslId, | psl.id as pslId, | ||||
| pm.bomMaterials, | pm.bomMaterials, | ||||
| pm.bomOutputQty, | |||||
| coalesce(jo.code, 'N/A') as jobNo, | coalesce(jo.code, 'N/A') as jobNo, | ||||
| psli.code, | psli.code, | ||||
| psli.name, | psli.name, | ||||
| psli.`type`, | psli.`type`, | ||||
| psl.prodQty as demandQty, | psl.prodQty as demandQty, | ||||
| pe.prodTimeInMinute, | pe.prodTimeInMinute, | ||||
| psl.itemPriority as priority | |||||
| psl.itemPriority as priority, | |||||
| pp.proportion | |||||
| 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 items psli on psli.id = psl.itemId | left join items psli on psli.id = psl.itemId | ||||
| left join job_order jo on jo.prodScheduleLineId = psl.id | left join job_order jo on jo.prodScheduleLineId = psl.id | ||||
| left join prod_prop pp on pp.pslId = psl.id | |||||
| left join prod_equip pe on pe.pslId = psl.id | left join prod_equip pe on pe.pslId = psl.id | ||||
| left join prod_material pm on pm.pslId = psl.id | left join prod_material pm on pm.pslId = psl.id | ||||
| where psl.deleted is false and pe.prodTimeInMinute is not null and pm.bomMaterials is not null | where psl.deleted is false and pe.prodTimeInMinute is not null and pm.bomMaterials is not null | ||||
| and ps.id = :id | and ps.id = :id | ||||
| group by psl.id, jo.id | |||||
| group by psl.id, jo.id, pp.proportion, pm.bomOutputQty | |||||
| ) prod group by prod.id limit 1 | ) prod group by prod.id limit 1 | ||||
| """ | """ | ||||
| ) | ) | ||||
| @@ -40,8 +40,11 @@ data class DetailedProdScheduleLineInfo( | |||||
| val name: String?, | val name: String?, | ||||
| val type: String?, | val type: String?, | ||||
| val demandQty: BigDecimal?, | val demandQty: BigDecimal?, | ||||
| val bomOutputQty: BigDecimal?, | |||||
| val prodTimeInMinute: List<DetailedProdScheduleLineProdTime>?, | val prodTimeInMinute: List<DetailedProdScheduleLineProdTime>?, | ||||
| val priority: BigDecimal? | |||||
| val priority: BigDecimal?, | |||||
| val approved: Boolean?, | |||||
| val proportion: BigDecimal? | |||||
| ) | ) | ||||
| data class DetailedProdScheduleLineBomMaterial ( | data class DetailedProdScheduleLineBomMaterial ( | ||||
| @@ -53,6 +56,15 @@ data class DetailedProdScheduleLineBomMaterial ( | |||||
| val demandQty: BigDecimal? | val demandQty: BigDecimal? | ||||
| ) | ) | ||||
| interface DetailedProdScheduleLineBomMaterialInterface { | |||||
| val id: Long? | |||||
| val code: String? | |||||
| val name: String? | |||||
| val type: String? | |||||
| val availableQty: BigDecimal? | |||||
| val demandQty: BigDecimal? | |||||
| } | |||||
| data class DetailedProdScheduleLineProdTime ( | data class DetailedProdScheduleLineProdTime ( | ||||
| val equipName: String?, | val equipName: String?, | ||||
| val totalMinutes: BigDecimal? | val totalMinutes: BigDecimal? | ||||
| @@ -15,15 +15,23 @@ 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.MessageResponse | ||||
| import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleLineRequest | 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.ffii.fpsms.modules.stock.entity.Inventory | |||||
| import com.ffii.fpsms.modules.stock.entity.InventoryRepository | |||||
| import com.ffii.fpsms.modules.stock.service.InventoryService | |||||
| import com.google.gson.Gson | import com.google.gson.Gson | ||||
| 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 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 org.springframework.transaction.annotation.Transactional | |||||
| import java.lang.reflect.Type | |||||
| 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 kotlin.NoSuchElementException | |||||
| import kotlin.collections.component1 | import kotlin.collections.component1 | ||||
| import kotlin.collections.component2 | import kotlin.collections.component2 | ||||
| import kotlin.jvm.optionals.getOrNull | import kotlin.jvm.optionals.getOrNull | ||||
| @@ -41,6 +49,9 @@ open class ProductionScheduleService( | |||||
| private val jobOrderBomMaterialService: JobOrderBomMaterialService, | private val jobOrderBomMaterialService: JobOrderBomMaterialService, | ||||
| private val bomService: BomService, | private val bomService: BomService, | ||||
| private val jobOrderProcessService: JobOrderProcessService, | private val jobOrderProcessService: JobOrderProcessService, | ||||
| private val inventoryService: InventoryService, | |||||
| private val inventoryRepository: InventoryRepository, | |||||
| private val itemUomService: ItemUomService | |||||
| ) : AbstractBaseEntityService<ProductionSchedule, Long, ProductionScheduleRepository>( | ) : AbstractBaseEntityService<ProductionSchedule, Long, ProductionScheduleRepository>( | ||||
| jdbcDao, | jdbcDao, | ||||
| productionScheduleRepository | productionScheduleRepository | ||||
| @@ -90,7 +101,7 @@ open class ProductionScheduleService( | |||||
| // BigDecimal.ONE | // BigDecimal.ONE | ||||
| // } | // } | ||||
| val proportion = BigDecimal(line.prodQty).divide(bm.bom?.outputQty ?: BigDecimal.ONE) | |||||
| val proportion = BigDecimal(line.prodQty).divide(bm.bom?.outputQty ?: BigDecimal.ONE, 2, RoundingMode.HALF_UP) | |||||
| val demandQty = bm.qty?.times(proportion) ?: zero | val demandQty = bm.qty?.times(proportion) ?: zero | ||||
| @@ -174,7 +185,7 @@ open class ProductionScheduleService( | |||||
| // BigDecimal.ONE | // BigDecimal.ONE | ||||
| // } | // } | ||||
| val proportion = BigDecimal(line.prodQty).divide(bm.bom?.outputQty ?: BigDecimal.ONE) | |||||
| val proportion = BigDecimal(line.prodQty).divide(bm.bom?.outputQty ?: BigDecimal.ONE, 2, RoundingMode.HALF_UP) | |||||
| val demandQty = bm.qty?.times(proportion) ?: zero | val demandQty = bm.qty?.times(proportion) ?: zero | ||||
| @@ -254,10 +265,19 @@ 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 { | open fun detailedProdScheduleDetail(id: Long): DetailedProdScheduleWithLine { | ||||
| val sqlResult = productionScheduleRepository.findDetailedProdScheduleWithLine(id) ?: throw NoSuchElementException() | val sqlResult = productionScheduleRepository.findDetailedProdScheduleWithLine(id) ?: throw NoSuchElementException() | ||||
| val gson = Gson() | |||||
| val gson = GsonBuilder() | |||||
| .registerTypeAdapter(Boolean::class.javaObjectType, BooleanTypeAdapter()) | |||||
| .create() | |||||
| val type = object : TypeToken<List<DetailedProdScheduleLineInfo>?>() {}.type | 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) | ||||
| @@ -270,14 +290,41 @@ open class ProductionScheduleService( | |||||
| ) | ) | ||||
| } | } | ||||
| @Transactional(rollbackFor = [java.lang.Exception::class]) | |||||
| open fun saveProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse { | |||||
| val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | |||||
| // Update Prod Schedule Line Prod qty | |||||
| prodScheduleLine.apply { | |||||
| prodQty = request.demandQty.toDouble() | |||||
| } | |||||
| productionScheduleLineRepository.saveAndFlush(prodScheduleLine) | |||||
| val bomMaterials = prodScheduleLine.id?.let { productionScheduleLineRepository.getBomMaterials(it) } | |||||
| return MessageResponse( | |||||
| id = request.id, | |||||
| name = null, | |||||
| code = null, | |||||
| type = null, | |||||
| message = "Success", | |||||
| entity = mapOf("bomMaterials" to bomMaterials), | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| @Transactional(rollbackFor = [java.lang.Exception::class]) | |||||
| open fun releaseProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse { | open fun releaseProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse { | ||||
| val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | ||||
| val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) } | val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) } | ||||
| val approver = SecurityUtils.getUser().getOrNull() | val approver = SecurityUtils.getUser().getOrNull() | ||||
| val proportion = request.demandQty.divide(bom?.outputQty ?: BigDecimal.ONE) | |||||
| val proportion = request.demandQty.divide(bom?.outputQty ?: BigDecimal.ONE, 2, RoundingMode.HALF_UP) | |||||
| // Update Prod Schedule Line Prod qty | // Update Prod Schedule Line Prod qty | ||||
| prodScheduleLine.apply { prodQty = request.demandQty.toDouble() } | |||||
| prodScheduleLine.apply { | |||||
| prodQty = request.demandQty.toDouble() | |||||
| approverId = approver?.id | |||||
| } | |||||
| productionScheduleLineRepository.save(prodScheduleLine) | productionScheduleLineRepository.save(prodScheduleLine) | ||||
| // Create Job Order | // Create Job Order | ||||
| @@ -291,16 +338,61 @@ open class ProductionScheduleService( | |||||
| val jo = jobOrderService.createJobOrder(joRequest) | val jo = jobOrderService.createJobOrder(joRequest) | ||||
| // Create Job Order Bom Materials | // 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) | |||||
| if (bom?.bomMaterials != null) { | |||||
| // Job Order Bom Material | |||||
| val jobmRequests = bom.bomMaterials.map { bm -> | |||||
| val demandQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO | |||||
| val jobm = CreateJobOrderBomMaterialRequest( | |||||
| joId = jo.id, | |||||
| itemId = bm.item?.id, | |||||
| reqQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO, | |||||
| uomId = bm.salesUnit?.id | |||||
| ) | |||||
| jobm | |||||
| } | |||||
| if (jobmRequests != null) { | |||||
| jobOrderBomMaterialService.createJobOrderBomMaterials(jobmRequests) | |||||
| } | |||||
| // Inventory | |||||
| val inventories = bom.bomMaterials.map { bm -> | |||||
| val demandQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO | |||||
| var inventory = bm.item?.id?.let { inventoryRepository.findByItemId(it).getOrNull() } | |||||
| if (inventory != null) { | |||||
| inventory.apply { | |||||
| this.onHoldQty = (this.onHoldQty ?: BigDecimal.ZERO).plus(demandQty) | |||||
| } | |||||
| } else { | |||||
| if (bm.item != null) { | |||||
| val itemUom = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | |||||
| inventory = Inventory().apply { | |||||
| item = bm.item | |||||
| onHandQty = BigDecimal.ZERO | |||||
| unavailableQty = BigDecimal.ZERO | |||||
| this.onHoldQty = demandQty | |||||
| uom = itemUom?.uom | |||||
| status = "unavailable" | |||||
| } | |||||
| } | |||||
| } | |||||
| inventory | |||||
| }.groupBy { it?.item } // Group by item | |||||
| .mapNotNull { (item, invList) -> | |||||
| if (invList.isNotEmpty()) { | |||||
| invList[0]?.apply { | |||||
| onHoldQty = invList.sumOf { it?.onHoldQty ?: BigDecimal.ZERO } | |||||
| } | |||||
| } else { | |||||
| null | |||||
| } | |||||
| } | |||||
| inventoryRepository.saveAllAndFlush(inventories) | |||||
| } | } | ||||
| // Create Job Order Process | // Create Job Order Process | ||||
| @@ -316,12 +408,17 @@ open class ProductionScheduleService( | |||||
| jobOrderProcessService.createJobOrderProcesses(jopRequests) | jobOrderProcessService.createJobOrderProcesses(jopRequests) | ||||
| } | } | ||||
| // Get Latest Data | |||||
| // val bomMaterials = prodScheduleLine.id?.let { productionScheduleLineRepository.getBomMaterials(it) } | |||||
| val latestDetail = prodScheduleLine.productionSchedule.id?.let { detailedProdScheduleDetail(it) } | |||||
| return MessageResponse( | return MessageResponse( | ||||
| id = request.id, | id = request.id, | ||||
| name = null, | name = null, | ||||
| code = null, | |||||
| code = jo.code, | |||||
| type = null, | type = null, | ||||
| message = "Success", | message = "Success", | ||||
| entity = mapOf("prodScheduleLines" to latestDetail?.prodScheduleLines), | |||||
| errorPosition = null | errorPosition = null | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -13,6 +13,7 @@ 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.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 jakarta.validation.Valid | |||||
| import org.springframework.web.bind.annotation.* | import org.springframework.web.bind.annotation.* | ||||
| import java.time.Duration | import java.time.Duration | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| @@ -54,15 +55,22 @@ class ProductionScheduleController( | |||||
| return productionScheduleService.detailedProdScheduleDetail(id) | return productionScheduleService.detailedProdScheduleDetail(id) | ||||
| } | } | ||||
| @PostMapping("/detail/detailed/save") | |||||
| fun saveDetailedProdScheduleDetail(@Valid @RequestBody request: ReleaseProdScheduleLineRequest): MessageResponse { | |||||
| return productionScheduleService.saveProdScheduleLine(request) | |||||
| } | |||||
| @PostMapping("/detail/detailed/releaseLine") | |||||
| fun releaseProdScheduleLine(@Valid @RequestBody request: ReleaseProdScheduleLineRequest): MessageResponse { | |||||
| return productionScheduleService.releaseProdScheduleLine(request) | |||||
| } | |||||
| @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 { | ||||
| @@ -1,8 +1,11 @@ | |||||
| package com.ffii.fpsms.modules.master.web.models | package com.ffii.fpsms.modules.master.web.models | ||||
| import jakarta.validation.constraints.NotNull | |||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| data class ReleaseProdScheduleLineRequest( | data class ReleaseProdScheduleLineRequest( | ||||
| @field:NotNull(message = "Id cannot be null") | |||||
| val id: Long, | val id: Long, | ||||
| @field:NotNull(message = "Demand Qty cannot be null") | |||||
| val demandQty: BigDecimal, | val demandQty: BigDecimal, | ||||
| ) | ) | ||||
| @@ -29,8 +29,8 @@ class PurchaseOrderController( | |||||
| // @RequestParam(required = false) pageNum: Int, | // @RequestParam(required = false) pageNum: Int, | ||||
| // @RequestParam(required = false) pageSize: Int | // @RequestParam(required = false) pageSize: Int | ||||
| ): RecordsRes<PurchaseOrderDataClass> { | ): RecordsRes<PurchaseOrderDataClass> { | ||||
| println("request") | |||||
| println(request) | |||||
| // println("request") | |||||
| // println(request) | |||||
| val criteriaArgs = CriteriaArgsBuilder.withRequest(request) | val criteriaArgs = CriteriaArgsBuilder.withRequest(request) | ||||
| .addStringLike("code") | .addStringLike("code") | ||||
| .addString("status") | .addString("status") | ||||
| @@ -11,19 +11,19 @@ import java.math.BigDecimal | |||||
| @Entity | @Entity | ||||
| @Table(name = "inventory") | @Table(name = "inventory") | ||||
| open class Inventory: BaseEntity<Long>(){ | open class Inventory: BaseEntity<Long>(){ | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @Column(name = "onHandQty") | @Column(name = "onHandQty") | ||||
| open var onHandQty: BigDecimal? = null | open var onHandQty: BigDecimal? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @Column(name = "onHoldQty") | @Column(name = "onHoldQty") | ||||
| open var onHoldQty: BigDecimal? = null | open var onHoldQty: BigDecimal? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @Column(name = "unavailableQty") | @Column(name = "unavailableQty") | ||||
| open var unavailableQty: BigDecimal? = null | open var unavailableQty: BigDecimal? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @Column(name = "price") | @Column(name = "price") | ||||
| open var price: BigDecimal? = null | open var price: BigDecimal? = null | ||||
| @@ -35,23 +35,23 @@ open class Inventory: BaseEntity<Long>(){ | |||||
| @JoinColumn(name = "currencyId") | @JoinColumn(name = "currencyId") | ||||
| open var currency: Currency? = null | open var currency: Currency? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @Column(name = "cpu") // cost per unit | @Column(name = "cpu") // cost per unit | ||||
| open var cpu: BigDecimal? = null | open var cpu: BigDecimal? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @Column(name = "cpuUnit") | @Column(name = "cpuUnit") | ||||
| open var cpuUnit: String? = null | open var cpuUnit: String? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @Column(name = "cpm") // cost per unit | @Column(name = "cpm") // cost per unit | ||||
| open var cpm: BigDecimal? = null | open var cpm: BigDecimal? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @Column(name = "cpmUnit") | @Column(name = "cpmUnit") | ||||
| open var cpmUnit: String? = null | open var cpmUnit: String? = null | ||||
| @NotNull | |||||
| // @NotNull | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "uomId") | @JoinColumn(name = "uomId") | ||||
| open var uom: UomConversion? = null | open var uom: UomConversion? = null | ||||
| @@ -5,6 +5,9 @@ import com.ffii.core.support.JdbcDao | |||||
| import com.ffii.fpsms.modules.common.CodeGenerator | import com.ffii.fpsms.modules.common.CodeGenerator | ||||
| import com.ffii.fpsms.modules.master.entity.Items | import com.ffii.fpsms.modules.master.entity.Items | ||||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | import com.ffii.fpsms.modules.master.entity.ItemsRepository | ||||
| import com.ffii.fpsms.modules.master.entity.UomConversionRepository | |||||
| import com.ffii.fpsms.modules.master.service.ItemUomService | |||||
| import com.ffii.fpsms.modules.master.service.UomConversionService | |||||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | import com.ffii.fpsms.modules.master.web.models.MessageResponse | ||||
| import com.ffii.fpsms.modules.stock.entity.Inventory | import com.ffii.fpsms.modules.stock.entity.Inventory | ||||
| import com.ffii.fpsms.modules.stock.entity.InventoryRepository | import com.ffii.fpsms.modules.stock.entity.InventoryRepository | ||||
| @@ -12,14 +15,19 @@ import com.ffii.fpsms.modules.stock.entity.projection.InventoryInfo | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveInventoryRequest | import com.ffii.fpsms.modules.stock.web.model.SaveInventoryRequest | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.io.IOException | import java.io.IOException | ||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import kotlin.jvm.optionals.getOrNull | |||||
| @Service | @Service | ||||
| open class InventoryService( | open class InventoryService( | ||||
| private val jdbcDao: JdbcDao, | private val jdbcDao: JdbcDao, | ||||
| private val inventoryRepository: InventoryRepository, | private val inventoryRepository: InventoryRepository, | ||||
| private val itemsRepository: ItemsRepository, | private val itemsRepository: ItemsRepository, | ||||
| private val uomConversionService: UomConversionService, | |||||
| private val uomConversionRepository: UomConversionRepository, | |||||
| private val itemUomService: ItemUomService, | |||||
| ): AbstractBaseEntityService<Inventory, Long, InventoryRepository>(jdbcDao, inventoryRepository) { | ): AbstractBaseEntityService<Inventory, Long, InventoryRepository>(jdbcDao, inventoryRepository) { | ||||
| open fun allInventory(): List<Inventory> { | open fun allInventory(): List<Inventory> { | ||||
| // TODO: Replace by actual logic | // TODO: Replace by actual logic | ||||
| @@ -39,6 +47,7 @@ open class InventoryService( | |||||
| return inventoryRepository.findInventoryInfoByItemIdInAndDeletedIsFalse(itemIds); | return inventoryRepository.findInventoryInfoByItemIdInAndDeletedIsFalse(itemIds); | ||||
| } | } | ||||
| // @Throws(IOException::class) | // @Throws(IOException::class) | ||||
| // open fun updateInventory(request: SaveInventoryRequest): MessageResponse { | // open fun updateInventory(request: SaveInventoryRequest): MessageResponse { | ||||
| // // out need id | // // out need id | ||||
| @@ -0,0 +1,11 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update_job_order_bom_material | |||||
| ALTER TABLE `job_order_bom_material` | |||||
| DROP FOREIGN KEY `FK_JOBM_TO_UOM_CONVERSION_ON_UOM_ID`; | |||||
| ALTER TABLE `job_order_bom_material` | |||||
| CHANGE COLUMN `uomId` `uomId` INT NULL ; | |||||
| ALTER TABLE `job_order_bom_material` | |||||
| ADD CONSTRAINT `FK_JOBM_TO_UOM_CONVERSION_ON_UOM_ID` | |||||
| FOREIGN KEY (`uomId`) | |||||
| REFERENCES `uom_conversion` (`id`); | |||||
| @@ -0,0 +1,19 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update_inventory_default | |||||
| ALTER TABLE `inventory` | |||||
| DROP FOREIGN KEY `FK_INVENTORY_TO_UOM_ON_UOM_ID`; | |||||
| ALTER TABLE `inventory` | |||||
| CHANGE COLUMN `onHandQty` `onHandQty` DECIMAL(14,2) NOT NULL DEFAULT '0.00' , | |||||
| CHANGE COLUMN `price` `price` INT NULL DEFAULT 0 , | |||||
| CHANGE COLUMN `cpu` `cpu` DECIMAL(14,2) NULL DEFAULT '0.00' , | |||||
| CHANGE COLUMN `cpuUnit` `cpuUnit` VARCHAR(255) NULL DEFAULT 'HKD' , | |||||
| CHANGE COLUMN `cpm` `cpm` DECIMAL(14,2) NULL DEFAULT '0.00' , | |||||
| CHANGE COLUMN `cpmUnit` `cpmUnit` VARCHAR(255) NULL DEFAULT 'HKD' , | |||||
| CHANGE COLUMN `uomId` `uomId` INT NULL , | |||||
| CHANGE COLUMN `status` `status` VARCHAR(255) NOT NULL DEFAULT 'available' ; | |||||
| ALTER TABLE `inventory` | |||||
| ADD CONSTRAINT `FK_INVENTORY_TO_UOM_ON_UOM_ID` | |||||
| FOREIGN KEY (`uomId`) | |||||
| REFERENCES `uom_conversion` (`id`); | |||||