瀏覽代碼

update

master
CANCERYS\kw093 2 天之前
父節點
當前提交
46b604f71d
共有 14 個文件被更改,包括 162 次插入124 次删除
  1. +2
    -2
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt
  2. +6
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
  3. +1
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  4. +42
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt
  5. +2
    -2
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  6. +2
    -2
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt
  7. +2
    -2
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt
  8. +1
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt
  9. +1
    -63
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt
  10. +13
    -13
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  11. +67
    -35
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt
  12. +2
    -2
      src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt
  13. +13
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveEscalationLogResponse.kt
  14. +8
    -0
      src/main/resources/db/changelog/changes/20260327_01_Enson/01_alter_stock_take.sql

+ 2
- 2
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt 查看文件

@@ -842,7 +842,7 @@ open class DeliveryOrderService(
this.item = pickOrderLine.item
this.status = StockOutLineStatus.PENDING.status
this.qty = 0.0
this.type = "Nor"
this.type = "NOR"
}
stockOutLineRepository.save(line)
}
@@ -1524,7 +1524,7 @@ open class DeliveryOrderService(
StockOutLineStatus.PENDING.status // 有正常库存批次时使用 PENDING
}
this.qty = 0.0
this.type = "Nor"
this.type = "NOR"
}
stockOutLineRepository.save(line)
}


+ 6
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt 查看文件

