From 9d65dd4e9300063e5deabfdd9ec1a4692f72ccaa Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Wed, 9 Apr 2025 10:23:34 +0800 Subject: [PATCH] update stockout + ledger --- src/main/java/com/ffii/fpsms/enums/Enums.kt | 6 - .../ffii/fpsms/m18/service/M18TokenService.kt | 2 +- .../fpsms/modules/stock/entity/StockLedger.kt | 35 +++++ .../stock/entity/StockLedgerRepository.kt | 8 ++ .../fpsms/modules/stock/entity/StockOut.kt | 41 ++++++ .../stock/entity/StockOutLIneRepository.kt | 11 ++ .../modules/stock/entity/StockOutLine.kt | 39 ++++++ .../stock/entity/StockOutRepository.kt | 8 ++ .../modules/stock/service/InventoryService.kt | 32 ++--- .../stock/service/StockOutLineService.kt | 119 +++++++++++++++++ .../modules/stock/service/StockOutService.kt | 125 ++++++++++++++++++ .../stock/web/model/SaveStockOutRequest.kt | 40 ++++++ .../01_update_stockoutline.sql | 6 + 13 files changed, 450 insertions(+), 22 deletions(-) delete mode 100644 src/main/java/com/ffii/fpsms/enums/Enums.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedger.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedgerRepository.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/entity/StockOut.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLine.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutRepository.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/service/StockOutService.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt create mode 100644 src/main/resources/db/changelog/changes/20250327_01_derek/01_update_stockoutline.sql diff --git a/src/main/java/com/ffii/fpsms/enums/Enums.kt b/src/main/java/com/ffii/fpsms/enums/Enums.kt deleted file mode 100644 index ce93a3d..0000000 --- a/src/main/java/com/ffii/fpsms/enums/Enums.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.ffii.fpsms.enums - -enum class Status(val value: Int) { - PENDING(1), - COMPLETE(2) -} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18TokenService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18TokenService.kt index 951d8ef..9b35c0d 100644 --- a/src/main/java/com/ffii/fpsms/m18/service/M18TokenService.kt +++ b/src/main/java/com/ffii/fpsms/m18/service/M18TokenService.kt @@ -16,7 +16,7 @@ open class M18TokenService( private val m18Config: M18Config ) { - @PostConstruct +// @Bean fun run() { // val params: MutableMap = mutableMapOf( // "grant_type" to m18Config.GRANT_TYPE, diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedger.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedger.kt new file mode 100644 index 0000000..a591910 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedger.kt @@ -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.JoinColumn +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import jakarta.validation.constraints.NotNull + +@Entity +@Table(name = "stock_ledger") +open class StockLedger: BaseEntity() { +// @OneToOne +// @JoinColumn(name = "stockInLineId") +// open var stockInLine: StockInLine? = null + + @OneToOne + @JoinColumn(name = "stockOutLineId") + open var stockOutLine: StockOutLine? = null + + @NotNull + @OneToOne + @JoinColumn(name = "inventoryId") + open var inventory: Inventory? = null + + @Column(name = "inQty") + open var inQty: Double? = null + + @Column(name = "outQty") + open var outQty: Double? = null + + @Column(name = "balance") + open var balance: Double? = null +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedgerRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedgerRepository.kt new file mode 100644 index 0000000..8fb12c7 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedgerRepository.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 StockLedgerRepository: AbstractRepository { +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOut.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOut.kt new file mode 100644 index 0000000..6e7d896 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOut.kt @@ -0,0 +1,41 @@ +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 java.time.LocalDateTime + +@Entity +@Table(name = "stock_out") +open class StockOut : BaseEntity(){ + @NotNull + @Column(name = "type") + open var type: String? = null + + @Column(name = "deliveryOrderCode") + open var deliveryOrderCode: String? = null + + @Column(name = "pickOrderCode") + open var pickOrderCode: String? = null + + @Column(name = "consoCode") + open var consoCode: String? = null + + @Column(name = "completeDate") + open var completeDate: LocalDateTime? = null + + @Column(name = "status") + open var status: String? = null + + @Column(name = "handlerId") + open var handler: Long? = null + + @Column(name = "targetOutletId") + open var targetOutletId: Long? = null + + @Column(name = "remarks") + open var remarks: String? = null + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt new file mode 100644 index 0000000..caa60c9 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt @@ -0,0 +1,11 @@ +package com.ffii.fpsms.modules.stock.entity + +import com.ffii.core.support.AbstractRepository +import com.ffii.fpsms.modules.stock.web.model.StockOutStatus +import org.springframework.stereotype.Repository + +@Repository +interface StockOutLIneRepository: AbstractRepository { + fun findAllByStockOutIdAndDeletedFalse(stockOutId: Long): List +// fun findAllByStockOutIdAndDeletedFalse(stockOutId: Long, status: StockOutStatus): List +} diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLine.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLine.kt new file mode 100644 index 0000000..64d7507 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLine.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.LocalDateTime + +@Entity +@Table(name = "stock_out_line") +open class StockOutLine: BaseEntity() { + @NotNull + @OneToOne + @JoinColumn(name = "itemId") + open var item: Items? = null + + @NotNull + @Column(name = "qty") + open var qty: Double? = null + + @NotNull + @JoinColumn(name = "stockOutId") + open var stockOut: StockOut? = null + + @NotNull + @OneToOne + @JoinColumn(name = "inventoryId") + open var inventory: Inventory? = null + + @NotNull + @Column(name = "status") + open var status: String? = null + + @Column(name = "pickTime") + open var pickTime: LocalDateTime? = null + + @Column(name = "pickerId") + open var pickerId: Long? = null +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutRepository.kt new file mode 100644 index 0000000..b170846 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutRepository.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 StockOutRepository: 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 index 5e7c0c2..ca42715 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt @@ -8,6 +8,7 @@ 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.service.InventoryService.SQL.INVENTORY_COUNT import com.ffii.fpsms.modules.stock.web.model.SaveInventoryRequest import org.springframework.stereotype.Service import java.io.IOException @@ -25,22 +26,28 @@ open class InventoryService( val inventory = inventoryRepository.findAll() 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 - val reqQty = if (request.type === "out") { - request.qty * -1 - } else { - request.qty - } + 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 be changing - // stock in line should be changing - // item id should be changing + // 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 @@ -64,15 +71,10 @@ open class InventoryService( 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 " + "itemId" to item.id ) val prefix = "LOT" - val count = jdbcDao.queryForInt(sql.toString(), args) + val count = jdbcDao.queryForInt(INVENTORY_COUNT.toString(), args) val newLotNo = CodeGenerator.generateCode(prefix, item.id!!, count) val newExpiry = request.expiryDate inventory.apply { diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt new file mode 100644 index 0000000..31fde15 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt @@ -0,0 +1,119 @@ +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.stock.entity.* +import com.ffii.fpsms.modules.stock.web.model.SaveStockOutLineRequest +import com.ffii.fpsms.modules.stock.web.model.SaveStockOutRequest +import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus +import com.ffii.fpsms.modules.stock.web.model.StockOutStatus +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.io.IOException +import java.time.LocalDate +import java.time.LocalDateTime + +@Service +open class StockOutLineService( + private val jdbcDao: JdbcDao, + private val stockOutRepository: StockOutRepository, + private val stockOutLIneRepository: StockOutLIneRepository, + private val itemRepository: ItemsRepository, + private val inventoryRepository: InventoryRepository, + ): AbstractBaseEntityService(jdbcDao, stockOutLIneRepository) { + @Throws(IOException::class) + @Transactional + open fun findAllByStockOutId(stockOutId: Long): List { + return stockOutLIneRepository.findAllByStockOutIdAndDeletedFalse(stockOutId) + } + @Throws(IOException::class) + @Transactional + open fun deleteById(id: Long): String { + val deleteStockOutline = stockOutLIneRepository.findById(id).orElseThrow().apply { + deleted = true + } + val updateItem = stockOutLIneRepository.save(deleteStockOutline) + val response = "mark deleted item: ${updateItem.id}" + return response + } + @Throws(IOException::class) + @Transactional + open fun deleteByStockOutId(id: Long): String { + val deleteStockOutline = stockOutLIneRepository.findAllByStockOutIdAndDeletedFalse(id).map {sol -> + sol.apply { + deleted = true + } + } + val updateItemIds = stockOutLIneRepository.saveAllAndFlush(deleteStockOutline).map { it.id } + val response = "mark deleted items: $updateItemIds" + return response + } + + @Throws(IOException::class) + @Transactional + /// only create?????? + open fun create(request: SaveStockOutLineRequest): MessageResponse { + val stockOutLine = StockOutLine() + val item = itemRepository.findById(request.itemId).orElseThrow() + stockOutLine.apply { + this.item = item + qty = request.qty + status = StockOutLineStatus.PENDING.status //create the base record = the original total sum of one item + } + val savedStockOutLine = stockOutLIneRepository.saveAndFlush(stockOutLine) + return MessageResponse( + id = savedStockOutLine.id, + code = item.code, + name = item.name, + type = savedStockOutLine.status, + message = "save success", + errorPosition = null + ) + } + // stock out -> stock out line record : {qty = 100} + // each pick create new stock out line {...sol, status = picked} + // when confirm all picked and complete in web + // update: + // 1.stock out status + // remove: + // 1.stock out line : {qty = 100} + @Throws(IOException::class) + @Transactional + open fun pick(request: SaveStockOutLineRequest): MessageResponse { + val item = itemRepository.findById(request.itemId).orElseThrow() + if (request.inventoryId === null) { + return MessageResponse( + id = request.id, + code = null, + name = item.name, + type = null, + message = "inventory is null or not exist", + errorPosition = "inventoryId" + ) + } + val stockOutLine = StockOutLine() + val _stockOut = stockOutRepository.findById(request.stockOutId).orElseThrow() + val _inventory = inventoryRepository.findById(request.inventoryId).orElseThrow() + val picker = request.pickerId + stockOutLine.apply { + this.item = item + qty = request.qty + stockOut = _stockOut + inventory = _inventory + status = "picked" + pickTime = LocalDateTime.now() + pickerId = picker + } + val savedOutLine = stockOutLIneRepository.saveAndFlush(stockOutLine) + return MessageResponse( + id = savedOutLine.id, + code = null, + name = null, + type = savedOutLine.status, + message = "item picked", + errorPosition = null + ) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutService.kt new file mode 100644 index 0000000..e3ba3e6 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutService.kt @@ -0,0 +1,125 @@ +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.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.* +import com.ffii.fpsms.modules.stock.web.model.SaveStockOutRequest +import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus +import com.ffii.fpsms.modules.stock.web.model.StockOutStatus +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.io.IOException +import java.time.LocalDate +import java.time.LocalDateTime + +@Service +open class StockOutService( + private val jdbcDao: JdbcDao, + private val stockOutRepository: StockOutRepository, + private val stockOutLIneRepository: StockOutLIneRepository, + private val stockOutLineService: StockOutLineService, + private val stockLedgerRepository: StockLedgerRepository, + ): AbstractBaseEntityService(jdbcDao, stockOutRepository) { + @Throws(IOException::class) + @Transactional + // update record data + open fun saveStockOut(request: SaveStockOutRequest): MessageResponse { + val stockOut = if (request.id !== null) stockOutRepository.findById(request.id).orElseThrow() else StockOut() + stockOut.apply { + type = request.type + deliveryOrderCode = request.deliveryOrderCode + pickOrderCode = request.pickOrderCode + consoCode = request.consoCode + completeDate = request.completeDate + handler = request.handler + targetOutletId = request.targetOutletId + remarks = request.remarks + } + val savedStockOut = stockOutRepository.saveAndFlush(stockOut) + // only create stock out line, no update + for (outLine in request.stockOutLine) { + stockOutLineService.create(outLine) + } + return MessageResponse( + id = savedStockOut.id, + code = savedStockOut.pickOrderCode ?: savedStockOut.deliveryOrderCode , + name = savedStockOut.consoCode, + type = savedStockOut.type, + message = "save success", + errorPosition = null + ) + } + @Throws(IOException::class) + @Transactional + open fun complete(request: SaveStockOutRequest): MessageResponse { + if (request.id === null) { + return MessageResponse( + id = null, + code = null, + name = null, + type = null, + message = "id cannot be null", + errorPosition = "id" + ) + } + /// checking pick quantity + val stockOut = stockOutRepository.findById(request.id).orElseThrow() + val allLines = stockOutLineService.findAllByStockOutId(request.id) + val isBalanced = allLines.all{ + val status = it.status + var sum = 0.0 + when (status) { + StockOutLineStatus.PENDING.status -> sum += it.qty ?: 0.0; + StockOutLineStatus.PICKED.status -> sum -= it.qty ?: 0.0; + } + sum == 0.0 + } + if (!isBalanced) { + return MessageResponse( + id = request.id, + code = stockOut.consoCode, + name = stockOut.pickOrderCode ?: stockOut.deliveryOrderCode, + type = stockOut.type, + message = "there are items not picked", + errorPosition = null + ) + } + // remove base record + val baseLines = allLines.filter { stockOutLine -> stockOutLine.status === StockOutLineStatus.PENDING.status} + // update pick record to complete + val pickLines = allLines + .filter { stockOutLine -> stockOutLine.status === StockOutLineStatus.PICKED.status} + .map { stockOutLine -> + stockOutLine.apply { + status = StockOutLineStatus.COMPLETE.status + } + } + // write ledger + val ledgers = pickLines.map {stockOutLine -> + StockLedger().apply { + this.stockOutLine = stockOutLine + this.inventory = stockOutLine.inventory + outQty = stockOutLine.qty + } + } + stockLedgerRepository.saveAll(ledgers) + stockOutLIneRepository.deleteAll(baseLines) + stockOutLIneRepository.saveAll(pickLines) + stockOut.apply { + completeDate = LocalDateTime.now() + status = StockOutStatus.COMPLETE.status + } + val savedStockOut = stockOutRepository.saveAndFlush(stockOut) + return MessageResponse( + id = savedStockOut.id, + code = savedStockOut.consoCode, + name = savedStockOut.pickOrderCode ?: savedStockOut.deliveryOrderCode, + type = savedStockOut.type, + message = "stock out completed", + errorPosition = null + ) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt new file mode 100644 index 0000000..2b77eb0 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt @@ -0,0 +1,40 @@ +package com.ffii.fpsms.modules.stock.web.model + +import java.time.LocalDate +import java.time.LocalDateTime + +enum class StockOutStatus(val status: String) { + PENDING("pending"), + COMPLETE("completed"), +// CANCELLED("cancelled") +} +enum class StockOutLineStatus(val status: String) { + PENDING("pending"), + PICKED("picked"), + COMPLETE("completed"), +// CANCELLED("cancelled") +} +data class SaveStockOutRequest( + val id: Long?, + val type: String, // delivery || pick || etc + val deliveryOrderCode: String?, + val pickOrderCode: String?, + val consoCode: String?, + val completeDate: LocalDateTime?, + val status: StockOutStatus?, + val handler: Long?, + val targetOutletId: Long?, + val remarks: String?, + val stockOutLine: List +) + +data class SaveStockOutLineRequest( + val id: Long?, + val itemId: Long, + val qty: Double, + val stockOutId: Long, + val inventoryId: Long?, + val status: StockOutLineStatus?, + val pickTime: LocalDateTime?, + val pickerId: Long? +) diff --git a/src/main/resources/db/changelog/changes/20250327_01_derek/01_update_stockoutline.sql b/src/main/resources/db/changelog/changes/20250327_01_derek/01_update_stockoutline.sql new file mode 100644 index 0000000..bea6dcb --- /dev/null +++ b/src/main/resources/db/changelog/changes/20250327_01_derek/01_update_stockoutline.sql @@ -0,0 +1,6 @@ +--liquibase formatted sql + +--changeset derek:m18_raw_data +ALTER TABLE `stock_out_line` +ADD COLUMN `stockOutId` INT NOT NULL AFTER `qty`, +ADD CONSTRAINT FK_STOCKOUT_LINE_STOCKOUT_ID FOREIGN KEY (stockOutId) REFERENCES stock_out (id); \ No newline at end of file