package service import ( "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" ) // 库存三态(2026-07-07):出库卖光 → sold 留痕不软删;部分出库不改状态;退单恢复 → 回 stock。 func TestApproveStockOut_SellOutMarksSold(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "STS001") wh := testutil.CreateTestWarehouse(db, shop.ID, "仓") p := testutil.CreateTestProduct(db, shop.ID, "茅台") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") inv := model.Inventory{ShopID: shop.ID, WarehouseID: &wh.ID, ProductID: &p.ID, Quantity: 5, Status: "on_sale"} require.NoError(t, db.Create(&inv).Error) order := model.StockOutOrder{ TenantBase: model.TenantBase{ShopID: shop.ID}, OrderNo: "OUT-STS-1", WarehouseID: wh.ID, OperatorID: user.ID, Status: "pending", OrderDate: model.Date{Time: time.Now()}, Items: []model.StockOutItem{{ShopID: shop.ID, ProductID: p.ID, Quantity: 5}}, } require.NoError(t, db.Create(&order).Error) require.NoError(t, NewStockService(db).ApproveStockOut(shop.ID, order.ID, user.ID)) // 卖光:行保留(deleted_at 空)、qty=0、status=sold var after model.Inventory require.NoError(t, db.Unscoped().Where("id = ?", inv.ID).First(&after).Error) assert.Equal(t, "sold", after.Status) assert.Equal(t, float64(0), after.Quantity) assert.Nil(t, after.DeletedAt, "卖光不再软删") } func TestApproveStockOut_PartialKeepsStatus(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "STS002") wh := testutil.CreateTestWarehouse(db, shop.ID, "仓") p := testutil.CreateTestProduct(db, shop.ID, "五粮液") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") inv := model.Inventory{ShopID: shop.ID, WarehouseID: &wh.ID, ProductID: &p.ID, Quantity: 10, Status: "on_sale"} require.NoError(t, db.Create(&inv).Error) order := model.StockOutOrder{ TenantBase: model.TenantBase{ShopID: shop.ID}, OrderNo: "OUT-STS-2", WarehouseID: wh.ID, OperatorID: user.ID, Status: "pending", OrderDate: model.Date{Time: time.Now()}, Items: []model.StockOutItem{{ShopID: shop.ID, ProductID: p.ID, Quantity: 3}}, } require.NoError(t, db.Create(&order).Error) require.NoError(t, NewStockService(db).ApproveStockOut(shop.ID, order.ID, user.ID)) var after model.Inventory require.NoError(t, db.Where("id = ?", inv.ID).First(&after).Error) assert.Equal(t, "on_sale", after.Status, "部分出库不改状态") assert.Equal(t, float64(7), after.Quantity) } func TestReturnStockOut_RestoresToStock(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "STS003") wh := testutil.CreateTestWarehouse(db, shop.ID, "仓") p := testutil.CreateTestProduct(db, shop.ID, "剑南春") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") inv := model.Inventory{ShopID: shop.ID, WarehouseID: &wh.ID, ProductID: &p.ID, Quantity: 2, Status: "on_sale"} require.NoError(t, db.Create(&inv).Error) order := model.StockOutOrder{ TenantBase: model.TenantBase{ShopID: shop.ID}, OrderNo: "OUT-STS-3", WarehouseID: wh.ID, OperatorID: user.ID, Status: "pending", OrderDate: model.Date{Time: time.Now()}, Items: []model.StockOutItem{{ShopID: shop.ID, ProductID: p.ID, Quantity: 2, SalePrice: 100}}, } require.NoError(t, db.Create(&order).Error) svc := NewStockService(db) require.NoError(t, svc.ApproveStockOut(shop.ID, order.ID, user.ID)) // 退单 → 新建的恢复行应为 stock(保守:需重新手动挂售) require.NoError(t, svc.ReturnStockOut(shop.ID, order.ID, user.ID, "admin", []uint64{order.Items[0].ID})) var restored model.Inventory require.NoError(t, db.Where("shop_id = ? AND product_id = ? AND quantity > 0 AND deleted_at IS NULL", shop.ID, p.ID). Order("id DESC").First(&restored).Error) assert.Equal(t, "stock", restored.Status) assert.Equal(t, float64(2), restored.Quantity) }