Kaynağa Gözat

Stock Adj update

master
kelvin.yau 22 saat önce
ebeveyn
işleme
8cccc01aa3
2 değiştirilmiş dosya ile 135 ekleme ve 7 silme
  1. +4
    -7
      src/main/java/com/ffii/fpsms/modules/stock/service/StockAdjustmentService.kt
  2. +131
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt

+ 4
- 7
src/main/java/com/ffii/fpsms/modules/stock/service/StockAdjustmentService.kt Dosyayı Görüntüle

@@ -32,12 +32,9 @@ open class StockAdjustmentService(
// Branch 4: Removed entries — stock out full qty and mark unavailable
val removed = request.originalLines.filter { it.id > 0 && it.id !in currentIds }
for (line in removed) {
val stockOutLine = stockOutLineService.createStockOut(
StockOutRequest(
inventoryLotLineId = line.id,
qty = line.adjustedQty.toDouble(),
type = "ADJ"
)
val stockOutLine = stockOutLineService.createStockOutForRemoval(
inventoryLotLineId = line.id,
type = "ADJ"
)
saveAdjustmentRecordForStockOut(stockOutLine)
}
@@ -77,7 +74,7 @@ open class StockAdjustmentService(
saveAdjustmentRecordForStockIn(stockInLine)
} else {
// Branch 3 (qty down): createStockOut
val stockOutLine = stockOutLineService.createStockOut(
val stockOutLine = stockOutLineService.createStockOutForAdjustment(
StockOutRequest(
inventoryLotLineId = current.id,
qty = diff.abs().toDouble(),


+ 131
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt Dosyayı Görüntüle

@@ -51,6 +51,8 @@ import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import jakarta.persistence.EntityManager
import jakarta.persistence.PersistenceContext
import java.util.UUID
import org.springframework.http.HttpStatus
import org.springframework.web.server.ResponseStatusException
@Service
open class StockOutLineService(
private val jdbcDao: JdbcDao,
@@ -1589,6 +1591,135 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
return savedStockOutLine
}

/**
* Stock adjustment should only reduce physical on-hand (inQty - outQty) and must NOT affect holdQty.
*
* Rule: delta outQty cannot exceed availableQty = inQty - outQty - holdQty.
* This prevents ending in an invalid state where onHand < onHold and avoids negative trigger-derived quantities.
*/
open fun createStockOutForAdjustment(request: StockOutRequest): StockOutLine {
val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow()
val qtyBd = BigDecimal.valueOf(request.qty)
if (qtyBd <= BigDecimal.ZERO) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Adjustment qty must be > 0")
}

val inQty = inventoryLotLine.inQty ?: BigDecimal.ZERO
val outQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
val holdQty = inventoryLotLine.holdQty ?: BigDecimal.ZERO
val availableQty = inQty.subtract(outQty).subtract(holdQty)
if (qtyBd > availableQty) {
throw ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Adjustment qty exceeds availableQty (availableQty=$availableQty, requested=$qtyBd). " +
"Rule: requested <= inQty - outQty - holdQty."
)
}

// Step 1: Increase outQty only; keep holdQty unchanged
val updatedInventoryLotLine = inventoryLotLine.apply {
this.outQty = outQty.add(qtyBd)
this.status = inventoryLotLineService.deriveInventoryLotLineStatus(
this.status,
this.inQty,
this.outQty,
this.holdQty
)
}
inventoryLotLineRepository.save(updatedInventoryLotLine)
// inventory aggregates: handled by DB trigger `inventory_lot_line_AFTER_UPDATE`

val itemId = updatedInventoryLotLine.inventoryLot?.item?.id
?: throw IllegalArgumentException("InventoryLotLine must have an associated item")

// Step 2: Create stock_out header
val currentUser = SecurityUtils.getUser().orElseThrow()
val stockOut = StockOut().apply {
this.type = request.type
this.completeDate = LocalDateTime.now()
this.handler = currentUser.id
this.status = StockOutStatus.COMPLETE.status
}
val savedStockOut = stockOutRepository.save(stockOut)

// Step 3: Create stock_out_line
val item = itemRepository.findById(itemId).orElseThrow()
val stockOutLine = StockOutLine().apply {
this.item = item
this.qty = request.qty
this.stockOut = savedStockOut
this.inventoryLotLine = updatedInventoryLotLine
this.status = StockOutLineStatus.COMPLETE.status
this.pickTime = LocalDateTime.now()
this.handledBy = currentUser.id
this.type = request.type
}
val savedStockOutLine = saveAndFlush(stockOutLine)

// Step 4: ledger
createStockLedgerForStockOut(savedStockOutLine)
return savedStockOutLine
}

/**
* Removal flow (stock adjustment "remove lot line"):
* - Physically reduce on-hand to zero by setting outQty = inQty
* - Clear holdQty = 0 to avoid invalid state (onHand < onHold) and negative available calculations
*
* A stock-out document/ledger is still created for audit trail.
*/
open fun createStockOutForRemoval(inventoryLotLineId: Long, type: String = "ADJ"): StockOutLine {
val inventoryLotLine = inventoryLotLineRepository.findById(inventoryLotLineId).orElseThrow()
val inQty = inventoryLotLine.inQty ?: BigDecimal.ZERO
val outQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
val deltaOut = inQty.subtract(outQty).coerceAtLeast(BigDecimal.ZERO)

// Step 1: Set outQty=inQty and clear holdQty
val updatedInventoryLotLine = inventoryLotLine.apply {
this.outQty = inQty
this.holdQty = BigDecimal.ZERO
this.status = inventoryLotLineService.deriveInventoryLotLineStatus(
this.status,
this.inQty,
this.outQty,
this.holdQty
)
}
inventoryLotLineRepository.save(updatedInventoryLotLine)
// inventory aggregates: handled by DB trigger `inventory_lot_line_AFTER_UPDATE`

val itemId = updatedInventoryLotLine.inventoryLot?.item?.id
?: throw IllegalArgumentException("InventoryLotLine must have an associated item")

// Step 2: Create stock_out header
val currentUser = SecurityUtils.getUser().orElseThrow()
val stockOut = StockOut().apply {
this.type = type
this.completeDate = LocalDateTime.now()
this.handler = currentUser.id
this.status = StockOutStatus.COMPLETE.status
}
val savedStockOut = stockOutRepository.save(stockOut)

// Step 3: Create stock_out_line (qty = physical delta)
val item = itemRepository.findById(itemId).orElseThrow()
val stockOutLine = StockOutLine().apply {
this.item = item
this.qty = deltaOut.toDouble()
this.stockOut = savedStockOut
this.inventoryLotLine = updatedInventoryLotLine
this.status = StockOutLineStatus.COMPLETE.status
this.pickTime = LocalDateTime.now()
this.handledBy = currentUser.id
this.type = type
}
val savedStockOutLine = saveAndFlush(stockOutLine)

// Step 4: ledger
createStockLedgerForStockOut(savedStockOutLine)
return savedStockOutLine
}


private fun createStockLedgerForPickDelta(
stockOutLine: StockOutLine,


Yükleniyor…
İptal
Kaydet