@@ -1,6 +1,7 @@
package com.ffii.fpsms.modules.deliveryOrder.service
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoReplenishment
@@ -18,6 +19,7 @@ import com.ffii.fpsms.modules.master.entity.UomConversionRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@@ -31,7 +33,9 @@ open class DoReplenishmentService(
private val deliveryOrderRepository: DeliveryOrderRepository,
private val deliveryOrderLineRepository: DeliveryOrderLineRepository,
private val doPickOrderRepository: DoPickOrderRepository,
private val doPickOrderLineRepository: DoPickOrderLineRepository,
private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
private val stockOutLIneRepository: StockOutLIneRepository,
private val uomConversionRepository: UomConversionRepository,
private val pickOrderRepository: PickOrderRepository,
private val pickOrderLineRepository: PickOrderLineRepository,
@@ -72,6 +76,7 @@ open class DoReplenishmentService(
existingPending.truckLaneCode =
resolveSourceDoTruckLaneCode(deliveryOrder, lineReq.truckLaneCode)
}
lineReq.reason?.trim()?.takeIf { it.isNotEmpty() }?.let { existingPending.reason = it }
created += doReplenishmentRepository.save(existingPending)
continue
}
@@ -108,6 +113,7 @@ open class DoReplenishmentService(
shopName = shop?.name
truckLaneCode = resolveSourceDoTruckLaneCode(deliveryOrder, lineReq.truckLaneCode)
status = DoReplenishment.STATUS_PENDING
reason = lineReq.reason?.trim()?.takeIf { it.isNotEmpty() }
}
created += doReplenishmentRepository.save(entity)
}
@@ -382,6 +388,7 @@ open class DoReplenishmentService(
merged[key] = existing.copy(
replenishQty = existing.replenishQty.add(line.replenishQty),
truckLaneCode = existing.truckLaneCode?.takeIf { it.isNotBlank() } ?: line.truckLaneCode,
reason = existing.reason?.takeIf { it.isNotBlank() } ?: line.reason,
)
}
}
@@ -408,6 +415,77 @@ open class DoReplenishmentService(
return requestTruckLaneCode?.trim()?.takeIf { it.isNotEmpty() }
}
/**
* Actual shipped qty per item on a completed source DO: sum of [stock_out_line.qty]
* for the linked pick order line. Falls back to [fallbackQtyByItemId] when no pick link exists
* (same rule as delivery note PDF).
*/
open fun resolveActualShippedQtyForDeliveryOrder(
doId: Long,
fallbackQtyByItemId: Map<Long, BigDecimal> = emptyMap(),
): Map<Long, BigDecimal> {
val keys = fallbackQtyByItemId.keys.map { doId to it }
if (keys.isEmpty()) {
return emptyMap()
}
return resolveActualShippedQtyBySourceKeys(keys, keys.associateWith { fallbackQtyByItemId[it.second]!! })
.mapKeys { it.key.second }
}
private fun resolveActualShippedQtyBySourceKeys(
keys: List<Pair<Long, Long>>,
fallbackQtyByKey: Map<Pair<Long, Long>, BigDecimal>,
): Map<Pair<Long, Long>, BigDecimal> {
if (keys.isEmpty()) {
return emptyMap()
}
val doIds = keys.map { it.first }.distinct()
val pickOrderIdByDoId = doIds.mapNotNull { doId ->
resolvePickOrderIdForDo(doId)?.let { doId to it }
}.toMap()
val pickOrderIds = pickOrderIdByDoId.values.distinct()
val pickOrderLines = if (pickOrderIds.isEmpty()) {
emptyList()
} else {
pickOrderLineRepository.findAllByPickOrderIdInAndDeletedFalse(pickOrderIds)
}
val pickOrderLinesByPoId = pickOrderLines.groupBy { it.pickOrder?.id }
val polIds = pickOrderLines.mapNotNull { it.id }
val stockOutQtyByPolId = if (polIds.isEmpty()) {
emptyMap()
} else {
stockOutLIneRepository.findAllByPickOrderLineIdInAndDeletedFalse(polIds)
.groupBy { it.pickOrderLine?.id }
.mapValues { (_, lines) ->
lines.fold(BigDecimal.ZERO) { acc, line ->
acc.add(BigDecimal.valueOf(line.qty ?: 0.0))
}
}
}
return keys.associateWith { (doId, itemId) ->
val pickOrderId = pickOrderIdByDoId[doId]
val polId = pickOrderId?.let { poId ->
pickOrderLinesByPoId[poId]?.firstOrNull { it.item?.id == itemId }?.id
}
if (polId != null) {
stockOutQtyByPolId[polId] ?: BigDecimal.ZERO
} else {
fallbackQtyByKey[doId to itemId] ?: BigDecimal.ZERO
}
}
}
private fun resolvePickOrderIdForDo(doId: Long): Long? {
pickOrderRepository.findByDeliveryOrderId(doId).firstOrNull()?.id?.let { return it }
return doPickOrderLineRepository.findByDoOrderIdAndDeletedFalse(doId)
.mapNotNull { it.pickOrderId }
.firstOrNull()
}
private fun toResponses(entities: List<DoReplenishment>): List<DoReplenishmentResponse> {
val uomIds = entities.mapNotNull { it.uomId }.distinct()
val shortUomById = if (uomIds.isEmpty()) {
@@ -418,6 +496,35 @@ open class DoReplenishmentService(
}
}
val sourceLineIds = entities.mapNotNull { it.sourceDoLineId }.distinct()
val sourceLineQtyById = if (sourceLineIds.isEmpty()) {
emptyMap()
} else {
deliveryOrderLineRepository.findAllById(sourceLineIds).associate { line ->
line.id!! to line.qty
}
}
val targetDoIds = entities.mapNotNull { it.targetDoId }.distinct()
val targetDoEtaById = if (targetDoIds.isEmpty()) {
emptyMap()
} else {
deliveryOrderRepository.findAllById(targetDoIds).associate { deliveryOrder ->
deliveryOrder.id!! to deliveryOrder.estimatedArrivalDate?.toLocalDate()
}
}
val sourceKeys = entities.map { it.sourceDoId!! to it.itemId!! }.distinct()
val fallbackQtyBySourceKey = entities.associate { row ->
(row.sourceDoId!! to row.itemId!!) to (
row.sourceDoLineId?.let { sourceLineQtyById[it] } ?: BigDecimal.ZERO
)
}
val actualShippedQtyBySourceKey = resolveActualShippedQtyBySourceKeys(
keys = sourceKeys,
fallbackQtyByKey = fallbackQtyBySourceKey,
)
return entities.map { row ->
DoReplenishmentResponse(
id = row.id!!,
@@ -429,6 +536,7 @@ open class DoReplenishmentService(
itemId = row.itemId!!,
itemNo = row.itemNo,
itemName = row.itemName,
originalQty = actualShippedQtyBySourceKey[row.sourceDoId!! to row.itemId!!],
replenishQty = row.replenishQty!!,
shortUom = row.uomId?.let { shortUomById[it] },
shopCode = row.shopCode,
@@ -436,9 +544,11 @@ open class DoReplenishmentService(
truckLaneCode = row.truckLaneCode,
targetDoId = row.targetDoId,
targetDoCode = row.targetDoCode,
targetDoEstimatedArrivalDate = row.targetDoId?.let { targetDoEtaById[it] },
pickOrderLineId = row.pickOrderLineId,
deliveryOrderPickOrderId = row.deliveryOrderPickOrderId,
status = row.status,
reason = row.reason,
created = row.created,
)
}