package repository import ( "apskel-pos-be/internal/entities" "context" "math/rand" "time" "gorm.io/gorm" ) type VoucherRepository struct { db *gorm.DB } func NewVoucherRepository(db *gorm.DB) *VoucherRepository { return &VoucherRepository{db: db} } func (r *VoucherRepository) Create(ctx context.Context, voucher *entities.Voucher) error { return r.db.WithContext(ctx).Create(voucher).Error } func (r *VoucherRepository) GetByID(ctx context.Context, id int64) (*entities.Voucher, error) { var voucher entities.Voucher err := r.db.WithContext(ctx).Where("id = ?", id).First(&voucher).Error if err != nil { return nil, err } return &voucher, nil } func (r *VoucherRepository) GetByVoucherCode(ctx context.Context, voucherCode string) (*entities.Voucher, error) { var voucher entities.Voucher err := r.db.WithContext(ctx).Where("voucher_code = ?", voucherCode).First(&voucher).Error if err != nil { return nil, err } return &voucher, nil } func (r *VoucherRepository) List(ctx context.Context, offset, limit int) ([]entities.Voucher, int64, error) { var vouchers []entities.Voucher var total int64 query := r.db.WithContext(ctx) if err := query.Model(&entities.Voucher{}).Count(&total).Error; err != nil { return nil, 0, err } err := query.Offset(offset).Limit(limit).Find(&vouchers).Error if err != nil { return nil, 0, err } return vouchers, total, nil } func (r *VoucherRepository) GetRandomVouchers(ctx context.Context, limit int) ([]entities.Voucher, error) { var vouchers []entities.Voucher // First, get the total count var total int64 if err := r.db.WithContext(ctx).Model(&entities.Voucher{}).Count(&total).Error; err != nil { return nil, err } if total == 0 { return vouchers, nil } // If we have fewer vouchers than requested, return all if int(total) <= limit { err := r.db.WithContext(ctx).Find(&vouchers).Error return vouchers, err } // Generate random offsets to get random vouchers rand.Seed(time.Now().UnixNano()) usedOffsets := make(map[int]bool) for len(vouchers) < limit && len(usedOffsets) < int(total) { offset := rand.Intn(int(total)) if usedOffsets[offset] { continue } usedOffsets[offset] = true var voucher entities.Voucher err := r.db.WithContext(ctx).Offset(offset).Limit(1).First(&voucher).Error if err != nil { continue } vouchers = append(vouchers, voucher) } return vouchers, nil } func (r *VoucherRepository) GetRandomVouchersByRows(ctx context.Context, rows int, winnerNumber *int) ([][]entities.Voucher, error) { var allVouchers []entities.Voucher var err error // First, try to get vouchers based on winner_number parameter if winnerNumber != nil { // If winner_number is provided, filter by it and exclude already won vouchers query := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", *winnerNumber, false) err = query.Find(&allVouchers).Error if err != nil { return nil, err } // If no vouchers found for the specified winner_number, fallback to winner_number = 0 if len(allVouchers) == 0 { fallbackQuery := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", 0, false) err = fallbackQuery.Find(&allVouchers).Error if err != nil { return nil, err } } // If still no vouchers found, try without is_winner filter for winner_number = 0 if len(allVouchers) == 0 { fallbackQuery2 := r.db.WithContext(ctx).Where("winner_number = ?", 0) err = fallbackQuery2.Find(&allVouchers).Error if err != nil { return nil, err } } // If still no vouchers found, get any vouchers available if len(allVouchers) == 0 { err = r.db.WithContext(ctx).Find(&allVouchers).Error if err != nil { return nil, err } } } else { // If winner_number is not provided, use default winner_number = 0 and exclude already won vouchers query := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", 0, false) err = query.Find(&allVouchers).Error if err != nil { return nil, err } // If no vouchers found, try without is_winner filter for winner_number = 0 if len(allVouchers) == 0 { fallbackQuery := r.db.WithContext(ctx).Where("winner_number = ?", 0) err = fallbackQuery.Find(&allVouchers).Error if err != nil { return nil, err } } // If still no vouchers found, get any vouchers available if len(allVouchers) == 0 { err = r.db.WithContext(ctx).Find(&allVouchers).Error if err != nil { return nil, err } } } if len(allVouchers) == 0 { // Return empty rows if no vouchers available return make([][]entities.Voucher, rows), nil } // Shuffle the vouchers for randomness rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(allVouchers), func(i, j int) { allVouchers[i], allVouchers[j] = allVouchers[j], allVouchers[i] }) // Calculate vouchers per row (distribute evenly) vouchersPerRow := len(allVouchers) / rows if vouchersPerRow == 0 { vouchersPerRow = 1 // At least 1 voucher per row } // Organize vouchers into rows voucherRows := make([][]entities.Voucher, rows) voucherIndex := 0 for i := 0; i < rows; i++ { // Calculate how many vouchers this row should have remainingRows := rows - i remainingVouchers := len(allVouchers) - voucherIndex // Distribute remaining vouchers evenly among remaining rows rowSize := remainingVouchers / remainingRows if remainingVouchers%remainingRows > 0 { rowSize++ } // Ensure we don't exceed available vouchers if voucherIndex+rowSize > len(allVouchers) { rowSize = len(allVouchers) - voucherIndex } // Create the row if voucherIndex < len(allVouchers) { endIdx := voucherIndex + rowSize if endIdx > len(allVouchers) { endIdx = len(allVouchers) } voucherRows[i] = allVouchers[voucherIndex:endIdx] voucherIndex = endIdx } else { voucherRows[i] = []entities.Voucher{} // Empty row } } return voucherRows, nil } func (r *VoucherRepository) Update(ctx context.Context, voucher *entities.Voucher) error { return r.db.WithContext(ctx).Save(voucher).Error } func (r *VoucherRepository) Delete(ctx context.Context, id int64) error { return r.db.WithContext(ctx).Delete(&entities.Voucher{}, id).Error } func (r *VoucherRepository) MarkAsWinner(ctx context.Context, id int64) error { return r.db.WithContext(ctx).Model(&entities.Voucher{}).Where("id = ?", id).Update("is_winner", true).Error }