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
+61 -5
View File
@@ -2,6 +2,7 @@ package handler
import (
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
@@ -25,10 +26,13 @@ func (h *FinanceHandler) ListRecords(c *gin.Context) {
shopID := middleware.GetShopID(c)
var q struct {
Type string `form:"type"`
Month string `form:"month"` // e.g. "2026-04"
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=50"`
Type string `form:"type"`
Month string `form:"month"` // e.g. "2026-04"
StartDate string `form:"start_date"` // YYYY-MM-DD(区间过滤,优先于 month)
EndDate string `form:"end_date"`
PartnerID uint64 `form:"partner_id"`
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=50"`
}
if err := c.ShouldBindQuery(&q); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -43,7 +47,18 @@ func (h *FinanceHandler) ListRecords(c *gin.Context) {
if q.Type != "" {
base = base.Where("type = ?", q.Type)
}
if q.Month != "" {
if q.PartnerID != 0 {
base = base.Where("partner_id = ?", q.PartnerID)
}
// 日期过滤:区间(本季/自定义)优先;否则按单月。日期串比较跨 MySQL/SQLite 可移植。
if q.StartDate != "" || q.EndDate != "" {
if q.StartDate != "" {
base = base.Where("record_date >= ?", q.StartDate)
}
if q.EndDate != "" {
base = base.Where("record_date <= ?", q.EndDate)
}
} else if q.Month != "" {
base = base.Where("DATE_FORMAT(record_date, '%Y-%m') = ?", q.Month)
}
@@ -161,6 +176,47 @@ func (h *FinanceHandler) CloseByRef(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// Trend GET /api/v1/finance/trend?months=6 — 近 N 个自然月收支趋势
// in=当月出库(销售)额、out=当月入库(采购)额;与 stock Summary 同口径
// (按 order_date、不筛状态、deleted_at IS NULL),日期串比较跨 MySQL/SQLite 可移植。
func (h *FinanceHandler) Trend(c *gin.Context) {
shopID := middleware.GetShopID(c)
months := 6
if v := c.Query("months"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n >= 1 && n <= 24 {
months = n
}
}
now := time.Now()
first := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
type point struct {
Month string `json:"month"`
In float64 `json:"in"`
Out float64 `json:"out"`
}
sum := func(table, from, to string) float64 {
var v float64
h.db.Table(table).
Where("shop_id = ? AND deleted_at IS NULL AND order_date >= ? AND order_date < ?",
shopID, from, to).
Select("COALESCE(SUM(total_amount),0)").Scan(&v)
return v
}
const f = "2006-01-02"
points := make([]point, 0, months)
for i := months - 1; i >= 0; i-- {
mStart := first.AddDate(0, -i, 0)
mEnd := mStart.AddDate(0, 1, 0)
points = append(points, point{
Month: mStart.Format("2006-01"),
In: sum("stock_out_orders", mStart.Format(f), mEnd.Format(f)),
Out: sum("stock_in_orders", mStart.Format(f), mEnd.Format(f)),
})
}
c.JSON(http.StatusOK, gin.H{"data": points})
}
// Summary GET /api/v1/finance/summary — 按往来单位汇总未结清
func (h *FinanceHandler) Summary(c *gin.Context) {
shopID := middleware.GetShopID(c)