feat(distribution): 独立分销前台商城多租户体系、货源与订单数据严格隔离
This commit is contained in:
@@ -346,17 +346,76 @@ func (r *Repository) FindOrder(ctx context.Context, adminID, id uint64) (*Distri
|
||||
}
|
||||
|
||||
func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, status string) ([]AccountDisplayDTO, error) {
|
||||
var rows []model.DistributionSiteAccount
|
||||
db := r.db.WithContext(ctx).Where("site_id = ?", siteID).Order("id DESC")
|
||||
if status != "" {
|
||||
db = db.Where("display_status = ?", status)
|
||||
type accountDisplayRow struct {
|
||||
ID uint64 `gorm:"column:id"`
|
||||
SiteID uint64 `gorm:"column:site_id"`
|
||||
AccountID uint64 `gorm:"column:account_id"`
|
||||
ListingID uint64 `gorm:"column:listing_id"`
|
||||
DisplayStatus string `gorm:"column:display_status"`
|
||||
ListingNo string `gorm:"column:listing_no"`
|
||||
Title string `gorm:"column:title"`
|
||||
GameName string `gorm:"column:game_name"`
|
||||
ServerRegion string `gorm:"column:server_region"`
|
||||
LoginPlatform string `gorm:"column:login_platform"`
|
||||
RankLevel string `gorm:"column:rank_level"`
|
||||
HafCoinAmount int64 `gorm:"column:haf_coin_amount"`
|
||||
PriceCent int64 `gorm:"column:price_cent"`
|
||||
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
|
||||
ListingStatus string `gorm:"column:listing_status"`
|
||||
InTransaction bool `gorm:"column:in_transaction"`
|
||||
}
|
||||
if err := db.Find(&rows).Error; err != nil {
|
||||
|
||||
var rows []accountDisplayRow
|
||||
query := r.db.WithContext(ctx).
|
||||
Table("distribution_site_accounts AS dsa").
|
||||
Select(`
|
||||
dsa.id,
|
||||
dsa.site_id,
|
||||
dsa.account_id,
|
||||
dsa.listing_id,
|
||||
dsa.display_status,
|
||||
COALESCE(l.listing_no, '') AS listing_no,
|
||||
COALESCE(ga.title, '') AS title,
|
||||
COALESCE(ga.game_name, '') AS game_name,
|
||||
COALESCE(ga.server_region, '') AS server_region,
|
||||
COALESCE(ga.login_platform, '') AS login_platform,
|
||||
COALESCE(ga.rank_level, '') AS rank_level,
|
||||
COALESCE(ga.haf_coin_amount, 0) AS haf_coin_amount,
|
||||
COALESCE(l.price_cent, 0) AS price_cent,
|
||||
COALESCE(l.deposit_amount_cent, 0) AS deposit_amount_cent,
|
||||
COALESCE(l.status, '') AS listing_status,
|
||||
COALESCE(l.in_transaction, 0) AS in_transaction
|
||||
`).
|
||||
Joins("LEFT JOIN rental_listings AS l ON l.id = dsa.listing_id").
|
||||
Joins("LEFT JOIN game_accounts AS ga ON ga.id = dsa.account_id").
|
||||
Where("dsa.site_id = ?", siteID)
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("dsa.display_status = ?", status)
|
||||
}
|
||||
if err := query.Order("dsa.id DESC").Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]AccountDisplayDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, AccountDisplayDTO{ID: row.ID, SiteID: row.SiteID, AccountID: row.AccountID, ListingID: row.ListingID, DisplayStatus: row.DisplayStatus})
|
||||
items = append(items, AccountDisplayDTO{
|
||||
ID: row.ID,
|
||||
SiteID: row.SiteID,
|
||||
AccountID: row.AccountID,
|
||||
ListingID: row.ListingID,
|
||||
DisplayStatus: row.DisplayStatus,
|
||||
ListingNo: row.ListingNo,
|
||||
Title: row.Title,
|
||||
GameName: row.GameName,
|
||||
ServerRegion: row.ServerRegion,
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
RankLevel: row.RankLevel,
|
||||
HafCoinAmount: row.HafCoinAmount,
|
||||
PriceCent: row.PriceCent,
|
||||
DepositAmountCent: row.DepositAmountCent,
|
||||
ListingStatus: row.ListingStatus,
|
||||
InTransaction: row.InTransaction,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
@@ -376,10 +435,107 @@ func (r *Repository) UpdateAccountDisplay(ctx context.Context, adminID, id uint6
|
||||
if err := r.db.WithContext(ctx).Save(&row).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item := &AccountDisplayDTO{ID: row.ID, SiteID: row.SiteID, AccountID: row.AccountID, ListingID: row.ListingID, DisplayStatus: row.DisplayStatus}
|
||||
item := &AccountDisplayDTO{
|
||||
ID: row.ID,
|
||||
SiteID: row.SiteID,
|
||||
AccountID: row.AccountID,
|
||||
ListingID: row.ListingID,
|
||||
DisplayStatus: row.DisplayStatus,
|
||||
}
|
||||
var listing model.RentalListing
|
||||
if err := r.db.WithContext(ctx).First(&listing, row.ListingID).Error; err == nil {
|
||||
item.ListingNo = listing.ListingNo
|
||||
item.PriceCent = listing.PriceCent
|
||||
item.DepositAmountCent = listing.DepositAmountCent
|
||||
item.ListingStatus = listing.Status
|
||||
item.InTransaction = listing.InTransaction
|
||||
}
|
||||
var account model.GameAccount
|
||||
if err := r.db.WithContext(ctx).First(&account, row.AccountID).Error; err == nil {
|
||||
item.Title = account.Title
|
||||
item.GameName = account.GameName
|
||||
item.ServerRegion = account.ServerRegion
|
||||
item.LoginPlatform = account.LoginPlatform
|
||||
item.RankLevel = account.RankLevel
|
||||
item.HafCoinAmount = account.HafCoinAmount
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (r *Repository) SyncSiteAccounts(ctx context.Context, adminID, siteID uint64) (*SyncSiteAccountsResult, error) {
|
||||
if r.db == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if err := r.ensureSiteAccess(ctx, adminID, siteID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var site model.DistributionSite
|
||||
if err := r.db.WithContext(ctx).First(&site, siteID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var activeListings []model.RentalListing
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("status = ? AND review_status = ?", "published", "approved").
|
||||
Order("id ASC").
|
||||
Find(&activeListings).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var existingAccounts []model.DistributionSiteAccount
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("site_id = ?", siteID).
|
||||
Find(&existingAccounts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingMap := make(map[uint64]model.DistributionSiteAccount, len(existingAccounts))
|
||||
for _, ea := range existingAccounts {
|
||||
existingMap[ea.AccountID] = ea
|
||||
}
|
||||
|
||||
var toInsert []model.DistributionSiteAccount
|
||||
seenAccount := make(map[uint64]bool)
|
||||
var skippedCount int64
|
||||
|
||||
for _, l := range activeListings {
|
||||
if seenAccount[l.AccountID] {
|
||||
continue
|
||||
}
|
||||
seenAccount[l.AccountID] = true
|
||||
|
||||
if ea, exists := existingMap[l.AccountID]; exists {
|
||||
if ea.ListingID != l.ID {
|
||||
_ = r.db.WithContext(ctx).Model(&model.DistributionSiteAccount{}).
|
||||
Where("id = ?", ea.ID).Update("listing_id", l.ID).Error
|
||||
}
|
||||
skippedCount++
|
||||
} else {
|
||||
toInsert = append(toInsert, model.DistributionSiteAccount{
|
||||
SiteID: siteID,
|
||||
AccountID: l.AccountID,
|
||||
ListingID: l.ID,
|
||||
DisplayStatus: "enabled",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(toInsert) > 0 {
|
||||
if err := r.db.WithContext(ctx).Create(&toInsert).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &SyncSiteAccountsResult{
|
||||
SyncedCount: int64(len(toInsert)),
|
||||
SkippedCount: skippedCount,
|
||||
TotalActive: int64(len(activeListings)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ListPaymentChannels(ctx context.Context, siteID uint64) ([]PaymentChannelDTO, error) {
|
||||
var rows []model.DistributionSitePaymentChannel
|
||||
if err := r.db.WithContext(ctx).Where("site_id = ?", siteID).Order("priority ASC, id ASC").Find(&rows).Error; err != nil {
|
||||
@@ -860,6 +1016,34 @@ func (r *Repository) OnboardSite(ctx context.Context, req OnboardSiteRequest) (*
|
||||
return err
|
||||
}
|
||||
|
||||
if displayEnabled {
|
||||
var activeListings []model.RentalListing
|
||||
if err := tx.Where("status = ? AND review_status = ?", "published", "approved").Order("id ASC").Find(&activeListings).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(activeListings) > 0 {
|
||||
siteAccounts := make([]model.DistributionSiteAccount, 0, len(activeListings))
|
||||
seenAccount := make(map[uint64]bool)
|
||||
for _, l := range activeListings {
|
||||
if seenAccount[l.AccountID] {
|
||||
continue
|
||||
}
|
||||
seenAccount[l.AccountID] = true
|
||||
siteAccounts = append(siteAccounts, model.DistributionSiteAccount{
|
||||
SiteID: site.ID,
|
||||
AccountID: l.AccountID,
|
||||
ListingID: l.ID,
|
||||
DisplayStatus: "enabled",
|
||||
})
|
||||
}
|
||||
if len(siteAccounts) > 0 {
|
||||
if err := tx.Create(&siteAccounts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp = OnboardSiteResponse{
|
||||
Site: siteDTO(site),
|
||||
Rule: ruleDTO(rule),
|
||||
@@ -882,3 +1066,150 @@ func (r *Repository) OnboardSite(ctx context.Context, req OnboardSiteRequest) (*
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ListPublicSiteListings(ctx context.Context, siteCode string, page, pageSize int) (*PublicDistributionListingsResult, error) {
|
||||
if r.db == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if strings.TrimSpace(siteCode) == "" {
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
var site model.DistributionSite
|
||||
if err := r.db.WithContext(ctx).Where("site_code = ? AND status = 'active'", siteCode).First(&site).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !site.DisplayAccountEnabled {
|
||||
return &PublicDistributionListingsResult{
|
||||
Items: []PublicDistributionListingDTO{},
|
||||
Total: 0,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type publicListingRow struct {
|
||||
ListingID uint64 `gorm:"column:listing_id"`
|
||||
ListingNo string `gorm:"column:listing_no"`
|
||||
AccountID uint64 `gorm:"column:account_id"`
|
||||
Title string `gorm:"column:title"`
|
||||
GameName string `gorm:"column:game_name"`
|
||||
ServerRegion string `gorm:"column:server_region"`
|
||||
LoginPlatform string `gorm:"column:login_platform"`
|
||||
RankLevel string `gorm:"column:rank_level"`
|
||||
HafCoinAmount int64 `gorm:"column:haf_coin_amount"`
|
||||
PriceCent int64 `gorm:"column:price_cent"`
|
||||
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
|
||||
InTransaction bool `gorm:"column:in_transaction"`
|
||||
}
|
||||
|
||||
baseQuery := r.db.WithContext(ctx).
|
||||
Table("distribution_site_accounts AS dsa").
|
||||
Joins("INNER JOIN rental_listings AS l ON l.id = dsa.listing_id AND l.status = 'published' AND l.review_status = 'approved'").
|
||||
Joins("INNER JOIN game_accounts AS ga ON ga.id = dsa.account_id").
|
||||
Where("dsa.site_id = ? AND dsa.display_status = 'enabled'", site.ID)
|
||||
|
||||
var total int64
|
||||
if err := baseQuery.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rows []publicListingRow
|
||||
err := baseQuery.
|
||||
Select(`
|
||||
dsa.listing_id,
|
||||
l.listing_no,
|
||||
dsa.account_id,
|
||||
COALESCE(ga.title, '') AS title,
|
||||
COALESCE(ga.game_name, '') AS game_name,
|
||||
COALESCE(ga.server_region, '') AS server_region,
|
||||
COALESCE(ga.login_platform, '') AS login_platform,
|
||||
COALESCE(ga.rank_level, '') AS rank_level,
|
||||
COALESCE(ga.haf_coin_amount, 0) AS haf_coin_amount,
|
||||
COALESCE(l.price_cent, 0) AS price_cent,
|
||||
COALESCE(l.deposit_amount_cent, 0) AS deposit_amount_cent,
|
||||
COALESCE(l.in_transaction, 0) AS in_transaction
|
||||
`).
|
||||
Order("dsa.id DESC").
|
||||
Offset((page - 1) * pageSize).
|
||||
Limit(pageSize).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]PublicDistributionListingDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, PublicDistributionListingDTO{
|
||||
ListingID: row.ListingID,
|
||||
ListingNo: row.ListingNo,
|
||||
AccountID: row.AccountID,
|
||||
Title: row.Title,
|
||||
GameName: row.GameName,
|
||||
ServerRegion: row.ServerRegion,
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
RankLevel: row.RankLevel,
|
||||
HafCoinAmount: row.HafCoinAmount,
|
||||
PriceCent: row.PriceCent,
|
||||
DepositAmountCent: row.DepositAmountCent,
|
||||
InTransaction: row.InTransaction,
|
||||
SiteCode: site.SiteCode,
|
||||
SiteName: site.SiteName,
|
||||
})
|
||||
}
|
||||
|
||||
return &PublicDistributionListingsResult{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetPublicSiteInfo(ctx context.Context, code, domain string) (*PublicSiteInfoDTO, error) {
|
||||
if r.db == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
code = strings.TrimSpace(code)
|
||||
domain = strings.TrimSpace(domain)
|
||||
if code == "" && domain == "" {
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
|
||||
var site model.DistributionSite
|
||||
query := r.db.WithContext(ctx).Where("status = 'active'")
|
||||
if code != "" {
|
||||
query = query.Where("site_code = ?", code)
|
||||
} else {
|
||||
query = query.Where("domain = ?", domain)
|
||||
}
|
||||
|
||||
if err := query.First(&site).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PublicSiteInfoDTO{
|
||||
SiteCode: site.SiteCode,
|
||||
SiteName: site.SiteName,
|
||||
Domain: site.Domain,
|
||||
Status: site.Status,
|
||||
DisplayAccountEnabled: site.DisplayAccountEnabled,
|
||||
DefaultSupportGroupID: site.DefaultSupportGroupID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user