diff --git a/apps/carrier-connector/internal/currency/rateplan_methods.go b/apps/carrier-connector/internal/currency/rateplan_methods.go new file mode 100644 index 0000000..fcc549c --- /dev/null +++ b/apps/carrier-connector/internal/currency/rateplan_methods.go @@ -0,0 +1,162 @@ +package currency + +import ( + "context" + "fmt" + "time" + + "github.com/sirupsen/logrus" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan" +) + +// GetPlansInCurrency gets rate plans with prices converted to a specific currency +func (rpci *RatePlanCurrencyIntegrator) GetPlansInCurrency(ctx context.Context, filter *rateplan.RatePlanFilter, targetCurrency string) ([]*rateplan.RatePlan, error) { + // Get plans using original filter + plans, err := rpci.ratePlanService.ListRatePlans(ctx, filter) + if err != nil { + return nil, fmt.Errorf("failed to get rate plans: %w", err) + } + + // Convert prices to target currency + for _, plan := range plans { + if plan.Currency != targetCurrency { + conversion, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, targetCurrency) + if err != nil { + rpci.logger.WithError(err).WithFields(logrus.Fields{ + "plan_id": plan.ID, + "from_currency": plan.Currency, + "to_currency": targetCurrency, + }).Warn("Failed to convert plan price") + continue + } + + // Store original price and update with converted price + if plan.Metadata == nil { + plan.Metadata = make(map[string]interface{}) + } + plan.Metadata["original_price"] = plan.BasePrice + plan.Metadata["original_currency"] = plan.Currency + plan.Metadata["exchange_rate"] = conversion.ExchangeRate + plan.BasePrice = conversion.ConvertedAmount + plan.Currency = targetCurrency + } + } + + return plans, nil +} + +// UpdatePlanCurrency updates a rate plan's currency and converts prices +func (rpci *RatePlanCurrencyIntegrator) UpdatePlanCurrency(ctx context.Context, planID string, newCurrency string) error { + // Get the current plan + plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID) + if err != nil { + return fmt.Errorf("failed to get rate plan: %w", err) + } + + // If currency is the same, no conversion needed + if plan.Currency == newCurrency { + return nil + } + + // Convert all monetary values to new currency + convertedPrice, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, newCurrency) + if err != nil { + return fmt.Errorf("failed to convert base price: %w", err) + } + + // Update plan with new currency and converted prices + plan.BasePrice = convertedPrice.ConvertedAmount + plan.Currency = newCurrency + + // Store conversion information in metadata + if plan.Metadata == nil { + plan.Metadata = make(map[string]interface{}) + } + plan.Metadata["currency_conversion"] = map[string]interface{}{ + "from_currency": plan.Metadata["original_currency"], + "to_currency": newCurrency, + "exchange_rate": convertedPrice.ExchangeRate, + "converted_at": time.Now(), + } + + // Update the plan + updatedPlan, err := rpci.ratePlanService.UpdateRatePlan(ctx, plan) + if err != nil { + return fmt.Errorf("failed to update rate plan: %w", err) + } + + // Use the updated plan for logging + plan = updatedPlan + + rpci.logger.WithFields(logrus.Fields{ + "plan_id": planID, + "from_currency": plan.Metadata["original_currency"], + "to_currency": newCurrency, + "exchange_rate": convertedPrice.ExchangeRate, + }).Info("Rate plan currency updated") + + return nil +} + +// calculateOverageCost calculates overage costs for usage +func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(ctx context.Context, plan *rateplan.RatePlan, usage *rateplan.RatePlanUsage) (float64, error) { + overageCost := 0.0 + + // Calculate data overage + if plan.DataAllowance != nil && usage.DataUsed > plan.DataAllowance.Amount { + dataOverage := usage.DataUsed - plan.DataAllowance.Amount + if plan.OverageRates != nil { + overageCost += float64(dataOverage) * plan.OverageRates.DataRate + } + } + + // Calculate voice overage + if plan.VoiceAllowance != nil && usage.VoiceUsed > plan.VoiceAllowance.Minutes { + voiceOverage := usage.VoiceUsed - plan.VoiceAllowance.Minutes + if plan.OverageRates != nil { + overageCost += float64(voiceOverage) * plan.OverageRates.VoiceRate + } + } + + // Calculate SMS overage + if plan.SMSAllowance != nil && usage.SMSUsed > plan.SMSAllowance.Messages { + smsOverage := usage.SMSUsed - plan.SMSAllowance.Messages + if plan.OverageRates != nil { + overageCost += float64(smsOverage) * plan.OverageRates.SMSRate + } + } + + return overageCost, nil +} + +// GetCurrencyUsageForPlan gets currency usage statistics for a specific rate plan +func (rpci *RatePlanCurrencyIntegrator) GetCurrencyUsageForPlan(ctx context.Context, planID string) (map[string]int64, error) { + // Get all subscriptions for this plan + filter := &rateplan.SubscriptionFilter{ + RatePlanID: planID, + Status: rateplan.SubscriptionStatusActive, + } + + subscriptions, err := rpci.ratePlanService.ListSubscriptions(ctx, "", filter) + if err != nil { + return nil, fmt.Errorf("failed to get subscriptions: %w", err) + } + + // Count currencies + currencyUsage := make(map[string]int64) + + for _, subscription := range subscriptions { + currency := rpci.baseCurrency // Default to base currency + if subscription.Metadata != nil { + if subCurrency, exists := subscription.Metadata["subscription_currency"]; exists { + if subCurrencyStr, ok := subCurrency.(string); ok { + currency = subCurrencyStr + } + } + } + currencyUsage[currency]++ + } + + return currencyUsage, nil +} diff --git a/apps/carrier-connector/internal/currency/repository_core.go b/apps/carrier-connector/internal/currency/repository_core.go new file mode 100644 index 0000000..8ed80f7 --- /dev/null +++ b/apps/carrier-connector/internal/currency/repository_core.go @@ -0,0 +1,190 @@ +package currency + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +// GormRepository implements the currency repository using GORM +type GormRepository struct { + db *gorm.DB + logger *logrus.Logger +} + +// NewGormRepository creates a new GORM currency repository +func NewGormRepository(db *gorm.DB, logger *logrus.Logger) *GormRepository { + return &GormRepository{ + db: db, + logger: logger, + } +} + +// CreateCurrency creates a new currency +func (r *GormRepository) CreateCurrency(ctx context.Context, currency *Currency) error { + model := r.currencyToModel(currency) + + if err := r.db.WithContext(ctx).Create(model).Error; err != nil { + r.logger.WithError(err).Error("Failed to create currency") + return fmt.Errorf("failed to create currency: %w", err) + } + + return nil +} + +// GetCurrency retrieves a currency by code +func (r *GormRepository) GetCurrency(ctx context.Context, code string) (*Currency, error) { + var model CurrencyModel + if err := r.db.WithContext(ctx).Where("code = ?", code).First(&model).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, fmt.Errorf("currency not found: %s", code) + } + r.logger.WithError(err).Error("Failed to get currency") + return nil, fmt.Errorf("failed to get currency: %w", err) + } + + return r.modelToCurrency(&model) +} + +// UpdateCurrency updates an existing currency +func (r *GormRepository) UpdateCurrency(ctx context.Context, currency *Currency) error { + model := r.currencyToModel(currency) + + if err := r.db.WithContext(ctx).Save(model).Error; err != nil { + r.logger.WithError(err).Error("Failed to update currency") + return fmt.Errorf("failed to update currency: %w", err) + } + + return nil +} + +// DeleteCurrency deletes a currency +func (r *GormRepository) DeleteCurrency(ctx context.Context, code string) error { + if err := r.db.WithContext(ctx).Delete(&CurrencyModel{}, "code = ?", code).Error; err != nil { + r.logger.WithError(err).Error("Failed to delete currency") + return fmt.Errorf("failed to delete currency: %w", err) + } + + return nil +} + +// ListCurrencies retrieves currencies based on filter +func (r *GormRepository) ListCurrencies(ctx context.Context, filter *CurrencyFilter) ([]*Currency, error) { + query := r.db.WithContext(ctx).Model(&CurrencyModel{}) + + // Apply filters + if filter.IsActive != nil { + query = query.Where("is_active = ?", *filter.IsActive) + } + if filter.Region != "" { + query = query.Where("supported_regions LIKE ?", "%"+filter.Region+"%") + } + + // Apply sorting + if filter.SortBy != "" { + order := filter.SortBy + if filter.SortOrder == "desc" { + order += " DESC" + } + query = query.Order(order) + } else { + query = query.Order("code ASC") + } + + // Apply pagination + if filter.Limit > 0 { + query = query.Limit(filter.Limit) + } + if filter.Offset > 0 { + query = query.Offset(filter.Offset) + } + + var models []CurrencyModel + if err := query.Find(&models).Error; err != nil { + r.logger.WithError(err).Error("Failed to list currencies") + return nil, fmt.Errorf("failed to list currencies: %w", err) + } + + currencies := make([]*Currency, 0, len(models)) + for _, model := range models { + curr, err := r.modelToCurrency(&model) + if err != nil { + r.logger.WithError(err).Error("Failed to convert currency model") + continue + } + currencies = append(currencies, curr) + } + + return currencies, nil +} + +// CountCurrencies counts currencies based on filter +func (r *GormRepository) CountCurrencies(ctx context.Context, filter *CurrencyFilter) (int, error) { + query := r.db.WithContext(ctx).Model(&CurrencyModel{}) + + // Apply filters + if filter.IsActive != nil { + query = query.Where("is_active = ?", *filter.IsActive) + } + if filter.Region != "" { + query = query.Where("supported_regions LIKE ?", "%"+filter.Region+"%") + } + + var count int64 + if err := query.Count(&count).Error; err != nil { + r.logger.WithError(err).Error("Failed to count currencies") + return 0, fmt.Errorf("failed to count currencies: %w", err) + } + + return int(count), nil +} + +// CreateExchangeRate creates a new exchange rate +func (r *GormRepository) CreateExchangeRate(ctx context.Context, rate *ExchangeRate) error { + model := r.exchangeRateToModel(rate) + + if err := r.db.WithContext(ctx).Create(model).Error; err != nil { + r.logger.WithError(err).Error("Failed to create exchange rate") + return fmt.Errorf("failed to create exchange rate: %w", err) + } + + return nil +} + +// GetExchangeRate retrieves an exchange rate by ID +func (r *GormRepository) GetExchangeRate(ctx context.Context, fromCurrency, toCurrency string) (*ExchangeRate, error) { + var model ExchangeRateModel + if err := r.db.WithContext(ctx).Where("from_currency = ? AND to_currency = ?", fromCurrency, toCurrency).First(&model).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, fmt.Errorf("exchange rate not found: %s to %s", fromCurrency, toCurrency) + } + r.logger.WithError(err).Error("Failed to get exchange rate") + return nil, fmt.Errorf("failed to get exchange rate: %w", err) + } + + return r.modelToExchangeRate(&model) +} + +// UpdateExchangeRate updates an existing exchange rate +func (r *GormRepository) UpdateExchangeRate(ctx context.Context, rate *ExchangeRate) error { + model := r.exchangeRateToModel(rate) + + if err := r.db.WithContext(ctx).Save(model).Error; err != nil { + r.logger.WithError(err).Error("Failed to update exchange rate") + return fmt.Errorf("failed to update exchange rate: %w", err) + } + + return nil +} + +// DeleteExchangeRate deletes an exchange rate +func (r *GormRepository) DeleteExchangeRate(ctx context.Context, id string) error { + if err := r.db.WithContext(ctx).Delete(&ExchangeRateModel{}, "id = ?", id).Error; err != nil { + r.logger.WithError(err).Error("Failed to delete exchange rate") + return fmt.Errorf("failed to delete exchange rate: %w", err) + } + + return nil +} diff --git a/apps/carrier-connector/internal/currency/repository_helpers.go b/apps/carrier-connector/internal/currency/repository_helpers.go new file mode 100644 index 0000000..b560bf3 --- /dev/null +++ b/apps/carrier-connector/internal/currency/repository_helpers.go @@ -0,0 +1,126 @@ +package currency + +import ( + "encoding/json" + "fmt" + "strings" +) + +// currencyToModel converts currency domain model to database model +func (r *GormRepository) currencyToModel(currency *Currency) *CurrencyModel { + return &CurrencyModel{ + Code: currency.Code, + Name: currency.Name, + Symbol: currency.Symbol, + DecimalPlaces: currency.DecimalPlaces, + IsActive: currency.IsActive, + SupportedRegions: strings.Join(currency.SupportedRegions, ","), + CreatedAt: currency.CreatedAt, + UpdatedAt: currency.UpdatedAt, + } +} + +// modelToCurrency converts database model to currency domain model +func (r *GormRepository) modelToCurrency(model *CurrencyModel) (*Currency, error) { + var regions []string + if model.SupportedRegions != "" { + regions = strings.Split(model.SupportedRegions, ",") + } + + return &Currency{ + Code: model.Code, + Name: model.Name, + Symbol: model.Symbol, + DecimalPlaces: model.DecimalPlaces, + IsActive: model.IsActive, + SupportedRegions: regions, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + }, nil +} + +// exchangeRateToModel converts exchange rate domain model to database model +func (r *GormRepository) exchangeRateToModel(rate *ExchangeRate) *ExchangeRateModel { + return &ExchangeRateModel{ + ID: rate.ID, + FromCurrency: rate.FromCurrency, + ToCurrency: rate.ToCurrency, + Rate: rate.Rate, + Source: rate.Source, + ValidFrom: rate.ValidFrom, + ValidTo: rate.ValidTo, + IsActive: rate.IsActive, + CreatedAt: rate.CreatedAt, + UpdatedAt: rate.UpdatedAt, + } +} + +// modelToExchangeRate converts database model to exchange rate domain model +func (r *GormRepository) modelToExchangeRate(model *ExchangeRateModel) (*ExchangeRate, error) { + return &ExchangeRate{ + ID: model.ID, + FromCurrency: model.FromCurrency, + ToCurrency: model.ToCurrency, + Rate: model.Rate, + Source: model.Source, + ValidFrom: model.ValidFrom, + ValidTo: model.ValidTo, + IsActive: model.IsActive, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + }, nil +} + +// transactionToModel converts transaction domain model to database model +func (r *GormRepository) transactionToModel(transaction *Transaction) *TransactionModel { + metadata := "" + if transaction.Metadata != nil { + if data, err := json.Marshal(transaction.Metadata); err == nil { + metadata = string(data) + } + } + + return &TransactionModel{ + ID: transaction.ID, + ProfileID: transaction.ProfileID, + SubscriptionID: transaction.SubscriptionID, + Type: string(transaction.Type), + Amount: transaction.Amount, + Currency: transaction.Currency, + BaseAmount: transaction.BaseAmount, + BaseCurrency: transaction.BaseCurrency, + ExchangeRate: transaction.ExchangeRate, + Description: transaction.Description, + Status: string(transaction.Status), + Metadata: metadata, + CreatedAt: transaction.CreatedAt, + UpdatedAt: transaction.UpdatedAt, + } +} + +// modelToTransaction converts database model to transaction domain model +func (r *GormRepository) modelToTransaction(model *TransactionModel) (*Transaction, error) { + var metadata map[string]interface{} + if model.Metadata != "" { + if err := json.Unmarshal([]byte(model.Metadata), &metadata); err != nil { + return nil, fmt.Errorf("failed to parse metadata: %w", err) + } + } + + return &Transaction{ + ID: model.ID, + ProfileID: model.ProfileID, + SubscriptionID: model.SubscriptionID, + Type: TransactionType(model.Type), + Amount: model.Amount, + Currency: model.Currency, + BaseAmount: model.BaseAmount, + BaseCurrency: model.BaseCurrency, + ExchangeRate: model.ExchangeRate, + Description: model.Description, + Status: TransactionStatus(model.Status), + Metadata: metadata, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + }, nil +} diff --git a/apps/carrier-connector/internal/currency/repository_rates.go b/apps/carrier-connector/internal/currency/repository_rates.go new file mode 100644 index 0000000..8ab5684 --- /dev/null +++ b/apps/carrier-connector/internal/currency/repository_rates.go @@ -0,0 +1,90 @@ +package currency + +import ( + "context" + "fmt" + "time" + + "gorm.io/gorm" +) + +// ListExchangeRates retrieves exchange rates based on filter +func (r *GormRepository) ListExchangeRates(ctx context.Context, filter *ExchangeRateFilter) ([]*ExchangeRate, error) { + query := r.db.WithContext(ctx).Model(&ExchangeRateModel{}) + + // Apply filters + if filter.FromCurrency != "" { + query = query.Where("from_currency = ?", filter.FromCurrency) + } + if filter.ToCurrency != "" { + query = query.Where("to_currency = ?", filter.ToCurrency) + } + if filter.Source != "" { + query = query.Where("source = ?", filter.Source) + } + if filter.IsValid != nil { + now := time.Now() + if *filter.IsValid { + query = query.Where("valid_from <= ? AND (valid_to IS NULL OR valid_to >= ?)", now, now) + } else { + query = query.Where("valid_to < ?", now) + } + } + + // Apply sorting + if filter.SortBy != "" { + order := filter.SortBy + if filter.SortOrder == "desc" { + order += " DESC" + } + query = query.Order(order) + } else { + query = query.Order("from_currency, to_currency, valid_from DESC") + } + + // Apply pagination + if filter.Limit > 0 { + query = query.Limit(filter.Limit) + } + if filter.Offset > 0 { + query = query.Offset(filter.Offset) + } + + var models []ExchangeRateModel + if err := query.Find(&models).Error; err != nil { + r.logger.WithError(err).Error("Failed to list exchange rates") + return nil, fmt.Errorf("failed to list exchange rates: %w", err) + } + + rates := make([]*ExchangeRate, 0, len(models)) + for _, model := range models { + rate, err := r.modelToExchangeRate(&model) + if err != nil { + r.logger.WithError(err).Error("Failed to convert exchange rate model") + continue + } + rates = append(rates, rate) + } + + return rates, nil +} + +// GetLatestExchangeRate gets the latest valid exchange rate +func (r *GormRepository) GetLatestExchangeRate(ctx context.Context, fromCurrency, toCurrency string) (*ExchangeRate, error) { + now := time.Now() + var model ExchangeRateModel + + if err := r.db.WithContext(ctx). + Where("from_currency = ? AND to_currency = ? AND is_active = ?", fromCurrency, toCurrency, true). + Where("valid_from <= ? AND (valid_to IS NULL OR valid_to >= ?)", now, now). + Order("valid_from DESC"). + First(&model).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, fmt.Errorf("no valid exchange rate found: %s to %s", fromCurrency, toCurrency) + } + r.logger.WithError(err).Error("Failed to get latest exchange rate") + return nil, fmt.Errorf("failed to get latest exchange rate: %w", err) + } + + return r.modelToExchangeRate(&model) +} diff --git a/apps/carrier-connector/internal/currency/repository_transactions.go b/apps/carrier-connector/internal/currency/repository_transactions.go new file mode 100644 index 0000000..2a86017 --- /dev/null +++ b/apps/carrier-connector/internal/currency/repository_transactions.go @@ -0,0 +1,169 @@ +package currency + +import ( + "context" + "fmt" + + "gorm.io/gorm" +) + +// CreateTransaction creates a new transaction +func (r *GormRepository) CreateTransaction(ctx context.Context, transaction *Transaction) error { + model := r.transactionToModel(transaction) + + if err := r.db.WithContext(ctx).Create(model).Error; err != nil { + r.logger.WithError(err).Error("Failed to create transaction") + return fmt.Errorf("failed to create transaction: %w", err) + } + + return nil +} + +// GetTransaction retrieves a transaction by ID +func (r *GormRepository) GetTransaction(ctx context.Context, id string) (*Transaction, error) { + var model TransactionModel + if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, fmt.Errorf("transaction not found: %s", id) + } + r.logger.WithError(err).Error("Failed to get transaction") + return nil, fmt.Errorf("failed to get transaction: %w", err) + } + + return r.modelToTransaction(&model) +} + +// UpdateTransaction updates an existing transaction +func (r *GormRepository) UpdateTransaction(ctx context.Context, transaction *Transaction) error { + model := r.transactionToModel(transaction) + + if err := r.db.WithContext(ctx).Save(model).Error; err != nil { + r.logger.WithError(err).Error("Failed to update transaction") + return fmt.Errorf("failed to update transaction: %w", err) + } + + return nil +} + +// DeleteTransaction deletes a transaction +func (r *GormRepository) DeleteTransaction(ctx context.Context, id string) error { + if err := r.db.WithContext(ctx).Delete(&TransactionModel{}, "id = ?", id).Error; err != nil { + r.logger.WithError(err).Error("Failed to delete transaction") + return fmt.Errorf("failed to delete transaction: %w", err) + } + + return nil +} + +// ListTransactions retrieves transactions based on filter +func (r *GormRepository) ListTransactions(ctx context.Context, filter *TransactionFilter) ([]*Transaction, error) { + query := r.db.WithContext(ctx).Model(&TransactionModel{}) + + // Apply filters + if filter.ProfileID != "" { + query = query.Where("profile_id = ?", filter.ProfileID) + } + if filter.SubscriptionID != "" { + query = query.Where("subscription_id = ?", filter.SubscriptionID) + } + if filter.Type != "" { + query = query.Where("type = ?", filter.Type) + } + if filter.Status != "" { + query = query.Where("status = ?", filter.Status) + } + if filter.Currency != "" { + query = query.Where("currency = ?", filter.Currency) + } + if filter.FromDate != nil { + query = query.Where("created_at >= ?", *filter.FromDate) + } + if filter.ToDate != nil { + query = query.Where("created_at <= ?", *filter.ToDate) + } + if filter.MinAmount > 0 { + query = query.Where("amount >= ?", filter.MinAmount) + } + if filter.MaxAmount > 0 { + query = query.Where("amount <= ?", filter.MaxAmount) + } + + // Apply sorting + if filter.SortBy != "" { + order := filter.SortBy + if filter.SortOrder == "desc" { + order += " DESC" + } + query = query.Order(order) + } else { + query = query.Order("created_at DESC") + } + + // Apply pagination + if filter.Limit > 0 { + query = query.Limit(filter.Limit) + } + if filter.Offset > 0 { + query = query.Offset(filter.Offset) + } + + var models []TransactionModel + if err := query.Find(&models).Error; err != nil { + r.logger.WithError(err).Error("Failed to list transactions") + return nil, fmt.Errorf("failed to list transactions: %w", err) + } + + transactions := make([]*Transaction, 0, len(models)) + for _, model := range models { + tx, err := r.modelToTransaction(&model) + if err != nil { + r.logger.WithError(err).Error("Failed to convert transaction model") + continue + } + transactions = append(transactions, tx) + } + + return transactions, nil +} + +// CountTransactions counts transactions based on filter +func (r *GormRepository) CountTransactions(ctx context.Context, filter *TransactionFilter) (int, error) { + query := r.db.WithContext(ctx).Model(&TransactionModel{}) + + // Apply filters + if filter.ProfileID != "" { + query = query.Where("profile_id = ?", filter.ProfileID) + } + if filter.SubscriptionID != "" { + query = query.Where("subscription_id = ?", filter.SubscriptionID) + } + if filter.Type != "" { + query = query.Where("type = ?", filter.Type) + } + if filter.Status != "" { + query = query.Where("status = ?", filter.Status) + } + if filter.Currency != "" { + query = query.Where("currency = ?", filter.Currency) + } + if filter.FromDate != nil { + query = query.Where("created_at >= ?", *filter.FromDate) + } + if filter.ToDate != nil { + query = query.Where("created_at <= ?", *filter.ToDate) + } + if filter.MinAmount > 0 { + query = query.Where("amount >= ?", filter.MinAmount) + } + if filter.MaxAmount > 0 { + query = query.Where("amount <= ?", filter.MaxAmount) + } + + var count int64 + if err := query.Count(&count).Error; err != nil { + r.logger.WithError(err).Error("Failed to count transactions") + return 0, fmt.Errorf("failed to count transactions: %w", err) + } + + return int(count), nil +}