@@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonManagedReference | |||
import com.ffii.core.entity.BaseEntity | |||
import com.ffii.fpsms.modules.master.entity.Items | |||
import com.ffii.fpsms.modules.master.entity.UomConversion | |||
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus | |||
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatusConverter | |||
import com.ffii.fpsms.modules.stock.entity.StockOutLine | |||
import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot | |||
import jakarta.persistence.* | |||
@@ -36,10 +38,10 @@ open class PickOrderLine : BaseEntity<Long>() { | |||
@JoinColumn(name = "uomId", nullable = false) | |||
open var uom: UomConversion? = null | |||
@Size(max = 30) | |||
@Convert(converter = PickOrderLineStatusConverter::class) | |||
@NotNull | |||
@Column(name = "status", nullable = false, length = 30) | |||
open var status: String? = null | |||
open var status: PickOrderLineStatus? = null | |||
@JsonManagedReference | |||
@OneToMany(mappedBy = "pickOrderLine", cascade = [CascadeType.ALL], orphanRemoval = true) | |||
@@ -53,4 +53,6 @@ interface PickOrderRepository : AbstractRepository<PickOrder, Long> { | |||
fun findAllByIdInAndConsoCodeIsNullAndStatus(id: List<Serializable>, status: PickOrderStatus): List<PickOrder> | |||
fun findPickOrderInfoByIdIn(id: List<Serializable>): List<PickOrderInfo> | |||
fun findAllByConsoCode(consoCode: String): List<PickOrder> | |||
} |
@@ -1,17 +1,21 @@ | |||
package com.ffii.fpsms.modules.pickOrder.service | |||
import com.ffii.core.response.RecordsRes | |||
import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrder | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||
import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderInfo | |||
import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderLineInfo | |||
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | |||
import com.ffii.fpsms.modules.pickOrder.web.models.ConsoPickOrderRequest | |||
import com.ffii.fpsms.modules.pickOrder.web.models.SearchPickOrderRequest | |||
import com.ffii.fpsms.modules.pickOrder.web.models.* | |||
import com.ffii.fpsms.modules.stock.service.StockOutLineService | |||
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService | |||
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPoRequest | |||
import org.springframework.context.annotation.Lazy | |||
import org.springframework.data.domain.PageRequest | |||
import org.springframework.stereotype.Service | |||
import java.io.Serializable | |||
import java.math.BigDecimal | |||
import java.time.LocalDate | |||
import java.time.LocalDateTime | |||
import java.time.format.DateTimeFormatter | |||
@@ -19,10 +23,12 @@ import java.time.format.DateTimeFormatter | |||
@Service | |||
open class PickOrderService( | |||
val pickOrderRepository: PickOrderRepository, | |||
val stockOutLineService: StockOutLineService, | |||
val suggestedPickLotService: SuggestedPickLotService, | |||
) { | |||
open fun LocalDateTimeParse(dateTime: String?, pattern: String? = "YYYY-MM-DD hh:mm:ss"): LocalDateTime? { | |||
open fun localDateTimeParse(dateTime: String?, pattern: String? = "YYYY-MM-DD hh:mm:ss"): LocalDateTime? { | |||
try { | |||
val formatter = DateTimeFormatter.ofPattern(pattern) | |||
val formatter = DateTimeFormatter.ofPattern(pattern!!) | |||
return LocalDateTime.parse(dateTime, formatter) | |||
} catch (e: Exception) { | |||
return null | |||
@@ -38,8 +44,8 @@ open class PickOrderService( | |||
val response = pickOrderRepository.findPickOrderInfoByConditionsAndPageable( | |||
code = request.code ?: "all", | |||
targetDateFrom = LocalDateTimeParse(request.targetDateFrom), | |||
targetDateTo = LocalDateTimeParse(request.targetDateTo), | |||
targetDateFrom = localDateTimeParse(request.targetDateFrom), | |||
targetDateTo = localDateTimeParse(request.targetDateTo), | |||
type = request.type ?: "all", | |||
status = request.status ?: "all", | |||
itemName = request.itemName ?: "all", | |||
@@ -76,26 +82,164 @@ open class PickOrderService( | |||
open fun consoPickOrders(request: ConsoPickOrderRequest): List<PickOrderInfo> { | |||
val newConsoCode = assignConsoCode() | |||
val pickOrders = pickOrderRepository.findAllByIdInAndConsoCodeIsNullAndStatus(request.ids, PickOrderStatus.PENDING) | |||
val pickOrders = pickOrderRepository.findAllByIdInAndStatus(request.ids, PickOrderStatus.PENDING) | |||
pickOrders.forEach { | |||
it.consoCode = newConsoCode | |||
} | |||
val updatedPickOrders = pickOrderRepository.saveAll(pickOrders) | |||
val updatedPickOrderInfos = updatedPickOrders.map { po -> po.id as Serializable }.let { pickOrderRepository.findPickOrderInfoByIdIn(it) } | |||
val updatedPickOrderInfos = updatedPickOrders.map { po -> po.id as Serializable } | |||
.let { pickOrderRepository.findPickOrderInfoByIdIn(it) } | |||
return updatedPickOrderInfos | |||
} | |||
open fun deconsoPickOrders(request: ConsoPickOrderRequest): List<PickOrderInfo> { | |||
val pickOrders = pickOrderRepository.findAllByIdInAndStatus(request.ids, PickOrderStatus.PENDING) | |||
val pickOrders = pickOrderRepository.findAllByIdInAndStatus(request.ids, PickOrderStatus.CONSOLIDATED) | |||
pickOrders.forEach { | |||
it.consoCode = null | |||
} | |||
val updatedPickOrders = pickOrderRepository.saveAll(pickOrders) | |||
val updatedPickOrderInfos = updatedPickOrders.map { po -> po.id as Serializable }.let { pickOrderRepository.findPickOrderInfoByIdIn(it) } | |||
val updatedPickOrderInfos = updatedPickOrders.map { po -> po.id as Serializable } | |||
.let { pickOrderRepository.findPickOrderInfoByIdIn(it) } | |||
return updatedPickOrderInfos | |||
} | |||
// TODO: Add actual pick lots | |||
open fun consoPickOrderDetail(consoCode: String): ConsoPickOrderResponse { | |||
val zero = BigDecimal.ZERO | |||
// pick orders | |||
val pos = pickOrderRepository.findAllByConsoCode(consoCode) | |||
// Suggestions for Pick Order | |||
val suggestions = suggestedPickLotService.suggestionForPickOrders(SuggestedPickLotForPoRequest(pickOrders = pos)) | |||
val suggestedList = suggestions.suggestedList | |||
// Mapping: PickOrder -> PickOrderInConso | |||
val finalPos = pos.map { po -> | |||
val pols = po.pickOrderLines | |||
// Suggestions for Pick Order Line | |||
// val suggestions = suggestedPickLotService.suggestionForPickOrderLines(pols) | |||
// Pick Order Lines | |||
// Mapping: PickOrderLine -> PickOrderLineInConso | |||
val finalPols = pols.map { pol -> | |||
val item = pol.item | |||
val uom = pol.uom | |||
// Check If already have suggestion | |||
var suggestion = pol.suggestedPickLots | |||
if (suggestion.isEmpty()) { | |||
suggestion = suggestedList.filter { it.pickOrderLine?.id == pol.id }.toMutableList() | |||
} | |||
// Mapping: SuggestedPickLot -> SuggestPickLotInConso | |||
val finalSuggestion = suggestion.map { | |||
val inventoryLotLine = it.suggestedLotLine | |||
val remainingQty = (inventoryLotLine?.inQty ?: zero) | |||
.minus(inventoryLotLine?.outQty ?: zero) | |||
.minus(inventoryLotLine?.holdQty ?: zero) | |||
val stockUom = inventoryLotLine?.stockUom?.uom | |||
// Mapping: InventoryLotLine -> InventoryLotLineInConso | |||
val finalInventoryLotLine = InventoryLotLineInConso( | |||
id = inventoryLotLine?.id, | |||
lotNo = inventoryLotLine?.inventoryLot?.lotNo, | |||
inQty = inventoryLotLine?.inQty, | |||
outQty = inventoryLotLine?.outQty, | |||
holdQty = inventoryLotLine?.holdQty, | |||
remainingQty = remainingQty, | |||
stockUom = IdCodeDesc(stockUom?.id, stockUom?.code, stockUom?.udfudesc), | |||
status = inventoryLotLine?.status?.value, | |||
remarks = inventoryLotLine?.remarks | |||
) | |||
// Return | |||
SuggestPickLotInConso( | |||
id = it.id, | |||
type = it.type?.value, | |||
qty = it.qty, | |||
inventoryLotLine = finalInventoryLotLine, | |||
pickSuggested = it.pickSuggested | |||
) | |||
} | |||
// Return | |||
PickOrderLineInConso( | |||
id = pol.id, | |||
item = IdCodeName(item?.id, item?.code, item?.name), | |||
qty = pol.qty, | |||
uom = IdCodeDesc(uom?.id, uom?.code, uom?.udfudesc), | |||
suggestPickLots = finalSuggestion, | |||
status = pol.status?.value, | |||
actualPickLots = mutableListOf() | |||
) | |||
} | |||
val releasedBy = po.releasedBy | |||
val assignTo = po.assignTo | |||
// Return | |||
PickOrderInConso( | |||
id = po.id, | |||
code = po.code, | |||
targetDate = po.targetDate, | |||
completeDate = po.completeDate, | |||
releasedDate = po.releasedDate, | |||
releasedBy = IdName(releasedBy?.id, releasedBy?.name), | |||
assignTo = IdName(assignTo?.id, assignTo?.name), | |||
pickOrderLines = finalPols, | |||
type = po.type?.value, | |||
status = po.status?.value, | |||
) | |||
} | |||
// Items | |||
val finalItems = finalPos | |||
.flatMap { it.pickOrderLines } | |||
.groupBy { it.item.id } | |||
.map { (_itemId, _line) -> | |||
val itemSuggestions = _line | |||
.flatMap { it.suggestPickLots } | |||
.groupBy { it.inventoryLotLine.id to it.pickSuggested } | |||
.map{ (_key, _lot) -> | |||
SuggestPickLotInConso( | |||
id = _key.first, | |||
type = _lot.first().type, | |||
inventoryLotLine = _lot.first().inventoryLotLine, | |||
qty = _lot.fold(zero) { sum, item -> sum + (item.qty ?: zero)}, | |||
pickSuggested = _key.second | |||
) | |||
} | |||
ItemInConso( | |||
id = _line.first().item.id, | |||
code = _line.first().item.code, | |||
name = _line.first().item.name, | |||
qty = _line.fold(zero) { sum, item -> sum + (item.qty ?: zero)}, | |||
suggestPickLots = itemSuggestions, | |||
actualPickLots = mutableListOf() | |||
) | |||
} | |||
val response = ConsoPickOrderResponse( | |||
consoCode = consoCode, | |||
pickOrders = finalPos, | |||
items = finalItems | |||
) | |||
return response | |||
} | |||
// open fun releaseConsoPickOrder(request: ReleaseConsoPickOrderRequest): MessageResponse { | |||
// val pos = pickOrderRepository.findAllByConsoCode(request.consoCode) | |||
// val suggestedPickLots = suggestedPickLotService.convertRequestsToEntities(request.suggestedPickLots) | |||
// | |||
// suggestedPickLotService.saveAll(suggestedPickLots) | |||
// // TODO: Add Response | |||
// } | |||
} |
@@ -5,6 +5,7 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||
import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderInfo | |||
import com.ffii.fpsms.modules.pickOrder.service.PickOrderService | |||
import com.ffii.fpsms.modules.pickOrder.web.models.ConsoPickOrderRequest | |||
import com.ffii.fpsms.modules.pickOrder.web.models.ConsoPickOrderResponse | |||
import com.ffii.fpsms.modules.pickOrder.web.models.SearchPickOrderRequest | |||
import jakarta.validation.Valid | |||
import org.springframework.data.domain.Page | |||
@@ -12,6 +13,7 @@ import org.springframework.data.domain.PageRequest | |||
import org.springframework.data.domain.Pageable | |||
import org.springframework.web.bind.annotation.GetMapping | |||
import org.springframework.web.bind.annotation.ModelAttribute | |||
import org.springframework.web.bind.annotation.PathVariable | |||
import org.springframework.web.bind.annotation.PostMapping | |||
import org.springframework.web.bind.annotation.RequestBody | |||
import org.springframework.web.bind.annotation.RequestMapping | |||
@@ -47,4 +49,9 @@ class PickOrderController( | |||
fun deconsoPickOrders(@Valid @RequestBody request: ConsoPickOrderRequest): List<PickOrderInfo> { | |||
return pickOrderService.deconsoPickOrders(request) | |||
} | |||
@GetMapping("/consoDetail/{consoCode}") | |||
fun consoPickOrderDetail(@PathVariable consoCode: String): ConsoPickOrderResponse { | |||
return pickOrderService.consoPickOrderDetail(consoCode); | |||
} | |||
} |
@@ -1,5 +1,14 @@ | |||
package com.ffii.fpsms.modules.pickOrder.web.models | |||
import com.ffii.fpsms.modules.stock.web.model.SaveSuggestedPickLotRequest | |||
// Consolidated / De-consolidated | |||
data class ConsoPickOrderRequest ( | |||
val ids: List<Long> | |||
) | |||
// Release Consolidated Pick Order | |||
data class ReleaseConsoPickOrderRequest ( | |||
val consoCode: String, | |||
val suggestedPickLots: List<SaveSuggestedPickLotRequest> | |||
) |
@@ -0,0 +1,89 @@ | |||
package com.ffii.fpsms.modules.pickOrder.web.models | |||
import java.math.BigDecimal | |||
import java.time.LocalDateTime | |||
// Final Response | |||
data class ConsoPickOrderResponse( | |||
val consoCode: String, | |||
val pickOrders: List<PickOrderInConso>, | |||
val items: List<ItemInConso> | |||
) | |||
// Components | |||
data class PickOrderInConso( | |||
val id: Long?, | |||
val code: String?, | |||
val targetDate: LocalDateTime?, | |||
val completeDate: LocalDateTime?, | |||
val releasedDate: LocalDateTime?, | |||
val releasedBy: IdName, | |||
val assignTo: IdName, | |||
val pickOrderLines: List<PickOrderLineInConso>, | |||
val type: String?, | |||
val status: String?, | |||
) | |||
data class PickOrderLineInConso( | |||
val id: Long?, | |||
val item: IdCodeName, | |||
val qty: BigDecimal?, | |||
val uom: IdCodeDesc, | |||
val status: String?, | |||
val suggestPickLots: List<SuggestPickLotInConso>, | |||
val actualPickLots: List<ActualPickLotInConso>, | |||
) | |||
data class SuggestPickLotInConso( | |||
val id: Long?, | |||
val type: String?, | |||
val inventoryLotLine: InventoryLotLineInConso, | |||
val qty: BigDecimal?, | |||
val pickSuggested: Boolean?, | |||
) | |||
data class ActualPickLotInConso( | |||
val id: Long, | |||
val type: String, | |||
val inventoryLotLine: InventoryLotLineInConso, | |||
val qty: BigDecimal, | |||
) | |||
data class InventoryLotLineInConso( | |||
val id: Long?, | |||
val lotNo: String?, | |||
val inQty: BigDecimal?, | |||
val outQty: BigDecimal?, | |||
val holdQty: BigDecimal?, | |||
val remainingQty: BigDecimal?, | |||
val stockUom: IdCodeDesc, | |||
val status: String?, | |||
val remarks: String?, | |||
) | |||
data class ItemInConso( | |||
val id: Long?, | |||
val code: String?, | |||
val name: String?, | |||
val qty: BigDecimal?, | |||
val suggestPickLots: List<SuggestPickLotInConso>, | |||
val actualPickLots: List<ActualPickLotInConso>, | |||
) | |||
// Common | |||
data class IdName( | |||
val id: Long?, | |||
val name: String?, | |||
) | |||
data class IdCodeName( | |||
val id: Long?, | |||
val code: String?, | |||
val name: String?, | |||
) | |||
data class IdCodeDesc( | |||
val id: Long?, | |||
val code: String?, | |||
val desc: String?, | |||
) |
@@ -1,9 +1,13 @@ | |||
package com.ffii.fpsms.modules.stock.entity | |||
import com.fasterxml.jackson.annotation.JsonBackReference | |||
import com.ffii.core.entity.BaseEntity | |||
import com.ffii.fpsms.modules.master.entity.ItemUom | |||
import com.ffii.fpsms.modules.master.entity.Warehouse | |||
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStringConverter | |||
import jakarta.persistence.Column | |||
import jakarta.persistence.Convert | |||
import jakarta.persistence.Entity | |||
import jakarta.persistence.JoinColumn | |||
import jakarta.persistence.ManyToOne | |||
@@ -16,6 +20,7 @@ import java.math.BigDecimal | |||
@Entity | |||
@Table(name = "inventory_lot_line") | |||
open class InventoryLotLine : BaseEntity<Long>() { | |||
// @JsonBackReference | |||
@NotNull | |||
@ManyToOne | |||
@JoinColumn(name = "inventoryLotId") | |||
@@ -40,8 +45,9 @@ open class InventoryLotLine : BaseEntity<Long>() { | |||
@Column(name = "holdQty") | |||
open var holdQty: BigDecimal? = null | |||
@Convert(converter = InventoryLotLineStringConverter::class) | |||
@Column(name = "status") | |||
open var status: String? = null | |||
open var status: InventoryLotLineStatus? = null | |||
@Column(name = "remarks") | |||
open var remarks: String? = null | |||
@@ -1,8 +1,11 @@ | |||
package com.ffii.fpsms.modules.stock.entity | |||
import com.ffii.core.support.AbstractRepository | |||
import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo | |||
import org.springframework.stereotype.Repository | |||
import java.io.Serializable | |||
@Repository | |||
interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | |||
fun findInventoryLotLineInfoByInventoryLotItemIdIn(ids: List<Serializable>): List<InventoryLotLineInfo> | |||
} |
@@ -69,10 +69,14 @@ open class StockInLine : BaseEntity<Long>() { | |||
@JoinColumn(name = "userId") | |||
open var user: User? = null | |||
@OneToOne | |||
@ManyToOne | |||
@JoinColumn(name = "inventoryLotId") | |||
open var inventoryLot: InventoryLot? = null | |||
@OneToOne | |||
@JoinColumn(name = "inventoryLotLineId") | |||
open var inventoryLotLine: InventoryLotLine? = null | |||
@Column(name = "lotNo") | |||
open var lotNo: String? = null | |||
@@ -1,8 +1,10 @@ | |||
package com.ffii.fpsms.modules.stock.entity | |||
import com.ffii.core.support.AbstractRepository | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine | |||
import org.springframework.stereotype.Repository | |||
@Repository | |||
interface SuggestPickLotRepository : AbstractRepository<SuggestedPickLot, Long> { | |||
fun findAllByPickOrderLineIn(lines: List<PickOrderLine>): List<SuggestedPickLot> | |||
} |
@@ -19,14 +19,14 @@ open class SuggestedPickLot: BaseEntity<Long>() { | |||
@Column(name = "type", nullable = false, length = 100) | |||
open var type: SuggestedPickLotType? = null | |||
@JsonBackReference | |||
// @JsonBackReference | |||
@ManyToOne | |||
@JoinColumn(name = "stockOutLineId") | |||
open var stockOutLine: StockOutLine? = null | |||
@JsonBackReference | |||
// @JsonBackReference | |||
@ManyToOne | |||
@JoinColumn(name = "suggestedLotLineId", nullable = false) | |||
@JoinColumn(name = "suggestedLotLineId") | |||
open var suggestedLotLine: InventoryLotLine? = null | |||
@JsonBackReference | |||
@@ -0,0 +1,6 @@ | |||
package com.ffii.fpsms.modules.stock.entity.enum | |||
enum class InventoryLotLineStatus(val value: String) { | |||
AVAILABLE ("available"), | |||
UNAVAILABLE ("unavailable") | |||
} |
@@ -0,0 +1,17 @@ | |||
package com.ffii.fpsms.modules.stock.entity.enum | |||
import jakarta.persistence.AttributeConverter | |||
import jakarta.persistence.Converter | |||
@Converter(autoApply = true) | |||
class InventoryLotLineStringConverter: AttributeConverter<InventoryLotLineStatus, String> { | |||
override fun convertToDatabaseColumn(status: InventoryLotLineStatus?): String? { | |||
return status?.value | |||
} | |||
override fun convertToEntityAttribute(value: String?): InventoryLotLineStatus? { | |||
return value?.let { v -> | |||
InventoryLotLineStatus.entries.find { it.value == v } | |||
} | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
package com.ffii.fpsms.modules.stock.entity.projection | |||
import org.springframework.beans.factory.annotation.Value | |||
import java.math.BigDecimal | |||
import java.time.LocalDate | |||
interface InventoryLotLineItemInfo { | |||
val id: Long | |||
val code: String | |||
val name: String | |||
} | |||
interface InventoryLotLineWarehouseInfo { | |||
val id: Long | |||
val code: String | |||
val name: String | |||
val description: String | |||
val capacity: BigDecimal | |||
} | |||
interface InventoryLotLineInfo { | |||
val id: Long? | |||
@get:Value("#{target.inventoryLot.item}") | |||
val item: InventoryLotLineItemInfo? | |||
val warehouse: InventoryLotLineWarehouseInfo? | |||
var inQty: BigDecimal? | |||
var outQty: BigDecimal? | |||
var holdQty: BigDecimal? | |||
@get:Value("#{target.status.value}") | |||
val status: String? | |||
val remarks: String? | |||
@get:Value("#{target.stockUom.uom.udfudesc}") | |||
val uom: String? | |||
@get:Value("#{target.inventoryLot.expiryDate}") | |||
val expiryDate: LocalDate | |||
} |
@@ -0,0 +1,64 @@ | |||
package com.ffii.fpsms.modules.stock.service | |||
import com.ffii.fpsms.modules.master.entity.ItemUomRespository | |||
import com.ffii.fpsms.modules.master.entity.WarehouseRepository | |||
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine | |||
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | |||
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo | |||
import com.ffii.fpsms.modules.stock.web.model.SaveInventoryLotLineRequest | |||
import org.springframework.stereotype.Service | |||
import org.springframework.transaction.annotation.Propagation | |||
import org.springframework.transaction.annotation.Transactional | |||
import java.math.BigDecimal | |||
import java.util.Optional | |||
import kotlin.jvm.optionals.getOrNull | |||
@Service | |||
open class InventoryLotLineService( | |||
private val inventoryLotLineRepository: InventoryLotLineRepository, | |||
private val inventoryLotRepository: InventoryLotRepository, | |||
private val warehouseRepository: WarehouseRepository, | |||
private val itemUomRespository: ItemUomRespository | |||
) { | |||
open fun findById(id: Long): Optional<InventoryLotLine> { | |||
return inventoryLotLineRepository.findById(id) | |||
} | |||
open fun allInventoryLotLinesByItemIdIn(itemIds: List<Long>): List<InventoryLotLineInfo> { | |||
return inventoryLotLineRepository.findInventoryLotLineInfoByInventoryLotItemIdIn(itemIds) | |||
} | |||
open fun saveInventoryLotLine(request: SaveInventoryLotLineRequest): InventoryLotLine { | |||
val inventoryLotLine = | |||
request.id?.let { inventoryLotLineRepository.findById(it).getOrNull() } ?: InventoryLotLine() | |||
val inventoryLot = request.inventoryLotId?.let { inventoryLotRepository.findById(it).getOrNull() } | |||
val warehouse = request.warehouseId?.let { warehouseRepository.findById(it).getOrNull() } | |||
val stockUom = request.stockUomId?.let { itemUomRespository.findById(it).getOrNull() } | |||
val remainingQty = | |||
(request.inQty ?: BigDecimal(0)) - (request.outQty ?: BigDecimal(0)) - (request.holdQty ?: BigDecimal(0)) | |||
val status = request.status?.let { _status -> InventoryLotLineStatus.entries.find { it.value == _status } } | |||
val qtyStatus = when (remainingQty > BigDecimal(0)) { | |||
true -> InventoryLotLineStatus.AVAILABLE | |||
else -> InventoryLotLineStatus.UNAVAILABLE | |||
} | |||
inventoryLotLine.apply { | |||
this.inventoryLot = inventoryLot | |||
this.warehouse = warehouse | |||
inQty = request.inQty | |||
outQty = request.outQty | |||
holdQty = request.holdQty | |||
this.stockUom = stockUom | |||
this.status = | |||
when (status == InventoryLotLineStatus.AVAILABLE && qtyStatus == InventoryLotLineStatus.AVAILABLE) { | |||
true -> InventoryLotLineStatus.AVAILABLE | |||
else -> InventoryLotLineStatus.UNAVAILABLE | |||
} | |||
remarks = request.remarks | |||
} | |||
return inventoryLotLineRepository.save(inventoryLotLine) | |||
} | |||
} |
@@ -30,6 +30,7 @@ import com.ffii.fpsms.modules.master.entity.WarehouseRepository | |||
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository | |||
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | |||
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus | |||
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
import com.ffii.fpsms.modules.stock.entity.projection.StockInLineInfo | |||
import java.io.FileNotFoundException | |||
import java.time.format.DateTimeFormatter | |||
@@ -143,7 +144,7 @@ open class StockInLineService( | |||
this.inventoryLot = stockInLine.inventoryLot | |||
this.warehouse = warehouse | |||
this.inQty = convertedBaseQty | |||
this.status = "available" | |||
this.status = InventoryLotLineStatus.AVAILABLE | |||
this.stockUom = stockItemUom | |||
} | |||
val savedInventoryLotLine = inventoryLotLineRepository.saveAndFlush(inventoryLotLine) | |||
@@ -251,6 +252,7 @@ open class StockInLineService( | |||
this.status = request.status | |||
this.expiryDate = stockInLine.expiryDate ?: request.expiryDate | |||
this.inventoryLot = stockInLine.inventoryLot ?: savedInventoryLot | |||
this.inventoryLotLine = savedInventoryLotLine | |||
this.lotNo = stockInLine.lotNo ?: savedInventoryLot?.lotNo | |||
} | |||
val savedStockInLine = saveAndFlush(stockInLine) | |||
@@ -320,6 +322,7 @@ open class StockInLineService( | |||
this.acceptedQty = request.acceptedQty | |||
this.status = request.status | |||
this.inventoryLot = savedInventoryLot ?: stockInLine.inventoryLot | |||
this.inventoryLotLine = savedInventoryLotLine | |||
this.lotNo = savedInventoryLot?.lotNo ?: stockInLine.lotNo | |||
this.expiryDate = stockInLine.expiryDate ?: request.expiryDate | |||
this.productLotNo = stockInLine.productLotNo ?: request.productLotNo | |||
@@ -0,0 +1,142 @@ | |||
package com.ffii.fpsms.modules.stock.service | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrder | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository | |||
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository | |||
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository | |||
import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot | |||
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo | |||
import com.ffii.fpsms.modules.stock.enums.SuggestedPickLotType | |||
import com.ffii.fpsms.modules.stock.web.model.SaveSuggestedPickLotRequest | |||
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPoRequest | |||
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPolRequest | |||
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotResponse | |||
import org.springframework.stereotype.Service | |||
import java.math.BigDecimal | |||
import kotlin.jvm.optionals.getOrDefault | |||
import kotlin.jvm.optionals.getOrNull | |||
@Service | |||
open class SuggestedPickLotService( | |||
val suggestedPickLotRepository: SuggestPickLotRepository, | |||
val stockOutLIneRepository: StockOutLIneRepository, | |||
val inventoryLotLineRepository: InventoryLotLineRepository, | |||
val pickOrderLineRepository: PickOrderLineRepository, | |||
val inventoryLotLineService: InventoryLotLineService, | |||
) { | |||
open fun suggestionForPickOrders(request: SuggestedPickLotForPoRequest): SuggestedPickLotResponse { | |||
val pos = request.pickOrders | |||
val suggestedList = mutableListOf<SuggestedPickLot>() | |||
var holdQtyMap = request.holdQtyMap | |||
pos.forEach { | |||
val response = suggestionForPickOrderLines(SuggestedPickLotForPolRequest( | |||
holdQtyMap = holdQtyMap, | |||
pickOrderLines = it.pickOrderLines | |||
)) | |||
holdQtyMap = response.holdQtyMap | |||
suggestedList += response.suggestedList | |||
} | |||
return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList) | |||
} | |||
open fun suggestionForPickOrderLines(request: SuggestedPickLotForPolRequest): SuggestedPickLotResponse { | |||
val pols = request.pickOrderLines | |||
val itemIds = pols.mapNotNull { it.item?.id } | |||
val zero = BigDecimal.ZERO | |||
val suggestedList: MutableList<SuggestedPickLot> = mutableListOf() | |||
val holdQtyMap: MutableMap<Long?, BigDecimal?> = request.holdQtyMap | |||
// get current inventory lot line qty & grouped by item Id | |||
val availableInventoryLotLines = inventoryLotLineService | |||
.allInventoryLotLinesByItemIdIn(itemIds) | |||
.filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero } | |||
.filter { it.status == InventoryLotLineStatus.AVAILABLE.value } | |||
.sortedBy { it.expiryDate } | |||
.groupBy { it.item?.id } | |||
// loop for suggest pick lot line | |||
pols.forEach { line -> | |||
val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() | |||
var remainingQty = line.qty ?: zero | |||
val updatedLotLines = mutableListOf<InventoryLotLineInfo>() | |||
lotLines.forEachIndexed { index, lotLine -> | |||
if (remainingQty <= zero) return@forEachIndexed | |||
val availableQty = (lotLine.inQty ?: zero) | |||
.minus(lotLine.outQty ?: zero) | |||
.minus((lotLine.holdQty ?: zero) | |||
.plus(holdQtyMap[lotLine.id] ?: zero) | |||
) | |||
if (availableQty <= zero) { | |||
updatedLotLines += lotLine | |||
return@forEachIndexed | |||
} | |||
val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } | |||
val originalHoldQty = inventoryLotLine?.holdQty | |||
// Update Qty | |||
val assignQty = minOf(availableQty, remainingQty) | |||
remainingQty = remainingQty.minus(assignQty) | |||
holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQty) | |||
// lotLine.holdQty = lotLine.holdQty?.plus(assignQty) | |||
suggestedList += SuggestedPickLot().apply { | |||
type = SuggestedPickLotType.PICK_ORDER | |||
suggestedLotLine = inventoryLotLine | |||
pickOrderLine = line | |||
qty = assignQty | |||
} | |||
} | |||
// if still have remainingQty | |||
if (remainingQty > zero) { | |||
suggestedList += SuggestedPickLot().apply { | |||
type = SuggestedPickLotType.PICK_ORDER | |||
suggestedLotLine = null | |||
pickOrderLine = line | |||
qty = remainingQty | |||
} | |||
} | |||
} | |||
return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList) | |||
} | |||
open fun convertRequestToEntity(request: SaveSuggestedPickLotRequest): SuggestedPickLot{ | |||
val suggestedPickLot = | |||
request.id?.let { id -> suggestedPickLotRepository.findById(id).getOrDefault(SuggestedPickLot()) } | |||
?: SuggestedPickLot() | |||
val type = request.type?.let { _type -> SuggestedPickLotType.entries.find { it.value == _type } } | |||
val stockOutLine = request.stockOutLineId?.let { id -> stockOutLIneRepository.findById(id).getOrNull() } | |||
val suggestedLotLine = | |||
request.suggestedLotLineId?.let { id -> inventoryLotLineRepository.findById(id).getOrNull() } | |||
val pickOrderLine = request.pickOrderLineId?.let { id -> pickOrderLineRepository.findById(id).getOrNull() } | |||
suggestedPickLot.apply { | |||
this.type = type | |||
this.stockOutLine = stockOutLine | |||
this.suggestedLotLine = suggestedLotLine | |||
this.pickOrderLine = pickOrderLine | |||
qty = request.qty | |||
} | |||
return suggestedPickLot | |||
} | |||
open fun saveSuggestedPickLot(request: SaveSuggestedPickLotRequest): SuggestedPickLot { | |||
val suggestedPickLot = convertRequestToEntity(request) | |||
return suggestedPickLotRepository.save(suggestedPickLot) | |||
} | |||
open fun saveAll(request: List<SuggestedPickLot>): List<SuggestedPickLot> { | |||
return suggestedPickLotRepository.saveAll(request) | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
package com.ffii.fpsms.modules.stock.web | |||
import com.ffii.fpsms.modules.pickOrder.web.models.ConsoPickOrderRequest | |||
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||
import jakarta.validation.Valid | |||
import org.springframework.web.bind.annotation.GetMapping | |||
import org.springframework.web.bind.annotation.PostMapping | |||
import org.springframework.web.bind.annotation.RequestBody | |||
import org.springframework.web.bind.annotation.RequestMapping | |||
import org.springframework.web.bind.annotation.RequestParam | |||
import org.springframework.web.bind.annotation.RestController | |||
@RequestMapping("/inventoryLotLine") | |||
@RestController | |||
class InventoryLotLineController ( | |||
private val inventoryLotLineRepository: InventoryLotLineRepository | |||
){ | |||
// @PostMapping("/test") | |||
// fun test(@Valid @RequestBody request: ConsoPickOrderRequest) :Any { | |||
// return inventoryLotLineRepository.findInventoryLotLineInfoByInventoryLotItemIdIn(request.ids) | |||
// } | |||
} |
@@ -0,0 +1,26 @@ | |||
package com.ffii.fpsms.modules.stock.web | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | |||
import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderLineService | |||
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService | |||
import org.springframework.web.bind.annotation.GetMapping | |||
import org.springframework.web.bind.annotation.PathVariable | |||
import org.springframework.web.bind.annotation.PostMapping | |||
import org.springframework.web.bind.annotation.RequestMapping | |||
import org.springframework.web.bind.annotation.RestController | |||
@RequestMapping("/suggestedPickLot") | |||
@RestController | |||
class SuggestedPickLotController( | |||
val suggestedPickLotService: SuggestedPickLotService, | |||
val pickOrderLineService: PurchaseOrderLineService, | |||
val pickOrderRepository: PickOrderRepository | |||
) { | |||
// @GetMapping("/test/{conso}") | |||
// fun test(@PathVariable conso: String): Any { | |||
// val test1 = pickOrderRepository.findAllByConsoCode(conso).flatMap { it.pickOrderLines } | |||
// return suggestedPickLotService.suggestionForPickOrderLines(test1) | |||
// } | |||
} |
@@ -0,0 +1,15 @@ | |||
package com.ffii.fpsms.modules.stock.web.model | |||
import java.math.BigDecimal | |||
data class SaveInventoryLotLineRequest( | |||
val id: Long?, | |||
val inventoryLotId: Long?, | |||
val warehouseId: Long?, | |||
val inQty: BigDecimal?, | |||
val outQty: BigDecimal?, | |||
val holdQty: BigDecimal?, | |||
val stockUomId: Long?, | |||
val status: String?, | |||
val remarks: String? | |||
) |
@@ -0,0 +1,12 @@ | |||
package com.ffii.fpsms.modules.stock.web.model | |||
import java.math.BigDecimal | |||
data class SaveSuggestedPickLotRequest ( | |||
val id: Long?, | |||
val type: String?, | |||
val stockOutLineId: Long?, | |||
val suggestedLotLineId: Long?, | |||
val pickOrderLineId: Long?, | |||
val qty: BigDecimal? | |||
) |
@@ -0,0 +1,17 @@ | |||
package com.ffii.fpsms.modules.stock.web.model | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrder | |||
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine | |||
import java.math.BigDecimal | |||
data class SuggestedPickLotForPoRequest( | |||
val holdQtyMap: MutableMap<Long?, BigDecimal?> = mutableMapOf(), | |||
val pickOrders: List<PickOrder> | |||
) | |||
data class SuggestedPickLotForPolRequest( | |||
val holdQtyMap: MutableMap<Long?, BigDecimal?> = mutableMapOf(), | |||
val pickOrderLines: List<PickOrderLine> | |||
) | |||
@@ -0,0 +1,16 @@ | |||
package com.ffii.fpsms.modules.stock.web.model | |||
import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot | |||
import java.math.BigDecimal | |||
//data class SuggestInventoryLotLine( | |||
// val type: String?, | |||
// val suggestedLotLine: InventoryLotLine?, | |||
// val pickOrderLine: PickOrderLine?, | |||
// val qty: BigDecimal? | |||
//) | |||
data class SuggestedPickLotResponse( | |||
val holdQtyMap: MutableMap<Long?, BigDecimal?>, | |||
val suggestedList: List<SuggestedPickLot> | |||
) |
@@ -0,0 +1,7 @@ | |||
-- liquibase formatted sql | |||
-- changeset derek:add_lot_line_id_to_stock_in_line | |||
ALTER TABLE `stock_in_line` | |||
ADD COLUMN `inventoryLotLineId` INT(11) NULL AFTER `inventoryLotId`, | |||
ADD CONSTRAINT FK_STOCK_IN_LINE_ON_LOT_LINE_ID FOREIGN KEY (inventoryLotLineId) REFERENCES inventory_lot_line (id) | |||
; |