package promo import ( "context" "errors" "github.com/redis/go-redis/v9" "gorm.io/gorm" "gorm.io/gorm/clause" ) type RuleWithPrice struct { Rule PromoPrice int64 `gorm:"column:promo_price"` } type RuleFilter struct { Page int Size int Type string Enabled *bool Search string } type SubscribePromoFilter struct { Page int Size int PromoRuleId int64 } type UsageFilter struct { Page int Size int PromoRuleId int64 UserId int64 SubscribeId int64 OrderNo string } type Model interface { QueryEligibleRules(ctx context.Context, subscribeId int64) ([]*RuleWithPrice, error) InsertUsage(ctx context.Context, data *Usage, tx ...*gorm.DB) error InsertRule(ctx context.Context, data *Rule) error FindRule(ctx context.Context, id int64) (*Rule, error) UpdateRule(ctx context.Context, data *Rule) error DeleteRule(ctx context.Context, id int64) error QueryRuleList(ctx context.Context, filter RuleFilter) (int64, []*Rule, error) UpsertSubscribePromos(ctx context.Context, data []*SubscribePromo) error FindSubscribePromo(ctx context.Context, id int64) (*SubscribePromo, error) DeleteSubscribePromo(ctx context.Context, id int64) error QuerySubscribePromoList(ctx context.Context, filter SubscribePromoFilter) (int64, []*SubscribePromo, error) QueryUsageList(ctx context.Context, filter UsageFilter) (int64, []*Usage, error) } type defaultPromoModel struct { db *gorm.DB } func NewModel(db *gorm.DB, _ *redis.Client) Model { return &defaultPromoModel{db: db} } func normalizePage(page, size int) (int, int) { if page <= 0 { page = 1 } if size <= 0 { size = 10 } if size > 100 { size = 100 } return page, size } func (m *defaultPromoModel) QueryEligibleRules(ctx context.Context, subscribeId int64) ([]*RuleWithPrice, error) { var list []*RuleWithPrice err := m.db.WithContext(ctx). Table("promo_rule AS pr"). Select("pr.*, sp.promo_price"). Joins("JOIN subscribe_promo AS sp ON sp.promo_rule_id = pr.id"). Where("sp.subscribe_id = ? AND sp.promo_price > 0 AND pr.enabled = ?", subscribeId, true). Where("pr.deleted_at IS NULL"). Order("pr.priority DESC"). Order("pr.id ASC"). Find(&list).Error return list, err } func (m *defaultPromoModel) InsertUsage(ctx context.Context, data *Usage, tx ...*gorm.DB) error { db := m.db.WithContext(ctx) if len(tx) > 0 { db = tx[0].WithContext(ctx) } return db.Model(&Usage{}).Create(data).Error } func (m *defaultPromoModel) InsertRule(ctx context.Context, data *Rule) error { return m.db.WithContext(ctx).Create(data).Error } func (m *defaultPromoModel) FindRule(ctx context.Context, id int64) (*Rule, error) { var data Rule err := m.db.WithContext(ctx).Model(&Rule{}).Where("id = ?", id).First(&data).Error return &data, err } func (m *defaultPromoModel) UpdateRule(ctx context.Context, data *Rule) error { return m.db.WithContext(ctx).Save(data).Error } func (m *defaultPromoModel) DeleteRule(ctx context.Context, id int64) error { result := m.db.WithContext(ctx).Delete(&Rule{}, id) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return gorm.ErrRecordNotFound } return nil } func (m *defaultPromoModel) QueryRuleList(ctx context.Context, filter RuleFilter) (int64, []*Rule, error) { page, size := normalizePage(filter.Page, filter.Size) var total int64 var list []*Rule query := m.db.WithContext(ctx).Model(&Rule{}) if filter.Type != "" { query = query.Where("type = ?", filter.Type) } if filter.Enabled != nil { query = query.Where("enabled = ?", *filter.Enabled) } if filter.Search != "" { search := "%" + filter.Search + "%" query = query.Where("name LIKE ?", search) } if err := query.Count(&total).Error; err != nil { return 0, nil, err } err := query.Order("priority DESC").Order("id DESC"). Limit(size).Offset((page - 1) * size). Find(&list).Error return total, list, err } func (m *defaultPromoModel) UpsertSubscribePromos(ctx context.Context, data []*SubscribePromo) error { if len(data) == 0 { return nil } return m.db.WithContext(ctx).Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "subscribe_id"}, {Name: "promo_rule_id"}}, DoUpdates: clause.AssignmentColumns([]string{"promo_price", "updated_at"}), }).Create(&data).Error } func (m *defaultPromoModel) FindSubscribePromo(ctx context.Context, id int64) (*SubscribePromo, error) { var data SubscribePromo err := m.db.WithContext(ctx).Model(&SubscribePromo{}).Where("id = ?", id).First(&data).Error return &data, err } func (m *defaultPromoModel) DeleteSubscribePromo(ctx context.Context, id int64) error { result := m.db.WithContext(ctx).Delete(&SubscribePromo{}, id) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return gorm.ErrRecordNotFound } return nil } func (m *defaultPromoModel) QuerySubscribePromoList(ctx context.Context, filter SubscribePromoFilter) (int64, []*SubscribePromo, error) { page, size := normalizePage(filter.Page, filter.Size) var total int64 var list []*SubscribePromo query := m.db.WithContext(ctx).Model(&SubscribePromo{}) if filter.PromoRuleId > 0 { query = query.Where("promo_rule_id = ?", filter.PromoRuleId) } if err := query.Count(&total).Error; err != nil { return 0, nil, err } err := query.Order("id DESC").Limit(size).Offset((page - 1) * size).Find(&list).Error return total, list, err } func (m *defaultPromoModel) QueryUsageList(ctx context.Context, filter UsageFilter) (int64, []*Usage, error) { page, size := normalizePage(filter.Page, filter.Size) var total int64 var list []*Usage query := m.db.WithContext(ctx).Model(&Usage{}) if filter.PromoRuleId > 0 { query = query.Where("promo_rule_id = ?", filter.PromoRuleId) } if filter.UserId > 0 { query = query.Where("user_id = ?", filter.UserId) } if filter.SubscribeId > 0 { query = query.Where("subscribe_id = ?", filter.SubscribeId) } if filter.OrderNo != "" { query = query.Where("order_no LIKE ?", "%"+filter.OrderNo+"%") } if err := query.Count(&total).Error; err != nil { return 0, nil, err } err := query.Order("id DESC").Limit(size).Offset((page - 1) * size).Find(&list).Error return total, list, err } func IsNotFound(err error) bool { return errors.Is(err, gorm.ErrRecordNotFound) }