Bläddra i källkod

update po stock in line

reset-do-picking-order
CANCERYS\kw093 1 vecka sedan
förälder
incheckning
40027497f3
5 ändrade filer med 123 tillägg och 39 borttagningar
  1. +33
    -9
      src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt
  2. +4
    -2
      src/main/java/com/ffii/fpsms/modules/report/service/ItemQcFailReportService.kt
  3. +6
    -6
      src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt
  4. +9
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt
  5. +71
    -22
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt

+ 33
- 9
src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt Visa fil

@@ -11,13 +11,14 @@ import java.math.RoundingMode
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import com.ffii.fpsms.modules.master.entity.ItemsRepository
@Service @Service
open class ItemUomService( open class ItemUomService(
val itemsService: ItemsService,
val uomConversionService: UomConversionService, val uomConversionService: UomConversionService,
val itemUomRespository: ItemUomRespository, val itemUomRespository: ItemUomRespository,
val currencyService: CurrencyService
val currencyService: CurrencyService,
val itemsRepository: ItemsRepository
) { ) {
val logger = org.slf4j.LoggerFactory.getLogger(this::class.java) val logger = org.slf4j.LoggerFactory.getLogger(this::class.java)


@@ -86,6 +87,16 @@ open class ItemUomService(
return stockQty; return stockQty;
} }


/** Inverse of convertPurchaseQtyToStockQty: stock qty -> purchase qty (for PO-origin StockInLine display). */
open fun convertStockQtyToPurchaseQty(itemId: Long, stockQty: BigDecimal): BigDecimal {
val purchaseUnit = findPurchaseUnitByItemId(itemId) ?: return stockQty
val stockUnit = findStockUnitByItemId(itemId) ?: return stockQty
val one = BigDecimal.ONE
val baseQty = stockQty.multiply(stockUnit.ratioN ?: one).divide(stockUnit.ratioD ?: one, 2, RoundingMode.UP)
val purchaseQty = baseQty.multiply(purchaseUnit.ratioD ?: one).divide(purchaseUnit.ratioN ?: one, 2, RoundingMode.UP)
return purchaseQty
}

open fun convertQtyToStockQty(itemId: Long, uomId: Long, sourceQty: BigDecimal): BigDecimal { open fun convertQtyToStockQty(itemId: Long, uomId: Long, sourceQty: BigDecimal): BigDecimal {
val itemUom = findFirstByItemIdAndUomId(itemId, uomId) ?: return sourceQty; val itemUom = findFirstByItemIdAndUomId(itemId, uomId) ?: return sourceQty;


@@ -107,7 +118,8 @@ open class ItemUomService(
// return itemUom // return itemUom
//} //}


val item = request.itemId?.let { itemsService.find(it).getOrNull() }
val item = request.itemId?.let { itemsRepository.findById(it).getOrNull() }
?: request.itemId?.let { itemsRepository.findByIdAndDeletedFalse(it) } // 取決於你 repo 有沒有這種方法
val uom = request.m18UomId?.let { uomConversionService.findByM18Id(it) } ?: request.uomId?.let { uomConversionService.find(it).getOrNull() } val uom = request.m18UomId?.let { uomConversionService.findByM18Id(it) } ?: request.uomId?.let { uomConversionService.find(it).getOrNull() }
val currency = request.currencyId?.let { currencyService.findById(it) } val currency = request.currencyId?.let { currencyService.findById(it) }


@@ -218,13 +230,25 @@ open class ItemUomService(
} }
} }
// var sourceItemUom = itemUomRespository.findFirstByItemIdAndUomIdAndDeletedIsFalse(request.itemId, request.uomId)

// 這裡先查 items、uom_conversion,準備錯誤訊息要用的 code
val item = itemsRepository.findById(request.itemId).getOrNull()
val itemCode = item?.code ?: request.itemId.toString()
val uom = uomConversionService.findById(request.uomId)
val uomCode = uom?.code ?: request.uomId.toString()
// If still no source ItemUom found, throw error // If still no source ItemUom found, throw error
sourceItemUom ?: throw IllegalArgumentException("Source ItemUom not found for itemId=${request.itemId}, uomId=${request.uomId}")
// Find target ItemUom by itemId and targetUnit
sourceItemUom ?: throw IllegalArgumentException(
"Source ItemUom not found for items.code=$itemCode, uom_conversion.code=$uomCode"
)
// Find target ItemUom by itemId and targetUnit(這段可視需求決定要不要也查 code)
val targetItemUom = findTargetItemUom(request.itemId, request.targetUnit) val targetItemUom = findTargetItemUom(request.itemId, request.targetUnit)
?: throw IllegalArgumentException("Target ItemUom not found for itemId=${request.itemId}, targetUnit=${request.targetUnit}")
?: throw IllegalArgumentException(
"Target ItemUom not found for items.code=$itemCode, targetUnit=${request.targetUnit}"
)
// Convert quantity using ratioN/ratioD via base unit // Convert quantity using ratioN/ratioD via base unit
val one = BigDecimal.ONE val one = BigDecimal.ONE
val sourceRatioN = sourceItemUom.ratioN ?: one val sourceRatioN = sourceItemUom.ratioN ?: one


+ 4
- 2
src/main/java/com/ffii/fpsms/modules/report/service/ItemQcFailReportService.kt Visa fil

@@ -58,8 +58,10 @@ open class ItemQcFailReportService(
COALESCE(qic.description, qi.description, qi.name, '') AS qcDefectCriteria, COALESCE(qic.description, qi.description, qi.name, '') AS qcDefectCriteria,


/* Lot Qty / Defect Qty:按 jrxml 字段类型输出 String */ /* Lot Qty / Defect Qty:按 jrxml 字段类型输出 String */
TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(
CASE WHEN sil.purchaseOrderId IS NOT NULL AND iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL
TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT( COALESCE(
CASE WHEN sil.purchaseOrderLineId IS NOT NULL
THEN sil.acceptedQty
WHEN iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL
THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0)) THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0))
ELSE sil.acceptedQty END, 0), 2))) AS lotQty, ELSE sil.acceptedQty END, 0), 2))) AS lotQty,
TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(qr.failQty, 0), 2))) AS defectQty, TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(qr.failQty, 0), 2))) AS defectQty,


