| @@ -4,6 +4,7 @@ import java.time.LocalDate | |||
| import java.time.format.DateTimeFormatter | |||
| object CodeGenerator { | |||
| // Default Value for Lot No | |||
| private var dateFormat = DateTimeFormatter.ofPattern("yyMMdd") | |||
| fun generateCode(prefix: String, itemId: Long, count: Int): String { | |||
| // prefix = "ITEM" || "LOT" | |||
| @@ -13,4 +14,33 @@ object CodeGenerator { | |||
| val countStr = String.format("%04d", count) | |||
| return "$prefix-$todayStr$itemStr$countStr" | |||
| } | |||
| // Default Value for Order No | |||
| val DEFAULT_SUFFIX_FORMAT = "%03d"; | |||
| val DEFAULT_PATTERN = "yyyyMMdd"; | |||
| val DEFAULT_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_PATTERN) | |||
| val DEFAULT_MIDFIX = LocalDate.now().format(DEFAULT_FORMATTER) | |||
| fun generateOrderNo( | |||
| suffixFormat: String? = null, | |||
| pattern: String? = null, | |||
| prefix: String, | |||
| midfix: String? = null, | |||
| latestCode: String? | |||
| ): String { | |||
| val formatter = pattern?.let { DateTimeFormatter.ofPattern(it) } ?: DEFAULT_FORMATTER | |||
| val finalSuffixFormat = suffixFormat ?: DEFAULT_SUFFIX_FORMAT | |||
| val midfix = midfix ?: LocalDate.now().format(formatter) | |||
| val suffix = String.format(finalSuffixFormat, 1) | |||
| if (latestCode != null) { | |||
| val splitLatestCode = latestCode.split("-") | |||
| if (splitLatestCode.size > 2) { | |||
| val latestNo = splitLatestCode[2].toInt() | |||
| return listOf<String>(prefix, midfix, String.format(finalSuffixFormat, latestNo + 1)).joinToString("-") | |||
| } | |||
| } | |||
| return listOf<String>(prefix, midfix, suffix).joinToString("-") | |||
| } | |||
| } | |||
| @@ -2,6 +2,8 @@ package com.ffii.fpsms.modules.jobOrder.entity | |||
| import com.fasterxml.jackson.annotation.JsonManagedReference | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatusConverter | |||
| import com.ffii.fpsms.modules.master.entity.Bom | |||
| import com.ffii.fpsms.modules.master.entity.ProductionScheduleLine | |||
| import com.ffii.fpsms.modules.user.entity.User | |||
| @@ -42,10 +44,11 @@ open class JobOrder : BaseEntity<Long>() { | |||
| @Column(name = "actualQty", precision = 14, scale = 2) | |||
| open var actualQty: BigDecimal? = null | |||
| @Size(max = 100) | |||
| // @Size(max = 100) | |||
| @NotNull | |||
| @Column(name = "status", nullable = false, length = 100) | |||
| open var status: String? = null | |||
| @Convert(converter = JobOrderStatusConverter::class) | |||
| open var status: JobOrderStatus? = null | |||
| @Size(max = 500) | |||
| @Column(name = "remarks", length = 500) | |||
| @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository | |||
| @Repository | |||
| interface JobOrderBomMaterialRepository : AbstractRepository<JobOrderBomMaterial, Long> { | |||
| fun findByJobOrderIdAndItemId(jobOrderId: Long, itemId: Long): JobOrderBomMaterial?; | |||
| } | |||
| @@ -20,6 +20,24 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||
| @Query( | |||
| nativeQuery = true, | |||
| value = """ | |||
| with picked_lot_no as ( | |||
| select | |||
| pol.itemId, | |||
| po.joId, | |||
| json_arrayagg( | |||
| json_object( | |||
| 'lotNo', il.lotNo, | |||
| 'qty', sol.qty | |||
| ) | |||
| ) as pickedLotNo | |||
| from pick_order po | |||
| left join pick_order_line pol on pol.poId = po.id | |||
| left join stock_out_line sol on sol.pickOrderLineId = pol.id | |||
| left join inventory_lot_line ill on ill.id = sol.inventoryLotLineId | |||
| left join inventory_lot il on il.id = ill.inventoryLotId | |||
| where po.joId = :id and il.lotNo is not null | |||
| group by pol.itemId, po.joId | |||
| ) | |||
| select | |||
| jo.id, | |||
| jo.code, | |||
| @@ -32,7 +50,7 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||
| 'id', jobm.id, | |||
| 'code', i.code, | |||
| 'name', i.name, | |||
| 'lotNo', il.lotNo, | |||
| 'pickedLotNo', pln.pickedLotNo, | |||
| 'reqQty', jobm.reqQty, | |||
| 'uom', uc.udfudesc, | |||
| 'status', jobm.status | |||
| @@ -46,15 +64,14 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||
| left join job_order_bom_material jobm on jo.id = jobm.jobOrderId | |||
| left join items i on i.id = jobm.itemId | |||
| left join uom_conversion uc on uc.id = jobm.uomId | |||
| left join stock_out_line sol on sol.id = jobm.stockOutLineId | |||
| left join inventory_lot_line ill on ill.id = sol.inventoryLotLineId | |||
| left join inventory_lot il on il.id = ill.inventoryLotId | |||
| left join picked_lot_no pln on pln.itemId = jobm.itemId and pln.joId = jo.id | |||
| where jo.id = :id | |||
| group by jo.id, uc2.udfudesc | |||
| limit 1 | |||
| """ | |||
| ) | |||
| fun findJobOrderDetailById(id: Long): JobOrderDetailWithJsonString?; | |||
| @Query( | |||
| nativeQuery = true, | |||
| value = """ | |||
| @@ -14,6 +14,8 @@ interface JobOrderInfo { | |||
| @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}") | |||
| val uom: String; | |||
| @get:Value("#{target.status.value}") | |||
| val status: String; | |||
| } | |||
| @@ -42,8 +44,13 @@ data class JobOrderDetailPickLine( | |||
| val id: Long?, | |||
| val code: String?, | |||
| val name: String?, | |||
| val lotNo: String?, | |||
| val pickedLotNo: List<JobOrderDetailPickedLotNo>?, | |||
| val reqQty: BigDecimal?, | |||
| val uom: String?, | |||
| val status: String? | |||
| ) | |||
| data class JobOrderDetailPickedLotNo( | |||
| val lotNo: String?, | |||
| val qty: BigDecimal?, | |||
| ) | |||
| @@ -0,0 +1,10 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.enums | |||
| enum class JobOrderStatus(val value: String) { | |||
| PLANNING("planning"), | |||
| PENDING("pending"), | |||
| PROCESSING("processing"), | |||
| PACKAGING("packaging"), | |||
| STORING("storing"), | |||
| COMPLETED("completed") | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.enums | |||
| import jakarta.persistence.AttributeConverter | |||
| import jakarta.persistence.Converter | |||
| // Job Order Status | |||
| @Converter(autoApply = true) | |||
| class JobOrderStatusConverter : AttributeConverter<JobOrderStatus, String> { | |||
| override fun convertToDatabaseColumn(status: JobOrderStatus?): String? { | |||
| return status?.value | |||
| } | |||
| override fun convertToEntityAttribute(value: String?): JobOrderStatus? { | |||
| return value?.let { v -> | |||
| JobOrderStatus.entries.find { it.value == v } | |||
| } | |||
| } | |||
| } | |||
| @@ -24,7 +24,7 @@ open class JobOrderBomMaterialService( | |||
| open fun createJobOrderBomMaterialRequests(joId: Long): List<CreateJobOrderBomMaterialRequest> { | |||
| val zero = BigDecimal.ZERO | |||
| val jo = jobOrderRepository.findById(joId).getOrNull() ?: throw NoSuchElementException() | |||
| val proportion = BigDecimal.ONE //(jo.reqQty ?: zero).divide(jo.bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP) | |||
| val proportion = (jo.reqQty ?: zero).divide(jo.bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP) | |||
| val jobmRequests = jo.bom?.bomMaterials?.map { bm -> | |||
| val salesUnit = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | |||
| @@ -35,7 +35,7 @@ open class JobOrderBomMaterialService( | |||
| reqQty = bm.qty?.times(proportion) ?: zero, | |||
| uomId = salesUnit?.uom?.id | |||
| ) | |||
| } ?: listOf() | |||
| } ?: listOf() | |||
| return jobmRequests | |||
| } | |||
| @@ -8,6 +8,7 @@ import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderReleaseRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest | |||
| @@ -111,6 +112,7 @@ open class JobOrderService( | |||
| val approver = request.approverId?.let { userService.find(it).getOrNull() } ?: SecurityUtils.getUser().getOrNull() | |||
| val prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() } | |||
| val code = assignJobNo() | |||
| val status = JobOrderStatus.entries.find { it.value == request.status } | |||
| jo.apply { | |||
| this.code = code | |||
| @@ -119,7 +121,7 @@ open class JobOrderService( | |||
| planStart = request.planStart ?: LocalDateTime.now() | |||
| planEnd = request.planEnd ?: LocalDateTime.now() | |||
| reqQty = request.reqQty | |||
| status = request.status | |||
| this.status = status | |||
| type = request.type | |||
| this.approver = approver | |||
| this.prodScheduleLine = prodScheduleLine | |||
| @@ -141,7 +143,7 @@ open class JobOrderService( | |||
| open fun releaseJobOrder(request: JobOrderReleaseRequest): MessageResponse { | |||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | |||
| jo.apply { | |||
| status = "pending" | |||
| status = JobOrderStatus.PENDING | |||
| } | |||
| jobOrderRepository.save(jo) | |||
| @@ -168,7 +170,7 @@ open class JobOrderService( | |||
| type = null, | |||
| message = null, | |||
| errorPosition = null, | |||
| entity = mapOf("status" to jo.status) | |||
| entity = mapOf("status" to jo.status?.value) | |||
| ) | |||
| } | |||
| } | |||
| @@ -13,4 +13,6 @@ interface BomMaterialRepository : AbstractRepository<BomMaterial, Long> { | |||
| fun findAllByBomItemIdAndDeletedIsFalse(itemId: Long): List<BomMaterial> | |||
| fun findAllByBomIdAndDeletedIsFalse(bomId: Long): List<BomMaterial> | |||
| fun findByBomIdAndItemId(bomId: Long, itemId: Long): BomMaterial? | |||
| } | |||
| @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository | |||
| @Repository | |||
| interface BomProcessMaterialRepository : AbstractRepository<BomProcessMaterial, Long> { | |||
| fun findByBomProcessIdAndBomMaterialId(bomProcessId: Long, bomMaterialId: Long): BomProcessMaterial?; | |||
| } | |||
| @@ -16,4 +16,6 @@ interface BomRepository : AbstractRepository<Bom, Long> { | |||
| fun findByItemIdAndDeletedIsFalse(itemId: Serializable): Bom? | |||
| fun findBomComboByDeletedIsFalse(): List<BomCombo> | |||
| fun findByCodeAndDeletedIsFalse(code: String): Bom? | |||
| } | |||
| @@ -3,8 +3,13 @@ package com.ffii.fpsms.modules.master.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface WarehouseRepository : AbstractRepository<Warehouse, Long> { | |||
| fun findWarehouseComboByDeletedFalse(): List<WarehouseCombo>; | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): Warehouse?; | |||
| fun findByCodeAndDeletedIsFalse(code: String): Warehouse?; | |||
| } | |||
| @@ -90,7 +90,8 @@ open class BomService( | |||
| private fun saveBomEntity(req: ImportBomRequest): Bom { | |||
| val item = itemsRepository.findByCodeAndDeletedFalse(req.code) ?: itemsRepository.findByNameAndDeletedFalse(req.name) | |||
| val uom = if (req.uomId != null) uomConversionRepository.findById(req.uomId!!).orElseThrow() else null | |||
| val bom = Bom().apply { | |||
| val bom = bomRepository.findByCodeAndDeletedIsFalse(req.code) ?: Bom() | |||
| bom.apply { | |||
| this.isDark = req.isDark | |||
| this.isFloat = req.isFloat | |||
| this.isDense = req.isDense | |||
| @@ -111,10 +112,11 @@ open class BomService( | |||
| private fun saveBomMaterial(req: ImportBomMatRequest): BomMaterial { | |||
| // val uom = uomConversionRepository.findByCodeAndDeletedFalse() | |||
| println("printing") | |||
| println(req) | |||
| println(req.item) | |||
| val bomMaterial = BomMaterial().apply { | |||
| // println("printing") | |||
| // println(req) | |||
| // println(req.item) | |||
| val bomMaterial = req.bom?.id?.let { bId -> req.item?.id?.let { iId -> bomMaterialRepository.findByBomIdAndItemId(bId, iId) } } ?: BomMaterial() | |||
| bomMaterial.apply { | |||
| this.item = req.item | |||
| this.itemName = req.item!!.name | |||
| this.isConsumable = req.isConsumable | |||
| @@ -124,12 +126,12 @@ open class BomService( | |||
| this.uom = req.uom | |||
| this.uomName = req.uomName | |||
| this.bom = req.bom | |||
| } | |||
| return bomMaterialRepository.saveAndFlush(bomMaterial) | |||
| } | |||
| private fun saveBomProcess(req: ImportBomProcessRequest): BomProcess { | |||
| val bomProcess = BomProcess().apply { | |||
| val bomProcess = req.bom!!.id?.let { id -> req.seqNo?.let { seqNo -> bomProcessRepository.findBySeqNoAndBomIdAndDeletedIsFalse(seqNo.toInt(), id) } } ?: BomProcess() | |||
| bomProcess.apply { | |||
| this.process = req.process | |||
| this.equipment = req.equipment | |||
| this.description = req.description | |||
| @@ -142,7 +144,8 @@ open class BomService( | |||
| return bomProcessRepository.saveAndFlush(bomProcess) | |||
| } | |||
| fun saveBomProcessMaterial(req: ImportBomProcessMaterialRequest): BomProcessMaterial { | |||
| val bomProcessMaterial = BomProcessMaterial().apply { | |||
| val bomProcessMaterial = req.bomProcess?.id?.let { pid -> req.bomMaterial?.id?.let { mid -> bomProcessMaterialRepository.findByBomProcessIdAndBomMaterialId(pid, mid)} } ?: BomProcessMaterial() | |||
| bomProcessMaterial.apply { | |||
| this.bomProcess = req.bomProcess | |||
| this.bomMaterial = req.bomMaterial | |||
| } | |||
| @@ -489,7 +492,7 @@ open class BomService( | |||
| // val folder = File(folderPath) | |||
| val resolver = PathMatchingResourcePatternResolver() | |||
| // val excels = resolver.getResources("bomImport/*.xlsx") | |||
| val excels = resolver.getResources("file:C:/Users/2Fi/Desktop/test folder/*.xlsx") | |||
| val excels = resolver.getResources("file:C:/Users/ffii_/Downloads/bom/new/*.xlsx") | |||
| // val excels = resolver.getResources("file:C:/Users/2Fi/Desktop/Third Wave of BOM Excel/*.xlsx") | |||
| println("size: ${excels.size}") | |||
| val logExcel = ClassPathResource("excelTemplate/bom_excel_issue_log.xlsx") | |||
| @@ -210,6 +210,10 @@ open class ItemsService( | |||
| return itemsRepository.findByIdAndDeletedFalse(id); | |||
| } | |||
| open fun findByCode(code: String): Items? { | |||
| return itemsRepository.findByCodeAndDeletedFalse(code); | |||
| } | |||
| open fun findByM18Id(m18Id: Long): Items? { | |||
| return itemsRepository.findByM18IdAndDeletedIsFalse(m18Id) | |||
| } | |||
| @@ -6,6 +6,7 @@ import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||
| import com.ffii.fpsms.modules.master.entity.Warehouse | |||
| import com.ffii.fpsms.modules.master.entity.WarehouseRepository | |||
| import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo | |||
| import com.ffii.fpsms.modules.master.web.models.SaveWarehouseRequest | |||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockInLine | |||
| @@ -14,6 +15,7 @@ import com.ffii.fpsms.modules.stock.entity.StockInRepository | |||
| import com.ffii.fpsms.modules.stock.service.InventoryLotService | |||
| import com.ffii.fpsms.modules.stock.service.StockInService | |||
| import org.springframework.stereotype.Service | |||
| import kotlin.jvm.optionals.getOrNull | |||
| @Service | |||
| open class WarehouseService( | |||
| @@ -28,4 +30,25 @@ open class WarehouseService( | |||
| open fun findCombo(): List<WarehouseCombo> { | |||
| return warehouseRepository.findWarehouseComboByDeletedFalse(); | |||
| } | |||
| open fun findById(id: Long): Warehouse? { | |||
| return warehouseRepository.findByIdAndDeletedIsFalse(id); | |||
| } | |||
| open fun findByCode(code: String): Warehouse? { | |||
| return warehouseRepository.findByCodeAndDeletedIsFalse(code); | |||
| } | |||
| open fun saveWarehouse(request: SaveWarehouseRequest): Warehouse { | |||
| val warehouse = request.id?.let { warehouseRepository.findById(it).getOrNull() } ?: Warehouse(); | |||
| warehouse.apply { | |||
| code = request.code | |||
| name = request.name | |||
| description = request.description | |||
| capacity = request.capacity | |||
| }; | |||
| return warehouseRepository.save(warehouse); | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| package com.ffii.fpsms.modules.master.web.models | |||
| import java.math.BigDecimal | |||
| data class SaveWarehouseRequest( | |||
| val id: Long? = null, | |||
| val code: String, | |||
| val name: String, | |||
| val description: String, | |||
| val capacity: BigDecimal, | |||
| ) | |||
| @@ -12,6 +12,9 @@ import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
| @Repository | |||
| interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): InventoryLotLine; | |||
| fun findInventoryLotLineInfoByInventoryLotItemIdIn(ids: List<Serializable>): List<InventoryLotLineInfo> | |||
| @Query("select ill from InventoryLotLine ill where :id is null or ill.inventoryLot.item.id = :id order by ill.id desc") | |||
| @@ -34,6 +37,9 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long | |||
| fun findAllByIdIn(ids: List<Serializable>): List<InventoryLotLine> | |||
| fun findAllByInventoryLotId(id: Serializable): List<InventoryLotLine> | |||
| fun findByInventoryLotStockInLineIdAndWarehouseId(inventoryLotStockInLineId: Long, warehouseId: Long): InventoryLotLine? | |||
| fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: String): List<InventoryLotLine> | |||
| fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: InventoryLotLineStatus): List<InventoryLotLine> | |||
| @@ -40,6 +40,10 @@ open class StockIn : BaseEntity<Long>() { | |||
| @JoinColumn(name = "stockOutId") | |||
| open var stockOutId: StockOut? = null | |||
| @ManyToOne | |||
| @JoinColumn(name = "stockTakeId") | |||
| open var stockTake: StockTake? = null | |||
| @Column(name = "orderDate") | |||
| open var orderDate: LocalDateTime? = null | |||
| @@ -37,6 +37,10 @@ open class StockInLine : BaseEntity<Long>() { | |||
| @JoinColumn(name = "purchaseOrderLineId") | |||
| open var purchaseOrderLine: PurchaseOrderLine? = null | |||
| @ManyToOne | |||
| @JoinColumn(name = "stockTakeLineId") | |||
| open var stockTakeLine: StockTakeLine? = null | |||
| @NotNull | |||
| @ManyToOne | |||
| @JoinColumn(name = "stockInId", nullable = false) | |||
| @@ -1,9 +1,18 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface StockInRepository : AbstractRepository<StockIn, Long> { | |||
| fun findByPurchaseOrderIdAndDeletedFalse(purchaseOrderId: Long): StockIn? | |||
| // @Query(""" | |||
| // select si from StockIn si where si.stockTake.id = :stockTakeId and si.deleted = false | |||
| // """) | |||
| fun findByStockTakeIdAndDeletedFalse(stockTakeId: Long): StockIn? | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockIn? | |||
| } | |||
| @@ -1,6 +1,8 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.stock.enums.StockTakeStatus | |||
| import com.ffii.fpsms.modules.stock.enums.StockTakeStatusConverter | |||
| import jakarta.persistence.* | |||
| import jakarta.validation.constraints.NotNull | |||
| import jakarta.validation.constraints.Size | |||
| @@ -28,10 +30,11 @@ open class StockTake: BaseEntity<Long>() { | |||
| @Column(name = "actualEnd") | |||
| open var actualEnd: LocalDateTime? = null | |||
| @Size(max = 20) | |||
| // @Size(max = 20) | |||
| @NotNull | |||
| @Column(name = "status", nullable = false, length = 20) | |||
| open var status: String? = null | |||
| @Convert(converter = StockTakeStatusConverter::class) | |||
| open var status: StockTakeStatus? = null | |||
| @Size(max = 500) | |||
| @Column(name = "remarks", length = 500) | |||
| @@ -2,6 +2,8 @@ package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.master.entity.UomConversion | |||
| import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatus | |||
| import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatusConverter | |||
| import jakarta.persistence.* | |||
| import jakarta.validation.constraints.NotNull | |||
| import jakarta.validation.constraints.Size | |||
| @@ -16,7 +18,6 @@ open class StockTakeLine : BaseEntity<Long>() { | |||
| @JoinColumn(name = "stockTakeId", nullable = false) | |||
| open var stockTake: StockTake? = null | |||
| @NotNull | |||
| @ManyToOne | |||
| @JoinColumn(name = "inventoryLotLineId", nullable = false) | |||
| open var inventoryLotLine: InventoryLotLine? = null | |||
| @@ -34,10 +35,11 @@ open class StockTakeLine : BaseEntity<Long>() { | |||
| @Column(name = "completeDate") | |||
| open var completeDate: LocalDateTime? = null | |||
| @Size(max = 20) | |||
| // @Size(max = 20) | |||
| @NotNull | |||
| @Column(name = "status", nullable = false, length = 20) | |||
| open var status: String? = null | |||
| @Convert(converter = StockTakeLineStatusConverter::class) | |||
| open var status: StockTakeLineStatus? = null | |||
| @Size(max = 500) | |||
| @Column(name = "remarks", length = 500) | |||
| @@ -2,7 +2,9 @@ package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface StockTakeLineRepository : AbstractRepository<StockTakeLine, Long> { | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeLine?; | |||
| } | |||
| @@ -1,8 +1,16 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface StockTakeRepository : AbstractRepository<StockTake, Long> { | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTake; | |||
| @Query(""" | |||
| select st.code from StockTake st where st.code like :prefix% order by st.code desc limit 1 | |||
| """) | |||
| fun findLatestCodeByPrefix(prefix: String): String? | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| package com.ffii.fpsms.modules.stock.enums | |||
| enum class StockTakeStatus(val value: String) { | |||
| PENDING("pending"), | |||
| STOCKTAKING("stockTaking"), | |||
| COMPLETED("completed"), | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| package com.ffii.fpsms.modules.stock.enums | |||
| import jakarta.persistence.AttributeConverter | |||
| import jakarta.persistence.Converter | |||
| // Stock Take Status | |||
| @Converter(autoApply = true) | |||
| class StockTakeStatusConverter : AttributeConverter<StockTakeStatus, String>{ | |||
| override fun convertToDatabaseColumn(status: StockTakeStatus?): String? { | |||
| return status?.value | |||
| } | |||
| override fun convertToEntityAttribute(value: String?): StockTakeStatus? { | |||
| return value?.let { v -> | |||
| StockTakeStatus.entries.find { it.value == v } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.fpsms.modules.stock.enums | |||
| enum class StockTakeLineStatus(val value: String) { | |||
| PENDING("pending"), | |||
| COMPLETED("completed"), | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| package com.ffii.fpsms.modules.stock.enums | |||
| import jakarta.persistence.AttributeConverter | |||
| import jakarta.persistence.Converter | |||
| // Stock Take Line Status | |||
| @Converter(autoApply = true) | |||
| class StockTakeLineStatusConverter : AttributeConverter<StockTakeLineStatus, String> { | |||
| override fun convertToDatabaseColumn(status: StockTakeLineStatus?): String? { | |||
| return status?.value | |||
| } | |||
| override fun convertToEntityAttribute(value: String?): StockTakeLineStatus? { | |||
| return value?.let { v -> | |||
| StockTakeLineStatus.entries.find { it.value == v } | |||
| } | |||
| } | |||
| } | |||
| @@ -65,6 +65,7 @@ open class StockInLineService( | |||
| private val warehouseRepository: WarehouseRepository, | |||
| private val itemUomRespository: ItemUomRespository, | |||
| private val printerService: PrinterService, | |||
| private val stockTakeLineRepository: StockTakeLineRepository, | |||
| ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | |||
| open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | |||
| @@ -78,30 +79,36 @@ open class StockInLineService( | |||
| open fun create(request: SaveStockInLineRequest): MessageResponse { | |||
| val stockInLine = StockInLine() | |||
| val item = itemRepository.findById(request.itemId).orElseThrow() | |||
| val purchaseOrderLine = polRepository.findById(request.purchaseOrderLineId).orElseThrow() | |||
| var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | |||
| purchaseOrderLine.apply { | |||
| val purchaseOrderLine = request.purchaseOrderLineId?.let { polRepository.findById(it).getOrNull() } | |||
| val stockTakeLine = request.stockTakeLineId?.let { stockTakeLineRepository.findById(it).getOrNull() } | |||
| var stockIn = request.stockInId?.let { stockInRepository.findByIdAndDeletedIsFalse(it) } | |||
| ?: request.purchaseOrderId?.let { stockInRepository.findByPurchaseOrderIdAndDeletedFalse(it) } | |||
| ?: request.stockTakeId?.let { stockInRepository.findByStockTakeIdAndDeletedFalse(it) } | |||
| purchaseOrderLine?.apply { | |||
| status = PurchaseOrderLineStatus.RECEIVING | |||
| } | |||
| val pol = polRepository.saveAndFlush(purchaseOrderLine) | |||
| val pol = purchaseOrderLine?.let { polRepository.saveAndFlush(it) } | |||
| if (stockIn == null) { | |||
| stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | |||
| // update po status to receiving | |||
| val po = purchaseOrderRepository.findById(request.purchaseOrderId).orElseThrow() | |||
| po.apply { | |||
| status = PurchaseOrderStatus.RECEIVING | |||
| val po = request.purchaseOrderId?.let { purchaseOrderRepository.findById(it).getOrNull() } | |||
| if (po != null) { | |||
| po.apply { | |||
| status = PurchaseOrderStatus.RECEIVING | |||
| } | |||
| purchaseOrderRepository.save(po) | |||
| } | |||
| purchaseOrderRepository.save(po) | |||
| } | |||
| val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!) | |||
| if (pol.qty!! < request.acceptedQty) { | |||
| if (pol != null && pol.qty!! < request.acceptedQty) { | |||
| throw BadRequestException() | |||
| } | |||
| stockInLine.apply { | |||
| this.item = item | |||
| itemNo = item.code | |||
| this.purchaseOrder = purchaseOrderLine.purchaseOrder | |||
| this.purchaseOrder = purchaseOrderLine?.purchaseOrder | |||
| this.purchaseOrderLine = purchaseOrderLine | |||
| this.stockTakeLine = stockTakeLine | |||
| this.stockIn = stockIn | |||
| acceptedQty = request.acceptedQty | |||
| dnNo = request.dnNo | |||
| @@ -156,7 +163,7 @@ open class StockInLineService( | |||
| itemId = request.itemId | |||
| ) | |||
| val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) | |||
| val convertedBaseQty = if (stockItemUom != null && purchaseItemUom != null) { | |||
| val convertedBaseQty = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) { | |||
| (line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) | |||
| } else { | |||
| (line.qty) | |||
| @@ -269,9 +276,9 @@ open class StockInLineService( | |||
| @Transactional | |||
| open fun updatePurchaseOrderStatus(request: SaveStockInLineRequest) { | |||
| if (request.status == StockInLineStatus.COMPLETE.status) { | |||
| val unfinishedLines = polRepository | |||
| .findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(purchaseOrderId = request.purchaseOrderId, status = PurchaseOrderLineStatus.COMPLETED) | |||
| if (unfinishedLines.isEmpty()) { | |||
| val unfinishedLines = request.purchaseOrderId?.let { polRepository | |||
| .findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(purchaseOrderId = it, status = PurchaseOrderLineStatus.COMPLETED) } | |||
| if (unfinishedLines != null && unfinishedLines.isEmpty()) { | |||
| val po = purchaseOrderRepository.findById(request.purchaseOrderId).orElseThrow() | |||
| po.apply { | |||
| status = PurchaseOrderStatus.COMPLETED | |||
| @@ -287,42 +294,52 @@ open class StockInLineService( | |||
| fun updatePurchaseOrderLineStatus(request: SaveStockInLineRequest) { | |||
| println(request.status) | |||
| if (request.status == StockInLineStatus.RECEIVING.status) { | |||
| val unQcedLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(purchaseOrderLineId = request.purchaseOrderLineId) | |||
| .filter { | |||
| val unQcedLines = request.purchaseOrderLineId?.let { stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(purchaseOrderLineId = it) } | |||
| ?.filter { | |||
| it.status != StockInLineStatus.RECEIVING.status | |||
| && it.status != StockInLineStatus.RECEIVED.status | |||
| && it.status != StockInLineStatus.COMPLETE.status | |||
| && it.status != StockInLineStatus.REJECT.status | |||
| } | |||
| if (unQcedLines.isEmpty()) { | |||
| if (unQcedLines != null && unQcedLines.isEmpty()) { | |||
| // all stock in lines finished | |||
| // change status of purchase order line | |||
| val purchaseOrderLine = polRepository.findById(request.purchaseOrderLineId).orElseThrow() | |||
| purchaseOrderLine.apply { | |||
| status = PurchaseOrderLineStatus.RECEIVING | |||
| val purchaseOrderLine = request.purchaseOrderLineId?.let { polRepository.findById(it).orElseThrow() } | |||
| if (purchaseOrderLine != null) { | |||
| purchaseOrderLine.apply { | |||
| status = PurchaseOrderLineStatus.RECEIVING | |||
| } | |||
| polRepository.saveAndFlush(purchaseOrderLine) | |||
| } | |||
| polRepository.saveAndFlush(purchaseOrderLine) | |||
| } else { | |||
| // still have unQcedLines lines | |||
| } | |||
| } | |||
| if (request.status == StockInLineStatus.COMPLETE.status || request.status == StockInLineStatus.REJECT.status) { | |||
| // val unfinishedLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndStatusNotAndDeletedFalse(purchaseOrderLineId = request.purchaseOrderLineId, status = request.status!!) | |||
| val unfinishedLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(purchaseOrderLineId = request.purchaseOrderLineId) | |||
| .filter { | |||
| val unfinishedLines = request.purchaseOrderLineId?.let { | |||
| stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse( | |||
| purchaseOrderLineId = it | |||
| ) | |||
| } | |||
| ?.filter { | |||
| it.status != StockInLineStatus.COMPLETE.status | |||
| && it.status != StockInLineStatus.REJECT.status | |||
| } | |||
| println("unfinishedLines") | |||
| println(unfinishedLines) | |||
| if (unfinishedLines.isEmpty()) { | |||
| if (unfinishedLines != null && unfinishedLines.isEmpty()) { | |||
| // all stock in lines finished | |||
| // change status of purchase order line | |||
| val purchaseOrderLine = polRepository.findById(request.purchaseOrderLineId).orElseThrow() | |||
| purchaseOrderLine.apply { | |||
| status = PurchaseOrderLineStatus.COMPLETED | |||
| val purchaseOrderLine = request.purchaseOrderLineId?.let { | |||
| polRepository.findById(it).orElseThrow() | |||
| } | |||
| if (purchaseOrderLine != null) { | |||
| purchaseOrderLine.apply { | |||
| status = PurchaseOrderLineStatus.COMPLETED | |||
| } | |||
| polRepository.saveAndFlush(purchaseOrderLine) | |||
| } | |||
| polRepository.saveAndFlush(purchaseOrderLine) | |||
| } else { | |||
| // still have unfinished lines | |||
| } | |||
| @@ -372,7 +389,7 @@ open class StockInLineService( | |||
| val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) | |||
| val stockItemUom = itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(request.itemId) | |||
| val ratio = if (stockItemUom != null && purchaseItemUom != null) { | |||
| val ratio = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) { | |||
| (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) | |||
| } else { | |||
| BigDecimal.ONE | |||
| @@ -4,45 +4,47 @@ import com.ffii.core.support.AbstractBaseEntityService | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockIn | |||
| import com.ffii.fpsms.modules.stock.entity.StockInLine | |||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockInRepository | |||
| import com.ffii.fpsms.modules.stock.entity.* | |||
| import com.ffii.fpsms.modules.stock.web.model.SaveStockInRequest | |||
| import com.ffii.fpsms.modules.stock.web.model.StockInStatus | |||
| import org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import java.io.IOException | |||
| import kotlin.jvm.optionals.getOrNull | |||
| @Service | |||
| open class StockInService( | |||
| private val jdbcDao: JdbcDao, | |||
| private val stockInRepository: StockInRepository, | |||
| private val purchaseOrderRepository: PurchaseOrderRepository, | |||
| private val stockTakeRepository: StockTakeRepository, | |||
| ): AbstractBaseEntityService<StockIn, Long, StockInRepository>(jdbcDao, stockInRepository) { | |||
| @Throws(IOException::class) | |||
| @Transactional | |||
| open fun create(request: SaveStockInRequest): MessageResponse { | |||
| val stockIn = StockIn() | |||
| val purchaseOrder = if (request.purchaseOrderId != null) | |||
| purchaseOrderRepository.findByIdAndDeletedFalse(request.purchaseOrderId).orElseThrow() | |||
| else return MessageResponse( | |||
| id = null, | |||
| code = null, | |||
| name = null, | |||
| type = "Found Null", | |||
| message = "request.purchaseOrderId is null", | |||
| errorPosition = "Stock In" | |||
| ) | |||
| // val purchaseOrder = if (request.purchaseOrderId != null) | |||
| // purchaseOrderRepository.findByIdAndDeletedFalse(request.purchaseOrderId).orElseThrow() | |||
| // else return MessageResponse( | |||
| // id = null, | |||
| // code = null, | |||
| // name = null, | |||
| // type = "Found Null", | |||
| // message = "request.purchaseOrderId is null", | |||
| // errorPosition = "Stock In" | |||
| // ) | |||
| val purchaseOrder = request.purchaseOrderId?.let { purchaseOrderRepository.findByIdAndDeletedFalse(request.purchaseOrderId).getOrNull() } | |||
| val stockTake = request.stockTakeId?.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) } | |||
| stockIn.apply { | |||
| code = purchaseOrder.code | |||
| supplier = purchaseOrder.supplier | |||
| code = request.code ?: purchaseOrder?.code | |||
| supplier = purchaseOrder?.supplier | |||
| this.purchaseOrder = purchaseOrder | |||
| this.stockTake = stockTake | |||
| // shop = purchaseOrder.shop | |||
| orderDate = purchaseOrder.orderDate | |||
| estimatedCompleteDate = purchaseOrder.estimatedArrivalDate?.toLocalDate() | |||
| completeDate = purchaseOrder.completeDate | |||
| orderDate = purchaseOrder?.orderDate | |||
| estimatedCompleteDate = purchaseOrder?.estimatedArrivalDate?.toLocalDate() | |||
| completeDate = purchaseOrder?.completeDate | |||
| status = StockInStatus.PENDING.status | |||
| } | |||
| val savedStockIn = saveAndFlush(stockIn) | |||
| @@ -0,0 +1,40 @@ | |||
| package com.ffii.fpsms.modules.stock.service | |||
| import com.ffii.fpsms.modules.master.entity.UomConversionRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockTakeLine | |||
| import com.ffii.fpsms.modules.stock.entity.StockTakeLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockTakeRepository | |||
| import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatus | |||
| import com.ffii.fpsms.modules.stock.web.model.SaveStockTakeLineRequest | |||
| import org.springframework.stereotype.Service | |||
| @Service | |||
| open class StockTakeLineService( | |||
| val stockTakeLineRepository: StockTakeLineRepository, | |||
| val stockTakeRepository: StockTakeRepository, | |||
| val inventoryLotLineRepository: InventoryLotLineRepository, | |||
| val uomConversionRepository: UomConversionRepository, | |||
| val inventoryLotLineService: InventoryLotLineService, | |||
| ) { | |||
| fun saveStockTakeLine(request: SaveStockTakeLineRequest): StockTakeLine { | |||
| val stockTake = request.stockTakeId?.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) } ?: throw Exception("Cant Find Stock Take"); | |||
| val inventoryLotLine = request.inventoryLotLineId?.let { inventoryLotLineRepository.findByIdAndDeletedIsFalse(it) }; | |||
| val uom = request.uomId?.let { uomConversionRepository.findByIdAndDeletedFalse(it) }; | |||
| val status = request.status?.let { _status -> StockTakeLineStatus.entries.find { it.value == _status} } | |||
| val stockTakeLine = request.id?.let { stockTakeLineRepository.findByIdAndDeletedIsFalse(it) } ?: StockTakeLine(); | |||
| // will skip null | |||
| stockTake.let { stockTakeLine.stockTake = it } | |||
| inventoryLotLine.let { stockTakeLine.inventoryLotLine = it } | |||
| uom.let { stockTakeLine.uom = it } | |||
| request.initialQty?.let { stockTakeLine.initialQty = it } | |||
| request.finalQty?.let { stockTakeLine.finalQty = it } | |||
| request.completeDate?.let { stockTakeLine.completeDate = it } | |||
| status?.let { stockTakeLine.status = it } | |||
| request.remarks?.let { stockTakeLine.remarks = it } | |||
| return stockTakeLineRepository.save(stockTakeLine); | |||
| } | |||
| } | |||
| @@ -0,0 +1,218 @@ | |||
| package com.ffii.fpsms.modules.stock.service | |||
| import com.ffii.core.utils.JwtTokenUtil | |||
| import com.ffii.fpsms.modules.common.CodeGenerator | |||
| import com.ffii.fpsms.modules.master.entity.WarehouseRepository | |||
| import com.ffii.fpsms.modules.master.service.ItemUomService | |||
| import com.ffii.fpsms.modules.master.service.ItemsService | |||
| import com.ffii.fpsms.modules.master.service.WarehouseService | |||
| import com.ffii.fpsms.modules.master.web.models.SaveWarehouseRequest | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockTake | |||
| import com.ffii.fpsms.modules.stock.entity.StockTakeRepository | |||
| import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatus | |||
| import com.ffii.fpsms.modules.stock.enums.StockTakeStatus | |||
| import com.ffii.fpsms.modules.stock.web.model.* | |||
| import org.apache.poi.ss.usermodel.Cell | |||
| import org.apache.poi.ss.usermodel.Sheet | |||
| import org.apache.poi.ss.usermodel.Workbook | |||
| import org.slf4j.Logger | |||
| import org.slf4j.LoggerFactory | |||
| import org.springframework.stereotype.Service | |||
| import java.math.BigDecimal | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| @Service | |||
| class StockTakeService( | |||
| val stockTakeRepository: StockTakeRepository, | |||
| val warehouseRepository: WarehouseRepository, | |||
| val warehouseService: WarehouseService, | |||
| val stockInService: StockInService, | |||
| val stockInLineService: StockInLineService, | |||
| val itemsService: ItemsService, | |||
| val itemUomService: ItemUomService, | |||
| val stockTakeLineService: StockTakeLineService, | |||
| val inventoryLotLineRepository: InventoryLotLineRepository, | |||
| ) { | |||
| val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) | |||
| fun assignStockTakeNo(): String { | |||
| val prefix = "ST" | |||
| val midfix = CodeGenerator.DEFAULT_MIDFIX | |||
| val latestCode = stockTakeRepository.findLatestCodeByPrefix("${prefix}-${midfix}") | |||
| return CodeGenerator.generateOrderNo(prefix = prefix, latestCode = latestCode) | |||
| } | |||
| fun saveStockTake(request: SaveStockTakeRequest): StockTake { | |||
| val stockTake = request.id?.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) } ?: StockTake(); | |||
| val status = request.status?.let { _status -> StockTakeStatus.entries.find { it.value == _status } } | |||
| request.code?.let { stockTake.code = it } | |||
| if (stockTake.code == null) { | |||
| stockTake.code = assignStockTakeNo() | |||
| } | |||
| request.planStart?.let { stockTake.planStart = it } | |||
| request.planEnd?.let { stockTake.planEnd = it } | |||
| request.actualStart?.let { stockTake.actualStart = it } | |||
| request.actualEnd?.let { stockTake.actualEnd = it } | |||
| status?.let { stockTake.status = it } | |||
| request.remarks?.let { stockTake.remarks = it } | |||
| return stockTakeRepository.save(stockTake); | |||
| } | |||
| // ---------------------------------------------- Import Excel ---------------------------------------------- // | |||
| fun importExcel(workbook: Workbook?): String { | |||
| logger.info("--------- Start - Import Stock Take Excel -------"); | |||
| if (workbook == null) { | |||
| logger.error("No Excel Import"); | |||
| return "Import Excel failure"; | |||
| } | |||
| val sheet: Sheet = workbook.getSheetAt(0); | |||
| // Columns | |||
| val COLUMN_ITEM_CODE_INDEX = 6; | |||
| val COLUMN_WAREHOSE_INDEX = 13; | |||
| val COLUMN_ZONE_INDEX = 14; | |||
| val COLUMN_SLOT_INDEX = 15; | |||
| val COLUMN_QTY_INDEX = 18; | |||
| val START_ROW_INDEX = 2; | |||
| // Start Import | |||
| val startTime = LocalDateTime.now(); | |||
| val stCode = assignStockTakeNo() | |||
| val saveStockTakeReq = SaveStockTakeRequest( | |||
| code = stCode, | |||
| planStart = startTime, | |||
| planEnd = startTime, | |||
| actualStart = startTime, | |||
| actualEnd = null, | |||
| status = StockTakeStatus.PENDING.value, | |||
| remarks = null | |||
| ) | |||
| val savedStockTake = saveStockTake(saveStockTakeReq); | |||
| val saveStockInReq = SaveStockInRequest( | |||
| code = stCode, | |||
| stockTakeId = savedStockTake.id | |||
| ) | |||
| val savedStockIn = stockInService.create(saveStockInReq) | |||
| for (i in START_ROW_INDEX ..< sheet.lastRowNum) { | |||
| val row = sheet.getRow(i) | |||
| // Warehouse | |||
| val warehouse = try { | |||
| val code = row.getCell(COLUMN_WAREHOSE_INDEX).stringCellValue | |||
| val zone = row.getCell(COLUMN_ZONE_INDEX).stringCellValue | |||
| val slot = row.getCell(COLUMN_SLOT_INDEX).stringCellValue | |||
| // logger.info("Warehouse code - zone - slot: ${row.getCell(COLUMN_WAREHOSE_INDEX).cellType} - ${row.getCell(COLUMN_ZONE_INDEX).cellType} - ${row.getCell(COLUMN_SLOT_INDEX).cellType}") | |||
| val defaultCapacity = BigDecimal(10000) | |||
| val warehouseCode = "$code-$zone-$slot" | |||
| val existingWarehouse = warehouseService.findByCode(warehouseCode) | |||
| if (existingWarehouse != null) { | |||
| existingWarehouse | |||
| } else { | |||
| val warehouseRequest = SaveWarehouseRequest( | |||
| code = warehouseCode, | |||
| name = warehouseCode, | |||
| description = warehouseCode, | |||
| capacity = defaultCapacity | |||
| ) | |||
| warehouseService.saveWarehouse(warehouseRequest) | |||
| } | |||
| } catch (e: Exception) { | |||
| logger.error("Import Error (Warehouse Error): ${e.message}") | |||
| null | |||
| } ?: continue | |||
| // Item | |||
| val item = try { | |||
| // logger.info("Item Type: ${row.getCell(COLUMN_ITEM_CODE_INDEX).cellType}") | |||
| val itemCode = row.getCell(COLUMN_ITEM_CODE_INDEX).stringCellValue; | |||
| itemsService.findByCode(itemCode) | |||
| } catch (e: Exception) { | |||
| logger.error("Import Error (Item Code Error): ${e.message}") | |||
| null | |||
| } ?: continue | |||
| // Stock Take Line (First Create) | |||
| val qty = try { | |||
| // logger.info("Qty Type: ${row.getCell(COLUMN_QTY_INDEX).cellType}") | |||
| row.getCell(COLUMN_QTY_INDEX).numericCellValue.toBigDecimal() | |||
| } catch (e: Exception) { | |||
| logger.error("Import Error (Qty Error): ${e.message}") | |||
| null | |||
| } ?: continue | |||
| val uom = itemUomService.findStockUnitByItemId(item.id!!)?.uom | |||
| val saveStockTakeLineReq = SaveStockTakeLineRequest( | |||
| stockTakeId = savedStockTake.id, | |||
| initialQty = qty, | |||
| finalQty = qty, | |||
| uomId = uom?.id, | |||
| status = StockTakeLineStatus.PENDING.value, | |||
| remarks = null, | |||
| ) | |||
| val savedStockTakeLine = stockTakeLineService.saveStockTakeLine(saveStockTakeLineReq) | |||
| // Stock In Line | |||
| val expiryDate = LocalDateTime.now().plusMonths(6) // TODO: Add expiry date from excel | |||
| val saveStockInLineReq = SaveStockInLineRequest( | |||
| stockInId = savedStockIn.id, | |||
| itemId = item.id!!, | |||
| acceptedQty = qty, | |||
| acceptQty = qty, | |||
| expiryDate = expiryDate.toLocalDate(), | |||
| warehouseId = warehouse.id, | |||
| stockTakeLineId = savedStockTakeLine.id, | |||
| qcAccept = true, | |||
| status = StockInLineStatus.PENDING.status | |||
| ) | |||
| val savedStockInLine = stockInLineService.create(saveStockInLineReq) | |||
| saveStockInLineReq.apply { | |||
| id = savedStockInLine.id | |||
| } | |||
| stockInLineService.update(saveStockInLineReq) | |||
| val inventoryLotLines = SaveInventoryLotLineForSil( | |||
| qty = qty, | |||
| warehouseId = warehouse.id | |||
| ) | |||
| saveStockInLineReq.apply { | |||
| status = StockInLineStatus.RECEIVED.status | |||
| this.inventoryLotLines = if (qty > BigDecimal.ZERO) mutableListOf(inventoryLotLines) else null | |||
| } | |||
| val finalStockInLine = stockInLineService.update(saveStockInLineReq) | |||
| // Stock Take Line (Second Completed) | |||
| val inventoryLotLine = inventoryLotLineRepository.findByInventoryLotStockInLineIdAndWarehouseId(inventoryLotStockInLineId = finalStockInLine.id!!, warehouseId = warehouse.id!!) | |||
| saveStockTakeLineReq.apply { | |||
| id = savedStockTakeLine.id | |||
| status = StockTakeLineStatus.COMPLETED.value | |||
| completeDate = LocalDateTime.now() | |||
| inventoryLotLineId = inventoryLotLine?.id | |||
| } | |||
| stockTakeLineService.saveStockTakeLine(saveStockTakeLineReq) | |||
| logger.info("[Stock Take]: Saved item '${item.name}' to warehouse '${warehouse.code}'") | |||
| } | |||
| // End of Import | |||
| val endTime = LocalDateTime.now(); | |||
| saveStockTakeReq.apply { | |||
| id = savedStockTake.id | |||
| actualEnd = endTime | |||
| status = StockTakeStatus.COMPLETED.value | |||
| } | |||
| saveStockTake(saveStockTakeReq) | |||
| logger.info("--------- End - Import Stock Take Excel -------") | |||
| return "Import Excel success"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| package com.ffii.fpsms.modules.stock.web | |||
| import com.ffii.fpsms.modules.stock.service.StockTakeService | |||
| import jakarta.servlet.http.HttpServletRequest | |||
| import org.apache.poi.ss.usermodel.Workbook | |||
| import org.apache.poi.util.IOUtils | |||
| import org.apache.poi.xssf.streaming.SXSSFWorkbook | |||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook | |||
| import org.springframework.http.ResponseEntity | |||
| import org.springframework.web.bind.ServletRequestBindingException | |||
| import org.springframework.web.bind.annotation.PostMapping | |||
| import org.springframework.web.bind.annotation.RequestMapping | |||
| import org.springframework.web.bind.annotation.RestController | |||
| import org.springframework.web.multipart.MultipartHttpServletRequest | |||
| @RequestMapping("/stockTake") | |||
| @RestController | |||
| class StockTakeController( | |||
| val stockTakeService: StockTakeService, | |||
| ) { | |||
| @PostMapping("/import") | |||
| @Throws(ServletRequestBindingException::class) | |||
| fun importExcel(request: HttpServletRequest): ResponseEntity<*> { | |||
| var workbook: Workbook? = null; | |||
| try { | |||
| println("Start") | |||
| val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList") | |||
| workbook = XSSFWorkbook(multipartFile?.inputStream) | |||
| // Use SXSSFWorkbook instead of XSSFWorkbook | |||
| // IOUtils.setByteArrayMaxOverride(300_000_000) | |||
| // workbook = SXSSFWorkbook(XSSFWorkbook(multipartFile?.inputStream)) | |||
| // Optional: Configure SXSSF to limit memory usage | |||
| // workbook.setCompressTempFiles(true) | |||
| } catch (e: Exception) { | |||
| println("Excel Wrong") | |||
| println(e) | |||
| } | |||
| return ResponseEntity.ok(stockTakeService.importExcel(workbook)) | |||
| } | |||
| } | |||
| @@ -33,32 +33,36 @@ data class SaveStockInRequest( | |||
| val completeDate: LocalDateTime? = null, | |||
| val status: String? = null, | |||
| val stockOutId: Long? = null, | |||
| val stockTakeId: Long? = null, | |||
| // val m18 | |||
| ) | |||
| data class SaveStockInLineRequest( | |||
| var id: Long?, | |||
| var purchaseOrderId: Long, | |||
| var purchaseOrderLineId: Long, | |||
| var id: Long? = null, | |||
| var purchaseOrderId: Long? = null, | |||
| var purchaseOrderLineId: Long? = null, | |||
| var itemId: Long, | |||
| var acceptedQty: BigDecimal, | |||
| var acceptQty: BigDecimal?, | |||
| var acceptedWeight: BigDecimal?, | |||
| var acceptedWeight: BigDecimal? = null, | |||
| var status: String?, | |||
| var expiryDate: LocalDate?, | |||
| var productLotNo: String?, | |||
| var dnNo: String?, | |||
| var invoiceNo: String?, | |||
| var remarks: String?, | |||
| var dnDate: LocalDate?, | |||
| var receiptDate: LocalDate?, | |||
| var productionDate: LocalDate?, | |||
| var qcAccept: Boolean?, | |||
| var qcResult: List<SaveQcResultRequest>?, | |||
| var escalationLog: SaveEscalationLogRequest?, | |||
| var productLotNo: String? = null, | |||
| var dnNo: String? = null, | |||
| var invoiceNo: String? = null, | |||
| var remarks: String? = null, | |||
| var dnDate: LocalDate? = null, | |||
| var receiptDate: LocalDate? = null, | |||
| var productionDate: LocalDate? = null, | |||
| var qcAccept: Boolean? = null, | |||
| var qcResult: List<SaveQcResultRequest>? = null, | |||
| var escalationLog: SaveEscalationLogRequest? = null, | |||
| var warehouseId: Long?, | |||
| var rejectQty: BigDecimal?, | |||
| var inventoryLotLines: List<SaveInventoryLotLineForSil>? | |||
| var rejectQty: BigDecimal? = null, | |||
| var inventoryLotLines: List<SaveInventoryLotLineForSil>? = null, | |||
| var stockTakeLineId: Long? = null, | |||
| var stockTakeId: Long? = null, | |||
| var stockInId: Long? = null, | |||
| ) | |||
| data class SaveInventoryLotLineForSil ( | |||
| @@ -0,0 +1,16 @@ | |||
| package com.ffii.fpsms.modules.stock.web.model | |||
| import java.math.BigDecimal | |||
| import java.time.LocalDateTime | |||
| data class SaveStockTakeLineRequest( | |||
| var id: Long? = null, | |||
| val stockTakeId: Long?, | |||
| var inventoryLotLineId: Long? = null, | |||
| val initialQty: BigDecimal?, | |||
| val finalQty: BigDecimal?, | |||
| val uomId: Long?, | |||
| var completeDate: LocalDateTime? = null, | |||
| var status: String?, | |||
| val remarks: String?, | |||
| ) | |||
| @@ -0,0 +1,14 @@ | |||
| package com.ffii.fpsms.modules.stock.web.model | |||
| import java.time.LocalDateTime | |||
| data class SaveStockTakeRequest( | |||
| var id: Long? = null, | |||
| val code: String?, | |||
| val planStart: LocalDateTime?, | |||
| val planEnd: LocalDateTime?, | |||
| val actualStart: LocalDateTime?, | |||
| var actualEnd: LocalDateTime?, | |||
| var status: String?, | |||
| val remarks: String?, | |||
| ) | |||
| @@ -0,0 +1,24 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset cyril:update_stock_in | |||
| ALTER TABLE `stock_in` | |||
| ADD COLUMN `stockTakeId` INT NULL DEFAULT NULL AFTER `stockOutId`, | |||
| ADD INDEX `FK_STOCK_IN_ON_STOCKTAKEID` (`stockTakeId` ASC) VISIBLE; | |||
| ; | |||
| ALTER TABLE `stock_in` | |||
| ADD CONSTRAINT `FK_STOCK_IN_ON_STOCKTAKEID` | |||
| FOREIGN KEY (`stockTakeId`) | |||
| REFERENCES `stock_take` (`id`) | |||
| ON DELETE RESTRICT | |||
| ON UPDATE RESTRICT; | |||
| ALTER TABLE `stock_in_line` | |||
| ADD COLUMN `stockTakeLineId` INT NULL DEFAULT NULL AFTER `purchaseOrderLineId`, | |||
| ADD INDEX `FK_STOCK_IN_LINE_ON_STOCKTAKELINEID` (`stockTakeLineId` ASC) VISIBLE; | |||
| ; | |||
| ALTER TABLE `stock_in_line` | |||
| ADD CONSTRAINT `FK_STOCK_IN_LINE_ON_STOCKTAKELINEID` | |||
| FOREIGN KEY (`stockTakeLineId`) | |||
| REFERENCES `stock_take_line` (`id`) | |||
| ON DELETE RESTRICT | |||
| ON UPDATE RESTRICT; | |||
| @@ -0,0 +1,11 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset cyril:update_stock_take_line | |||
| ALTER TABLE `stock_take_line` | |||
| DROP FOREIGN KEY `FK_STOCK_TAKE_LINE_ON_INVENTORYLOTLINEID`; | |||
| ALTER TABLE `stock_take_line` | |||
| CHANGE COLUMN `inventoryLotLineId` `inventoryLotLineId` INT NULL ; | |||
| ALTER TABLE `stock_take_line` | |||
| ADD CONSTRAINT `FK_STOCK_TAKE_LINE_ON_INVENTORYLOTLINEID` | |||
| FOREIGN KEY (`inventoryLotLineId`) | |||
| REFERENCES `inventory_lot_line` (`id`); | |||