package promo import ( "context" "errors" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) type RuleWithPrice struct { Rule PromoPrice int64 `gorm:"column:promo_price"` } type Model interface { QueryEligibleRules(ctx context.Context, subscribeId int64, quantity 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, page, size int, ruleType string, enabled *bool, search string) (int64, []*Rule, error) UpsertPrices(ctx context.Context, ruleId int64, items []*SubscribePromo) error FindPrice(ctx context.Context, id int64) (*SubscribePromo, error) DeletePrice(ctx context.Context, id int64) error QueryPriceList(ctx context.Context, ruleId int64, page, size int) (int64, []*SubscribePromo, error) QueryUsageList(ctx context.Context, params UsageFilter) (int64, []*Usage, error) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error } type UsageFilter struct { Page int Size int RuleId int64 UserId int64 SubscribeId int64 OrderNo string } type defaultPromoModel struct { db *gorm.DB } func NewModel(db *gorm.DB, _ *redis.Client) Model { return &defaultPromoModel{db: db} } func (m *defaultPromoModel) QueryEligibleRules(ctx context.Context, subscribeId int64, quantity 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.quantity = ? AND sp.promo_price > 0 AND pr.enabled = ?", subscribeId, quantity, 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 resp Rule if err := m.db.WithContext(ctx).Model(&Rule{}).Where("id = ?", id).First(&resp).Error; err != nil { return nil, err } return &resp, nil } 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 { return m.db.WithContext(ctx).Delete(&Rule{}, id).Error } func (m *defaultPromoModel) QueryRuleList(ctx context.Context, page, size int, ruleType string, enabled *bool, search string) (int64, []*Rule, error) { if page <= 0 { page = 1 } if size <= 0 { size = 10 } var total int64 var list []*Rule db := m.db.WithContext(ctx).Model(&Rule{}) if ruleType != "" { db = db.Where("type = ?", ruleType) } if enabled != nil { db = db.Where("enabled = ?", *enabled) } if search != "" { db = db.Where("name LIKE ?", "%"+search+"%") } if err := db.Count(&total).Error; err != nil { return 0, nil, err } err := db.Order("priority DESC").Order("id DESC").Limit(size).Offset((page - 1) * size).Find(&list).Error return total, list, err } func (m *defaultPromoModel) UpsertPrices(ctx context.Context, ruleId int64, items []*SubscribePromo) error { return m.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { for _, item := range items { if item == nil { continue } item.PromoRuleId = ruleId var existing SubscribePromo err := tx.Model(&SubscribePromo{}). Where("subscribe_id = ? AND quantity = ? AND promo_rule_id = ?", item.SubscribeId, item.Quantity, ruleId). First(&existing).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } if errors.Is(err, gorm.ErrRecordNotFound) { return tx.Create(item).Error } existing.Quantity = item.Quantity existing.PromoPrice = item.PromoPrice if err := tx.Save(&existing).Error; err != nil { return err } } return nil }) } func (m *defaultPromoModel) FindPrice(ctx context.Context, id int64) (*SubscribePromo, error) { var resp SubscribePromo if err := m.db.WithContext(ctx).Model(&SubscribePromo{}).Where("id = ?", id).First(&resp).Error; err != nil { return nil, err } return &resp, nil } func (m *defaultPromoModel) DeletePrice(ctx context.Context, id int64) error { return m.db.WithContext(ctx).Delete(&SubscribePromo{}, id).Error } func (m *defaultPromoModel) QueryPriceList(ctx context.Context, ruleId int64, page, size int) (int64, []*SubscribePromo, error) { if page <= 0 { page = 1 } if size <= 0 { size = 10 } var total int64 var list []*SubscribePromo db := m.db.WithContext(ctx).Model(&SubscribePromo{}).Where("promo_rule_id = ?", ruleId) if err := db.Count(&total).Error; err != nil { return 0, nil, err } err := db.Order("id DESC").Limit(size).Offset((page - 1) * size).Find(&list).Error return total, list, err } func (m *defaultPromoModel) QueryUsageList(ctx context.Context, params UsageFilter) (int64, []*Usage, error) { if params.Page <= 0 { params.Page = 1 } if params.Size <= 0 { params.Size = 10 } var total int64 var list []*Usage db := m.db.WithContext(ctx).Model(&Usage{}) if params.RuleId > 0 { db = db.Where("promo_rule_id = ?", params.RuleId) } if params.UserId > 0 { db = db.Where("user_id = ?", params.UserId) } if params.SubscribeId > 0 { db = db.Where("subscribe_id = ?", params.SubscribeId) } if params.OrderNo != "" { db = db.Where("order_no = ?", params.OrderNo) } if err := db.Count(&total).Error; err != nil { return 0, nil, err } err := db.Order("id DESC").Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&list).Error return total, list, err } func (m *defaultPromoModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error { return m.db.WithContext(ctx).Transaction(fn) }