问题:分站上架商品审核通过后主站能搜到、分站看不到,且没有自动同步。 根因:分站前台/搜索只认 distribution_site_accounts 中 display_status='enabled' 的货架行,而主站前台不按站点过滤。货架行此前只由「号主在该站上架」或站长手动 「一键同步主站商品」写入,审核通过不回写;同时一键同步的失效清理把「非 published+approved」的货架行一律置为 disabled,商品重新在售后也不会恢复。 - 新增 EnsureListingShelvedTx:商品变为 published+approved 时自动铺到所有 display_account_enabled=true 的站点货架(缺行新增、系统下架恢复、人工下架不动), 经 ListingShelfSyncer 接口注入,避免 listing 反向依赖 distribution - 新增 distribution_site_accounts.display_disabled_by(operator/system/空)区分下架 来源;人工上下架各入口写入来源标记 - 一键同步的失效清理收紧为只清理「货源记录已删除」的孤儿行,并支持恢复系统下架行, 结果新增 reenabled_count - 新增迁移 000074 回填存量缺失货架行,部署后无需逐站手动同步 - 修复 CreateSite 显式传 false/0 的配置(不允许自动展示、结算冷却 0 天)被 GORM default 标签忽略而静默回落默认值 - 前端:去掉会跨站串扰的 localStorage 站点码回退,站点探测完成后再挂载应用, 避免分站发布因缺少站点码被记成主站商品;同步弹窗文案与统计项同步更新 - 补充分销货架自动铺货、清理口径、建站零值落库与审核铺货回归测试
157 lines
6.1 KiB
Go
157 lines
6.1 KiB
Go
package distribution
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"hfb_sys/backend/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// TestEnsureListingShelvedTxAutoShelf 覆盖「所有站点共用同一套已审核货源」的自动铺货语义:
|
|
// 开启自动展示的站点自动上架、人工下架不被覆盖、系统下架可恢复、重复调用幂等,
|
|
// 并且铺货后的商品能在分站前台公开列表里被搜到。
|
|
func TestEnsureListingShelvedTxAutoShelf(t *testing.T) {
|
|
db := distributionTestDB(t)
|
|
repo := NewRepository(db)
|
|
|
|
autoSite, err := repo.CreateSite(t.Context(), CreateSiteRequest{
|
|
SiteCode: "auto_shelf_on", SiteName: "自动铺货站", Domain: "auto-on.hfb.local",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !autoSite.DisplayAccountEnabled {
|
|
t.Fatalf("expected display_account_enabled true, got %+v", autoSite)
|
|
}
|
|
// 关闭自动展示:建站时显式传 false 必须真正落库,否则自动铺货会铺到站长明确排除的站点。
|
|
displayOff := false
|
|
manualSite, err := repo.CreateSite(t.Context(), CreateSiteRequest{
|
|
SiteCode: "auto_shelf_off", SiteName: "手动铺货站", Domain: "auto-off.hfb.local",
|
|
DisplayAccountEnabled: &displayOff,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if manualSite.DisplayAccountEnabled {
|
|
t.Fatalf("display_account_enabled=false 应在建站时生效, got %+v", manualSite)
|
|
}
|
|
|
|
account := model.GameAccount{
|
|
Title: "自动铺货商品", GameName: "delta_force", ServerRegion: "QQ",
|
|
LoginPlatform: "PC", RankLevel: "王者", HafCoinAmount: 10_000_000, Status: "published",
|
|
}
|
|
if err := db.Create(&account).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
listing := model.RentalListing{
|
|
ListingNo: "L-AUTO-SHELF-1", AccountID: account.ID, OwnerID: 1,
|
|
PriceCent: 5000, DepositAmountCent: 10000, Status: "published", ReviewStatus: "approved",
|
|
}
|
|
if err := db.Create(&listing).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// 1. 只铺到开启自动展示的站点,加价保持 0(不改变主站原价)
|
|
if err := repo.EnsureListingShelvedTx(db, listing.ID, account.ID); err != nil {
|
|
t.Fatalf("EnsureListingShelvedTx failed: %v", err)
|
|
}
|
|
rows := loadShelfRows(t, db, listing.ID)
|
|
if len(rows) != 1 {
|
|
t.Fatalf("expected 1 shelf row for auto display site, got %+v", rows)
|
|
}
|
|
if rows[0].SiteID != autoSite.ID || rows[0].DisplayStatus != "enabled" || rows[0].ShelfRatioDelta != 0 {
|
|
t.Fatalf("unexpected shelf row: %+v", rows[0])
|
|
}
|
|
|
|
// 2. 幂等:重复铺货不产生重复行
|
|
if err := repo.EnsureListingShelvedTx(db, listing.ID, account.ID); err != nil {
|
|
t.Fatalf("EnsureListingShelvedTx repeat failed: %v", err)
|
|
}
|
|
if rows = loadShelfRows(t, db, listing.ID); len(rows) != 1 {
|
|
t.Fatalf("auto shelf must be idempotent, got %+v", rows)
|
|
}
|
|
|
|
// 3. 站长人工下架是选品决策,自动铺货不得覆盖
|
|
if _, err := repo.UpdateAccountDisplay(t.Context(), 0, rows[0].ID, "disabled"); err != nil {
|
|
t.Fatalf("UpdateAccountDisplay failed: %v", err)
|
|
}
|
|
if err := repo.EnsureListingShelvedTx(db, listing.ID, account.ID); err != nil {
|
|
t.Fatalf("EnsureListingShelvedTx after operator disable failed: %v", err)
|
|
}
|
|
rows = loadShelfRows(t, db, listing.ID)
|
|
if rows[0].DisplayStatus != "disabled" || rows[0].DisplayDisabledBy != displayDisabledByOperator {
|
|
t.Fatalf("operator disable must be preserved: %+v", rows[0])
|
|
}
|
|
|
|
// 4. 系统下架(含历史无来源标记)可被自动铺货恢复
|
|
if err := db.Model(&model.DistributionSiteAccount{}).Where("id = ?", rows[0].ID).
|
|
Updates(map[string]any{"display_status": "disabled", "display_disabled_by": displayDisabledBySystem}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repo.EnsureListingShelvedTx(db, listing.ID, account.ID); err != nil {
|
|
t.Fatalf("EnsureListingShelvedTx after system disable failed: %v", err)
|
|
}
|
|
rows = loadShelfRows(t, db, listing.ID)
|
|
if rows[0].DisplayStatus != "enabled" || rows[0].DisplayDisabledBy != "" {
|
|
t.Fatalf("system disable must be recovered: %+v", rows[0])
|
|
}
|
|
|
|
// 5. 铺货结果能在分站前台公开列表看到;未开启自动展示的站点看不到
|
|
page, err := repo.ListPublicSiteListings(t.Context(), autoSite.SiteCode, 1, 20)
|
|
if err != nil {
|
|
t.Fatalf("ListPublicSiteListings failed: %v", err)
|
|
}
|
|
if page.Total != 1 || len(page.Items) != 1 || page.Items[0].ListingID != listing.ID {
|
|
t.Fatalf("auto display site should expose the listing, got %+v", page)
|
|
}
|
|
pageOff, err := repo.ListPublicSiteListings(t.Context(), manualSite.SiteCode, 1, 20)
|
|
if err != nil {
|
|
t.Fatalf("ListPublicSiteListings manual failed: %v", err)
|
|
}
|
|
if pageOff.Total != 0 || len(pageOff.Items) != 0 {
|
|
t.Fatalf("display_account_enabled=false site must not expose listings, got %+v", pageOff)
|
|
}
|
|
}
|
|
|
|
func loadShelfRows(t *testing.T, db *gorm.DB, listingID uint64) []model.DistributionSiteAccount {
|
|
t.Helper()
|
|
var rows []model.DistributionSiteAccount
|
|
if err := db.Where("listing_id = ?", listingID).Find(&rows).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
// TestCreateSitePersistsExplicitZeroFlags 回归:模型上带 default 标签的字段在显式传
|
|
// false/0 时不能被 GORM 忽略并回落数据库默认值,否则「不允许自动展示」会静默变成开启,
|
|
// 从而让自动铺货铺到站长明确排除的站点。
|
|
func TestCreateSitePersistsExplicitZeroFlags(t *testing.T) {
|
|
db := distributionTestDB(t)
|
|
repo := NewRepository(db)
|
|
|
|
displayOff := false
|
|
zeroHoldDays := 0
|
|
dto, err := repo.CreateSite(t.Context(), CreateSiteRequest{
|
|
SiteCode: "explicit_zero_site", SiteName: "显式零值站点", Domain: "explicit-zero.hfb.local",
|
|
DisplayAccountEnabled: &displayOff, SettlementHoldDays: &zeroHoldDays,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateSite failed: %v", err)
|
|
}
|
|
if dto.DisplayAccountEnabled || dto.SettlementHoldDays != 0 {
|
|
t.Fatalf("DTO 未反映显式零值: enabled=%v hold=%d", dto.DisplayAccountEnabled, dto.SettlementHoldDays)
|
|
}
|
|
|
|
var site model.DistributionSite
|
|
if err := db.Where("site_code = ?", "explicit_zero_site").First(&site).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if site.DisplayAccountEnabled {
|
|
t.Fatalf("display_account_enabled 显式 false 未落库")
|
|
}
|
|
if site.SettlementHoldDays != 0 {
|
|
t.Fatalf("settlement_hold_days 显式 0 未落库, got %d", site.SettlementHoldDays)
|
|
}
|
|
}
|