+ 6
- 6
src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt Visa fil

@@ -120,17 +120,17 @@ in_agg AS (
SELECT SELECT
ill.id AS inventoryLotLineId, ill.id AS inventoryLotLineId,
SUM(CASE WHEN DATE(sil.receiptDate) < :fromDate THEN SUM(CASE WHEN DATE(sil.receiptDate) < :fromDate THEN
CASE WHEN sil.purchaseOrderId IS NOT NULL
AND iu_purchase.id IS NOT NULL
AND iu_stock.id IS NOT NULL
CASE WHEN sil.purchaseOrderLineId IS NOT NULL
THEN COALESCE(sil.acceptedQty, 0)
WHEN iu_purchase.id IS NOT NULL AND iu_stock.id IS NOT NULL
THEN COALESCE(sil.acceptedQty, 0) * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu_stock.ratioN / NULLIF(iu_stock.ratioD, 0)) THEN COALESCE(sil.acceptedQty, 0) * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu_stock.ratioN / NULLIF(iu_stock.ratioD, 0))
ELSE COALESCE(sil.acceptedQty, 0) ELSE COALESCE(sil.acceptedQty, 0)
END END
ELSE 0 END) AS inBefore, ELSE 0 END) AS inBefore,
SUM(CASE WHEN DATE(sil.receiptDate) BETWEEN :fromDate AND :toDate THEN SUM(CASE WHEN DATE(sil.receiptDate) BETWEEN :fromDate AND :toDate THEN
CASE WHEN sil.purchaseOrderId IS NOT NULL
AND iu_purchase.id IS NOT NULL
AND iu_stock.id IS NOT NULL
CASE WHEN sil.purchaseOrderLineId IS NOT NULL
THEN COALESCE(sil.acceptedQty, 0)
WHEN iu_purchase.id IS NOT NULL AND iu_stock.id IS NOT NULL
THEN COALESCE(sil.acceptedQty, 0) * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu_stock.ratioN / NULLIF(iu_stock.ratioD, 0)) THEN COALESCE(sil.acceptedQty, 0) * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu_stock.ratioN / NULLIF(iu_stock.ratioD, 0))
ELSE COALESCE(sil.acceptedQty, 0) ELSE COALESCE(sil.acceptedQty, 0)
END END


+ 9
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt Visa fil

