feat(backend): 商品价格历史接口(从入库单价派生)

GET /products/:id/price-history:从已审核入库单明细 unit_price 按 order_date
倒序派生进价变更点,去相邻重复价,守多租户 shop_id(零额外表)。
新增测试 PriceHistory(含草稿排除 + 倒序断言)。go build/vet/test 全过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
This commit is contained in:
wangjia
2026-06-25 16:06:56 +08:00
parent 38e2ea1c6c
commit deb5e503f6
4 changed files with 74 additions and 0 deletions
+39
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -266,3 +267,41 @@ func TestProductHandler_UniqueShopCode(t *testing.T) {
require.Error(t, err)
assert.True(t, errors.Is(err, gorm.ErrDuplicatedKey))
}
func TestProductHandler_PriceHistory(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PH001")
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
wh := testutil.CreateTestWarehouse(db, shop.ID, "W")
product := testutil.CreateTestProduct(db, shop.ID, "Maotai")
token := getAuthToken(user.ID, shop.ID, "admin")
r := setupProtectedRouter(db)
mkOrder := func(no string, daysAgo int, price float64, status string) {
o := &model.StockInOrder{
TenantBase: model.TenantBase{ShopID: shop.ID},
OrderNo: no,
WarehouseID: wh.ID,
OperatorID: user.ID,
Status: status,
OrderDate: model.Date{Time: time.Now().AddDate(0, 0, -daysAgo)},
}
require.NoError(t, db.Create(o).Error)
require.NoError(t, db.Create(&model.StockInItem{
OrderID: o.ID, ShopID: shop.ID, ProductID: product.ID,
UnitPrice: price, Quantity: 1,
}).Error)
}
mkOrder("RK-PH-1", 60, 2580, "approved")
mkOrder("RK-PH-2", 30, 2620, "approved")
mkOrder("RK-PH-3", 5, 2680, "approved")
mkOrder("RK-PH-4", 1, 9999, "draft") // 草稿不计入
w := makeRequest(r, "GET",
fmt.Sprintf("/api/v1/products/%d/price-history", product.ID), token, nil)
assert.Equal(t, http.StatusOK, w.Code)
data := parseResponse(w)["data"].([]interface{})
assert.Len(t, data, 3) // 三个已审核不同价;草稿排除
// 倒序:最新价在前
assert.Equal(t, float64(2680), data[0].(map[string]interface{})["price"])
}