@@ -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 | |||
) { | |||
@PostConstruct | |||
// @Bean | |||
fun run() { | |||
// val params: MutableMap<String, String> = mutableMapOf( | |||
// "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.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 { | |||
@@ -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); |