feat(backend): 出入库搜索重构 + 商品编码搜索 + KPI/确认售价/详细搜索
- 搜索 keyword 命中改为 单号/酒名(名+拼音)/商品编码,去掉往来单位匹配 - applyStockOrderDetailFilters 合并明细过滤为单条子查询(同一明细语义)+ 新增 product_code - 新增出入库 KPI summary、出库确认售价(confirm-sale) 及路由 - 补充/更新对应单元测试 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,12 @@ type CostConfirmItem struct {
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
}
|
||||
|
||||
// SaleConfirmItem 出库「确认售价」的一条明细(先出后定价)。
|
||||
type SaleConfirmItem struct {
|
||||
ItemID uint64 `json:"item_id"`
|
||||
SalePrice float64 `json:"sale_price"`
|
||||
}
|
||||
|
||||
// ConfirmStockInCost 「确认进价」:调货等场景以 0 价(暂估)入库并已审核/已出库后,
|
||||
// 价格确定时补填真实进价。不反审核、不撤销任何已发生的动作,而是在一个事务内
|
||||
// 前向写补偿:
|
||||
@@ -262,6 +268,63 @@ func (s *StockService) ConfirmStockInCost(shopID, orderID, userID uint64, role s
|
||||
})
|
||||
}
|
||||
|
||||
// ConfirmStockOutSale 出库「先出后定价」:对 sale_price<=0 的明细写真实售价,
|
||||
// 重算应收(total_amount=Σ售价×数量)并补应收差额流水。仅 admin/superadmin、已审核单可确认。
|
||||
func (s *StockService) ConfirmStockOutSale(shopID, orderID, userID uint64, role string, items []SaleConfirmItem) error {
|
||||
if role != "admin" && role != "superadmin" {
|
||||
return ErrForbidden
|
||||
}
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
var order model.StockOutOrder
|
||||
if err := tx.Preload("Items").Where("id = ? AND shop_id = ?", orderID, shopID).
|
||||
First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.Status != "approved" {
|
||||
return errors.New("只有已审核单据可确认售价")
|
||||
}
|
||||
|
||||
want := make(map[uint64]float64, len(items))
|
||||
for _, it := range items {
|
||||
want[it.ItemID] = it.SalePrice
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
var totalDiff float64
|
||||
for i := range order.Items {
|
||||
it := &order.Items[i]
|
||||
newPrice, ok := want[it.ID]
|
||||
if !ok || newPrice == it.SalePrice {
|
||||
continue
|
||||
}
|
||||
diff := (newPrice - it.SalePrice) * it.Quantity
|
||||
totalDiff += diff
|
||||
if err := tx.Model(it).Update("sale_price", newPrice).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if totalDiff == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 应收总额按差额重算
|
||||
if err := tx.Model(&order).
|
||||
Update("total_amount", gorm.Expr("total_amount + ?", totalDiff)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 应收差额调整流水(滚动余额,与出库审核建应收同口径 last+amount)
|
||||
bal := partnerLastBalance(tx, shopID, order.PartnerID) + totalDiff
|
||||
oid := order.ID
|
||||
return tx.Create(&model.FinanceRecord{
|
||||
ShopID: shopID, PartnerID: order.PartnerID, Type: "receivable",
|
||||
Amount: totalDiff, Balance: bal, Status: "open",
|
||||
RefType: "stock_out_sale_adjust", RefID: &oid, OperatorID: userID, RecordDate: now,
|
||||
}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// ApproveStockOut 审核出库单,FIFO 扣减批次库存
|
||||
func (s *StockService) ApproveStockOut(shopID, orderID, reviewerID uint64) error {
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
Reference in New Issue
Block a user