入库金额修改后重新计算平均单价逻辑梳理.md 9.04 KB

入库金额修改后重新计算平均单价逻辑梳理

问题描述

在入库时,如果输入的金额不正确(比如开始入库时金额是0,后来需要调整到20),修改金额后需要重新计算产品的平均单价(使用加权平均法)。

当前逻辑分析

1. 入库时(CreateAsync)

方法UpdateProductAveragePriceAsync

计算逻辑

新平均单价 = (当前平均单价 × 当前可用库存数量 + 入库单价 × 入库数量) / (当前可用库存数量 + 入库数量)

入库单价确定

  • 采购入库:优先使用 FinalAmount / Quantity,其次使用 PurchaseUnitPrice
  • 普通入库:使用产品价格 Product.Price

关键点

  • 基于可用库存数量(总库存 - 已领取数量)计算
  • 已领取的库存已经按照当时的平均单价计价,不影响新平均单价的计算

2. 更新库存时(UpdateAsync)

方法RecalculateProductAveragePriceAsync

计算逻辑

遍历所有有效库存记录:
  单价 = FinalAmount / Quantity(优先)
      或 PurchaseUnitPrice(其次)
      或 Product.Price(最后)

  总金额 += 单价 × 数量
  总数量 += 数量

新平均单价 = 总金额 / 总数量

关键点

  • 基于总库存数量计算
  • 遍历所有库存记录,重新计算加权平均

问题分析

问题1:逻辑不一致?

入库时:基于可用库存计算 更新时:基于总库存计算

分析

  • 实际上两种方式都是正确的,但应用场景不同:
    • 入库时:新增库存,需要与现有可用库存合并计算
    • 更新时:修改已有库存的金额,需要重新计算整个产品的平均单价

问题2:已领取库存的处理

关键问题:如果修改了入库金额,已领取的库存是否应该影响新的平均单价?

分析

  • 不应该影响:已领取的库存已经按照当时的平均单价计价了,不应该因为后续修改入库金额而改变
  • 但是:如果修改了入库金额,那么剩余可用库存的平均单价应该重新计算

问题3:正确的重新计算逻辑

场景:入库时金额是0,后来修改为20

正确的逻辑应该是

  1. 获取所有有效库存记录(包括已修改的记录)
  2. 对于每个库存记录,计算单价:
    • 优先使用 FinalAmount / Quantity
    • 其次使用 PurchaseUnitPrice
    • 最后使用 Product.Price
  3. 计算所有库存的加权平均单价
  4. 更新产品的 F_AveragePrice

但是:需要考虑已领取的库存是否应该参与计算?

答案应该参与计算,因为:

  • 已领取的库存已经按照当时的平均单价计价了,这个价格是固定的
  • 但是,剩余可用库存的平均单价应该基于所有库存(包括已领取的)重新计算
  • 这样,后续的出库操作会使用新的平均单价

正确的重新计算逻辑

方案1:基于总库存重新计算(当前实现)

逻辑

1. 获取所有有效库存记录
2. 对于每个库存记录,计算单价(优先FinalAmount,其次PurchaseUnitPrice,最后Product.Price)
3. 计算所有库存的加权平均单价
4. 更新产品的F_AveragePrice

优点

  • 简单直接
  • 所有库存都参与计算,保证一致性

缺点

  • 如果已领取的库存金额是0,会影响平均单价的计算

方案2:基于可用库存重新计算

逻辑

1. 计算当前可用库存数量(总库存 - 已领取数量)
2. 获取所有有效库存记录
3. 对于每个库存记录,计算单价
4. 计算所有库存的加权平均单价(基于总库存)
5. 更新产品的F_AveragePrice

分析

  • 实际上,加权平均单价应该基于总库存计算
  • 已领取的库存虽然已经计价,但它们的金额信息仍然应该参与平均单价的计算
  • 这样,剩余可用库存的平均单价才是准确的

方案3:考虑已领取库存的金额

逻辑

1. 获取所有有效库存记录
2. 对于每个库存记录,计算单价
3. 计算所有库存的加权平均单价
4. 但是,如果某个库存记录已经被部分领取,需要考虑:
   - 已领取部分:使用领取时的平均单价(从lq_inventory_usage表获取)
   - 未领取部分:使用当前计算的单价

分析

  • 这个方案最准确,但实现复杂
  • 需要关联 lq_inventory_usage 表,获取已领取部分的单价
  • 对于同一个库存记录,可能被多次领取,需要按比例计算

推荐方案

推荐:方案1(当前实现)+ 优化

