feat(backend): 出入库定价字段消歧 + 总利润落库 + 成本仅管理员可见
- 字段重命名(旧列弃用保留,启动幂等回填 BackfillPricingColumns): 入库 unit_price/total_price/total_amount → cost_price/cost_amount/cost_total; 出库同前缀 + 新增 sale_amount(售价小计)/ sale_total(应收)/ profit_total(总利润) - 建单落三值;确认售价联动重算 sale_amount/sale_total/profit_total; 确认进价回填成本后联动重算受影响出库单利润(recalcStockOutProfit) - 出库 List/Get 对 operator/readonly 抹零成本与利润(stripStockOutCost 服务端兜底) - 兼容一版:Create/Update 接受旧 key unit_price 回落(v1.0.87 及之前客户端) - SUM 聚合/价格趋势/导入/种子工具同步切新列;测试全量改名 + 6 个新回归 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
@@ -497,7 +497,7 @@ func (ic *importCtx) writeOrder(
|
||||
TenantBase: model.TenantBase{ShopID: ic.shopID},
|
||||
OrderNo: orderNo, Type: orderType, WarehouseID: ic.whID,
|
||||
PartnerID: partnerID, OperatorID: operatorID, CreatorID: creatorID,
|
||||
ReviewerID: reviewerID, Status: status, OrderDate: orderDate, TotalAmount: totalAmount,
|
||||
ReviewerID: reviewerID, Status: status, OrderDate: orderDate, CostTotal: totalAmount,
|
||||
}
|
||||
if err := tx.Create(&o).Error; err != nil {
|
||||
return err
|
||||
@@ -510,8 +510,8 @@ func (ic *importCtx) writeOrder(
|
||||
Series: get(dr, dCols, "系列"),
|
||||
Spec: get(dr, dCols, "规格"),
|
||||
Quantity: parseFloatLoose(get(dr, dCols, "数量")),
|
||||
UnitPrice: parseFloatLoose(get(dr, dCols, "单价")),
|
||||
TotalPrice: parseFloatLoose(get(dr, dCols, "金额")),
|
||||
CostPrice: parseFloatLoose(get(dr, dCols, "单价")),
|
||||
CostAmount: parseFloatLoose(get(dr, dCols, "金额")),
|
||||
BatchNo: get(dr, dCols, "批次号"),
|
||||
}
|
||||
it.ProductionDate = parseDatePtr(get(dr, dCols, "生产日期"))
|
||||
@@ -529,7 +529,8 @@ func (ic *importCtx) writeOrder(
|
||||
TenantBase: model.TenantBase{ShopID: ic.shopID},
|
||||
OrderNo: orderNo, Type: orderType, WarehouseID: ic.whID,
|
||||
PartnerID: partnerID, OperatorID: operatorID, CreatorID: creatorID,
|
||||
ReviewerID: reviewerID, Status: status, OrderDate: orderDate, TotalAmount: totalAmount,
|
||||
// 忠实导入:源「金额」合计沿旧行为落应收合计(sale_price=0 待定价)
|
||||
ReviewerID: reviewerID, Status: status, OrderDate: orderDate, SaleTotal: totalAmount,
|
||||
}
|
||||
if err := tx.Create(&o).Error; err != nil {
|
||||
return err
|
||||
@@ -542,8 +543,8 @@ func (ic *importCtx) writeOrder(
|
||||
Series: get(dr, dCols, "系列"),
|
||||
Spec: get(dr, dCols, "规格"),
|
||||
Quantity: parseFloatLoose(get(dr, dCols, "数量")),
|
||||
UnitPrice: parseFloatLoose(get(dr, dCols, "单价")),
|
||||
TotalPrice: parseFloatLoose(get(dr, dCols, "金额")),
|
||||
CostPrice: parseFloatLoose(get(dr, dCols, "单价")),
|
||||
CostAmount: parseFloatLoose(get(dr, dCols, "金额")),
|
||||
BatchNo: get(dr, dCols, "批次号"),
|
||||
}
|
||||
it.ProductionDate = parseDatePtr(get(dr, dCols, "生产日期"))
|
||||
|
||||
+18
-15
@@ -408,14 +408,14 @@ func main() {
|
||||
// 财务记录(与已审核出库单对应的应收款,按售价口径)
|
||||
// ═══════════════════════════════════════════════════
|
||||
createFinanceRecord(db, shop.ID, partners["CUS001"].ID, admin.ID,
|
||||
"receivable", out1.order.TotalAmount, out1.order.TotalAmount, "stock_out", out1.order.ID,
|
||||
"receivable", out1.order.SaleTotal, out1.order.SaleTotal, "stock_out", out1.order.ID,
|
||||
d(3), "君悦大酒店4月供货应收款")
|
||||
createFinanceRecord(db, shop.ID, partners["CUS002"].ID, admin.ID,
|
||||
"receivable", out2.order.TotalAmount, out2.order.TotalAmount, "stock_out", out2.order.ID,
|
||||
"receivable", out2.order.SaleTotal, out2.order.SaleTotal, "stock_out", out2.order.ID,
|
||||
d(2), "外滩华尔道夫进口酒应收款")
|
||||
// 模拟一笔已收款
|
||||
createFinanceRecord(db, shop.ID, partners["CUS001"].ID, admin.ID,
|
||||
"receipt", out1.order.TotalAmount, 0, "stock_out", out1.order.ID,
|
||||
"receipt", out1.order.SaleTotal, 0, "stock_out", out1.order.ID,
|
||||
d(1), "君悦大酒店回款,结清")
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
@@ -556,19 +556,19 @@ func (s *seeder) createStockIn(
|
||||
Series: g.series,
|
||||
Spec: g.spec,
|
||||
Quantity: ln.qty,
|
||||
UnitPrice: ln.price,
|
||||
TotalPrice: ln.qty * ln.price,
|
||||
CostPrice: ln.price,
|
||||
CostAmount: ln.qty * ln.price,
|
||||
BatchNo: ln.batch,
|
||||
ProductionDate: ln.prodDate,
|
||||
Remark: ln.remark,
|
||||
}
|
||||
s.db.Create(&item)
|
||||
total += item.TotalPrice
|
||||
total += item.CostAmount
|
||||
items = append(items, item)
|
||||
prods = append(prods, p)
|
||||
}
|
||||
s.db.Model(&o).Update("total_amount", total)
|
||||
o.TotalAmount = total
|
||||
s.db.Model(&o).Update("cost_total", total)
|
||||
o.CostTotal = total
|
||||
|
||||
fmt.Printf("✅ 入库单:%s [%s] 供应商ID=%d 仓库=%s 行数=%d 金额=%.0f\n",
|
||||
orderNo, status, partnerID, whName, len(lines), total)
|
||||
@@ -604,7 +604,7 @@ func (s *seeder) applyStockInToInventory(r stockInResult) {
|
||||
whIDCopy := r.order.WarehouseID
|
||||
pidCopy := it.ProductID
|
||||
itemIDCopy := it.ID
|
||||
price := it.UnitPrice
|
||||
price := it.CostPrice
|
||||
inv := model.Inventory{
|
||||
ShopID: s.shopID,
|
||||
WarehouseID: &whIDCopy,
|
||||
@@ -664,7 +664,7 @@ func (s *seeder) createStockOut(
|
||||
s.db.Create(&o)
|
||||
|
||||
var (
|
||||
total float64 // 应收 = Σ 售价×数量
|
||||
total, profit float64 // 应收 = Σ 售价×数量;利润 = Σ(售价-成本)×数量
|
||||
items []model.StockOutItem
|
||||
)
|
||||
for _, ln := range lines {
|
||||
@@ -678,18 +678,21 @@ func (s *seeder) createStockOut(
|
||||
Series: p.Series,
|
||||
Spec: p.Spec,
|
||||
Quantity: ln.qty,
|
||||
UnitPrice: p.PurchasePrice, // 成本单价(快照)
|
||||
SalePrice: ln.sale, // 售价
|
||||
TotalPrice: ln.qty * p.PurchasePrice, // 成本小计
|
||||
CostPrice: p.PurchasePrice, // 成本单价(快照)
|
||||
SalePrice: ln.sale, // 售价
|
||||
CostAmount: ln.qty * p.PurchasePrice, // 成本小计
|
||||
SaleAmount: ln.qty * ln.sale, // 售价小计
|
||||
BatchNo: p.BatchNo,
|
||||
Remark: ln.remark,
|
||||
}
|
||||
s.db.Create(&item)
|
||||
total += ln.qty * ln.sale
|
||||
profit += (ln.sale - p.PurchasePrice) * ln.qty
|
||||
items = append(items, item)
|
||||
}
|
||||
s.db.Model(&o).Update("total_amount", total)
|
||||
o.TotalAmount = total
|
||||
s.db.Model(&o).Updates(map[string]interface{}{"sale_total": total, "profit_total": profit})
|
||||
o.SaleTotal = total
|
||||
o.ProfitTotal = profit
|
||||
|
||||
fmt.Printf("✅ 出库单:%s [%s] 客户ID=%d 仓库ID=%d 行数=%d 应收=%.0f\n",
|
||||
orderNo, status, partnerID, whID, len(lines), total)
|
||||
|
||||
Reference in New Issue
Block a user