| @@ -10,12 +10,12 @@ import java.time.LocalDateTime | |||||
| @Table(name = "item_uom") | @Table(name = "item_uom") | ||||
| open class ItemUom : BaseEntity<Long>() { | open class ItemUom : BaseEntity<Long>() { | ||||
| @NotNull | @NotNull | ||||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "uomId", nullable = false) | @JoinColumn(name = "uomId", nullable = false) | ||||
| open var uom: UomConversion? = null | open var uom: UomConversion? = null | ||||
| @NotNull | @NotNull | ||||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "itemId", nullable = false) | @JoinColumn(name = "itemId", nullable = false) | ||||
| open var item: Items? = null | open var item: Items? = null | ||||
| @@ -4,6 +4,7 @@ import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.fpsms.modules.master.entity.Items | import com.ffii.fpsms.modules.master.entity.Items | ||||
| import com.ffii.fpsms.m18.entity.M18DataLog | import com.ffii.fpsms.m18.entity.M18DataLog | ||||
| import com.ffii.fpsms.modules.master.entity.Currency | import com.ffii.fpsms.modules.master.entity.Currency | ||||
| import com.ffii.fpsms.modules.master.entity.UomConversion | |||||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | ||||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatusConverter | import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatusConverter | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| @@ -50,10 +51,9 @@ open class PurchaseOrderLine : BaseEntity<Long>(){ | |||||
| @JoinColumn(name = "m18DataLogId", nullable = false) | @JoinColumn(name = "m18DataLogId", nullable = false) | ||||
| open var m18DataLog: M18DataLog? = null | open var m18DataLog: M18DataLog? = null | ||||
| @Column(name = "uomId") | |||||
| open var uomId: Long? = null | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "uomId") | |||||
| // @Column(name = "uomId") | |||||
| open var uom: UomConversion? = null | |||||
| @ManyToOne(fetch = FetchType.LAZY) | |||||
| @JoinColumn(name = "currencyId") | |||||
| open var currency: Currency? = null | |||||
| } | } | ||||
| @@ -24,6 +24,7 @@ data class PoLineWithStockInLine ( | |||||
| val itemNo: String, | val itemNo: String, | ||||
| val itemName: String?, | val itemName: String?, | ||||
| val qty: BigDecimal, | val qty: BigDecimal, | ||||
| val uom: String? = null, | |||||
| val price: BigDecimal, | val price: BigDecimal, | ||||
| val status: String, | val status: String, | ||||
| @@ -53,7 +53,6 @@ open class PurchaseOrderLineService( | |||||
| this.purchaseOrder = purchaseOrder | this.purchaseOrder = purchaseOrder | ||||
| qty = request.qty | qty = request.qty | ||||
| price = request.price | price = request.price | ||||
| this.currency = currency | |||||
| this.status = status | this.status = status | ||||
| this.m18DataLog = m18DataLog ?: this.m18DataLog | this.m18DataLog = m18DataLog ?: this.m18DataLog | ||||
| } | } | ||||
| @@ -56,6 +56,7 @@ open class PurchaseOrderService( | |||||
| thisPol.itemNo!!, | thisPol.itemNo!!, | ||||
| thisPol.item!!.name, | thisPol.item!!.name, | ||||
| thisPol.qty!!, | thisPol.qty!!, | ||||
| thisPol.uom!!.code, | |||||
| thisPol.price!!, | thisPol.price!!, | ||||
| thisPol.status!!.toString(), | thisPol.status!!.toString(), | ||||
| inLine | inLine | ||||
| @@ -1,7 +1,9 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | package com.ffii.fpsms.modules.stock.entity | ||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.master.entity.Currency | |||||
| import com.ffii.fpsms.modules.master.entity.Items | import com.ffii.fpsms.modules.master.entity.Items | ||||
| import com.ffii.fpsms.modules.master.entity.UomConversion | |||||
| import com.ffii.fpsms.modules.master.entity.Warehouse | import com.ffii.fpsms.modules.master.entity.Warehouse | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| @@ -13,42 +15,42 @@ import java.time.LocalDate | |||||
| @Table(name = "inventory") | @Table(name = "inventory") | ||||
| open class Inventory: BaseEntity<Long>(){ | open class Inventory: BaseEntity<Long>(){ | ||||
| @NotNull | @NotNull | ||||
| @OneToOne | |||||
| @JoinColumn(name = "itemId") | |||||
| open var item: Items? = null | |||||
| @Column(name = "qty") | |||||
| open var qty: BigDecimal? = null | |||||
| @NotNull | |||||
| @Column(name = "price") | |||||
| open var price: BigDecimal? = null | |||||
| @Column(name = "stockInLineId") | @Column(name = "stockInLineId") | ||||
| open var stockInLine: Long? = null // change this later | open var stockInLine: Long? = null // change this later | ||||
| @ManyToOne | |||||
| @JoinColumn(name = "currencyId") | |||||
| open var currency: Currency? = null | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "qty") | |||||
| open var qty: Double? = null | |||||
| @Column(name = "cpu") // cost per unit | |||||
| open var cpu: BigDecimal? = null | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "lotNo") | |||||
| open var lotNo: String? = null | |||||
| @Column(name = "cpuUnit") | |||||
| open var cpuUnit: String? = null | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "expiryDate") | |||||
| open var expiryDate: LocalDate? = null | |||||
| @Column(name = "cpm") // cost per unit | |||||
| open var cpm: BigDecimal? = null | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "uomId") | |||||
| open var uomId: Long? = null | |||||
| @Column(name = "cpmUnit") | |||||
| open var cpmUnit: String? = null | |||||
| @NotNull | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "uomId") | |||||
| open var uomId: UomConversion? = null | |||||
| // @NotNull | // @NotNull | ||||
| @Column(name = "status") | @Column(name = "status") | ||||
| open var status: String? = null | open var status: String? = null | ||||
| @NotNull | |||||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | |||||
| @JoinColumn(name = "warehouseId", nullable = false) | |||||
| open var warehouse: Warehouse? = null | |||||
| @Column(name = "price", precision = 14, scale = 2) | |||||
| open var price: BigDecimal? = null | |||||
| @Size(max = 5) | |||||
| @Column(name = "priceUnit", length = 5) | |||||
| open var priceUnit: String? = null | |||||
| } | } | ||||
| @@ -0,0 +1,42 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.fpsms.modules.master.entity.Items | |||||
| import jakarta.persistence.* | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import jakarta.validation.constraints.Size | |||||
| import org.hibernate.annotations.JdbcTypeCode | |||||
| import org.hibernate.type.SqlTypes | |||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | |||||
| @Entity | |||||
| @Table(name = "inventory_lot") | |||||
| open class InventoryLot : BaseEntity<Long>() { | |||||
| @NotNull | |||||
| @OneToOne | |||||
| @JoinColumn(name = "itemId", nullable = false) | |||||
| open var item: Items? = null | |||||
| @NotNull | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "stockInLineId", nullable = false) | |||||
| open var stockInLine: StockInLine? = null | |||||
| @Column(name = "productionDate") | |||||
| open var productionDate: LocalDateTime? = null | |||||
| @Column(name = "stockInDate") | |||||
| open var stockInDate: LocalDateTime? = null | |||||
| @NotNull | |||||
| @Column(name = "expiryDate") | |||||
| open var expiryDate: LocalDate? = null | |||||
| @Column(name = "lotNo") | |||||
| open var lotNo: String? = null | |||||
| // @JdbcTypeCode(SqlTypes.JSON) | |||||
| // @Column(name = "qrCodeJson") | |||||
| // open var qrCodeJson: MutableMap<String, Any>? = null | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import org.hibernate.annotations.JdbcTypeCode | |||||
| import org.hibernate.type.SqlTypes | |||||
| import java.math.BigDecimal | |||||
| @Entity | |||||
| @Table(name = "inventory_lot_line") | |||||
| open class InventoryLotLine : BaseEntity<Long>() { | |||||
| @NotNull | |||||
| @Column(name = "inQty") | |||||
| open var inQty: BigDecimal? = null | |||||
| @NotNull | |||||
| @Column(name = "outQty") | |||||
| open var outQty: BigDecimal? = null | |||||
| @NotNull | |||||
| @Column(name = "holdQty") | |||||
| open var holdQty: BigDecimal? = null | |||||
| @NotNull | |||||
| @Column(name = "status") | |||||
| open var status: String? = null | |||||
| @JdbcTypeCode(SqlTypes.JSON) | |||||
| @Column(name = "qrCode") | |||||
| open var qrCode: MutableMap<String, Any?>? = null | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| import org.springframework.stereotype.Repository | |||||
| @Repository | |||||
| interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | |||||
| } | |||||
| @@ -1,25 +0,0 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import jakarta.persistence.* | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import jakarta.validation.constraints.Size | |||||
| import org.hibernate.annotations.JdbcTypeCode | |||||
| import org.hibernate.type.SqlTypes | |||||
| @Entity | |||||
| @Table(name = "inventory_lot_no") | |||||
| open class InventoryLotNo : BaseEntity<Long>() { | |||||
| @NotNull | |||||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | |||||
| @JoinColumn(name = "inventoryId", nullable = false) | |||||
| open var inventory: Inventory? = null | |||||
| @Size(max = 30) | |||||
| @Column(name = "lotNo", length = 30) | |||||
| open var lotNo: String? = null | |||||
| @JdbcTypeCode(SqlTypes.JSON) | |||||
| @Column(name = "qrCodeJson") | |||||
| open var qrCodeJson: MutableMap<String, Any>? = null | |||||
| } | |||||
| @@ -4,5 +4,5 @@ import com.ffii.core.support.AbstractRepository | |||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| @Repository | @Repository | ||||
| interface InventoryLotNoRepository : AbstractRepository<InventoryLotNo, Long> { | |||||
| interface InventoryLotRepository: AbstractRepository<InventoryLot, Long> { | |||||
| } | } | ||||
| @@ -4,5 +4,5 @@ import com.ffii.core.support.AbstractRepository | |||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| @Repository | @Repository | ||||
| interface InventoryRepository : AbstractRepository<Inventory, Long> { | |||||
| interface InventoryRepository: AbstractRepository<Inventory, Long> { | |||||
| } | } | ||||
| @@ -9,6 +9,7 @@ import jakarta.validation.constraints.NotNull | |||||
| import jakarta.validation.constraints.Size | import jakarta.validation.constraints.Size | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.time.Instant | import java.time.Instant | ||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| @Entity | @Entity | ||||
| @@ -46,20 +47,27 @@ open class StockInLine : BaseEntity<Long>() { | |||||
| @Column(name = "priceUnit", length = 5) | @Column(name = "priceUnit", length = 5) | ||||
| open var priceUnit: String? = null | open var priceUnit: String? = null | ||||
| @Column(name = "productDate") | |||||
| open var productDate: LocalDateTime? = null | |||||
| @Column(name = "receiptDate") | |||||
| open var receiptDate: LocalDateTime? = null | |||||
| @Column(name = "shelfLifeDate") | |||||
| open var shelfLifeDate: LocalDateTime? = null | |||||
| @Column(name = "productionDate") | |||||
| open var productionDate: LocalDateTime? = null | |||||
| @Column(name = "expiryDate") | |||||
| open var expiryDate: LocalDate? = null | |||||
| // @Size(max = 10) | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "status", nullable = false, length = 10) | |||||
| // @Enumerated(EnumType.STRING) | |||||
| @Column(name = "status") | |||||
| open var status: String? = null | open var status: String? = null | ||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "userId") | @JoinColumn(name = "userId") | ||||
| open var user: User? = null | open var user: User? = null | ||||
| @Column(name = "lotNo") | |||||
| open var lotNo: String? = null | |||||
| @Column(name = "productLotNo") | |||||
| open var productLotNo: String? = null | |||||
| } | } | ||||
| @@ -4,6 +4,7 @@ import com.ffii.fpsms.modules.master.entity.Items | |||||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | import com.ffii.fpsms.modules.master.entity.ItemsRepository | ||||
| import org.springframework.beans.factory.annotation.Value | import org.springframework.beans.factory.annotation.Value | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| interface StockInLineInfo { | interface StockInLineInfo { | ||||
| @@ -21,7 +22,10 @@ interface StockInLineInfo { | |||||
| val acceptedQty: BigDecimal | val acceptedQty: BigDecimal | ||||
| val price: BigDecimal? | val price: BigDecimal? | ||||
| val priceUnit: BigDecimal? | val priceUnit: BigDecimal? | ||||
| val productDate: LocalDateTime? | |||||
| val shelfLifeDate: LocalDateTime? | |||||
| val receiptDate: LocalDateTime? | |||||
| val productionDate: LocalDateTime? | |||||
| val expiryDate: LocalDate? | |||||
| val status: String | val status: String | ||||
| var lotNo: String? | |||||
| var productLotNo: String? | |||||
| } | } | ||||
| @@ -0,0 +1,46 @@ | |||||
| package com.ffii.fpsms.modules.stock.service | |||||
| import com.ffii.core.support.AbstractBaseEntityService | |||||
| import com.ffii.core.support.JdbcDao | |||||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder | |||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLot | |||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveInventoryRequest | |||||
| import org.springframework.stereotype.Service | |||||
| import org.springframework.transaction.annotation.Transactional | |||||
| import java.io.IOException | |||||
| @Service | |||||
| open class InventoryLotService( | |||||
| private val jdbcDao: JdbcDao, | |||||
| private val itemsRepository: ItemsRepository, | |||||
| private val inventoryLotRepository: InventoryLotRepository, | |||||
| ): AbstractBaseEntityService<InventoryLot, Long, InventoryLotRepository>(jdbcDao, inventoryLotRepository) { | |||||
| @Throws(IOException::class) | |||||
| @Transactional | |||||
| open fun createOrUpdate(request: SaveInventoryRequest): MessageResponse { | |||||
| val inventoryLot = if (request.id !== null) inventoryLotRepository.findById(request.id).orElseThrow() else InventoryLot() | |||||
| val thisExpiryDate = if (request.id !== null) inventoryLot.expiryDate else request.expiryDate | |||||
| val item = itemsRepository.findById(request.itemId).orElseThrow() | |||||
| inventoryLot.apply { | |||||
| this.item = item | |||||
| stockInDate = request.stockInDate | |||||
| expiryDate = thisExpiryDate | |||||
| lotNo = request.lotNo | |||||
| } | |||||
| val savedInventoryLot = saveAndFlush(inventoryLot) | |||||
| return MessageResponse( | |||||
| id = savedInventoryLot.id, | |||||
| code = savedInventoryLot.lotNo, | |||||
| name = item.name, | |||||
| type = item.type, | |||||
| message = "Inventory Lot Save Success", | |||||
| errorPosition = null, | |||||
| entity = savedInventoryLot, | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -8,7 +8,6 @@ import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | import com.ffii.fpsms.modules.master.web.models.MessageResponse | ||||
| import com.ffii.fpsms.modules.stock.entity.Inventory | import com.ffii.fpsms.modules.stock.entity.Inventory | ||||
| import com.ffii.fpsms.modules.stock.entity.InventoryRepository | import com.ffii.fpsms.modules.stock.entity.InventoryRepository | ||||
| import com.ffii.fpsms.modules.stock.service.InventoryService.SQL.INVENTORY_COUNT | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveInventoryRequest | import com.ffii.fpsms.modules.stock.web.model.SaveInventoryRequest | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.io.IOException | import java.io.IOException | ||||
| @@ -26,76 +25,67 @@ open class InventoryService( | |||||
| val inventory = inventoryRepository.findAll() | val inventory = inventoryRepository.findAll() | ||||
| return inventory | return inventory | ||||
| } | } | ||||
| object SQL { | |||||
| val INVENTORY_COUNT = StringBuilder("select" | |||||
| + " count(id) " | |||||
| + " from inventory i " | |||||
| + " where i.created >= :from " | |||||
| + " and i.created = :to " | |||||
| + " and i.itemId = :itemId" | |||||
| ) | |||||
| } | |||||
| @Throws(IOException::class) | |||||
| open fun updateInventory(request: SaveInventoryRequest): MessageResponse { | |||||
| // out need id | |||||
| // in not necessary | |||||
| var reqQty = request.qty | |||||
| if (request.type === "out") reqQty *= -1 | |||||
| if (request.id !== null) { // old record | |||||
| val inventory = inventoryRepository.findById(request.id).orElseThrow() | |||||
| val newStatus = request.status ?: inventory.status | |||||
| val newExpiry = request.expiryDate ?: inventory.expiryDate | |||||
| // uom should not be changing | |||||
| // stock in line should not be changing | |||||
| // item id should not be changing | |||||
| inventory.apply { | |||||
| qty = inventory.qty!! + reqQty | |||||
| expiryDate = newExpiry | |||||
| status = newStatus | |||||
| } | |||||
| val savedInventory = inventoryRepository.saveAndFlush(inventory) | |||||
| return MessageResponse( | |||||
| id = savedInventory.id, | |||||
| code = savedInventory.lotNo, | |||||
| name = savedInventory.item!!.name, | |||||
| type = savedInventory.status, | |||||
| message = "update success", | |||||
| errorPosition = null | |||||
| ) | |||||
| } else { // new record | |||||
| val inventory = Inventory() | |||||
| val item = itemsRepository.findById(request.itemId).orElseThrow() | |||||
| val from = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE) | |||||
| val to = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE) | |||||
| // val stockInLine = .... | |||||
| val args = mapOf( | |||||
| "from" to from, | |||||
| "to" to to, | |||||
| "itemId" to item.id | |||||
| ) | |||||
| val prefix = "LOT" | |||||
| val count = jdbcDao.queryForInt(INVENTORY_COUNT.toString(), args) | |||||
| val newLotNo = CodeGenerator.generateCode(prefix, item.id!!, count) | |||||
| val newExpiry = request.expiryDate | |||||
| inventory.apply { | |||||
| // this.stockInLine = stockInline | |||||
| this.item = item | |||||
| stockInLine = 0 | |||||
| qty = reqQty | |||||
| lotNo = newLotNo | |||||
| expiryDate = newExpiry | |||||
| uomId = 0 | |||||
| // status default "pending" in db | |||||
| } | |||||
| val savedInventory = inventoryRepository.saveAndFlush(inventory) | |||||
| return MessageResponse( | |||||
| id = savedInventory.id, | |||||
| code = savedInventory.lotNo, | |||||
| name = savedInventory.item!!.name, | |||||
| type = savedInventory.status, | |||||
| message = "save success", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| // @Throws(IOException::class) | |||||
| // open fun updateInventory(request: SaveInventoryRequest): MessageResponse { | |||||
| // // out need id | |||||
| // // in not necessary | |||||
| // var reqQty = request.qty | |||||
| // if (request.type === "out") reqQty *= -1 | |||||
| // if (request.id !== null) { // old record | |||||
| // val inventory = inventoryRepository.findById(request.id).orElseThrow() | |||||
| // val newStatus = request.status ?: inventory.status | |||||
| // val newExpiry = request.expiryDate ?: inventory.expiryDate | |||||
| // // uom should not be changing | |||||
| // // stock in line should not be changing | |||||
| // // item id should not be changing | |||||
| // inventory.apply { | |||||
| // qty = inventory.qty!! + reqQty | |||||
| // expiryDate = newExpiry | |||||
| // status = newStatus | |||||
| // } | |||||
| // val savedInventory = inventoryRepository.saveAndFlush(inventory) | |||||
| // return MessageResponse( | |||||
| // id = savedInventory.id, | |||||
| // code = savedInventory.lotNo, | |||||
| // name = "savedInventory.item!!.name", | |||||
| // type = savedInventory.status, | |||||
| // message = "update success", | |||||
| // errorPosition = null | |||||
| // ) | |||||
| // } else { // new record | |||||
| // val inventory = Inventory() | |||||
| // val item = itemsRepository.findById(request.itemId).orElseThrow() | |||||
| // val from = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE) | |||||
| // val to = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE) | |||||
| //// val stockInLine = .... | |||||
| // val args = mapOf( | |||||
| // "from" to from, | |||||
| // "to" to to, | |||||
| // "itemId" to item.id | |||||
| // ) | |||||
| // val prefix = "LOT" | |||||
| // val count = jdbcDao.queryForInt(INVENTORY_COUNT.toString(), args) | |||||
| // val newLotNo = CodeGenerator.generateCode(prefix, item.id!!, count) | |||||
| // val newExpiry = request.expiryDate | |||||
| // inventory.apply { | |||||
| //// this.stockInLine = stockInline | |||||
| //// this.item = item | |||||
| // stockInLine = 0 | |||||
| // qty = reqQty | |||||
| // lotNo = newLotNo | |||||
| // expiryDate = newExpiry | |||||
| // uomId = 0 | |||||
| // // status default "pending" in db | |||||
| // } | |||||
| // val savedInventory = inventoryRepository.saveAndFlush(inventory) | |||||
| // return MessageResponse( | |||||
| // id = savedInventory.id, | |||||
| // code = savedInventory.lotNo, | |||||
| // name = "savedInventory.item!!.name", | |||||
| // type = savedInventory.status, | |||||
| // message = "save success", | |||||
| // errorPosition = null | |||||
| // ) | |||||
| // } | |||||
| // } | |||||
| } | } | ||||
| @@ -2,6 +2,7 @@ package com.ffii.fpsms.modules.stock.service | |||||
| import com.ffii.core.support.AbstractBaseEntityService | import com.ffii.core.support.AbstractBaseEntityService | ||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.fpsms.modules.common.CodeGenerator | |||||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | import com.ffii.fpsms.modules.master.entity.ItemsRepository | ||||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | import com.ffii.fpsms.modules.master.web.models.MessageResponse | ||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository | import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository | ||||
| @@ -9,10 +10,14 @@ import com.ffii.fpsms.modules.stock.entity.* | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest | import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest | ||||
| import com.ffii.fpsms.modules.stock.web.model.SaveStockInRequest | import com.ffii.fpsms.modules.stock.web.model.SaveStockInRequest | ||||
| import com.ffii.fpsms.modules.stock.web.model.StockInLineStatus | import com.ffii.fpsms.modules.stock.web.model.StockInLineStatus | ||||
| import com.ffii.fpsms.modules.stock.sql.StockSql.SQL.INVENTORY_COUNT | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveInventoryRequest | |||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||
| import java.io.IOException | import java.io.IOException | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | |||||
| @Service | @Service | ||||
| open class StockInLineService( | open class StockInLineService( | ||||
| @@ -21,7 +26,9 @@ open class StockInLineService( | |||||
| private val stockInService: StockInService, | private val stockInService: StockInService, | ||||
| private val stockInRepository: StockInRepository, | private val stockInRepository: StockInRepository, | ||||
| private val stockInLineRepository: StockInLineRepository, | private val stockInLineRepository: StockInLineRepository, | ||||
| private val inventoryLotRepository: InventoryLotRepository, | |||||
| private val itemRepository: ItemsRepository, | private val itemRepository: ItemsRepository, | ||||
| private val inventoryLotService: InventoryLotService, | |||||
| ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | ||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| @@ -53,6 +60,25 @@ open class StockInLineService( | |||||
| ) | ) | ||||
| } | } | ||||
| @Transactional | |||||
| fun saveInventoryLotWhenStockIn(request: SaveStockInLineRequest, stockInLine: StockInLine): InventoryLot { | |||||
| val inventoryLot = InventoryLot() | |||||
| val args = mapOf( | |||||
| "from" to LocalDate.now().atStartOfDay(), | |||||
| "to" to LocalDateTime.now(), | |||||
| "itemId" to stockInLine.item!!.id | |||||
| ) | |||||
| val inventoryCount = jdbcDao.queryForInt(INVENTORY_COUNT.toString(), args) | |||||
| val newLotNo = CodeGenerator.generateCode(prefix = "POLOT", itemId = stockInLine.item!!.id!!, count = inventoryCount) | |||||
| inventoryLot.apply { | |||||
| this.item = stockInLine.item | |||||
| this.stockInLine = stockInLine | |||||
| stockInDate = LocalDateTime.now() | |||||
| expiryDate = request.expiryDate// frontend form input | |||||
| lotNo = newLotNo | |||||
| } | |||||
| return inventoryLotRepository.saveAndFlush(inventoryLot) | |||||
| } | |||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| @Transactional | @Transactional | ||||
| open fun update(request: SaveStockInLineRequest): MessageResponse { | open fun update(request: SaveStockInLineRequest): MessageResponse { | ||||
| @@ -66,15 +92,29 @@ open class StockInLineService( | |||||
| errorPosition = null, | errorPosition = null, | ||||
| ) | ) | ||||
| // return list of stock in line, update data grid with the list | // return list of stock in line, update data grid with the list | ||||
| println(request.acceptedQty) | |||||
| println(stockInLine.acceptedQty) | |||||
| println(request.acceptedQty == stockInLine.acceptedQty) | |||||
| println(request.acceptedQty.equals(stockInLine.acceptedQty)) | |||||
| println(request.acceptedQty.compareTo(stockInLine.acceptedQty) == 0) | |||||
| if (request.acceptedQty.compareTo(stockInLine.acceptedQty) == 0) { | if (request.acceptedQty.compareTo(stockInLine.acceptedQty) == 0) { | ||||
| var savedInventoryLot: InventoryLot? = null | |||||
| if (request.status == StockInLineStatus.RECEIVED.status) { | |||||
| if (request.expiryDate == null) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| code = null, | |||||
| name = null, | |||||
| type = "Found Null", | |||||
| message = "missing expiry", | |||||
| errorPosition = "expiryDate", | |||||
| ) | |||||
| } | |||||
| savedInventoryLot = saveInventoryLotWhenStockIn(request = request, stockInLine = stockInLine) | |||||
| } | |||||
| stockInLine.apply { | stockInLine.apply { | ||||
| // user = null | // user = null | ||||
| productionDate = request.productionDate?.atStartOfDay() // maybe need to change the request to LocalDateTime | |||||
| productLotNo = request.productLotNo | |||||
| receiptDate = request.receiptDate?.atStartOfDay() | |||||
| status = request.status | status = request.status | ||||
| expiryDate = request.expiryDate | |||||
| lotNo = savedInventoryLot?.lotNo | |||||
| } | } | ||||
| val savedStockInLine = saveAndFlush(stockInLine) | val savedStockInLine = saveAndFlush(stockInLine) | ||||
| val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedStockInLine.id!!) | val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedStockInLine.id!!) | ||||
| @@ -108,19 +148,40 @@ open class StockInLineService( | |||||
| acceptedQty = stockInLine.acceptedQty!!.minus(request.acceptedQty) | acceptedQty = stockInLine.acceptedQty!!.minus(request.acceptedQty) | ||||
| price = stockInLine.price | price = stockInLine.price | ||||
| priceUnit = stockInLine.priceUnit | priceUnit = stockInLine.priceUnit | ||||
| productDate = stockInLine.productDate | |||||
| shelfLifeDate = stockInLine.shelfLifeDate | |||||
| productionDate = stockInLine.productionDate | |||||
| expiryDate = stockInLine.expiryDate | |||||
| status = stockInLine.status // this does update status | status = stockInLine.status // this does update status | ||||
| user = stockInLine.user | user = stockInLine.user | ||||
| } | } | ||||
| var savedInventoryLot: InventoryLot? = null | |||||
| if (request.status == StockInLineStatus.RECEIVED.status) { | |||||
| if (request.expiryDate == null) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| code = null, | |||||
| name = null, | |||||
| type = "Found Null", | |||||
| message = "missing expiry", | |||||
| errorPosition = "expiryDate", | |||||
| ) | |||||
| } | |||||
| savedInventoryLot = saveInventoryLotWhenStockIn(request = request, stockInLine = stockInLine) | |||||
| } | |||||
| stockInLine.apply { | stockInLine.apply { | ||||
| receiptDate = request.receiptDate?.atStartOfDay() | |||||
| productionDate = request.productionDate?.atStartOfDay() | |||||
| acceptedQty = request.acceptedQty | acceptedQty = request.acceptedQty | ||||
| status = request.status | status = request.status | ||||
| lotNo = savedInventoryLot?.lotNo | |||||
| expiryDate = request.expiryDate | |||||
| productLotNo = request.productLotNo | |||||
| } | } | ||||
| val stockInLineEntries = listOf(stockInLine, newStockInLine) | val stockInLineEntries = listOf(stockInLine, newStockInLine) | ||||
| val savedEntries = stockInLineRepository.saveAllAndFlush(stockInLineEntries) | val savedEntries = stockInLineRepository.saveAllAndFlush(stockInLineEntries) | ||||
| val ids = savedEntries.map { it.id!! } | val ids = savedEntries.map { it.id!! } | ||||
| val lineInfos = stockInLineRepository.findStockInLineInfoByIdInAndDeletedFalse(ids) | |||||
| val lineInfoList = stockInLineRepository.findStockInLineInfoByIdInAndDeletedFalse(ids) | |||||
| return MessageResponse( | return MessageResponse( | ||||
| id = stockInLine.id, | id = stockInLine.id, | ||||
| code = null, | code = null, | ||||
| @@ -128,7 +189,7 @@ open class StockInLineService( | |||||
| type = "Save success", | type = "Save success", | ||||
| message = "created 2 stock in line", | message = "created 2 stock in line", | ||||
| errorPosition = null, | errorPosition = null, | ||||
| entity = lineInfos | |||||
| entity = lineInfoList | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,13 @@ | |||||
| package com.ffii.fpsms.modules.stock.sql | |||||
| open class StockSql { | |||||
| object SQL { | |||||
| val INVENTORY_COUNT = StringBuilder("select" | |||||
| + " count(id) " | |||||
| + " from inventory_lot i " | |||||
| + " where i.stockInDate <= :from " | |||||
| + " and i.stockInDate < :to " | |||||
| + " and i.itemId = :itemId" | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -3,19 +3,12 @@ package com.ffii.fpsms.modules.stock.web.model | |||||
| import jakarta.validation.constraints.NotBlank | import jakarta.validation.constraints.NotBlank | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.LocalDateTime | |||||
| data class SaveInventoryRequest( | data class SaveInventoryRequest( | ||||
| val id: Long?, | val id: Long?, | ||||
| val stockInLineId: Long?, | |||||
| val status: String?, | |||||
| val uomId: Long?, | |||||
| val expiryDate: LocalDate?, | |||||
| @field:NotNull(message = "itemId cannot be null") | |||||
| val itemId: Long, | val itemId: Long, | ||||
| @field:NotNull(message = "qty cannot be null") | |||||
| val qty: Double, | |||||
| // FOR POSTING STOCK IN AND STOCK OUT | |||||
| @field:NotBlank(message = "type cannot be empty") | |||||
| val type: String, // E.G. "in", "out", "disable": for disable lot | |||||
| val stockInDate: LocalDateTime?, | |||||
| val expiryDate: LocalDate?, | |||||
| val lotNo: String?, | |||||
| ) | ) | ||||
| @@ -17,6 +17,7 @@ enum class StockInLineStatus(val status: String) { | |||||
| SecondDetermine("determine2"), | SecondDetermine("determine2"), | ||||
| ThirdDetermine("determine3"), | ThirdDetermine("determine3"), | ||||
| RECEIVING("receiving"), | RECEIVING("receiving"), | ||||
| RECEIVED("received"), | |||||
| COMPLETE("completed"); | COMPLETE("completed"); | ||||
| } | } | ||||
| data class SaveStockInRequest( | data class SaveStockInRequest( | ||||
| @@ -38,5 +39,10 @@ data class SaveStockInLineRequest( | |||||
| val purchaseOrderLineId: Long, | val purchaseOrderLineId: Long, | ||||
| val itemId: Long, | val itemId: Long, | ||||
| val acceptedQty: BigDecimal, | val acceptedQty: BigDecimal, | ||||
| val acceptedWeight: BigDecimal?, | |||||
| val status: String?, | val status: String?, | ||||
| val expiryDate: LocalDate?, | |||||
| val productLotNo: String?, | |||||
| val receiptDate: LocalDate?, | |||||
| val productionDate: LocalDate?, | |||||
| ) | ) | ||||
| @@ -0,0 +1,68 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:create inventory related | |||||
| ALTER TABLE `stock_ledger` | |||||
| DROP CONSTRAINT `fk_ledger_inventory_id`; | |||||
| DROP TABLE `inventory_lot_no`; | |||||
| DROP TABLE `inventory`; | |||||
| CREATE TABLE `inventory` | |||||
| ( | |||||
| `id` INT NOT NULL AUTO_INCREMENT, | |||||
| `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `createdBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `version` INT NOT NULL DEFAULT '0', | |||||
| `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `deleted` TINYINT(1) NOT NULL DEFAULT '0', | |||||
| `qty` DECIMAL(14, 2) NOT NULL, | |||||
| `price` VARCHAR(30) NOT NULL, | |||||
| `currencyId` INT(11) NOT NULL, | |||||
| `cpu` DECIMAL(14, 2) NOT NULL, | |||||
| `cpuUnit` VARCHAR(255) NOT NULL, | |||||
| `cpm` DECIMAL(14, 2) NOT NULL, | |||||
| `cpmUnit` VARCHAR(255) NOT NULL, | |||||
| `uomId` INT(11) NOT NULL, | |||||
| `status` VARCHAR(255) NOT NULL, | |||||
| CONSTRAINT pk_inventory PRIMARY KEY (id), | |||||
| CONSTRAINT FK_INVENTORY_TO_CURRENCY_ON_CURRENCY_ID FOREIGN KEY (`currencyId`) REFERENCES `currency` (`id`), | |||||
| CONSTRAINT FK_INVENTORY_TO_UOM_ON_UOM_ID FOREIGN KEY (`uomId`) REFERENCES `uom_conversion` (`id`) | |||||
| ); | |||||
| CREATE TABLE `inventory_lot` | |||||
| ( | |||||
| `id` INT NOT NULL AUTO_INCREMENT, | |||||
| `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `createdBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `version` INT NOT NULL DEFAULT '0', | |||||
| `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `deleted` TINYINT(1) NOT NULL DEFAULT '0', | |||||
| `itemId` INT(11) NOT NULL, | |||||
| `stockInLineId` INT(30) NOT NULL, | |||||
| `productionDate` DATETIME NULL, | |||||
| `stockInDate` DATETIME NULL, | |||||
| `expiryDate` DATE NOT NULL, | |||||
| `lotNo` VARCHAR(512) NOT NULL, | |||||
| CONSTRAINT pk_inventory_lot PRIMARY KEY (id), | |||||
| CONSTRAINT FK_INVENTORY_LOT_TO_ITEMS_ON_ITEM_ID FOREIGN KEY (`itemId`) REFERENCES `items` (`id`), | |||||
| CONSTRAINT FK_INVENTORY_LOT_TO_STOCK_IN_LINE_ON_STOCKINLINE_ID FOREIGN KEY (`stockInLineId`) REFERENCES `stock_in_line` (`id`) | |||||
| ); | |||||
| CREATE TABLE `inventory_lot_line` | |||||
| ( | |||||
| `id` INT NOT NULL AUTO_INCREMENT, | |||||
| `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `createdBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `version` INT NOT NULL DEFAULT '0', | |||||
| `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `deleted` TINYINT(1) NOT NULL DEFAULT '0', | |||||
| `inQty` DECIMAL(14, 2) NOT NULL, | |||||
| `outQty` DECIMAL(14, 2) NOT NULL, | |||||
| `holdQty` DECIMAL(14, 2) NOT NULL, | |||||
| `status` VARCHAR(255) NOT NULL DEFAULT 'AVAILABLE', | |||||
| `remarks` VARCHAR(1024) NULL, | |||||
| `qrCode` JSON NOT NULL, | |||||
| CONSTRAINT pk_inventory_lot PRIMARY KEY (id) | |||||
| ); | |||||
| ALTER TABLE `stock_ledger` | |||||
| ADD CONSTRAINT `fk_ledger_inventory_id` FOREIGN KEY (inventoryId) REFERENCES `inventory` (`id`); | |||||
| @@ -0,0 +1,5 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:update inventory data type | |||||
| ALTER TABLE `inventory` | |||||
| MODIFY COLUMN `price` INT NOT NULL; | |||||
| @@ -0,0 +1,7 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:update inventory lot line data type | |||||
| ALTER TABLE `inventory_lot_line` | |||||
| MODIFY COLUMN `inQty` DECIMAL(14, 2) NOT NULL DEFAULT 0, | |||||
| MODIFY COLUMN `outQty` DECIMAL(14, 2) NOT NULL DEFAULT 0, | |||||
| MODIFY COLUMN `holdQty` DECIMAL(14, 2) NOT NULL DEFAULT 0; | |||||
| @@ -0,0 +1,5 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:add lotNo in stock in line | |||||
| ALTER TABLE `stock_in_line` | |||||
| ADD COLUMN `lotNo` VARCHAR(512) NULL AFTER `userId`; | |||||
| @@ -0,0 +1,5 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:add receipt date in stock in line | |||||
| ALTER TABLE `stock_in_line` | |||||
| ADD COLUMN `receiptDate` DATETIME NULL AFTER `priceUnit`; | |||||
| @@ -0,0 +1,5 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:add receipt date in stock in line | |||||
| ALTER TABLE `stock_in_line` | |||||
| CHANGE COLUMN `shelfLifeDate` `expiryDate` DATE NULL; | |||||
| @@ -0,0 +1,5 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:add product lotno in stock in line | |||||
| ALTER TABLE `stock_in_line` | |||||
| ADD COLUMN `productLotNo` VARCHAR(255) NULL; | |||||
| @@ -0,0 +1,5 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:RENAME PRODUCT DATE in stock in line | |||||
| ALTER TABLE `stock_in_line` | |||||
| CHANGE COLUMN `productDate` `productionDate` DATETIME NULL; | |||||