| @@ -0,0 +1,16 @@ | |||||
| package com.ffii.fpsms.modules.common | |||||
| import java.time.LocalDate | |||||
| import java.time.format.DateTimeFormatter | |||||
| object CodeGenerator { | |||||
| private var dateFormat = DateTimeFormatter.ofPattern("yyMMdd") | |||||
| fun generateCode(prefix: String, itemId: Long, count: Int): String { | |||||
| // prefix = "ITEM" || "LOT" | |||||
| // count = number of record from db | |||||
| val todayStr = LocalDate.now().format(dateFormat) | |||||
| val itemStr = String.format("%04d", itemId) | |||||
| val countStr = String.format("%04d", count) | |||||
| return "$prefix-$todayStr$itemStr$countStr" | |||||
| } | |||||
| } | |||||
| @@ -1,7 +1,5 @@ | |||||
| package com.ffii.fpsms.modules.master.entity | package com.ffii.fpsms.modules.master.entity | ||||
| import com.fasterxml.jackson.annotation.JsonInclude | |||||
| import com.fasterxml.jackson.annotation.JsonManagedReference | |||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| @@ -0,0 +1,39 @@ | |||||
| 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 java.time.LocalDate | |||||
| @Entity | |||||
| @Table(name = "inventory") | |||||
| open class Inventory: BaseEntity<Long>(){ | |||||
| @NotNull | |||||
| @OneToOne | |||||
| @JoinColumn(name = "itemId") | |||||
| open var item: Items? = null | |||||
| @Column(name = "stockInLineId") | |||||
| open var stockInLine: Long? = null // change this later | |||||
| @NotNull | |||||
| @Column(name = "qty") | |||||
| open var qty: Double? = null | |||||
| @NotNull | |||||
| @Column(name = "lotNo") | |||||
| open var lotNo: String? = null | |||||
| @NotNull | |||||
| @Column(name = "expiryDate") | |||||
| open var expiryDate: LocalDate? = null | |||||
| @NotNull | |||||
| @Column(name = "uomId") | |||||
| open var uomId: Long? = null | |||||
| // @NotNull | |||||
| @Column(name = "status") | |||||
| open var status: String? = 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 InventoryRepository : AbstractRepository<Inventory, Long> { | |||||
| } | |||||
| @@ -0,0 +1,99 @@ | |||||
| package com.ffii.fpsms.modules.stock.service | |||||
| import com.ffii.core.support.AbstractBaseEntityService | |||||
| import com.ffii.core.support.JdbcDao | |||||
| import com.ffii.fpsms.modules.common.CodeGenerator | |||||
| import com.ffii.fpsms.modules.master.entity.Items | |||||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||||
| import com.ffii.fpsms.modules.stock.entity.Inventory | |||||
| import com.ffii.fpsms.modules.stock.entity.InventoryRepository | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveInventoryRequest | |||||
| import org.springframework.stereotype.Service | |||||
| import java.io.IOException | |||||
| import java.time.LocalDate | |||||
| import java.time.format.DateTimeFormatter | |||||
| @Service | |||||
| open class InventoryService( | |||||
| private val jdbcDao: JdbcDao, | |||||
| private val inventoryRepository: InventoryRepository, | |||||
| private val itemsRepository: ItemsRepository, | |||||
| ): AbstractBaseEntityService<Inventory, Long, InventoryRepository>(jdbcDao, inventoryRepository) { | |||||
| open fun allInventory(): List<Inventory> { | |||||
| // TODO: Replace by actual logic | |||||
| val inventory = inventoryRepository.findAll() | |||||
| return inventory | |||||
| } | |||||
| @Throws(IOException::class) | |||||
| open fun updateInventory(request: SaveInventoryRequest): MessageResponse { | |||||
| // out need id | |||||
| // in not necessary | |||||
| val reqQty = if (request.type === "out") { | |||||
| request.qty * -1 | |||||
| } else { | |||||
| request.qty | |||||
| } | |||||
| 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 be changing | |||||
| // stock in line should be changing | |||||
| // item id should 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, | |||||
| ) | |||||
| val sql = StringBuilder("select" | |||||
| + " count(id) " | |||||
| + " from inventory i " | |||||
| + " where i.created >= :from " | |||||
| + " and i.created = :to " | |||||
| ) | |||||
| val prefix = "LOT" | |||||
| val count = jdbcDao.queryForInt(sql.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 | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| package com.ffii.fpsms.modules.stock.web.model | |||||
| import jakarta.validation.constraints.NotBlank | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import java.time.LocalDate | |||||
| data class SaveInventoryRequest( | |||||
| 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, | |||||
| @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 | |||||
| ) | |||||
| @@ -0,0 +1,75 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:create_stock_and_out | |||||
| 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', | |||||
| `lotNo` VARCHAR(255) NOT NULL, | |||||
| `itemId` INT NOT NULL, | |||||
| `stockInLineId` INT NOT NULL, | |||||
| `qty` DECIMAL(14,2) NOT NULL DEFAULT 0, | |||||
| `expiryDate` DATE NOT NULL, | |||||
| `uomId` INT(11) NOT NULL DEFAULT 0, | |||||
| `status` VARCHAR(50) NOT NULL DEFAULT 'stored', | |||||
| PRIMARY KEY (`id`), | |||||
| CONSTRAINT fk_inventory_item_id FOREIGN KEY (`itemId`) REFERENCES `items` (`id`) | |||||
| ); | |||||
| CREATE TABLE `stock_out` ( | |||||
| `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', | |||||
| `type` VARCHAR(50) NOT NULL, | |||||
| `deliveryOrderCode` VARCHAR(255) NULL, | |||||
| `pickOrderCode` VARCHAR(255) NULL, | |||||
| `consoCode` VARCHAR(255) NULL, | |||||
| `completeDate` DATETIME NULL, | |||||
| `status` VARCHAR(50) NOT NULL DEFAULT 'pending', | |||||
| `handlerId` INT(11) NOT NULL, | |||||
| `targetOutletId` INT(11) NULL, | |||||
| `remarks` VARCHAR(255) NULL, | |||||
| PRIMARY KEY (`id`) | |||||
| ); | |||||
| CREATE TABLE `stock_out_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', | |||||
| `itemId` INT(11) NOT NULL, | |||||
| `qty` DECIMAL(14,2) NOT NULL, | |||||
| `inventoryId` INT(11) NULL, | |||||
| `status` VARCHAR(30) NOT NULL DEFAULT 'pending', | |||||
| `pickTime` DATETIME NULL, | |||||
| `pickerId` INT(11) NULL, | |||||
| PRIMARY KEY (`id`), | |||||
| CONSTRAINT fk_stock_out_line_items_id FOREIGN KEY (`itemId`) REFERENCES `items` (`id`) | |||||
| ); | |||||
| CREATE TABLE `stock_ledger` ( | |||||
| `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', | |||||
| `stockInLineId` INT(11) NULL, | |||||
| `stockOutLineId` INT(11) NULL, | |||||
| `inventoryId` INT(11) NOT NULL, | |||||
| `inQty` DECIMAL(14,2) NULL, | |||||
| `outQty` DECIMAL(14,2) NULL, | |||||
| `balance` DECIMAL(14,2) NULL, | |||||
| PRIMARY KEY (`id`), | |||||
| CONSTRAINT fk_ledger_stock_out_line_id FOREIGN KEY (`stockOutLineId`) REFERENCES `stock_out_line` (`id`), | |||||
| CONSTRAINT fk_ledger_inventory_id FOREIGN KEY (`inventoryId`) REFERENCES `inventory` (`id`) | |||||
| ); | |||||
| @@ -0,0 +1,14 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset derek:m18_raw_data | |||||
| CREATE TABLE `m18_raw_data` ( | |||||
| `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', | |||||
| `data` VARCHAR(1024) NOT NULL, | |||||
| PRIMARY KEY (`id`) | |||||
| ); | |||||