@@ -28,6 +28,8 @@ interface StockInLineInfo {
val receivedQty: BigDecimal? val receivedQty: BigDecimal?
val demandQty: BigDecimal? val demandQty: BigDecimal?
val acceptedQty: BigDecimal val acceptedQty: BigDecimal
@get:Value("#{target.purchaseOrderLine != null && target.item != null && target.acceptedQty != null ? @itemUomService.convertStockQtyToPurchaseQty(target.item.id, target.acceptedQty) : null}")
val purchaseAcceptedQty: BigDecimal?
@get:Value("#{target.purchaseOrderLine?.qty}") @get:Value("#{target.purchaseOrderLine?.qty}")
val qty: BigDecimal? val qty: BigDecimal?
val price: BigDecimal? val price: BigDecimal?
@@ -44,6 +46,10 @@ interface StockInLineInfo {
val supplier: String? val supplier: String?
@get:Value("#{target.item?.itemUoms.^[salesUnit == true && deleted == false]?.uom}") //TODO review @get:Value("#{target.item?.itemUoms.^[salesUnit == true && deleted == false]?.uom}") //TODO review
val uom: UomConversion? val uom: UomConversion?
@get:Value("#{target.purchaseOrderLine?.uom?.udfudesc}")
val purchaseUomDesc: String?
@get:Value("#{target.item?.itemUoms.^[stockUnit == true && deleted == false]?.uom?.udfudesc}")
val stockUomDesc: String?
@get:Value("#{target.stockIn?.purchaseOrder?.code}") @get:Value("#{target.stockIn?.purchaseOrder?.code}")
val poCode: String? val poCode: String?
@get:Value("#{target.jobOrder?.code}") @get:Value("#{target.jobOrder?.code}")
@@ -61,6 +67,9 @@ interface StockInLineInfo {


interface PutAwayLineForSil { interface PutAwayLineForSil {
val id: Long?; val id: Long?;
/** Stock qty (inventory lot line inQty) */
@get:Value("#{target.inQty}")
val stockQty: BigDecimal?;
@get:Value("#{target.inQty " + @get:Value("#{target.inQty " +
"* ((target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD))" + "* ((target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD))" +
"/ ((target.inventoryLot.item.itemUoms.^[purchaseUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[purchaseUnit == true && deleted == false]?.ratioD))}") "/ ((target.inventoryLot.item.itemUoms.^[purchaseUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[purchaseUnit == true && deleted == false]?.ratioD))}")


+ 71
- 22
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt Visa fil

@@ -27,6 +27,7 @@ import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus
import com.ffii.fpsms.modules.master.entity.ItemUomRespository import com.ffii.fpsms.modules.master.entity.ItemUomRespository
import com.ffii.fpsms.modules.master.entity.WarehouseRepository import com.ffii.fpsms.modules.master.entity.WarehouseRepository
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.master.service.PrinterService import com.ffii.fpsms.modules.master.service.PrinterService
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine
@@ -87,6 +88,7 @@ open class StockInLineService(
private val itemRepository: ItemsRepository, private val itemRepository: ItemsRepository,
private val warehouseRepository: WarehouseRepository, private val warehouseRepository: WarehouseRepository,
private val itemUomRepository: ItemUomRespository, private val itemUomRepository: ItemUomRespository,
private val itemUomService: ItemUomService,
private val printerService: PrinterService, private val printerService: PrinterService,
private val stockTakeLineRepository: StockTakeLineRepository, private val stockTakeLineRepository: StockTakeLineRepository,
private val inventoryLotLineService: InventoryLotLineService, private val inventoryLotLineService: InventoryLotLineService,
@@ -194,15 +196,21 @@ open class StockInLineService(
this.item = item this.item = item
itemNo = item.code itemNo = item.code
this.stockIn = stockIn this.stockIn = stockIn
acceptedQty = request.acceptedQty
// PO-origin: store in stock qty; others: store as received
acceptedQty = if (pol != null && item.id != null) {
itemUomService.convertPurchaseQtyToStockQty(item.id!!, request.acceptedQty ?: BigDecimal.ZERO)
} else {
request.acceptedQty
}
// Set demandQty based on source // Set demandQty based on source
if (jo != null && jo?.bom != null) { if (jo != null && jo?.bom != null) {
// For job orders, demandQty comes from BOM's outputQty // For job orders, demandQty comes from BOM's outputQty
this.demandQty = jo?.bom?.outputQty this.demandQty = jo?.bom?.outputQty


} else if (pol != null) {
// For purchase orders, demandQty comes from PurchaseOrderLine's qty
this.demandQty = pol.qty
} else if (pol != null && item.id != null) {
pol.qty?.let { polQty ->
this.demandQty = itemUomService.convertPurchaseQtyToStockQty(item.id!!, polQty)
}
} }
dnNo = request.dnNo dnNo = request.dnNo
receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now() receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now()
@@ -264,7 +272,10 @@ open class StockInLineService(
itemId = request.itemId itemId = request.itemId
) )
val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId)
val convertedBaseQty = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
// PO-origin: frontend sends qty in stock; non-PO: treat as purchase and convert to stock
val convertedBaseQty = if (stockInLine.purchaseOrderLine != null) {
line.qty
} else if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
(line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) (line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!)
} else { } else {
(line.qty) (line.qty)
@@ -631,14 +642,21 @@ open class StockInLineService(
this.productLotNo = request.productLotNo ?: this.productLotNo this.productLotNo = request.productLotNo ?: this.productLotNo
this.dnNo = request.dnNo ?: this.dnNo this.dnNo = request.dnNo ?: this.dnNo
// this.dnDate = request.dnDate?.atStartOfDay() ?: this.dnDate // this.dnDate = request.dnDate?.atStartOfDay() ?: this.dnDate
this.acceptedQty = request.acceptQty ?: request.acceptedQty ?: this.acceptedQty
// QC and PutAway should never overwrite acceptedQty (received qty).
if (request.qcAccept != true && request.status != StockInLineStatus.RECEIVED.status) {
val requestQty = request.acceptQty ?: request.acceptedQty
this.acceptedQty = if (this.purchaseOrderLine != null && this.item?.id != null && requestQty != null) {
itemUomService.convertPurchaseQtyToStockQty(this.item!!.id!!, requestQty)
} else {
requestQty ?: this.acceptedQty
}
}
// Set demandQty based on source // Set demandQty based on source
if (this.jobOrder != null && this.jobOrder?.bom != null) { if (this.jobOrder != null && this.jobOrder?.bom != null) {
// For job orders, demandQty comes from BOM's outputQty // For job orders, demandQty comes from BOM's outputQty
this.demandQty = this.jobOrder?.bom?.outputQty ?: this.demandQty this.demandQty = this.jobOrder?.bom?.outputQty ?: this.demandQty
} else if (this.purchaseOrderLine != null) {
// For purchase orders, demandQty comes from PurchaseOrderLine's qty
this.demandQty = this.purchaseOrderLine?.qty ?: this.demandQty
} else if (this.purchaseOrderLine != null && this.item?.id != null && this.purchaseOrderLine?.qty != null) {
this.demandQty = itemUomService.convertPurchaseQtyToStockQty(this.item!!.id!!, this.purchaseOrderLine!!.qty!!)
} }
// Don't overwrite demandQty with acceptQty from QC form // Don't overwrite demandQty with acceptQty from QC form
this.invoiceNo = request.invoiceNo this.invoiceNo = request.invoiceNo
@@ -655,29 +673,58 @@ open class StockInLineService(
val savedInventoryLotLines = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine) val savedInventoryLotLines = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine)
val inventoryLotLines = stockInLine.inventoryLot?.let { it.id?.let { _id -> inventoryLotLineRepository.findAllByInventoryLotId(_id) } } ?: listOf() val inventoryLotLines = stockInLine.inventoryLot?.let { it.id?.let { _id -> inventoryLotLineRepository.findAllByInventoryLotId(_id) } } ?: listOf()


val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId)
val stockItemUom = itemUomRepository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(request.itemId)
val ratio = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
(purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!)
// ✅ 每次上架都寫一筆 stock_ledger(inQty = 本次上架的庫存數量)
val putAwayDeltaStockQty = (request.inventoryLotLines ?: listOf()).sumOf { line ->
val stockItemUom = itemUomRepository.findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse(
itemId = request.itemId
)
val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId)

val convertedBaseQty = if (stockInLine.purchaseOrderLine != null) {
// PO-origin: qty is already stock qty
line.qty
} else if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
// Legacy: treat as purchase qty, convert to stock qty
(line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!)
} else {
line.qty
}
convertedBaseQty
}
if (putAwayDeltaStockQty > BigDecimal.ZERO) {
createStockLedgerForStockIn(stockInLine, putAwayDeltaStockQty.toDouble())
}

val putAwayStockQty = inventoryLotLines.sumOf { it.inQty ?: BigDecimal.ZERO }

// PO-origin: acceptedQty is already STOCK qty; Non-PO: keep legacy rule (acceptQty is purchase qty)
val requiredStockQty = if (stockInLine.purchaseOrderLine != null) {
stockInLine.acceptedQty ?: BigDecimal.ZERO
} else { } else {
BigDecimal.ONE
val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId)
val stockItemUom = itemUomRepository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(request.itemId)
val ratio = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
(purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!)
} else {
BigDecimal.ONE
}
(request.acceptQty ?: request.acceptedQty)?.times(ratio) ?: BigDecimal.ZERO
} }