@@ -159,7 +159,12 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> {
SELECT jo FROM JobOrder jo
WHERE jo.deleted = false
AND (:code IS NULL OR jo.code LIKE CONCAT('%', :code, '%'))
AND (:bomName IS NULL OR jo.bom.name LIKE CONCAT('%', :bomName, '%'))
AND (
:bomName IS NULL
OR jo.bom.name LIKE CONCAT('%', :bomName, '%')
OR jo.bom.item.code LIKE CONCAT('%', :bomName, '%')
OR jo.bom.item.name LIKE CONCAT('%', :bomName, '%')
)
AND (:planStartFrom IS NULL OR jo.planStart >= :planStartFrom)
AND (:planStartTo IS NULL OR jo.planStart <= :planStartTo)
AND (


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt 查看文件

@@ -673,7 +673,7 @@ open class JobOrderService(
this.item = pickOrderLine.item
this.status = StockOutLineStatus.PENDING.status
this.qty = 0.0
this.type = "Nor"
this.type = "NOR"
}
stockOutLineRepository.save(line)
}


+ 42
- 0
src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt 查看文件

@@ -203,6 +203,48 @@ open class ItemUomService(
return stockQty.setScale(0, RoundingMode.DOWN)
}

/**
* Convert purchase qty -> stock qty and keep decimal precision.
* Used for PO flows where decimal quantity must be preserved.
*/
open fun convertPurchaseQtyToStockQtyPrecise(itemId: Long, purchaseQty: BigDecimal): BigDecimal {
val purchaseUnit = findPurchaseUnitByItemId(itemId) ?: return purchaseQty
val stockUnit = findStockUnitByItemId(itemId) ?: return purchaseQty
val one = BigDecimal.ONE
val calcScale = 10

val baseQty = purchaseQty
.multiply(purchaseUnit.ratioN ?: one)
.divide(purchaseUnit.ratioD ?: one, calcScale, RoundingMode.HALF_UP)

val stockQty = baseQty
.multiply(stockUnit.ratioD ?: one)
.divide(stockUnit.ratioN ?: one, calcScale, RoundingMode.HALF_UP)

return stockQty.setScale(2, RoundingMode.HALF_UP)
}

/**
* Convert qty from a specific UOM -> stock qty and keep decimal precision.
* Used for PO (M18 UOM) conversion where round-down is not allowed.
*/
open fun convertQtyToStockQtyPrecise(itemId: Long, uomId: Long, sourceQty: BigDecimal): BigDecimal {
val itemUom = findFirstByItemIdAndUomId(itemId, uomId) ?: return sourceQty
val stockUnit = findStockUnitByItemId(itemId) ?: return BigDecimal.ZERO
val one = BigDecimal.ONE
val calcScale = 10

val baseQty = sourceQty
.multiply(itemUom.ratioN ?: one)
.divide(itemUom.ratioD ?: one, calcScale, RoundingMode.HALF_UP)

val stockQty = baseQty
.multiply(stockUnit.ratioD ?: one)
.divide(stockUnit.ratioN ?: one, calcScale, RoundingMode.HALF_UP)

return stockQty.setScale(2, RoundingMode.HALF_UP)
}

// See if need to update the response
open fun saveItemUom(request: ItemUomRequest): ItemUom {
val itemUom = request.m18Id?.let { itemUomRespository.findFirstByM18IdOrderByIdDesc(it) }


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

@@ -1150,7 +1150,7 @@ open class PickOrderService(
this.status =
com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status
this.qty = 0.0
this.type = "Nor"
this.type = "NOR"
}
stockOutLIneRepository.save(line)
precreated++
@@ -2110,7 +2110,7 @@ open class PickOrderService(
this.status =
com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status
this.qty = 0.0
this.type = "Nor"
this.type = "NOR"
}
stockOutLIneRepository.save(line)
precreated++


+ 2
- 2
src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt 查看文件

@@ -269,7 +269,7 @@ open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> {
val qtyM18 = thisPol.qtyM18
val uomM18Id = thisPol.uomM18?.id
val stockQtyFromM18 = if (itemId != null && qtyM18 != null && uomM18Id != null) {
itemUomService.convertQtyToStockQtyRoundDown(itemId, uomM18Id, qtyM18)
itemUomService.convertQtyToStockQtyPrecise(itemId, uomM18Id, qtyM18)
} else {
BigDecimal.ZERO
}
@@ -279,7 +279,7 @@ open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> {
stockUomDesc = iu?.uom?.udfudesc,
stockQty = if (stockQtyFromM18 > BigDecimal.ZERO) stockQtyFromM18 else {
// fallback to legacy behavior when M18 fields are missing
iu?.item?.id?.let { iId -> itemUomService.convertPurchaseQtyToStockQty(iId, (thisPol.qty ?: BigDecimal.ZERO)) } ?: BigDecimal.ZERO
iu?.item?.id?.let { iId -> itemUomService.convertPurchaseQtyToStockQtyPrecise(iId, (thisPol.qty ?: BigDecimal.ZERO)) } ?: BigDecimal.ZERO
},
stockRatioN = iu?.ratioN,
stockRatioD = iu?.ratioD,


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

@@ -63,7 +63,7 @@ open class StockInLine : BaseEntity<Long>() {
open var acceptedQty: BigDecimal? = null

@Column(name = "acceptedQtyM18")
open var acceptedQtyM18: Int? = null
open var acceptedQtyM18: BigDecimal? = null
@Column(name = "price", precision = 14, scale = 2)
open var price: BigDecimal? = null

@@ -134,6 +134,6 @@ open class StockInLine : BaseEntity<Long>() {
*/
fun getReceivedQtyM18ForPol(): BigDecimal? =
purchaseOrderLine?.stockInLines?.sumOf { sil ->
sil.acceptedQtyM18?.let { BigDecimal.valueOf(it.toLong()) } ?: BigDecimal.ZERO
sil.acceptedQtyM18 ?: BigDecimal.ZERO
}
}

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

@@ -28,7 +28,7 @@ interface StockInLineInfo {
val receivedQty: BigDecimal?
val demandQty: BigDecimal?
val acceptedQty: BigDecimal
@get:Value("#{target.acceptedQtyM18 != null ? new java.math.BigDecimal(target.acceptedQtyM18) : null}")
@get:Value("#{target.acceptedQtyM18}")
val purchaseAcceptedQty: BigDecimal?
@get:Value("#{target.purchaseOrderLine?.qtyM18}")
val qty: BigDecimal?


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

@@ -352,67 +352,5 @@ open class InventoryService(

}

// @Throws(IOException::class)
// open fun updateInventory(request: SaveInventoryRequest): MessageResponse {
// // out need id
// // in not necessary
// var reqQty = request.qty
// if (request.type === "out") reqQty *= -1
// if (request.id !== null) { // old record
// val inventory = inventoryRepository.findById(request.id).orElseThrow()
// val newStatus = request.status ?: inventory.status
// val newExpiry = request.expiryDate ?: inventory.expiryDate
// // uom should not be changing
// // stock in line should not be changing
// // item id should not be changing
// inventory.apply {
// qty = inventory.qty!! + reqQty
// expiryDate = newExpiry
// status = newStatus
// }
// val savedInventory = inventoryRepository.saveAndFlush(inventory)
// return MessageResponse(
// id = savedInventory.id,
// code = savedInventory.lotNo,
// name = "savedInventory.item!!.name",
// type = savedInventory.status,
// message = "update success",
// errorPosition = null
// )
// } else { // new record
// val inventory = Inventory()
// val item = itemsRepository.findById(request.itemId).orElseThrow()
// val from = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)
// val to = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE)
//// val stockInLine = ....
// val args = mapOf(
// "from" to from,
// "to" to to,
// "itemId" to item.id
// )
// val prefix = "LOT"
// val count = jdbcDao.queryForInt(INVENTORY_COUNT.toString(), args)
// val newLotNo = CodeGenerator.generateCode(prefix, item.id!!, count)
// val newExpiry = request.expiryDate
// inventory.apply {
//// this.stockInLine = stockInline
//// this.item = item
// stockInLine = 0
// qty = reqQty
// lotNo = newLotNo
// expiryDate = newExpiry
// uomId = 0
// // status default "pending" in db
// }
// val savedInventory = inventoryRepository.saveAndFlush(inventory)
// return MessageResponse(
// id = savedInventory.id,
// code = savedInventory.lotNo,
// name = "savedInventory.item!!.name",
// type = savedInventory.status,
// message = "save success",
// errorPosition = null
// )
// }
// }

}

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

@@ -254,20 +254,20 @@ open class StockInLineService(
itemNo = item.code
this.stockIn = stockIn
// PO-origin:
// 1) store user-input qty in acceptedQtyM18 (M18 unit)
// 2) calculate stock acceptedQty by converting from M18 unit
// 1) keep user-input qty in acceptedQtyM18 (M18 unit, decimal allowed)
// 2) calculate stock acceptedQty by converting from M18 unit without round-down
if (pol != null && item.id != null) {
acceptedQtyM18 = request.acceptedQty.toInt()
acceptedQtyM18 = request.acceptedQty
val m18UomId = pol.uomM18?.id
acceptedQty = if (m18UomId != null) {
itemUomService.convertQtyToStockQtyRoundDown(
itemUomService.convertQtyToStockQtyPrecise(
item.id!!,
m18UomId,
request.acceptedQty
)
} else {
// fallback to legacy: treat request.acceptedQty as purchase unit qty
itemUomService.convertPurchaseQtyToStockQtyRoundDown(
itemUomService.convertPurchaseQtyToStockQtyPrecise(
item.id!!,
request.acceptedQty
)
@@ -285,7 +285,7 @@ open class StockInLineService(
val m18UomId = pol.uomM18?.id
val qtyM18 = pol.qtyM18
this.demandQty = if (m18UomId != null && qtyM18 != null) {
itemUomService.convertQtyToStockQtyRoundDown(
itemUomService.convertQtyToStockQtyPrecise(
item.id!!,
m18UomId,
qtyM18
@@ -293,14 +293,14 @@ open class StockInLineService(
} else {
// fallback to legacy: treat pol.qty as purchase unit qty
pol.qty?.let { polQty ->
itemUomService.convertPurchaseQtyToStockQty(item.id!!, polQty)
itemUomService.convertPurchaseQtyToStockQtyPrecise(item.id!!, polQty)
}
}
}
dnNo = request.dnNo
receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now()
productLotNo = request.productLotNo
this.type = "Nor"
this.type = "NOR"
status = StockInLineStatus.PENDING.status
}
if (jo != null) {
@@ -598,7 +598,7 @@ open class StockInLineService(
val pol = sil.purchaseOrderLine!!
// For PO-origin GRN, M18 ant qty must use the M18 UOM and received M18 qty.
val totalQtyM18 = silList.sumOf {
it.acceptedQtyM18?.let { qty -> BigDecimal.valueOf(qty.toLong()) } ?: BigDecimal.ZERO
it.acceptedQtyM18 ?: BigDecimal.ZERO
}
val unitIdFromDataLog = (pol.m18DataLog?.dataLog?.get("unitId") as? Number)?.toLong()?.toInt()
val itemName = (sil.item?.name ?: pol.item?.name).orEmpty() // always non-null for M18 bDesc/bDesc_en
@@ -790,7 +790,7 @@ open class StockInLineService(
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)
itemUomService.convertPurchaseQtyToStockQtyPrecise(this.item!!.id!!, requestQty)
} else {
requestQty ?: this.acceptedQty
}
@@ -804,7 +804,7 @@ open class StockInLineService(
val requestQty = request.acceptQty ?: request.acceptedQty
if (requestQty != null) {
this.acceptedQty = if (this.purchaseOrderLine != null && this.item?.id != null) {
itemUomService.convertPurchaseQtyToStockQty(this.item!!.id!!, requestQty)
itemUomService.convertPurchaseQtyToStockQtyPrecise(this.item!!.id!!, requestQty)
} else {
requestQty
}
@@ -820,10 +820,10 @@ open class StockInLineService(
val m18UomId = pol.uomM18?.id
val qtyM18 = pol.qtyM18
this.demandQty = if (m18UomId != null && qtyM18 != null) {
itemUomService.convertQtyToStockQtyRoundDown(itemId, m18UomId, qtyM18)
itemUomService.convertQtyToStockQtyPrecise(itemId, m18UomId, qtyM18)
} else if (pol.qty != null) {
// fallback to legacy fields when M18 fields are missing
itemUomService.convertPurchaseQtyToStockQty(itemId, pol.qty!!)
itemUomService.convertPurchaseQtyToStockQtyPrecise(itemId, pol.qty!!)
} else {
this.demandQty
}


+ 67
- 35
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt 查看文件

@@ -45,6 +45,7 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository
import java.time.LocalTime
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository
import java.util.UUID
@Service
open class StockOutLineService(
private val jdbcDao: JdbcDao,
@@ -184,7 +185,7 @@ val existingStockOutLine = stockOutLineRepository.findByPickOrderLineIdAndInvent
this.inventoryLotLine = inventoryLotLine
this.pickOrderLine = pickOrderLine
this.status = StockOutLineStatus.PENDING.status
this.type = "Nor"
this.type = "NOR"
}
val savedStockOutLine = saveAndFlush(stockOutLine)
createStockLedgerForStockOut(savedStockOutLine)
@@ -283,7 +284,7 @@ open fun createWithoutConso(request: CreateStockOutLineWithoutConsoRequest): Mes
this.inventoryLotLine = inventoryLotLine
this.pickOrderLine = updatedPickOrderLine
this.status = StockOutLineStatus.PENDING.status
this.type = "Nor"
this.type = "NOR"
}
println("Created stockOutLine with qty: ${request.qty}")
@@ -1167,7 +1168,9 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta
@Transactional(rollbackFor = [Exception::class])
open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
val startTime = System.currentTimeMillis()
val traceId = "BATCH-${UUID.randomUUID().toString().substring(0, 8)}"
println("=== BATCH SUBMIT START ===")
println("[$traceId] Batch submit request received")
println("Start time: ${java.time.LocalDateTime.now()}")
println("Request lines count: ${request.lines.size}")

@@ -1208,8 +1211,9 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {

// 3) Process each request line
request.lines.forEach { line: QrPickSubmitLineRequest ->
val lineTrace = "$traceId|SOL=${line.stockOutLineId}"
try {
println("Processing line: stockOutLineId=${line.stockOutLineId}, noLot=${line.noLot}")
println("[$lineTrace] Processing line, noLot=${line.noLot}")
if (line.noLot) {
// noLot branch
@@ -1219,7 +1223,7 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
qty = 0.0
))
processedIds += line.stockOutLineId
println(" ✓ noLot item processed")
println("[$lineTrace] noLot processed (status->completed, qty=0)")
return@forEach
}

@@ -1228,7 +1232,7 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
?: throw IllegalStateException("StockOutLine ${line.stockOutLineId} not found")
val currentStatus = stockOutLine.status?.trim()?.lowercase()
if (currentStatus == "completed" || currentStatus == "complete") {
println(" Skipping already completed stockOutLineId=${line.stockOutLineId}")
println("[$lineTrace] Skip because current status is already completed")
return@forEach
}
@@ -1236,19 +1240,19 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
val targetActual = line.actualPickQty ?: BigDecimal.ZERO
val required = line.requiredQty ?: BigDecimal.ZERO
println(" Current qty: $currentActual, Target qty: $targetActual, Required: $required")
println("[$lineTrace] currentActual=$currentActual, targetActual=$targetActual, required=$required")
// 计算增量(前端发送的是目标累计值)
val submitQty = targetActual - currentActual
println(" Submit qty (increment): $submitQty")
println("[$lineTrace] submitQty(increment)=$submitQty")
// 使用前端发送的状态,否则根据数量自动判断
val newStatus = line.stockOutLineStatus
?: if (targetActual >= required) "completed" else "partially_completed"
if (submitQty <= BigDecimal.ZERO) {
println(" Submit qty <= 0, only update status for stockOutLineId=${line.stockOutLineId}")
println("[$lineTrace] submitQty<=0, only update status, skip inventory+ledger")
updateStatus(
UpdateStockOutLineStatusRequest(
@@ -1274,6 +1278,7 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
skipInventoryWrite = true
)
)
println("[$lineTrace] stock_out_line qty/status updated with delta=$submitQty (inventory+ledger deferred)")

// Inventory updates - 修复:使用增量数量
// ✅ 修复:如果 inventoryLotLineId 为 null,从 stock_out_line 中获取
@@ -1282,7 +1287,7 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
// 在 newBatchSubmit 方法中,修改这部分代码(大约在 1169-1185 行)
if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) {
println(" Updating inventory lot line ${actualInventoryLotLineId} with qty $submitQty")
println("[$lineTrace] Updating inventory lot line $actualInventoryLotLineId with qty=$submitQty")
// ✅ 修复:在更新 inventory_lot_line 之前获取 inventory 的当前 onHandQty
val item = stockOutLine.item
@@ -1291,7 +1296,7 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) {
}
val onHandQtyBeforeUpdate = (inventoryBeforeUpdate?.onHandQty ?: BigDecimal.ZERO).toDouble()
println(" Inventory before update: onHandQty=$onHandQtyBeforeUpdate")
println("[$lineTrace] Inventory before update: onHandQty=$onHandQtyBeforeUpdate")
inventoryLotLineService.updateInventoryLotLineQuantities(
UpdateInventoryLotLineQuantitiesRequest(
@@ -1303,7 +1308,7 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) {
if (submitQty > BigDecimal.ZERO) {
// ✅ 修复:传入更新前的 onHandQty,让 createStockLedgerForPickDelta 使用它
createStockLedgerForPickDelta(line.stockOutLineId, submitQty, onHandQtyBeforeUpdate)
createStockLedgerForPickDelta(line.stockOutLineId, submitQty, onHandQtyBeforeUpdate, lineTrace)
}
} else if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId == null) {
// ✅ 修复:即使没有 inventoryLotLineId,也应该获取 inventory.onHandQty
@@ -1313,8 +1318,8 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) {
}
val onHandQtyBeforeUpdate = (inventoryBeforeUpdate?.onHandQty ?: BigDecimal.ZERO).toDouble()
println(" Warning: No inventoryLotLineId found, but creating stock ledger anyway for stockOutLineId ${line.stockOutLineId}")
createStockLedgerForPickDelta(line.stockOutLineId, submitQty, onHandQtyBeforeUpdate)
println("[$lineTrace] Warning: No inventoryLotLineId, still trying ledger creation")
createStockLedgerForPickDelta(line.stockOutLineId, submitQty, onHandQtyBeforeUpdate, lineTrace)
}
try {
val stockOutLine = stockOutLines[line.stockOutLineId]
@@ -1356,9 +1361,9 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) {
// 不中断主流程,只记录错误
}
processedIds += line.stockOutLineId
println(" Line processed successfully")
println("[$lineTrace] Line processed successfully")
} catch (e: Exception) {
println(" ✗ Error processing line ${line.stockOutLineId}: ${e.message}")
println("[$lineTrace] Error processing line: ${e.message}")
e.printStackTrace()
errors += "stockOutLineId=${line.stockOutLineId}: ${e.message}"
}
@@ -1534,31 +1539,44 @@ affectedConsoCodes.forEach { consoCode ->
private fun createStockLedgerForPickDelta(
stockOutLineId: Long,
deltaQty: BigDecimal,
onHandQtyBeforeUpdate: Double? = null // ✅ 新增参数:更新前的 onHandQty
onHandQtyBeforeUpdate: Double? = null, // ✅ 新增参数:更新前的 onHandQty
traceTag: String? = null
) {
if (deltaQty <= BigDecimal.ZERO) return
val tracePrefix = traceTag?.let { "[$it] " } ?: "[SOL=$stockOutLineId] "
if (deltaQty <= BigDecimal.ZERO) {
println("${tracePrefix}Skip ledger creation: deltaQty <= 0 (deltaQty=$deltaQty)")
return
}
val sol = stockOutLineRepository.findById(stockOutLineId).orElse(null) ?: return
val item = sol.item ?: return
val sol = stockOutLineRepository.findById(stockOutLineId).orElse(null)
if (sol == null) {
println("${tracePrefix}Skip ledger creation: stockOutLine not found")
return
}
val item = sol.item
if (item == null) {
println("${tracePrefix}Skip ledger creation: stockOutLine.item is null")
return
}
val inventory = inventoryRepository.findAllByItemIdAndDeletedIsFalse(item.id!!)
.firstOrNull() ?: return
// ✅ 修复:如果传入了 onHandQtyBeforeUpdate,使用它;否则回退到原来的逻辑
val previousBalance = if (onHandQtyBeforeUpdate != null) {
// 使用更新前的 onHandQty 作为基础
onHandQtyBeforeUpdate
} else {
// 回退逻辑:优先使用最新的 ledger balance,如果没有则使用 inventory.onHandQty
val latestLedger = stockLedgerRepository.findLatestByItemId(item.id!!).firstOrNull()
latestLedger?.balance ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()
.firstOrNull()
if (inventory == null) {
println("${tracePrefix}Skip ledger creation: inventory not found by itemId=${item.id}")
return
}
val previousBalance = resolvePreviousBalance(
itemId = item.id!!,
inventory = inventory,
onHandQtyBeforeUpdate = onHandQtyBeforeUpdate
)
val newBalance = previousBalance - deltaQty.toDouble()
println(" Creating stock ledger: previousBalance=$previousBalance, deltaQty=$deltaQty, newBalance=$newBalance")
println("${tracePrefix}Creating ledger: previousBalance=$previousBalance, deltaQty=$deltaQty, newBalance=$newBalance, inventoryId=${inventory.id}")
if (onHandQtyBeforeUpdate != null) {
println(" Using onHandQtyBeforeUpdate: $onHandQtyBeforeUpdate")
println("${tracePrefix}Using onHandQtyBeforeUpdate: $onHandQtyBeforeUpdate")
}
val ledger = StockLedger().apply {
@@ -1576,6 +1594,19 @@ affectedConsoCodes.forEach { consoCode ->
}
stockLedgerRepository.saveAndFlush(ledger)
println("${tracePrefix}Ledger created successfully for stockOutLineId=$stockOutLineId")
}

private fun resolvePreviousBalance(
itemId: Long,
inventory: Inventory,
onHandQtyBeforeUpdate: Double? = null
): Double {
if (onHandQtyBeforeUpdate != null) {
return onHandQtyBeforeUpdate
}
val latestLedger = stockLedgerRepository.findLatestByItemId(itemId).firstOrNull()
return latestLedger?.balance ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()
}

@Transactional(rollbackFor = [Exception::class])
@@ -1769,7 +1800,7 @@ open fun batchScan(request: com.ffii.fpsms.modules.stock.web.model.BatchScanRequ
this.inventoryLotLine = inventoryLotLine
this.pickOrderLine = pickOrderLine
this.status = StockOutLineStatus.CHECKED.status
this.type = "Nor"
this.type = "NOR"
this.startTime = LocalDateTime.now()
}

@@ -1933,9 +1964,10 @@ fun applyStockOutLineDelta(
val item = savedSol.item ?: return savedSol
val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return savedSol

// unified balance source: latest ledger first, fallback inventory.onHand
val latestLedger = stockLedgerRepository.findLatestByItemId(item.id!!).firstOrNull()
val previousBalance = latestLedger?.balance ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()
val previousBalance = resolvePreviousBalance(
itemId = item.id!!,
inventory = inventory
)
val outQty = deltaQty.toDouble()
val newBalance = previousBalance - outQty



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

@@ -493,7 +493,7 @@ open class SuggestedPickLotService(
this.qty = 0.0
this.status = StockOutLineStatus.PENDING.status
this.deleted = false
this.type = "Nor"
this.type = "NOR"
}
val savedStockOutLine = stockOutLIneRepository.save(stockOutLine)
@@ -543,7 +543,7 @@ open class SuggestedPickLotService(
this.inventoryLotLine = suggestedLotLine
this.pickOrderLine = updatedPickOrderLine
this.status = StockOutLineStatus.PENDING.status
this.type = "Nor"
this.type = "NOR"
}
val savedStockOutLine = stockOutLIneRepository.saveAndFlush(stockOutLine)


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

@@ -14,3 +14,16 @@ data class SaveEscalationLogResponse(
val status: String?,
val reason: String?,
)
enum class LedgerFailFactor {
INVALID_DELTA_QTY,
STOCK_OUT_LINE_NOT_FOUND,
ITEM_NOT_FOUND,
INVENTORY_NOT_FOUND,
LEDGER_SAVE_EXCEPTION,
UNKNOWN
}
data class LedgerCreateResult(
val success: Boolean,
val failFactor: LedgerFailFactor? = null,
val message: String? = null
)

+ 8
- 0
src/main/resources/db/changelog/changes/20260327_01_Enson/01_alter_stock_take.sql 查看文件

@@ -0,0 +1,8 @@
-- liquibase formatted sql
-- changeset Enson:alter_stock_in_line_acceptedQtyM18

ALTER TABLE `fpsmsdb`.`stock_in_line`
MODIFY COLUMN `acceptedQtyM18` DECIMAL(10,2);




Loading…
取消
儲存