Files
wangjia 4fd0bb8b83 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
2026-07-03 12:44:22 +08:00

114 lines
3.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wangjia/jiu/backend/internal/model"
"github.com/wangjia/jiu/backend/testutil"
)
// Trend:近 N 月按 order_date 聚合出库(in)/入库(out)金额,多租户隔离
func TestFinanceHandler_Trend(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "FIN001")
other := testutil.CreateTestShop(db, "FIN001B")
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Main")
token := getAuthToken(user.ID, shop.ID, "admin")
r := setupProtectedRouter(db)
now := time.Now()
thisMonth := time.Date(now.Year(), now.Month(), 5, 0, 0, 0, 0, time.UTC)
lastMonth := thisMonth.AddDate(0, -1, 0)
mkOut := func(shopID uint64, date time.Time, amount float64) {
require.NoError(t, db.Create(&model.StockOutOrder{
TenantBase: model.TenantBase{ShopID: shopID},
OrderNo: fmt.Sprintf("CK-%d-%d", shopID, time.Now().UnixNano()),
WarehouseID: warehouse.ID,
OperatorID: user.ID,
Status: "approved",
OrderDate: model.Date{Time: date},
SaleTotal: amount,
}).Error)
}
mkIn := func(shopID uint64, date time.Time, amount float64) {
require.NoError(t, db.Create(&model.StockInOrder{
TenantBase: model.TenantBase{ShopID: shopID},
OrderNo: fmt.Sprintf("RK-%d-%d", shopID, time.Now().UnixNano()),
WarehouseID: warehouse.ID,
OperatorID: user.ID,
Status: "approved",
OrderDate: model.Date{Time: date},
CostTotal: amount,
}).Error)
}
mkOut(shop.ID, thisMonth, 1000)
mkOut(shop.ID, thisMonth, 860)
mkOut(shop.ID, lastMonth, 500)
mkIn(shop.ID, thisMonth, 700)
mkOut(other.ID, thisMonth, 99999) // 别店数据不得串店
w := makeRequest(r, "GET", "/api/v1/finance/trend?months=2", token, nil)
require.Equal(t, http.StatusOK, w.Code)
data := parseResponse(w)["data"].([]interface{})
require.Len(t, data, 2)
prev := data[0].(map[string]interface{})
cur := data[1].(map[string]interface{})
assert.Equal(t, lastMonth.Format("2006-01"), prev["month"])
assert.Equal(t, float64(500), prev["in"])
assert.Equal(t, float64(0), prev["out"])
assert.Equal(t, thisMonth.Format("2006-01"), cur["month"])
assert.Equal(t, float64(1860), cur["in"])
assert.Equal(t, float64(700), cur["out"])
}
// ListRecords 日期区间:start_date/end_date 过滤 record_date(区间优先于 month
func TestFinanceHandler_ListRecords_DateRange(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "FIN002")
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
token := getAuthToken(user.ID, shop.ID, "admin")
r := setupProtectedRouter(db)
mk := func(date string, amount float64) {
d, err := time.Parse("2006-01-02", date)
require.NoError(t, err)
require.NoError(t, db.Create(&model.FinanceRecord{
ShopID: shop.ID,
Type: "receipt",
Amount: amount,
Status: "closed",
OperatorID: user.ID,
RecordDate: d,
}).Error)
}
mk("2026-04-10", 100)
mk("2026-05-15", 200)
mk("2026-06-20", 300)
// 区间 [2026-05-01, 2026-06-30] → 命中 2 条
w := makeRequest(r, "GET",
"/api/v1/finance/records?start_date=2026-05-01&end_date=2026-06-30", token, nil)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, float64(2), parseResponse(w)["total"].(float64))
// 只给 start → 下界过滤
w = makeRequest(r, "GET",
"/api/v1/finance/records?start_date=2026-06-01", token, nil)
assert.Equal(t, float64(1), parseResponse(w)["total"].(float64))
// 区间优先于 monthmonth 会被忽略)
w = makeRequest(r, "GET",
"/api/v1/finance/records?start_date=2026-04-01&end_date=2026-04-30&month=2026-06",
token, nil)
assert.Equal(t, float64(1), parseResponse(w)["total"].(float64))
}