From d0c71a9252dffa869161e9293ebc53b98ca6b5a1 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Thu, 2 Jul 2026 11:44:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(backend):=20=E5=BE=80=E6=9D=A5=E5=8D=95?= =?UTF-8?q?=E4=BD=8D=E4=B8=8B=E6=8B=89=E8=BF=94=E5=9B=9E=E5=85=A8=E9=83=A8?= =?UTF-8?q?=20+=20=E5=85=A5=E5=BA=93=E5=94=AE=E4=BB=B7=E5=86=99=E5=85=A5?= =?UTF-8?q?=E4=BA=A7=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - partner List 分页上限 200→1000:避免下拉请求大页被 ValidatePageSize 回退到默认 20(超上限返回默认值而非截断),供应商/客户下拉一次取全部 - 入库明细 sale_price 建独立产品时写入 product.SalePrice(建议售价); SalePrice 为 gorm:"-" 仅作建库入参,不落 stock_in_items 表 - 补 partner 大页返回全部、入库售价入产品两条回归测试 Co-Authored-By: Claude Opus 4.8 --- backend/internal/handler/partner.go | 4 ++- backend/internal/handler/partner_test.go | 23 ++++++++++++++ backend/internal/handler/product.go | 3 +- backend/internal/handler/stock_in.go | 4 +-- backend/internal/handler/stock_in_test.go | 38 +++++++++++++++++++++++ backend/internal/model/stock.go | 2 ++ 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/backend/internal/handler/partner.go b/backend/internal/handler/partner.go index afac2d7..7aec0ad 100644 --- a/backend/internal/handler/partner.go +++ b/backend/internal/handler/partner.go @@ -25,7 +25,9 @@ func (h *PartnerHandler) List(c *gin.Context) { shopID := middleware.GetShopID(c) page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) - pageSize = util.ValidatePageSize(pageSize, 20, 200) + // 往来单位是有界的参考数据,下拉需一次取全部;放宽上限到 1000, + // 避免前端请求大页被 ValidatePageSize 回退到默认 20(超上限即返回默认值,非截断)。 + pageSize = util.ValidatePageSize(pageSize, 20, 1000) query := h.db.Model(&model.Partner{}). Where("shop_id = ? AND deleted_at IS NULL", shopID) diff --git a/backend/internal/handler/partner_test.go b/backend/internal/handler/partner_test.go index 5e38850..663283c 100644 --- a/backend/internal/handler/partner_test.go +++ b/backend/internal/handler/partner_test.go @@ -205,6 +205,29 @@ func TestPartnerHandler_List_KeywordSearch(t *testing.T) { assert.Equal(t, float64(0), resp["total"].(float64)) } +// 下拉需一次取全部:请求大页(>默认 20)应返回全部,而非被 ValidatePageSize +// 回退到默认 20(超上限即返回默认值的历史坑)。 +func TestPartnerHandler_List_LargePageSizeReturnsAll(t *testing.T) { + db := testutil.SetupTestDB() + shop := testutil.CreateTestShop(db, "PT007") + user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") + token := getAuthToken(user.ID, shop.ID, "admin") + r := setupProtectedRouter(db) + + for i := 0; i < 25; i++ { + makeRequest(r, "POST", "/api/v1/partners", token, map[string]interface{}{ + "name": fmt.Sprintf("Partner %02d", i), + "type": "supplier", + }) + } + + w := makeRequest(r, "GET", "/api/v1/partners?page_size=1000", token, nil) + resp := parseResponse(w) + assert.Equal(t, float64(25), resp["total"].(float64)) + assert.Equal(t, float64(1000), resp["page_size"].(float64)) + assert.Len(t, resp["data"].([]interface{}), 25) +} + func TestPartnerHandler_ShopIDFromToken(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PT007") diff --git a/backend/internal/handler/product.go b/backend/internal/handler/product.go index 1e87c27..dbb73d4 100644 --- a/backend/internal/handler/product.go +++ b/backend/internal/handler/product.go @@ -88,7 +88,7 @@ func nextProductCode(tx *gorm.DB, shopID uint64) (string, error) { // createIndependentProduct 为入库明细新建一个独立产品(特有产品/序列号),返回新 product。 // "入库每行 = 一个特有产品"模型:每条明细建一个独立 product、发新序列号,不按名称复用。 // 含 nextProductCode 自增 + 撞 uk_shop_code 唯一约束时重试。必须在事务内调用。 -func createIndependentProduct(tx *gorm.DB, shopID uint64, name, series, spec, batchNo string, prodDate *model.Date, price float64) (model.Product, error) { +func createIndependentProduct(tx *gorm.DB, shopID uint64, name, series, spec, batchNo string, prodDate *model.Date, price, salePrice float64) (model.Product, error) { namePinyin, nameInitials := util.ToPinyin(name) var prod model.Product var err error @@ -107,6 +107,7 @@ func createIndependentProduct(tx *gorm.DB, shopID uint64, name, series, spec, ba BatchNo: batchNo, ProductionDate: prodDate, PurchasePrice: price, + SalePrice: salePrice, NamePinyin: namePinyin, NameInitials: nameInitials, } diff --git a/backend/internal/handler/stock_in.go b/backend/internal/handler/stock_in.go index ed97ac3..2b90035 100644 --- a/backend/internal/handler/stock_in.go +++ b/backend/internal/handler/stock_in.go @@ -228,7 +228,7 @@ func (h *StockInHandler) Create(c *gin.Context) { if it.BatchNo == "" { it.BatchNo = fmt.Sprintf("%s-%02d", req.OrderNo, i+1) } - prod, e := createIndependentProduct(tx, shopID, it.ProductName, it.Series, it.Spec, it.BatchNo, it.ProductionDate, it.UnitPrice) + prod, e := createIndependentProduct(tx, shopID, it.ProductName, it.Series, it.Spec, it.BatchNo, it.ProductionDate, it.UnitPrice, it.SalePrice) if e != nil { return e } @@ -277,7 +277,7 @@ func (h *StockInHandler) Update(c *gin.Context) { it.BatchNo = fmt.Sprintf("%s-%02d", order.OrderNo, i+1) } // 编辑草稿:旧明细已删,每条按新模型重建独立产品(旧草稿 product 无库存,暂留待后续清理) - prod, e := createIndependentProduct(tx, shopID, it.ProductName, it.Series, it.Spec, it.BatchNo, it.ProductionDate, it.UnitPrice) + prod, e := createIndependentProduct(tx, shopID, it.ProductName, it.Series, it.Spec, it.BatchNo, it.ProductionDate, it.UnitPrice, it.SalePrice) if e != nil { return e } diff --git a/backend/internal/handler/stock_in_test.go b/backend/internal/handler/stock_in_test.go index 9692b59..8f34f5e 100644 --- a/backend/internal/handler/stock_in_test.go +++ b/backend/internal/handler/stock_in_test.go @@ -75,6 +75,44 @@ func TestStockInHandler_FullFlow(t *testing.T) { assert.Equal(t, float64(10), logs[0].Quantity) } +// 入库明细带 sale_price 时,为该行新建的独立产品应写入 product.SalePrice(建议售价)。 +func TestStockInHandler_Create_SalePriceToProduct(t *testing.T) { + db := testutil.SetupTestDB() + shop := testutil.CreateTestShop(db, "SISALE") + 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) + + w := makeRequest(r, "POST", "/api/v1/stock-in/orders", token, map[string]interface{}{ + "warehouse_id": warehouse.ID, + "order_date": time.Now().Format(time.RFC3339), + "items": []map[string]interface{}{ + { + "product_name": "茅台飞天", + "series": "53度", + "spec": "500ml", + "quantity": 3.0, + "unit_price": 2680.0, + "sale_price": 2980.0, + }, + }, + }) + require.Equal(t, http.StatusCreated, w.Code) + orderID := extractID(w) + + // 找到该单据明细新建的独立产品,核对进价/售价 + var item model.StockInItem + require.NoError(t, db.Where("order_id = ?", orderID).First(&item).Error) + require.NotZero(t, item.ProductID) + var prod model.Product + require.NoError(t, db.Where("id = ? AND shop_id = ?", item.ProductID, shop.ID).First(&prod).Error) + assert.Equal(t, 2680.0, prod.PurchasePrice) + assert.Equal(t, 2980.0, prod.SalePrice) + // sale_price 不落 stock_in_items(gorm:"-"),仅作产品建库入参 + assert.Equal(t, "茅台飞天", prod.Name) +} + func TestStockInHandler_List(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "SI002") diff --git a/backend/internal/model/stock.go b/backend/internal/model/stock.go index ee96c15..5939e5b 100644 --- a/backend/internal/model/stock.go +++ b/backend/internal/model/stock.go @@ -42,6 +42,8 @@ type StockInItem struct { Spec string `gorm:"size:100" json:"spec"` Quantity float64 `gorm:"type:decimal(12,3);not null" json:"quantity"` UnitPrice float64 `gorm:"type:decimal(16,2);default:0" json:"unit_price"` + // 建议售价:仅用于入库建产品时写入 product.SalePrice,不落 stock_in_items 表(gorm:"-")。 + SalePrice float64 `gorm:"-" json:"sale_price"` TotalPrice float64 `gorm:"type:decimal(16,2);default:0" json:"total_price"` // 已退数量:0=未退,=Quantity 表示整行已退单(整行退,不做部分数量) ReturnedQuantity float64 `gorm:"type:decimal(12,3);default:0" json:"returned_quantity"`