| @@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonManagedReference | |||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.master.entity.Items | import com.ffii.fpsms.modules.master.entity.Items | ||||
| import com.ffii.fpsms.modules.master.entity.UomConversion | 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.StockOutLine | ||||
| import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot | import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| @@ -36,10 +38,10 @@ open class PickOrderLine : BaseEntity<Long>() { | |||||
| @JoinColumn(name = "uomId", nullable = false) | @JoinColumn(name = "uomId", nullable = false) | ||||
| open var uom: UomConversion? = null | open var uom: UomConversion? = null | ||||
| @Size(max = 30) | |||||
| @Convert(converter = PickOrderLineStatusConverter::class) | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "status", nullable = false, length = 30) | @Column(name = "status", nullable = false, length = 30) | ||||
| open var status: String? = null | |||||
| open var status: PickOrderLineStatus? = null | |||||
| @JsonManagedReference | @JsonManagedReference | ||||
| @OneToMany(mappedBy = "pickOrderLine", cascade = [CascadeType.ALL], orphanRemoval = true) | @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 findAllByIdInAndConsoCodeIsNullAndStatus(id: List<Serializable>, status: PickOrderStatus): List<PickOrder> | ||||
| fun findPickOrderInfoByIdIn(id: List<Serializable>): List<PickOrderInfo> | fun findPickOrderInfoByIdIn(id: List<Serializable>): List<PickOrderInfo> | ||||
| fun findAllByConsoCode(consoCode: String): List<PickOrder> | |||||
| } | } | ||||
| @@ -6,12 +6,15 @@ 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.PickOrderInfo | ||||
| import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderLineInfo | import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderLineInfo | ||||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | 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.SuggestInventoryLotLineResponse | |||||
| import org.springframework.context.annotation.Lazy | import org.springframework.context.annotation.Lazy | ||||
| import org.springframework.data.domain.PageRequest | import org.springframework.data.domain.PageRequest | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.io.Serializable | import java.io.Serializable | ||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| @@ -19,10 +22,12 @@ import java.time.format.DateTimeFormatter | |||||
| @Service | @Service | ||||
| open class PickOrderService( | open class PickOrderService( | ||||
| val pickOrderRepository: PickOrderRepository, | 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 { | try { | ||||
| val formatter = DateTimeFormatter.ofPattern(pattern) | |||||
| val formatter = DateTimeFormatter.ofPattern(pattern!!) | |||||
| return LocalDateTime.parse(dateTime, formatter) | return LocalDateTime.parse(dateTime, formatter) | ||||
| } catch (e: Exception) { | } catch (e: Exception) { | ||||
| return null | return null | ||||
| @@ -76,26 +81,154 @@ open class PickOrderService( | |||||
| open fun consoPickOrders(request: ConsoPickOrderRequest): List<PickOrderInfo> { | open fun consoPickOrders(request: ConsoPickOrderRequest): List<PickOrderInfo> { | ||||
| val newConsoCode = assignConsoCode() | val newConsoCode = assignConsoCode() | ||||
| val pickOrders = pickOrderRepository.findAllByIdInAndConsoCodeIsNullAndStatus(request.ids, PickOrderStatus.PENDING) | |||||
| val pickOrders = pickOrderRepository.findAllByIdInAndStatus(request.ids, PickOrderStatus.PENDING) | |||||
| pickOrders.forEach { | pickOrders.forEach { | ||||
| it.consoCode = newConsoCode | it.consoCode = newConsoCode | ||||
| } | } | ||||
| val updatedPickOrders = pickOrderRepository.saveAll(pickOrders) | 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 | return updatedPickOrderInfos | ||||
| } | } | ||||
| open fun deconsoPickOrders(request: ConsoPickOrderRequest): List<PickOrderInfo> { | 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 { | pickOrders.forEach { | ||||
| it.consoCode = null | it.consoCode = null | ||||
| } | } | ||||
| val updatedPickOrders = pickOrderRepository.saveAll(pickOrders) | 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 | return updatedPickOrderInfos | ||||
| } | } | ||||
| open fun consoPickOrderDetail(consoCode: String): ConsoPickOrderResponse { | |||||
| // Conso code | |||||
| // Pick orders with items | |||||
| // Items | |||||
| val zero = BigDecimal.ZERO | |||||
| // pick orders | |||||
| // Mapping: PickOrder -> PickOrderInConso | |||||
| val pos = pickOrderRepository.findAllByConsoCode(consoCode) | |||||
| 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 = suggestions.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, | |||||
| 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 = _line | |||||
| .flatMap { it.suggestPickLots }, | |||||
| actualPickLots = mutableListOf() | |||||
| ) | |||||
| } | |||||
| val response = ConsoPickOrderResponse( | |||||
| consoCode = consoCode, | |||||
| pickOrders = finalPos, | |||||
| items = finalItems | |||||
| ) | |||||
| return 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.entity.projection.PickOrderInfo | ||||
| import com.ffii.fpsms.modules.pickOrder.service.PickOrderService | 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.ConsoPickOrderRequest | ||||
| import com.ffii.fpsms.modules.pickOrder.web.models.ConsoPickOrderResponse | |||||
| import com.ffii.fpsms.modules.pickOrder.web.models.SearchPickOrderRequest | import com.ffii.fpsms.modules.pickOrder.web.models.SearchPickOrderRequest | ||||
| import jakarta.validation.Valid | import jakarta.validation.Valid | ||||
| import org.springframework.data.domain.Page | 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.data.domain.Pageable | ||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||
| import org.springframework.web.bind.annotation.ModelAttribute | 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.PostMapping | ||||
| import org.springframework.web.bind.annotation.RequestBody | import org.springframework.web.bind.annotation.RequestBody | ||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||
| @@ -47,4 +49,9 @@ class PickOrderController( | |||||
| fun deconsoPickOrders(@Valid @RequestBody request: ConsoPickOrderRequest): List<PickOrderInfo> { | fun deconsoPickOrders(@Valid @RequestBody request: ConsoPickOrderRequest): List<PickOrderInfo> { | ||||
| return pickOrderService.deconsoPickOrders(request) | return pickOrderService.deconsoPickOrders(request) | ||||
| } | } | ||||
| @GetMapping("/consoDetail/{consoCode}") | |||||
| fun consoPickOrderDetail(@PathVariable consoCode: String): ConsoPickOrderResponse { | |||||
| return pickOrderService.consoPickOrderDetail(consoCode); | |||||
| } | |||||
| } | } | ||||
| @@ -12,33 +12,33 @@ data class ConsoPickOrderResponse( | |||||
| // Components | // Components | ||||
| data class PickOrderInConso( | data class PickOrderInConso( | ||||
| val id: Long, | |||||
| val code: String, | |||||
| val targetDate: LocalDateTime, | |||||
| val completeDate: LocalDateTime, | |||||
| val releasedDate: LocalDateTime, | |||||
| val releasedById: IdName, | |||||
| val id: Long?, | |||||
| val code: String?, | |||||
| val targetDate: LocalDateTime?, | |||||
| val completeDate: LocalDateTime?, | |||||
| val releasedDate: LocalDateTime?, | |||||
| val releasedBy: IdName, | |||||
| val assignTo: IdName, | val assignTo: IdName, | ||||
| val pickOrderLines: List<PickOrderLineInConso>, | val pickOrderLines: List<PickOrderLineInConso>, | ||||
| val type: String, | |||||
| val status: String, | |||||
| val type: String?, | |||||
| val status: String?, | |||||
| ) | ) | ||||
| data class PickOrderLineInConso( | data class PickOrderLineInConso( | ||||
| val id: Long, | |||||
| val item: IdName, | |||||
| val qty: BigDecimal, | |||||
| val id: Long?, | |||||
| val item: IdCodeName, | |||||
| val qty: BigDecimal?, | |||||
| val uom: IdCodeDesc, | val uom: IdCodeDesc, | ||||
| val status: String, | |||||
| val status: String?, | |||||
| val suggestPickLots: List<SuggestPickLotInConso>, | val suggestPickLots: List<SuggestPickLotInConso>, | ||||
| val actualPickLots: List<ActualPickLotInConso>, | val actualPickLots: List<ActualPickLotInConso>, | ||||
| ) | ) | ||||
| data class SuggestPickLotInConso( | data class SuggestPickLotInConso( | ||||
| val id: Long, | |||||
| val type: String, | |||||
| val id: Long?, | |||||
| val type: String?, | |||||
| val inventoryLotLine: InventoryLotLineInConso, | val inventoryLotLine: InventoryLotLineInConso, | ||||
| val qty: BigDecimal, | |||||
| val qty: BigDecimal?, | |||||
| val pickSuggested: Boolean?, | val pickSuggested: Boolean?, | ||||
| ) | ) | ||||
| @@ -50,33 +50,39 @@ data class ActualPickLotInConso( | |||||
| ) | ) | ||||
| data class InventoryLotLineInConso( | data class InventoryLotLineInConso( | ||||
| val id: Long, | |||||
| val inQty: BigDecimal, | |||||
| val outQty: BigDecimal, | |||||
| val holdQty: BigDecimal, | |||||
| val remainingQty: BigDecimal, | |||||
| val id: Long?, | |||||
| val inQty: BigDecimal?, | |||||
| val outQty: BigDecimal?, | |||||
| val holdQty: BigDecimal?, | |||||
| val remainingQty: BigDecimal?, | |||||
| val stockUom: IdCodeDesc, | val stockUom: IdCodeDesc, | ||||
| val status: String, | |||||
| val remarks: String, | |||||
| val status: String?, | |||||
| val remarks: String?, | |||||
| ) | ) | ||||
| data class ItemInConso( | data class ItemInConso( | ||||
| val id: Long, | |||||
| val code: String, | |||||
| val name: String, | |||||
| val qty: BigDecimal, | |||||
| val id: Long?, | |||||
| val code: String?, | |||||
| val name: String?, | |||||
| val qty: BigDecimal?, | |||||
| val suggestPickLots: List<SuggestPickLotInConso>, | val suggestPickLots: List<SuggestPickLotInConso>, | ||||
| val actualPickLots: List<ActualPickLotInConso>, | val actualPickLots: List<ActualPickLotInConso>, | ||||
| ) | ) | ||||
| // Common | // Common | ||||
| data class IdName( | data class IdName( | ||||
| val id: Long, | |||||
| val name: String, | |||||
| val id: Long?, | |||||
| val name: String?, | |||||
| ) | |||||
| data class IdCodeName( | |||||
| val id: Long?, | |||||
| val code: String?, | |||||
| val name: String?, | |||||
| ) | ) | ||||
| data class IdCodeDesc( | data class IdCodeDesc( | ||||
| val id: Long, | |||||
| val code: String, | |||||
| val desc: String, | |||||
| val id: Long?, | |||||
| val code: String?, | |||||
| val desc: String?, | |||||
| ) | ) | ||||
| @@ -1,9 +1,13 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | package com.ffii.fpsms.modules.stock.entity | ||||
| import com.fasterxml.jackson.annotation.JsonBackReference | |||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.master.entity.ItemUom | import com.ffii.fpsms.modules.master.entity.ItemUom | ||||
| import com.ffii.fpsms.modules.master.entity.Warehouse | 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.Column | ||||
| import jakarta.persistence.Convert | |||||
| import jakarta.persistence.Entity | import jakarta.persistence.Entity | ||||
| import jakarta.persistence.JoinColumn | import jakarta.persistence.JoinColumn | ||||
| import jakarta.persistence.ManyToOne | import jakarta.persistence.ManyToOne | ||||
| @@ -16,6 +20,7 @@ import java.math.BigDecimal | |||||
| @Entity | @Entity | ||||
| @Table(name = "inventory_lot_line") | @Table(name = "inventory_lot_line") | ||||
| open class InventoryLotLine : BaseEntity<Long>() { | open class InventoryLotLine : BaseEntity<Long>() { | ||||
| // @JsonBackReference | |||||
| @NotNull | @NotNull | ||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "inventoryLotId") | @JoinColumn(name = "inventoryLotId") | ||||
| @@ -40,8 +45,9 @@ open class InventoryLotLine : BaseEntity<Long>() { | |||||
| @Column(name = "holdQty") | @Column(name = "holdQty") | ||||
| open var holdQty: BigDecimal? = null | open var holdQty: BigDecimal? = null | ||||
| @Convert(converter = InventoryLotLineStringConverter::class) | |||||
| @Column(name = "status") | @Column(name = "status") | ||||
| open var status: String? = null | |||||
| open var status: InventoryLotLineStatus? = null | |||||
| @Column(name = "remarks") | @Column(name = "remarks") | ||||
| open var remarks: String? = null | open var remarks: String? = null | ||||
| @@ -1,8 +1,11 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | package com.ffii.fpsms.modules.stock.entity | ||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo | |||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.io.Serializable | |||||
| @Repository | @Repository | ||||
| interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | ||||
| fun findInventoryLotLineInfoByInventoryLotItemIdIn(ids: List<Serializable>): List<InventoryLotLineInfo> | |||||
| } | } | ||||
| @@ -1,8 +1,10 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | package com.ffii.fpsms.modules.stock.entity | ||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine | |||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| @Repository | @Repository | ||||
| interface SuggestPickLotRepository : AbstractRepository<SuggestedPickLot, Long> { | 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) | @Column(name = "type", nullable = false, length = 100) | ||||
| open var type: SuggestedPickLotType? = null | open var type: SuggestedPickLotType? = null | ||||
| @JsonBackReference | |||||
| // @JsonBackReference | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "stockOutLineId") | @JoinColumn(name = "stockOutLineId") | ||||
| open var stockOutLine: StockOutLine? = null | open var stockOutLine: StockOutLine? = null | ||||
| @JsonBackReference | |||||
| // @JsonBackReference | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "suggestedLotLineId", nullable = false) | |||||
| @JoinColumn(name = "suggestedLotLineId") | |||||
| open var suggestedLotLine: InventoryLotLine? = null | open var suggestedLotLine: InventoryLotLine? = null | ||||
| @JsonBackReference | @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.entity.PurchaseOrderRepository | ||||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | ||||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus | 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 com.ffii.fpsms.modules.stock.entity.projection.StockInLineInfo | ||||
| import java.io.FileNotFoundException | import java.io.FileNotFoundException | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| @@ -143,7 +144,7 @@ open class StockInLineService( | |||||
| this.inventoryLot = stockInLine.inventoryLot | this.inventoryLot = stockInLine.inventoryLot | ||||
| this.warehouse = warehouse | this.warehouse = warehouse | ||||
| this.inQty = convertedBaseQty | this.inQty = convertedBaseQty | ||||
| this.status = "available" | |||||
| this.status = InventoryLotLineStatus.AVAILABLE | |||||
| this.stockUom = stockItemUom | this.stockUom = stockItemUom | ||||
| } | } | ||||
| val savedInventoryLotLine = inventoryLotLineRepository.saveAndFlush(inventoryLotLine) | val savedInventoryLotLine = inventoryLotLineRepository.saveAndFlush(inventoryLotLine) | ||||
| @@ -0,0 +1,124 @@ | |||||
| package com.ffii.fpsms.modules.stock.service | |||||
| 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.SuggestInventoryLotLineResponse | |||||
| 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 suggestionForPickOrderLines(pickOrderlines: List<PickOrderLine>): List<SuggestedPickLot> { | |||||
| val itemIds = pickOrderlines.mapNotNull { it.item?.id } | |||||
| val zero = BigDecimal.ZERO | |||||
| val suggestionList: MutableList<SuggestedPickLot> = mutableListOf() | |||||
| // 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 | |||||
| pickOrderlines.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) | |||||
| if (availableQty <= zero) { | |||||
| updatedLotLines += lotLine | |||||
| return@forEachIndexed | |||||
| } | |||||
| val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } | |||||
| // println("1:${inventoryLotLine?.id}: ${inventoryLotLine?.holdQty}") | |||||
| val originalHoldQty = inventoryLotLine?.holdQty | |||||
| // Update Qty | |||||
| val assignQty = minOf(availableQty, remainingQty) | |||||
| remainingQty = remainingQty.minus(assignQty) | |||||
| lotLine.holdQty = lotLine.holdQty?.plus(assignQty) | |||||
| // println("2:${inventoryLotLine?.id}: ${inventoryLotLine?.holdQty}") | |||||
| // println("3:${inventoryLotLine?.id}: ${originalHoldQty}") | |||||
| suggestionList += SuggestedPickLot().apply { | |||||
| type = SuggestedPickLotType.PICK_ORDER | |||||
| suggestedLotLine = inventoryLotLine?.apply { | |||||
| holdQty = originalHoldQty | |||||
| } | |||||
| pickOrderLine = line | |||||
| qty = assignQty | |||||
| } | |||||
| // suggestionList += SuggestInventoryLotLineResponse( | |||||
| // type = SuggestedPickLotType.PICK_ORDER.value, | |||||
| // suggestedLotLine = inventoryLotLine, | |||||
| // pickOrderLine = line, | |||||
| // qty = assignQty | |||||
| // ) | |||||
| } | |||||
| // if still have remainingQty | |||||
| if (remainingQty > zero) { | |||||
| suggestionList += SuggestedPickLot().apply { | |||||
| type = SuggestedPickLotType.PICK_ORDER | |||||
| suggestedLotLine = null | |||||
| pickOrderLine = line | |||||
| qty = null | |||||
| } | |||||
| // suggestionList += SuggestInventoryLotLineResponse( | |||||
| // type = SuggestedPickLotType.PICK_ORDER.value, | |||||
| // suggestedLotLine = null, | |||||
| // pickOrderLine = line, | |||||
| // qty = null | |||||
| // ) | |||||
| } | |||||
| } | |||||
| return suggestionList | |||||
| } | |||||
| open fun saveSuggestedPickLot(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 suggestedPickLotRepository.save(suggestedPickLot) | |||||
| } | |||||
| } | |||||
| @@ -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,25 @@ | |||||
| 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.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,13 @@ | |||||
| package com.ffii.fpsms.modules.stock.web.model | |||||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine | |||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLine | |||||
| import com.ffii.fpsms.modules.stock.enums.SuggestedPickLotType | |||||
| import java.math.BigDecimal | |||||
| data class SuggestInventoryLotLineResponse( | |||||
| val type: String?, | |||||
| val suggestedLotLine: InventoryLotLine?, | |||||
| val pickOrderLine: PickOrderLine?, | |||||
| val qty: BigDecimal? | |||||
| ) | |||||