fix(distribution): 修复分站搜不到已审核商品并实现审核通过自动铺货
问题:分站上架商品审核通过后主站能搜到、分站看不到,且没有自动同步。 根因:分站前台/搜索只认 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 站点码回退,站点探测完成后再挂载应用, 避免分站发布因缺少站点码被记成主站商品;同步弹窗文案与统计项同步更新 - 补充分销货架自动铺货、清理口径、建站零值落库与审核铺货回归测试
This commit is contained in:
@@ -1308,7 +1308,50 @@ func TestSyncSiteAccountsOptimizations(t *testing.T) {
|
||||
t.Fatalf("dsa1 mismatch: ratio=%v, status=%s", dsa1.ShelfRatioDelta, dsa1.DisplayStatus)
|
||||
}
|
||||
|
||||
// 3. 验证主站商品下架联动(CleanStaleListings = true)
|
||||
// 3. 站长人工下架属于选品决策,再次同步不得覆盖
|
||||
var dsa1After model.DistributionSiteAccount
|
||||
if _, err := repo.UpdateAccountDisplay(t.Context(), 0, dsa1.ID, "disabled"); err != nil {
|
||||
t.Fatalf("UpdateAccountDisplay failed: %v", err)
|
||||
}
|
||||
resKeep, err := repo.SyncSiteAccounts(t.Context(), 0, site.ID, SyncSiteAccountsRequest{
|
||||
CleanStaleListings: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SyncSiteAccounts keep failed: %v", err)
|
||||
}
|
||||
if resKeep.ReenabledCount != 0 {
|
||||
t.Fatalf("人工下架不应被自动恢复: %+v", resKeep)
|
||||
}
|
||||
if err := db.First(&dsa1After, dsa1.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dsa1After.DisplayStatus != "disabled" || dsa1After.DisplayDisabledBy != displayDisabledByOperator {
|
||||
t.Fatalf("人工下架标记丢失: status=%s by=%s", dsa1After.DisplayStatus, dsa1After.DisplayDisabledBy)
|
||||
}
|
||||
|
||||
// 3.1 系统下架(含历史无来源标记的数据)应被铺货恢复
|
||||
if err := db.Model(&model.DistributionSiteAccount{}).Where("id = ?", dsa1.ID).
|
||||
Updates(map[string]any{"display_status": "disabled", "display_disabled_by": ""}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resRepair, err := repo.SyncSiteAccounts(t.Context(), 0, site.ID, SyncSiteAccountsRequest{
|
||||
CleanStaleListings: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SyncSiteAccounts repair failed: %v", err)
|
||||
}
|
||||
if resRepair.ReenabledCount != 1 {
|
||||
t.Fatalf("expected 1 reenabled shelf row, got %+v", resRepair)
|
||||
}
|
||||
if err := db.First(&dsa1After, dsa1.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dsa1After.DisplayStatus != "enabled" || dsa1After.DisplayDisabledBy != "" {
|
||||
t.Fatalf("系统下架应被恢复: status=%s by=%s", dsa1After.DisplayStatus, dsa1After.DisplayDisabledBy)
|
||||
}
|
||||
|
||||
// 3.2 商品下架不清理货架行:分站前台本来就要求 published+approved 才展示,
|
||||
// 一旦把货架行写成 disabled,商品重新上架后就再也不会出现在分站。
|
||||
db.Model(&l1).Update("status", "offline")
|
||||
|
||||
resClean, err := repo.SyncSiteAccounts(t.Context(), 0, site.ID, SyncSiteAccountsRequest{
|
||||
@@ -1317,15 +1360,35 @@ func TestSyncSiteAccountsOptimizations(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("SyncSiteAccounts clean failed: %v", err)
|
||||
}
|
||||
if resClean.DisabledStaleCount != 1 {
|
||||
t.Fatalf("expected 1 disabled stale listing, got %d", resClean.DisabledStaleCount)
|
||||
if resClean.DisabledStaleCount != 0 || resClean.ReenabledCount != 0 {
|
||||
t.Fatalf("下架商品不应清理货架行: %+v", resClean)
|
||||
}
|
||||
if err := db.First(&dsa1After, dsa1.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dsa1After.DisplayStatus != "enabled" {
|
||||
t.Fatalf("商品下架后货架行应保持 enabled,got: %s", dsa1After.DisplayStatus)
|
||||
}
|
||||
|
||||
// 验证 dsa1 已被标记为 disabled
|
||||
var dsa1After model.DistributionSiteAccount
|
||||
db.First(&dsa1After, dsa1.ID)
|
||||
if dsa1After.DisplayStatus != "disabled" {
|
||||
t.Fatalf("expected dsa1 disabled, got: %s", dsa1After.DisplayStatus)
|
||||
// 3.3 只有货源已被删除的孤儿货架行才会被清理
|
||||
if err := db.Delete(&model.RentalListing{}, l2.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resStale, err := repo.SyncSiteAccounts(t.Context(), 0, site.ID, SyncSiteAccountsRequest{
|
||||
CleanStaleListings: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SyncSiteAccounts stale failed: %v", err)
|
||||
}
|
||||
if resStale.DisabledStaleCount != 1 {
|
||||
t.Fatalf("expected 1 disabled stale listing, got %+v", resStale)
|
||||
}
|
||||
var dsa2 model.DistributionSiteAccount
|
||||
if err := db.Where("site_id = ? AND listing_id = ?", site.ID, l2.ID).First(&dsa2).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dsa2.DisplayStatus != "disabled" || dsa2.DisplayDisabledBy != displayDisabledBySystem {
|
||||
t.Fatalf("已删除货源应被系统下架: status=%s by=%s", dsa2.DisplayStatus, dsa2.DisplayDisabledBy)
|
||||
}
|
||||
|
||||
// 4. 验证并发互斥锁(ErrSyncInProgress)
|
||||
|
||||
Reference in New Issue
Block a user