if (inventoryLotLines.sumOf { it.inQty ?: BigDecimal.ZERO } >= request.acceptQty?.times(ratio)) {
if (putAwayStockQty >= requiredStockQty) {
stockInLine.apply { stockInLine.apply {
val isWipJobOrder = stockInLine.jobOrder?.bom?.description == "WIP" val isWipJobOrder = stockInLine.jobOrder?.bom?.description == "WIP"
this.status = if (isWipJobOrder) { this.status = if (isWipJobOrder) {
StockInLineStatus.COMPLETE.status StockInLineStatus.COMPLETE.status
} else { } else {
// For non-WIP, use original logic // For non-WIP, use original logic
if (request.acceptQty?.compareTo(request.acceptedQty) == 0)
if (putAwayStockQty.compareTo(requiredStockQty) == 0)
StockInLineStatus.COMPLETE.status StockInLineStatus.COMPLETE.status
else else
StockInLineStatus.PARTIALLY_COMPLETE.status StockInLineStatus.PARTIALLY_COMPLETE.status
} }
// this.inventoryLotLine = savedInventoryLotLine // this.inventoryLotLine = savedInventoryLotLine
} }
createStockLedgerForStockIn(stockInLine)
// Update JO Status // Update JO Status
if (stockInLine.jobOrder != null) { //TODO Improve if (stockInLine.jobOrder != null) { //TODO Improve
val jo = stockInLine.jobOrder val jo = stockInLine.jobOrder
@@ -789,7 +836,10 @@ open class StockInLineService(
info.itemId info.itemId
) )
val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(info.itemId) val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(info.itemId)
val acceptedQty = if (stockItemUom != null && purchaseItemUom != null) {
// PO-origin: acceptedQty is already stock qty; non-PO: convert purchase -> stock for display
val acceptedQty = if (info.purchaseOrderLineId != null) {
info.acceptedQty
} else if (stockItemUom != null && purchaseItemUom != null) {
(info.acceptedQty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) (info.acceptedQty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!)
} else { } else {
(info.acceptedQty) (info.acceptedQty)
@@ -936,10 +986,9 @@ open class StockInLineService(
} }
@Transactional @Transactional


private fun createStockLedgerForStockIn(stockInLine: StockInLine) {
private fun createStockLedgerForStockIn(stockInLine: StockInLine, inQty: Double) {
val item = stockInLine.item ?: return val item = stockInLine.item ?: return
val inventory = inventoryRepository.findFirstByItemIdAndDeletedIsFalseOrderByIdAsc(item.id!!) ?: return val inventory = inventoryRepository.findFirstByItemIdAndDeletedIsFalseOrderByIdAsc(item.id!!) ?: return
val inQty = stockInLine.acceptedQty?.toDouble() ?: 0.0
// ✅ 修复:查询最新的 stock_ledger 记录,基于前一笔 balance 计算 // ✅ 修复:查询最新的 stock_ledger 记录,基于前一笔 balance 计算
val latestLedger = stockLedgerRepository.findLatestByItemId(item.id!!).firstOrNull() val latestLedger = stockLedgerRepository.findLatestByItemId(item.id!!).firstOrNull()
@@ -1047,7 +1096,7 @@ open class StockInLineService(
saveAndFlush(savedStockInLine) saveAndFlush(savedStockInLine)


// Step 5: Create Stock Ledger entry // Step 5: Create Stock Ledger entry
createStockLedgerForStockIn(savedStockInLine)
createStockLedgerForStockIn(savedStockInLine, request.acceptedQty.toDouble())


return savedStockInLine return savedStockInLine
} }
@@ -1095,7 +1144,7 @@ open class StockInLineService(
val savedStockInLine = saveAndFlush(stockInLine) val savedStockInLine = saveAndFlush(stockInLine)


// Step 3: Create Stock Ledger entry // Step 3: Create Stock Ledger entry
createStockLedgerForStockIn(savedStockInLine)
createStockLedgerForStockIn(savedStockInLine, request.acceptedQty.toDouble())


return savedStockInLine return savedStockInLine
} }


Laddar…
Avbryt
Spara