jason.lam 2 个月前
父节点
当前提交
e7de2fd6c6
共有 24 个文件被更改,包括 670 次插入18 次删除
  1. +4
    -2
      src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderLine.kt
  2. +2
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderRepository.kt
  3. +154
    -10
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  4. +7
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt
  5. +9
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/ConsoPickOrderRequest.kt
  6. +89
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/ConsoPickOrderResponse.kt
  7. +7
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLine.kt
  8. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  9. +5
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt
  10. +2
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/SuggestPickLotRepository.kt
  11. +3
    -3
      src/main/java/com/ffii/fpsms/modules/stock/entity/SuggestedPickLot.kt
  12. +6
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/enum/InventoryLotLineEnum.kt
  13. +17
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/enum/InventoryLotLineEnumConverter.kt
  14. +36
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryLotLineInfo.kt
  15. +64
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt
  16. +4
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  17. +142
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt
  18. +23
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt
  19. +26
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/SuggestedPickLotController.kt
  20. +15
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveInventoryLotLineRequest.kt
  21. +12
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveSuggestedPickLotRequest.kt
  22. +17
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SuggestedPickLotRequest.kt
  23. +16
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SuggestedPickLotResponse.kt
  24. +7
    -0
      src/main/resources/db/changelog/changes/20250620_01_derek/01_add_lot_line_id_to_stock_in_line.sql

+ 4
- 2
src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderLine.kt 查看文件

@@ -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)


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderRepository.kt 查看文件

@@ -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>
} }

+ 154
- 10
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt 查看文件

@@ -1,17 +1,21 @@
package com.ffii.fpsms.modules.pickOrder.service package com.ffii.fpsms.modules.pickOrder.service


import com.ffii.core.response.RecordsRes 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.PickOrder
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository 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.SuggestedPickLotForPoRequest
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 +23,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
@@ -38,8 +44,8 @@ open class PickOrderService(


val response = pickOrderRepository.findPickOrderInfoByConditionsAndPageable( val response = pickOrderRepository.findPickOrderInfoByConditionsAndPageable(
code = request.code ?: "all", code = request.code ?: "all",
targetDateFrom = LocalDateTimeParse(request.targetDateFrom),
targetDateTo = LocalDateTimeParse(request.targetDateTo),
targetDateFrom = localDateTimeParse(request.targetDateFrom),
targetDateTo = localDateTimeParse(request.targetDateTo),
type = request.type ?: "all", type = request.type ?: "all",
status = request.status ?: "all", status = request.status ?: "all",
itemName = request.itemName ?: "all", itemName = request.itemName ?: "all",
@@ -76,26 +82,164 @@ 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
} }

// 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
// }
} }

+ 7
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt 查看文件

@@ -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);
}
} }

+ 9
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/ConsoPickOrderRequest.kt 查看文件

@@ -1,5 +1,14 @@
package com.ffii.fpsms.modules.pickOrder.web.models package com.ffii.fpsms.modules.pickOrder.web.models


import com.ffii.fpsms.modules.stock.web.model.SaveSuggestedPickLotRequest

// Consolidated / De-consolidated
data class ConsoPickOrderRequest ( data class ConsoPickOrderRequest (
val ids: List<Long> val ids: List<Long>
)

// Release Consolidated Pick Order
data class ReleaseConsoPickOrderRequest (
val consoCode: String,
val suggestedPickLots: List<SaveSuggestedPickLotRequest>
) )

+ 89
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/ConsoPickOrderResponse.kt 查看文件

@@ -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?,
)

+ 7
- 1
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLine.kt 查看文件

@@ -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


+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt 查看文件

@@ -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>
} }

+ 5
- 1
src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt 查看文件

@@ -69,10 +69,14 @@ open class StockInLine : BaseEntity<Long>() {
@JoinColumn(name = "userId") @JoinColumn(name = "userId")
open var user: User? = null open var user: User? = null


@OneToOne
@ManyToOne
@JoinColumn(name = "inventoryLotId") @JoinColumn(name = "inventoryLotId")
open var inventoryLot: InventoryLot? = null open var inventoryLot: InventoryLot? = null


@OneToOne
@JoinColumn(name = "inventoryLotLineId")
open var inventoryLotLine: InventoryLotLine? = null

@Column(name = "lotNo") @Column(name = "lotNo")
open var lotNo: String? = null open var lotNo: String? = null




+ 2
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/SuggestPickLotRepository.kt 查看文件

@@ -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>
} }

+ 3
- 3
src/main/java/com/ffii/fpsms/modules/stock/entity/SuggestedPickLot.kt 查看文件

@@ -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


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/enum/InventoryLotLineEnum.kt 查看文件

@@ -0,0 +1,6 @@
package com.ffii.fpsms.modules.stock.entity.enum

enum class InventoryLotLineStatus(val value: String) {
AVAILABLE ("available"),
UNAVAILABLE ("unavailable")
}

+ 17
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/enum/InventoryLotLineEnumConverter.kt 查看文件

@@ -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 }
}
}
}

+ 36
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryLotLineInfo.kt 查看文件

@@ -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
}

+ 64
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt 查看文件

@@ -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)
}
}

+ 4
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt 查看文件

@@ -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)
@@ -251,6 +252,7 @@ open class StockInLineService(
this.status = request.status this.status = request.status
this.expiryDate = stockInLine.expiryDate ?: request.expiryDate this.expiryDate = stockInLine.expiryDate ?: request.expiryDate
this.inventoryLot = stockInLine.inventoryLot ?: savedInventoryLot this.inventoryLot = stockInLine.inventoryLot ?: savedInventoryLot
this.inventoryLotLine = savedInventoryLotLine
this.lotNo = stockInLine.lotNo ?: savedInventoryLot?.lotNo this.lotNo = stockInLine.lotNo ?: savedInventoryLot?.lotNo
} }
val savedStockInLine = saveAndFlush(stockInLine) val savedStockInLine = saveAndFlush(stockInLine)
@@ -320,6 +322,7 @@ open class StockInLineService(
this.acceptedQty = request.acceptedQty this.acceptedQty = request.acceptedQty
this.status = request.status this.status = request.status
this.inventoryLot = savedInventoryLot ?: stockInLine.inventoryLot this.inventoryLot = savedInventoryLot ?: stockInLine.inventoryLot
this.inventoryLotLine = savedInventoryLotLine
this.lotNo = savedInventoryLot?.lotNo ?: stockInLine.lotNo this.lotNo = savedInventoryLot?.lotNo ?: stockInLine.lotNo
this.expiryDate = stockInLine.expiryDate ?: request.expiryDate this.expiryDate = stockInLine.expiryDate ?: request.expiryDate
this.productLotNo = stockInLine.productLotNo ?: request.productLotNo this.productLotNo = stockInLine.productLotNo ?: request.productLotNo


+ 142
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt 查看文件

@@ -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)
}
}

+ 23
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt 查看文件

@@ -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)
// }
}

+ 26
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/SuggestedPickLotController.kt 查看文件

@@ -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)
// }
}

+ 15
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveInventoryLotLineRequest.kt 查看文件

@@ -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?
)

+ 12
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveSuggestedPickLotRequest.kt 查看文件

@@ -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?
)

+ 17
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/SuggestedPickLotRequest.kt 查看文件

@@ -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>
)



+ 16
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/SuggestedPickLotResponse.kt 查看文件

@@ -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>
)

+ 7
- 0
src/main/resources/db/changelog/changes/20250620_01_derek/01_add_lot_line_id_to_stock_in_line.sql 查看文件

@@ -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)
;

正在加载...
取消
保存