package notification import ( "context" "errors" "strings" "time" "hfb_sys/backend/internal/model" "gorm.io/gorm" ) type Repository struct { db *gorm.DB } type Entry struct { UserID uint64 Type string Title string Content string BizType string BizID *uint64 DistributionSiteID *uint64 } func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } func (r *Repository) resolveSite(ctx context.Context, siteCode string) (uint64, bool, error) { siteCode = strings.TrimSpace(siteCode) if siteCode == "" { return 0, false, nil } 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 0, false, nil } return 0, false, err } return site.ID, true, nil } func (r *Repository) applySiteFilter(db *gorm.DB, siteID uint64, isSite bool) *gorm.DB { if isSite && siteID > 0 { return db.Where("distribution_site_id = ?", siteID) } return db.Where("distribution_site_id IS NULL OR distribution_site_id = 0") } func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int, siteCode ...string) (*PaginatedResult, error) { var code string if len(siteCode) > 0 { code = siteCode[0] } siteID, isSite, err := r.resolveSite(ctx, code) if err != nil { return nil, err } query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ?", userID) query = r.applySiteFilter(query, siteID, isSite) var total int64 if err := query.Count(&total).Error; err != nil { return nil, err } offset := (page - 1) * pageSize var rows []model.Notification dataQuery := r.db.WithContext(ctx).Where("user_id = ?", userID) dataQuery = r.applySiteFilter(dataQuery, siteID, isSite) if err := dataQuery.Order("id DESC").Offset(offset).Limit(pageSize).Find(&rows).Error; err != nil { return nil, err } items := make([]NotificationDTO, 0, len(rows)) for _, row := range rows { items = append(items, toDTO(row)) } return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil } func (r *Repository) MarkRead(ctx context.Context, userID uint64, id uint64, siteCode ...string) error { var code string if len(siteCode) > 0 { code = siteCode[0] } siteID, isSite, err := r.resolveSite(ctx, code) if err != nil { return err } now := time.Now() query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("id = ? AND user_id = ?", id, userID) query = r.applySiteFilter(query, siteID, isSite) result := query.Update("read_at", now) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { checkQuery := r.db.WithContext(ctx).Model(&model.Notification{}).Where("id = ? AND user_id = ?", id, userID) checkQuery = r.applySiteFilter(checkQuery, siteID, isSite) var total int64 if err := checkQuery.Count(&total).Error; err != nil { return err } if total == 0 { return ErrNotificationNotFound } } return nil } func (r *Repository) UnreadCount(ctx context.Context, userID uint64, siteCode ...string) (int64, error) { var code string if len(siteCode) > 0 { code = siteCode[0] } siteID, isSite, err := r.resolveSite(ctx, code) if err != nil { return 0, err } query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ? AND read_at IS NULL", userID) query = r.applySiteFilter(query, siteID, isSite) var total int64 err = query.Count(&total).Error return total, err } func (r *Repository) MarkAllRead(ctx context.Context, userID uint64, siteCode ...string) (int64, error) { var code string if len(siteCode) > 0 { code = siteCode[0] } siteID, isSite, err := r.resolveSite(ctx, code) if err != nil { return 0, err } now := time.Now() query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ? AND read_at IS NULL", userID) query = r.applySiteFilter(query, siteID, isSite) result := query.Update("read_at", now) return result.RowsAffected, result.Error } func Append(tx *gorm.DB, entries ...Entry) error { for _, entry := range entries { if entry.UserID == 0 || entry.Title == "" { continue } siteID := entry.DistributionSiteID if siteID == nil { if (entry.BizType == "order" || entry.BizType == "order_handoff") && entry.BizID != nil && *entry.BizID > 0 { var orderSiteID *uint64 var mode string row := tx.Table("rental_orders").Where("id = ?", *entry.BizID).Select("distribution_site_id, distribution_mode").Row() if err := row.Scan(&orderSiteID, &mode); err == nil && orderSiteID != nil && mode != "two_party_distributor_receive" { siteID = orderSiteID } } else if entry.BizType == "dispute" && entry.BizID != nil && *entry.BizID > 0 { var orderSiteID *uint64 var mode string row := tx.Table("order_disputes d").Joins("JOIN rental_orders o ON o.id = d.order_id").Where("d.id = ?", *entry.BizID).Select("o.distribution_site_id, o.distribution_mode").Row() if err := row.Scan(&orderSiteID, &mode); err == nil && orderSiteID != nil && mode != "two_party_distributor_receive" { siteID = orderSiteID } } } row := model.Notification{ UserID: entry.UserID, Type: entry.Type, Title: entry.Title, Content: entry.Content, BizType: entry.BizType, BizID: entry.BizID, DistributionSiteID: siteID, } if row.Type == "" { row.Type = "system" } if err := tx.Create(&row).Error; err != nil { return err } } return nil } func toDTO(row model.Notification) NotificationDTO { return NotificationDTO{ ID: row.ID, UserID: row.UserID, Type: row.Type, Title: row.Title, Content: row.Content, BizType: row.BizType, BizID: row.BizID, DistributionSiteID: row.DistributionSiteID, ReadAt: row.ReadAt, CreatedAt: row.CreatedAt, } }