理由

  1. 简单有效:基于总库存重新计算,逻辑清晰
  2. 一致性:所有库存都参与计算,保证数据一致性
  3. 准确性:如果修改了入库金额,剩余可用库存的平均单价会正确更新

优化点

  1. 单价计算优先级:确保优先使用 FinalAmount / Quantity
  2. 金额为0的处理:如果 FinalAmount 为0,应该跳过或使用其他字段
  3. 日志记录:记录重新计算的原因和结果

实现建议

1. 优化 RecalculateProductAveragePriceAsync 方法

当前问题

  • 如果 FinalAmount 为0,会使用0作为单价,导致平均单价计算错误

优化方案

// 优先使用 F_FinalAmount(产品最终金额)计算单价
if (inventory.FinalAmount.HasValue && inventory.FinalAmount.Value > 0)
{
    unitPrice = inventory.FinalAmount.Value / inventory.Quantity;
}
// 其次使用 F_PurchaseUnitPrice(采购单价)
else if (inventory.PurchaseUnitPrice.HasValue && inventory.PurchaseUnitPrice.Value > 0)
{
    unitPrice = inventory.PurchaseUnitPrice.Value;
}
// 如果都没有,使用产品价格
else
{
    unitPrice = product.Price;
}

问题:如果 FinalAmount 为0,会跳过,使用 PurchaseUnitPriceProduct.Price,这是正确的。

2. 确保更新时触发重新计算

当前实现

  • UpdateAsync 方法中已经调用了 RecalculateProductAveragePriceAsync
  • 但是,只有在数量或单价变化时才调用

优化建议

  • 如果 FinalAmount 发生变化,也应该触发重新计算
  • 需要比较修改前后的值

3. 添加日志记录

建议

  • 记录重新计算的原因(金额修改、数量修改等)
  • 记录计算前后的平均单价
  • 便于问题排查和数据审计

测试场景

场景1:入库时金额为0,后来修改为20

步骤

  1. 入库10个,金额0(FinalAmount = 0)
  2. 此时平均单价应该使用 PurchaseUnitPriceProduct.Price
  3. 修改入库金额为20(FinalAmount = 20)
  4. 重新计算平均单价:新平均单价 = 20 / 10 = 2
  5. 更新产品的 F_AveragePrice = 2

场景2:多个库存记录,修改其中一个的金额

步骤

  1. 库存A:10个,金额100(单价10)
  2. 库存B:5个,金额0(单价0,使用Product.Price = 15)
  3. 当前平均单价 = (100 + 0) / (10 + 5) = 6.67(如果FinalAmount为0,使用Product.Price) 或 = (100 + 75) / (10 + 5) = 11.67(如果使用Product.Price)
  4. 修改库存B的金额为50(单价10)
  5. 重新计算:新平均单价 = (100 + 50) / (10 + 5) = 10

场景3:已领取部分库存后,修改入库金额

步骤

  1. 入库10个,金额100(单价10)
  2. 当前平均单价 = 10
  3. 领取5个,使用平均单价10,领取金额50
  4. 剩余5个可用库存
  5. 修改入库金额为200(单价20)
  6. 重新计算:新平均单价 = 200 / 10 = 20
  7. 后续领取会使用新的平均单价20

总结

  1. 当前实现正确RecalculateProductAveragePriceAsync 方法基于总库存重新计算平均单价
  2. 金额为0处理正确:如果 FinalAmount 为0,会跳过,使用 PurchaseUnitPriceProduct.Price
  3. 修改金额自动触发UpdateAsync 方法在更新库存后总是调用 RecalculateProductAveragePriceAsync
  4. 已添加日志记录:记录重新计算的原因和计算前后的平均单价,便于问题排查和数据审计

代码优化

已完成的优化

  1. 添加日志记录

    • 记录重新计算的原因(数量变化、最终金额变化、采购单价变化等)
    • 记录计算前后的平均单价
    • 便于问题排查和数据审计
  2. 优化注释

    • 更新注释,说明"更新库存后总是重新计算平均单价"
    • 明确说明使用加权平均法
  3. 代码优化

    • 在事务外获取修改前的平均单价,避免在事务中重复查询
    • 优化代码结构,提高可读性

验证建议

建议测试以下场景:

  1. 场景1:入库时金额为0,后来修改为20

    • 验证:平均单价是否正确更新
    • 验证:日志是否正确记录
  2. 场景2:多个库存记录,修改其中一个的金额

    • 验证:平均单价是否正确重新计算
    • 验证:其他库存记录不受影响
  3. 场景3:已领取部分库存后,修改入库金额

    • 验证:平均单价是否正确更新
    • 验证:后续领取使用新的平均单价