diff --git a/src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt b/src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt new file mode 100644 index 0000000..cc0a6eb --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt @@ -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" + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/Items.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/Items.kt index 1518c61..8fed9e5 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/Items.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/Items.kt @@ -1,7 +1,5 @@ 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 jakarta.persistence.* import jakarta.validation.constraints.NotNull diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/Inventory.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/Inventory.kt new file mode 100644 index 0000000..33b85fd --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/Inventory.kt @@ -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(){ + @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 +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt new file mode 100644 index 0000000..b9c0571 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt @@ -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 { +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt new file mode 100644 index 0000000..5e7c0c2 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt @@ -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(jdbcDao, inventoryRepository) { + open fun allInventory(): List { + // 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 + ) + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveInventoryRequest.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveInventoryRequest.kt new file mode 100644 index 0000000..3e8809e --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveInventoryRequest.kt @@ -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 +) \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20250326_01_derek/01_create_stock_and_out.sql b/src/main/resources/db/changelog/changes/20250326_01_derek/01_create_stock_and_out.sql new file mode 100644 index 0000000..0534b48 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20250326_01_derek/01_create_stock_and_out.sql @@ -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`) +); \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20250326_01_derek/02_m18_raw_data.sql b/src/main/resources/db/changelog/changes/20250326_01_derek/02_m18_raw_data.sql new file mode 100644 index 0000000..2a6589b --- /dev/null +++ b/src/main/resources/db/changelog/changes/20250326_01_derek/02_m18_raw_data.sql @@ -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`) +); \ No newline at end of file