@@ -1,6 +0,0 @@ | |||||
package com.ffii.fpsms.enums | |||||
enum class Status(val value: Int) { | |||||
PENDING(1), | |||||
COMPLETE(2) | |||||
} |
@@ -16,7 +16,7 @@ open class M18TokenService( | |||||
private val m18Config: M18Config | private val m18Config: M18Config | ||||
) { | ) { | ||||
@PostConstruct | |||||
// @Bean | |||||
fun run() { | fun run() { | ||||
// val params: MutableMap<String, String> = mutableMapOf( | // val params: MutableMap<String, String> = mutableMapOf( | ||||
// "grant_type" to m18Config.GRANT_TYPE, | // "grant_type" to m18Config.GRANT_TYPE, | ||||
@@ -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<Long>() { | |||||
// @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 | |||||
} |
@@ -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<StockLedger, Long> { | |||||
} |
@@ -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<Long>(){ | |||||
@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 | |||||
} |
@@ -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<StockOutLine, Long> { | |||||
fun findAllByStockOutIdAndDeletedFalse(stockOutId: Long): List<StockOutLine> | |||||
// fun findAllByStockOutIdAndDeletedFalse(stockOutId: Long, status: StockOutStatus): List<StockOutLine> | |||||
} |
@@ -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<Long>() { | |||||
@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 | |||||
} |
@@ -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<StockOut, Long> { | |||||
} |
@@ -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.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 | ||||
@@ -25,22 +26,28 @@ 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) | @Throws(IOException::class) | ||||
open fun updateInventory(request: SaveInventoryRequest): MessageResponse { | open fun updateInventory(request: SaveInventoryRequest): MessageResponse { | ||||
// out need id | // out need id | ||||
// in not necessary | // 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 | if (request.id !== null) { // old record | ||||
val inventory = inventoryRepository.findById(request.id).orElseThrow() | val inventory = inventoryRepository.findById(request.id).orElseThrow() | ||||
val newStatus = request.status ?: inventory.status | val newStatus = request.status ?: inventory.status | ||||
val newExpiry = request.expiryDate ?: inventory.expiryDate | 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 { | inventory.apply { | ||||
qty = inventory.qty!! + reqQty | qty = inventory.qty!! + reqQty | ||||
expiryDate = newExpiry | expiryDate = newExpiry | ||||
@@ -64,15 +71,10 @@ open class InventoryService( | |||||
val args = mapOf( | val args = mapOf( | ||||
"from" to from, | "from" to from, | ||||
"to" to to, | "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 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 newLotNo = CodeGenerator.generateCode(prefix, item.id!!, count) | ||||
val newExpiry = request.expiryDate | val newExpiry = request.expiryDate | ||||
inventory.apply { | inventory.apply { | ||||
@@ -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<StockOutLine, Long, StockOutLIneRepository>(jdbcDao, stockOutLIneRepository) { | |||||
@Throws(IOException::class) | |||||
@Transactional | |||||
open fun findAllByStockOutId(stockOutId: Long): List<StockOutLine> { | |||||
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 | |||||
) | |||||
} | |||||
} |
@@ -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<StockOut, Long, StockOutRepository>(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 | |||||
) | |||||
} | |||||
} |
@@ -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<SaveStockOutLineRequest> | |||||
) | |||||
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? | |||||
) |
@@ -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); |