feat(backend): 出入库编码搜索双路匹配+建单填快照;finance 趋势/日期区间;partner/product 拼音搜索

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
wangjia
2026-07-02 22:27:13 +08:00
parent af56055eef
commit 9099c4af99
21 changed files with 610 additions and 39 deletions
+24 -2
View File
@@ -77,6 +77,11 @@ func (h *InventoryHandler) List(c *gin.Context) {
like := "%" + keyword + "%"
args = append(args, like, like, like, like)
}
// 商品编码独立筛选(库存屏工具栏第二个搜索框,对齐原型 codeInput)
if code := c.Query("code"); code != "" {
baseWhere += " AND COALESCE(NULLIF(p.code,''), NULLIF(sii.product_code,''), inv.product_code, '') LIKE ?"
args = append(args, "%"+code+"%")
}
if warehouseIDStr != "" {
baseWhere += " AND inv.warehouse_id = ?"
args = append(args, warehouseIDStr)
@@ -167,24 +172,41 @@ type inventorySummary struct {
InStockQty float64 `json:"in_stock_qty"` // 在库总数量
ShortageCount int64 `json:"shortage_count"` // 缺货数(qty<=0
WarningCount int64 `json:"warning_count"` // 预警数(0<qty<安全库存)
// 月初快照(供 KPI 环比):本月净变动由 inventory_logs 反推
LastMonthSku int64 `json:"last_month_sku"` // 月初存在的库存行数
LastMonthQty float64 `json:"last_month_qty"` // 月初在库总数量
LastMonthValue float64 `json:"last_month_value"` // 月初库存货值
}
// Summary GET /api/v1/inventory/summary —— 全店汇总(守多租户)
func (h *InventoryHandler) Summary(c *gin.Context) {
shopID := middleware.GetShopID(c)
now := time.Now()
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
// 月初数量 = 当前数量 − 本月净变动(product=序列号模型下 log 与库存行按 product_id+warehouse_id 一一对应)
const sql = `
SELECT
COUNT(*) AS sku_count,
COALESCE(SUM(inv.quantity * COALESCE(sii.unit_price, inv.unit_price, p.purchase_price)), 0) AS stock_value,
COALESCE(SUM(inv.quantity), 0) AS in_stock_qty,
COALESCE(SUM(CASE WHEN inv.quantity <= 0 THEN 1 ELSE 0 END), 0) AS shortage_count,
COALESCE(SUM(CASE WHEN inv.quantity > 0 AND p.min_stock IS NOT NULL AND inv.quantity < p.min_stock THEN 1 ELSE 0 END), 0) AS warning_count
COALESCE(SUM(CASE WHEN inv.quantity > 0 AND p.min_stock IS NOT NULL AND inv.quantity < p.min_stock THEN 1 ELSE 0 END), 0) AS warning_count,
COALESCE(SUM(CASE WHEN inv.created_at < ? THEN 1 ELSE 0 END), 0) AS last_month_sku,
COALESCE(SUM(CASE WHEN inv.created_at < ? THEN inv.quantity - COALESCE(lg.net, 0) ELSE 0 END), 0) AS last_month_qty,
COALESCE(SUM(CASE WHEN inv.created_at < ? THEN (inv.quantity - COALESCE(lg.net, 0)) * COALESCE(sii.unit_price, inv.unit_price, p.purchase_price) ELSE 0 END), 0) AS last_month_value
FROM inventories inv
LEFT JOIN stock_in_items sii ON sii.id = inv.stock_in_item_id
LEFT JOIN products p ON p.id = inv.product_id AND p.deleted_at IS NULL
LEFT JOIN (
SELECT product_id, warehouse_id,
SUM(CASE WHEN direction = 'in' THEN quantity ELSE -quantity END) AS net
FROM inventory_logs
WHERE shop_id = ? AND created_at >= ?
GROUP BY product_id, warehouse_id
) lg ON lg.product_id = inv.product_id AND lg.warehouse_id = inv.warehouse_id
WHERE inv.shop_id = ? AND inv.deleted_at IS NULL`
var s inventorySummary
if err := h.db.Raw(sql, shopID).Scan(&s).Error; err != nil {
if err := h.db.Raw(sql, monthStart, monthStart, monthStart, shopID, monthStart, shopID).Scan(&s).Error; err != nil {
util.RespondError(c, http.StatusInternalServerError, "QUERY_ERROR", "查询失败")
return
}