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:
@@ -417,9 +417,21 @@ func (r *Repository) CreateSite(ctx context.Context, req CreateSiteRequest) (*Si
|
||||
} else {
|
||||
row.SettlementHoldDays = r.resolveDefaultSettlementHoldDays(ctx)
|
||||
}
|
||||
// 模型上带 default 标签的布尔/整型字段(display_account_enabled、tax_enabled、
|
||||
// settlement_hold_days)在零值(false/0)时会被 GORM 忽略并回落数据库默认值,
|
||||
// 因此显式传 false/0 的配置必须在插入后补写,否则会被静默改掉。
|
||||
if err := r.db.WithContext(ctx).Create(&row).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if explicitZeros := explicitZeroSiteColumns(req); len(explicitZeros) > 0 {
|
||||
if err := r.db.WithContext(ctx).Model(&model.DistributionSite{}).Where("id = ?", row.ID).
|
||||
Updates(explicitZeros).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.db.WithContext(ctx).First(&row, row.ID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := wallet.EnsureAccountTx(r.db.WithContext(ctx), 0, row.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1523,6 +1535,7 @@ func (r *Repository) UpdateAccountDisplay(ctx context.Context, adminID, id uint6
|
||||
return nil, err
|
||||
}
|
||||
row.DisplayStatus = strings.TrimSpace(status)
|
||||
row.DisplayDisabledBy = displayDisabledBy(row.DisplayStatus)
|
||||
if err := r.db.WithContext(ctx).Save(&row).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1651,6 +1664,7 @@ func (r *Repository) SyncSiteAccounts(ctx context.Context, adminID, siteID uint6
|
||||
seenAccount := make(map[uint64]bool)
|
||||
var skippedCount int64
|
||||
var updatedCount int64
|
||||
var reenabledCount int64
|
||||
|
||||
for _, l := range candidates {
|
||||
if seenAccount[l.AccountID] {
|
||||
@@ -1659,11 +1673,23 @@ func (r *Repository) SyncSiteAccounts(ctx context.Context, adminID, siteID uint6
|
||||
seenAccount[l.AccountID] = true
|
||||
|
||||
if ea, exists := existingMap[l.AccountID]; exists {
|
||||
updates := map[string]any{}
|
||||
if ea.ListingID != l.ID {
|
||||
_ = r.db.WithContext(ctx).Model(&model.DistributionSiteAccount{}).
|
||||
Where("id = ?", ea.ID).Update("listing_id", l.ID).Error
|
||||
updates["listing_id"] = l.ID
|
||||
updatedCount++
|
||||
}
|
||||
// 系统下架(或历史无来源标记)的行随铺货恢复展示;站长人工下架的行保持不动。
|
||||
if ea.DisplayStatus != "enabled" && shelfDisabledBySystem(ea.DisplayDisabledBy) {
|
||||
updates["display_status"] = "enabled"
|
||||
updates["display_disabled_by"] = ""
|
||||
reenabledCount++
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
if err := r.db.WithContext(ctx).Model(&model.DistributionSiteAccount{}).
|
||||
Where("id = ?", ea.ID).Updates(updates).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
skippedCount++
|
||||
} else {
|
||||
toInsert = append(toInsert, model.DistributionSiteAccount{
|
||||
@@ -1683,12 +1709,16 @@ func (r *Repository) SyncSiteAccounts(ctx context.Context, adminID, siteID uint6
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 联动下架主站非在售/已失效的货架商品(若开启 CleanStaleListings)
|
||||
// 3. 联动下架货源已被删除的货架商品(若开启 CleanStaleListings)。
|
||||
//
|
||||
// 这里只处理「商品记录本身已不存在」的孤儿货架行。已下架、已出租、待审核等
|
||||
// 只是暂时不可售的商品必须保留展示状态:分站前台本来就要求 published+approved
|
||||
// 才展示,而一旦把它们的货架行置为 disabled,商品重新上架后就再也不会出现在分站。
|
||||
var disabledStaleCount int64
|
||||
if req.CleanStaleListings {
|
||||
var staleIDs []uint64
|
||||
err := r.db.WithContext(ctx).Table("distribution_site_accounts AS dsa").
|
||||
Joins("LEFT JOIN rental_listings AS l ON l.id = dsa.listing_id AND l.status = 'published' AND l.review_status = 'approved'").
|
||||
Joins("LEFT JOIN rental_listings AS l ON l.id = dsa.listing_id").
|
||||
Where("dsa.site_id = ? AND dsa.display_status = 'enabled' AND l.id IS NULL", siteID).
|
||||
Pluck("dsa.id", &staleIDs).Error
|
||||
if err == nil && len(staleIDs) > 0 {
|
||||
@@ -1699,7 +1729,10 @@ func (r *Repository) SyncSiteAccounts(ctx context.Context, adminID, siteID uint6
|
||||
}
|
||||
_ = r.db.WithContext(ctx).Model(&model.DistributionSiteAccount{}).
|
||||
Where("id IN ?", staleIDs[i:end]).
|
||||
Update("display_status", "disabled").Error
|
||||
Updates(map[string]any{
|
||||
"display_status": "disabled",
|
||||
"display_disabled_by": displayDisabledBySystem,
|
||||
}).Error
|
||||
}
|
||||
disabledStaleCount = int64(len(staleIDs))
|
||||
}
|
||||
@@ -1709,6 +1742,7 @@ func (r *Repository) SyncSiteAccounts(ctx context.Context, adminID, siteID uint6
|
||||
SyncedCount: int64(len(toInsert)),
|
||||
SkippedCount: skippedCount,
|
||||
UpdatedCount: updatedCount,
|
||||
ReenabledCount: reenabledCount,
|
||||
DisabledStaleCount: disabledStaleCount,
|
||||
TotalActive: int64(len(candidates)),
|
||||
}, nil
|
||||
@@ -2816,7 +2850,10 @@ func (r *Repository) BatchUpdateAccountDisplay(ctx context.Context, adminID, sit
|
||||
} else {
|
||||
q = q.Where("listing_id IN ?", listingIDs)
|
||||
}
|
||||
return q.Update("display_status", targetStatus).Error
|
||||
return q.Updates(map[string]any{
|
||||
"display_status": targetStatus,
|
||||
"display_disabled_by": displayDisabledBy(targetStatus),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *Repository) ListReceivedListings(ctx context.Context, adminID, siteID uint64, q ReceivedListingQuery) (*ReceivedListingPage, error) {
|
||||
@@ -2994,17 +3031,21 @@ func (r *Repository) UpdateReceivedListingStatus(ctx context.Context, adminID, l
|
||||
err = r.db.WithContext(ctx).Where("site_id = ? AND listing_id = ?", siteID, listing.ID).First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
row = model.DistributionSiteAccount{
|
||||
SiteID: siteID,
|
||||
AccountID: listing.AccountID,
|
||||
ListingID: listing.ID,
|
||||
DisplayStatus: displayStatus,
|
||||
SiteID: siteID,
|
||||
AccountID: listing.AccountID,
|
||||
ListingID: listing.ID,
|
||||
DisplayStatus: displayStatus,
|
||||
DisplayDisabledBy: displayDisabledBy(displayStatus),
|
||||
}
|
||||
return r.db.WithContext(ctx).Create(&row).Error
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.db.WithContext(ctx).Model(&row).Update("display_status", displayStatus).Error
|
||||
return r.db.WithContext(ctx).Model(&row).Updates(map[string]any{
|
||||
"display_status": displayStatus,
|
||||
"display_disabled_by": displayDisabledBy(displayStatus),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetSiteWallet(ctx context.Context, adminID, siteID uint64) (*SiteWalletDTO, error) {
|
||||
|
||||
Reference in New Issue
Block a user