Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6570dfc240 |
@@ -5,6 +5,11 @@
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.77] - 2026-06-22
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
- 出库单列表支持按「商品编码」精确反查:可查出包含某个商品编码的所有出库单。同时为出库明细新增 (门店, 商品编码) 复合索引,在数万条明细下仍保持毫秒级查询,不影响写入。
|
||||||
|
|
||||||
## [1.0.76] - 2026-06-22
|
## [1.0.76] - 2026-06-22
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ func (h *StockOutHandler) List(c *gin.Context) {
|
|||||||
like, shopID, like,
|
like, shopID, like,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// 按商品编码反查单据:精确匹配,走 idx_so_items_shop_code(shop_id, product_code) 索引
|
||||||
|
if code := strings.TrimSpace(c.Query("product_code")); code != "" {
|
||||||
|
query = query.Where(
|
||||||
|
"id IN (SELECT order_id FROM stock_out_items WHERE shop_id = ? AND product_code = ? AND deleted_at IS NULL)",
|
||||||
|
shopID, code,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
query.Count(&total)
|
query.Count(&total)
|
||||||
|
|||||||
@@ -133,6 +133,40 @@ func TestStockOutHandler_List(t *testing.T) {
|
|||||||
assert.Equal(t, float64(2), resp["total"].(float64))
|
assert.Equal(t, float64(2), resp["total"].(float64))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按商品编码精确反查出库单(明细 product_code = ?)
|
||||||
|
func TestStockOutHandler_List_FilterByProductCode(t *testing.T) {
|
||||||
|
db := testutil.SetupTestDB()
|
||||||
|
shop := testutil.CreateTestShop(db, "SO_CODE")
|
||||||
|
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
|
||||||
|
warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse")
|
||||||
|
product := testutil.CreateTestProduct(db, shop.ID, "Beer")
|
||||||
|
token := getAuthToken(user.ID, shop.ID, "admin")
|
||||||
|
r := setupProtectedRouter(db)
|
||||||
|
|
||||||
|
makeRequest(r, "POST", "/api/v1/stock-out/orders", token, map[string]interface{}{
|
||||||
|
"warehouse_id": warehouse.ID, "order_date": time.Now().Format(time.RFC3339),
|
||||||
|
"items": []map[string]interface{}{
|
||||||
|
{"product_id": product.ID, "product_code": "ZXZ-FIND", "quantity": 1.0},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
makeRequest(r, "POST", "/api/v1/stock-out/orders", token, map[string]interface{}{
|
||||||
|
"warehouse_id": warehouse.ID, "order_date": time.Now().Format(time.RFC3339),
|
||||||
|
"items": []map[string]interface{}{
|
||||||
|
{"product_id": product.ID, "product_code": "OTHER-CODE", "quantity": 1.0},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 精确命中 1 单
|
||||||
|
w := makeRequest(r, "GET", "/api/v1/stock-out/orders?product_code=ZXZ-FIND", token, nil)
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, float64(1), parseResponse(w)["total"].(float64))
|
||||||
|
|
||||||
|
// 不存在的编码 → 0;前缀不算精确(不命中)
|
||||||
|
w = makeRequest(r, "GET", "/api/v1/stock-out/orders?product_code=ZXZ", token, nil)
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, float64(0), parseResponse(w)["total"].(float64))
|
||||||
|
}
|
||||||
|
|
||||||
func TestStockOutHandler_List_FilterByKeyword(t *testing.T) {
|
func TestStockOutHandler_List_FilterByKeyword(t *testing.T) {
|
||||||
db := testutil.SetupTestDB()
|
db := testutil.SetupTestDB()
|
||||||
shop := testutil.CreateTestShop(db, "SO011")
|
shop := testutil.CreateTestShop(db, "SO011")
|
||||||
|
|||||||
@@ -144,5 +144,12 @@ func autoMigrate(db *gorm.DB) {
|
|||||||
log.Fatalf("create unique index uk_shop_code failed: %v", err)
|
log.Fatalf("create unique index uk_shop_code failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// stock_out_items(shop_id, product_code) 复合索引:支撑出库列表「按商品编码反查单据」,
|
||||||
|
// 避免在 2.6 万+ 明细行上全表扫。ShopID 在嵌入字段上无法用 struct tag 表达,故显式幂等建。
|
||||||
|
if !db.Migrator().HasIndex(&model.StockOutItem{}, "idx_so_items_shop_code") {
|
||||||
|
if err := db.Exec("CREATE INDEX idx_so_items_shop_code ON stock_out_items (shop_id, product_code)").Error; err != nil {
|
||||||
|
log.Fatalf("create index idx_so_items_shop_code failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
log.Println("AutoMigrate completed")
|
log.Println("AutoMigrate completed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,7 +369,8 @@ CREATE TABLE IF NOT EXISTS `stock_out_items` (
|
|||||||
`remark` VARCHAR(255) DEFAULT NULL,
|
`remark` VARCHAR(255) DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `idx_order_id` (`order_id`),
|
KEY `idx_order_id` (`order_id`),
|
||||||
KEY `idx_shop_product` (`shop_id`, `product_id`)
|
KEY `idx_shop_product` (`shop_id`, `product_id`),
|
||||||
|
KEY `idx_so_items_shop_code` (`shop_id`, `product_code`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单明细';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单明细';
|
||||||
|
|
||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user