diff --git a/src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt b/src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt index cc0a6eb..d4c633c 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt @@ -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(prefix, midfix, String.format(finalSuffixFormat, latestNo + 1)).joinToString("-") + } + } + + return listOf(prefix, midfix, suffix).joinToString("-") + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt index 9f1907b..5511fed 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt @@ -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() { @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) diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt index 825a1f2..8395771 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository @Repository interface JobOrderBomMaterialRepository : AbstractRepository { + fun findByJobOrderIdAndItemId(jobOrderId: Long, itemId: Long): JobOrderBomMaterial?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt index a9b5c5d..5c532dd 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt @@ -20,6 +20,24 @@ interface JobOrderRepository : AbstractRepository { @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 { '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 { 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 = """ diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt index 143e5f4..d4e3ab3 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt @@ -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?, val reqQty: BigDecimal?, val uom: String?, val status: String? +) + +data class JobOrderDetailPickedLotNo( + val lotNo: String?, + val qty: BigDecimal?, ) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnum.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnum.kt new file mode 100644 index 0000000..e90c8af --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnum.kt @@ -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") +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnumConverter.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnumConverter.kt new file mode 100644 index 0000000..7b2e5de --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnumConverter.kt @@ -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 { + 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 } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt index ed1294b..178a2f3 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt @@ -24,7 +24,7 @@ open class JobOrderBomMaterialService( open fun createJobOrderBomMaterialRequests(joId: Long): List { 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 } diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt index 0b99164..0aff8c8 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt @@ -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) ) } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterialRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterialRepository.kt index d0ed3f5..c6f2903 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterialRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterialRepository.kt @@ -13,4 +13,6 @@ interface BomMaterialRepository : AbstractRepository { fun findAllByBomItemIdAndDeletedIsFalse(itemId: Long): List fun findAllByBomIdAndDeletedIsFalse(bomId: Long): List + + fun findByBomIdAndItemId(bomId: Long, itemId: Long): BomMaterial? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessMaterialRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessMaterialRepository.kt index 3fbf890..0fd0cf6 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessMaterialRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessMaterialRepository.kt @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository @Repository interface BomProcessMaterialRepository : AbstractRepository { + fun findByBomProcessIdAndBomMaterialId(bomProcessId: Long, bomMaterialId: Long): BomProcessMaterial?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt index 1d3a0ef..5b48070 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt @@ -16,4 +16,6 @@ interface BomRepository : AbstractRepository { fun findByItemIdAndDeletedIsFalse(itemId: Serializable): Bom? fun findBomComboByDeletedIsFalse(): List + + fun findByCodeAndDeletedIsFalse(code: String): Bom? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt index 94bf93d..4707a3a 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt @@ -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 { fun findWarehouseComboByDeletedFalse(): List; + + fun findByIdAndDeletedIsFalse(id: Serializable): Warehouse?; + + fun findByCodeAndDeletedIsFalse(code: String): Warehouse?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt index 65eb645..d5cb103 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt @@ -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") diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt index 04d8bfc..26e5e73 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt @@ -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) } diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt index af3bfab..2f91488 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt @@ -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 { 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); + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/models/SaveWarehouseRequest.kt b/src/main/java/com/ffii/fpsms/modules/master/web/models/SaveWarehouseRequest.kt new file mode 100644 index 0000000..317ad93 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/web/models/SaveWarehouseRequest.kt @@ -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, +) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt index 689988e..42b7804 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt @@ -12,6 +12,9 @@ import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus @Repository interface InventoryLotLineRepository : AbstractRepository { + + fun findByIdAndDeletedIsFalse(id: Serializable): InventoryLotLine; + fun findInventoryLotLineInfoByInventoryLotItemIdIn(ids: List): List @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): List fun findAllByInventoryLotId(id: Serializable): List + + fun findByInventoryLotStockInLineIdAndWarehouseId(inventoryLotStockInLineId: Long, warehouseId: Long): InventoryLotLine? + fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: String): List fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: InventoryLotLineStatus): List diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockIn.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockIn.kt index b37cacc..4a65a0e 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockIn.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockIn.kt @@ -40,6 +40,10 @@ open class StockIn : BaseEntity() { @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 diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt index 566ec9f..f70d3be 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt @@ -37,6 +37,10 @@ open class StockInLine : BaseEntity() { @JoinColumn(name = "purchaseOrderLineId") open var purchaseOrderLine: PurchaseOrderLine? = null + @ManyToOne + @JoinColumn(name = "stockTakeLineId") + open var stockTakeLine: StockTakeLine? = null + @NotNull @ManyToOne @JoinColumn(name = "stockInId", nullable = false) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInRepository.kt index 7cfabfd..14e4038 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInRepository.kt @@ -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 { 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? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt index f0e5d70..a255704 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt @@ -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() { @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) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLine.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLine.kt index 9bbc03a..f60d4d5 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLine.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLine.kt @@ -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() { @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() { @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) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLineRepository.kt index 72bc64f..1a78615 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLineRepository.kt @@ -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 { + fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeLine?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRepository.kt index 0636461..4ce0b1c 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRepository.kt @@ -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 { + 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? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnum.kt b/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnum.kt new file mode 100644 index 0000000..54d8326 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnum.kt @@ -0,0 +1,7 @@ +package com.ffii.fpsms.modules.stock.enums + +enum class StockTakeStatus(val value: String) { + PENDING("pending"), + STOCKTAKING("stockTaking"), + COMPLETED("completed"), +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnumConverter.kt b/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnumConverter.kt new file mode 100644 index 0000000..dd19836 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnumConverter.kt @@ -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{ + 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 } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnum.kt b/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnum.kt new file mode 100644 index 0000000..2a8b9de --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnum.kt @@ -0,0 +1,6 @@ +package com.ffii.fpsms.modules.stock.enums + +enum class StockTakeLineStatus(val value: String) { + PENDING("pending"), + COMPLETED("completed"), +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnumConverter.kt b/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnumConverter.kt new file mode 100644 index 0000000..f12d636 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnumConverter.kt @@ -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 { + 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 } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index dd5f8a1..6303b59 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -65,6 +65,7 @@ open class StockInLineService( private val warehouseRepository: WarehouseRepository, private val itemUomRespository: ItemUomRespository, private val printerService: PrinterService, + private val stockTakeLineRepository: StockTakeLineRepository, ): AbstractBaseEntityService(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 diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInService.kt index 3fc36ca..6f25892 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInService.kt @@ -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(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) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeLineService.kt new file mode 100644 index 0000000..c73b496 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeLineService.kt @@ -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); + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt new file mode 100644 index 0000000..0836cf5 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt @@ -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"; + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeController.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeController.kt new file mode 100644 index 0000000..3aef8aa --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeController.kt @@ -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)) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt index 4575a82..172458b 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt @@ -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?, - 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? = null, + var escalationLog: SaveEscalationLogRequest? = null, var warehouseId: Long?, - var rejectQty: BigDecimal?, - var inventoryLotLines: List? + var rejectQty: BigDecimal? = null, + var inventoryLotLines: List? = null, + var stockTakeLineId: Long? = null, + var stockTakeId: Long? = null, + var stockInId: Long? = null, ) data class SaveInventoryLotLineForSil ( diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeLineRequest.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeLineRequest.kt new file mode 100644 index 0000000..24530ba --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeLineRequest.kt @@ -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?, +) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeRequest.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeRequest.kt new file mode 100644 index 0000000..6ce4798 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeRequest.kt @@ -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?, +) diff --git a/src/main/resources/db/changelog/changes/20250918_01_cyril/01_update_stock_in_line.sql b/src/main/resources/db/changelog/changes/20250918_01_cyril/01_update_stock_in_line.sql new file mode 100644 index 0000000..5dea155 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20250918_01_cyril/01_update_stock_in_line.sql @@ -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; diff --git a/src/main/resources/db/changelog/changes/20250918_01_cyril/02_update_stock_take_line.sql b/src/main/resources/db/changelog/changes/20250918_01_cyril/02_update_stock_take_line.sql new file mode 100644 index 0000000..8b43b04 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20250918_01_cyril/02_update_stock_take_line.sql @@ -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`);