入库金额修改后重新计算平均单价逻辑梳理.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
正确的逻辑应该是:
- 获取所有有效库存记录(包括已修改的记录)
- 对于每个库存记录,计算单价:
- 优先使用
FinalAmount / Quantity - 其次使用
PurchaseUnitPrice - 最后使用
Product.Price
- 优先使用
- 计算所有库存的加权平均单价
- 更新产品的
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(当前实现)+ 优化
理由:
- 简单有效:基于总库存重新计算,逻辑清晰
- 一致性:所有库存都参与计算,保证数据一致性
- 准确性:如果修改了入库金额,剩余可用库存的平均单价会正确更新
优化点:
- 单价计算优先级:确保优先使用
FinalAmount / Quantity - 金额为0的处理:如果
FinalAmount为0,应该跳过或使用其他字段 - 日志记录:记录重新计算的原因和结果
实现建议
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,会跳过,使用 PurchaseUnitPrice 或 Product.Price,这是正确的。
2. 确保更新时触发重新计算
当前实现:
UpdateAsync方法中已经调用了RecalculateProductAveragePriceAsync- 但是,只有在数量或单价变化时才调用
优化建议:
- 如果
FinalAmount发生变化,也应该触发重新计算 - 需要比较修改前后的值
3. 添加日志记录
建议:
- 记录重新计算的原因(金额修改、数量修改等)
- 记录计算前后的平均单价
- 便于问题排查和数据审计
测试场景
场景1:入库时金额为0,后来修改为20
步骤:
- 入库10个,金额0(FinalAmount = 0)
- 此时平均单价应该使用
PurchaseUnitPrice或Product.Price - 修改入库金额为20(FinalAmount = 20)
- 重新计算平均单价:
新平均单价 = 20 / 10 = 2 - 更新产品的
F_AveragePrice = 2
场景2:多个库存记录,修改其中一个的金额
步骤:
- 库存A:10个,金额100(单价10)
- 库存B:5个,金额0(单价0,使用Product.Price = 15)
- 当前平均单价 = (100 + 0) / (10 + 5) = 6.67(如果FinalAmount为0,使用Product.Price) 或 = (100 + 75) / (10 + 5) = 11.67(如果使用Product.Price)
- 修改库存B的金额为50(单价10)
- 重新计算:新平均单价 = (100 + 50) / (10 + 5) = 10
场景3:已领取部分库存后,修改入库金额
步骤:
- 入库10个,金额100(单价10)
- 当前平均单价 = 10
- 领取5个,使用平均单价10,领取金额50
- 剩余5个可用库存
- 修改入库金额为200(单价20)
- 重新计算:新平均单价 = 200 / 10 = 20
- 后续领取会使用新的平均单价20
总结
- 当前实现正确:
RecalculateProductAveragePriceAsync方法基于总库存重新计算平均单价 - 金额为0处理正确:如果
FinalAmount为0,会跳过,使用PurchaseUnitPrice或Product.Price - 修改金额自动触发:
UpdateAsync方法在更新库存后总是调用RecalculateProductAveragePriceAsync - 已添加日志记录:记录重新计算的原因和计算前后的平均单价,便于问题排查和数据审计
代码优化
已完成的优化
添加日志记录:
- 记录重新计算的原因(数量变化、最终金额变化、采购单价变化等)
- 记录计算前后的平均单价
- 便于问题排查和数据审计
优化注释:
- 更新注释,说明"更新库存后总是重新计算平均单价"
- 明确说明使用加权平均法
代码优化:
- 在事务外获取修改前的平均单价,避免在事务中重复查询
- 优化代码结构,提高可读性
验证建议
建议测试以下场景:
场景1:入库时金额为0,后来修改为20
- 验证:平均单价是否正确更新
- 验证:日志是否正确记录
场景2:多个库存记录,修改其中一个的金额
- 验证:平均单价是否正确重新计算
- 验证:其他库存记录不受影响
场景3:已领取部分库存后,修改入库金额
- 验证:平均单价是否正确更新
- 验证:后续领取使用新的平均单价