feat: Add repository types package with rate plan, subscription, usage, and analytics domain models

- Add Repository interface with rate plan CRUD, subscription management, usage tracking, and analytics operations
- Add RatePlan struct with pricing, allowances, overage rates, discounts, and metadata fields
- Add RatePlanSubscription struct with billing cycle, auto-renewal, and applied discounts support
- Add RatePlanUsage struct with cycle tracking and data/voice/SMS usage metrics
- Add filter types for rate plans, subscriptions, usage analytics, and revenue analytics
This commit is contained in:
nutcas3
2026-05-02 01:24:23 +03:00
parent a9df60801b
commit 4fe9d22284
6 changed files with 365 additions and 45 deletions
@@ -0,0 +1,315 @@
package repository
import (
"context"
"time"
)
// Repository defines the interface for rate plan data operations
type Repository interface {
// Rate Plan operations
CreateRatePlan(ctx context.Context, plan *RatePlan) error
GetRatePlan(ctx context.Context, id string) (*RatePlan, error)
UpdateRatePlan(ctx context.Context, plan *RatePlan) error
DeleteRatePlan(ctx context.Context, id string) error
ListRatePlans(ctx context.Context, filter *RatePlanFilter) ([]*RatePlan, error)
// Subscription operations
CreateSubscription(ctx context.Context, subscription *RatePlanSubscription) error
GetSubscription(ctx context.Context, id string) (*RatePlanSubscription, error)
UpdateSubscription(ctx context.Context, subscription *RatePlanSubscription) error
GetActiveSubscription(ctx context.Context, profileID string) (*RatePlanSubscription, error)
ListSubscriptions(ctx context.Context, profileID string, filter *SubscriptionFilter) ([]*RatePlanSubscription, error)
// Usage operations
CreateUsage(ctx context.Context, usage *RatePlanUsage) error
GetUsage(ctx context.Context, id string) (*RatePlanUsage, error)
UpdateUsage(ctx context.Context, usage *RatePlanUsage) error
GetCurrentUsage(ctx context.Context, profileID string) (*RatePlanUsage, error)
ListUsageHistory(ctx context.Context, profileID string, limit int) ([]*RatePlanUsage, error)
// Analytics operations
GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsFilter) (*UsageAnalytics, error)
GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyticsFilter) (*RevenueAnalytics, error)
GetPopularPlans(ctx context.Context, limit int) ([]*RatePlan, error)
}
// RatePlanUsage represents usage data for a rate plan
type RatePlanUsage struct {
ID string `json:"id" gorm:"primaryKey"`
RatePlanID string `json:"rate_plan_id" gorm:"index"`
ProfileID string `json:"profile_id" gorm:"index"`
CycleStart time.Time `json:"cycle_start"`
CycleEnd time.Time `json:"cycle_end"`
DataUsed int64 `json:"data_used"`
VoiceUsed int64 `json:"voice_used"`
SMSUsed int64 `json:"sms_used"`
LastUpdated time.Time `json:"last_updated"`
}
// TableName returns the table name for RatePlanUsage
func (RatePlanUsage) TableName() string {
return "rate_plan_usage"
}
// RatePlanSubscription represents a subscription to a rate plan
type RatePlanSubscription struct {
ID string `json:"id" gorm:"primaryKey"`
ProfileID string `json:"profile_id" gorm:"index"`
RatePlanID string `json:"rate_plan_id" gorm:"index"`
Status SubscriptionStatus `json:"status" gorm:"index"`
StartedAt time.Time `json:"started_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
BillingCycle BillingCycle `json:"billing_cycle"`
NextBillingDate time.Time `json:"next_billing_date"`
AutoRenew bool `json:"auto_renew"`
CurrentCycle time.Time `json:"current_cycle"`
AppliedDiscounts []string `json:"applied_discounts,omitempty" gorm:"serializer:json"`
Metadata map[string]interface{} `json:"metadata,omitempty" gorm:"serializer:json"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TableName returns the table name for RatePlanSubscription
func (RatePlanSubscription) TableName() string {
return "rate_plan_subscriptions"
}
// SubscriptionStatus represents the status of a subscription
type SubscriptionStatus string
const (
SubscriptionStatusActive SubscriptionStatus = "active"
SubscriptionStatusCancelled SubscriptionStatus = "cancelled"
SubscriptionStatusExpired SubscriptionStatus = "expired"
SubscriptionStatusSuspended SubscriptionStatus = "suspended"
)
// BillingCycle represents the billing cycle type
type BillingCycle string
const (
BillingCycleDaily BillingCycle = "daily"
BillingCycleWeekly BillingCycle = "weekly"
BillingCycleMonthly BillingCycle = "monthly"
BillingCycleQuarterly BillingCycle = "quarterly"
BillingCycleYearly BillingCycle = "yearly"
)
// SubscriptionFilter defines filtering options for subscription queries
type SubscriptionFilter struct {
Status SubscriptionStatus `json:"status,omitempty"`
RatePlanID string `json:"rate_plan_id,omitempty"`
StartedAfter *time.Time `json:"started_after,omitempty"`
StartedBefore *time.Time `json:"started_before,omitempty"`
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
}
// RatePlan represents a rate plan
type RatePlan struct {
ID string `json:"id" gorm:"primaryKey"`
Name string `json:"name"`
Description string `json:"description"`
CarrierID string `json:"carrier_id" gorm:"index"`
Region string `json:"region" gorm:"index"`
PlanType PlanType `json:"plan_type"`
BasePrice float64 `json:"base_price"`
Currency string `json:"currency"`
BillingCycle BillingCycle `json:"billing_cycle"`
DataAllowance *DataAllowance `json:"data_allowance,omitempty" gorm:"serializer:json"`
VoiceAllowance *VoiceAllowance `json:"voice_allowance,omitempty" gorm:"serializer:json"`
SMSAllowance *SMSAllowance `json:"sms_allowance,omitempty" gorm:"serializer:json"`
OverageRates *OverageRates `json:"overage_rates,omitempty" gorm:"serializer:json"`
Discounts []*Discount `json:"discounts,omitempty" gorm:"serializer:json"`
ValidFrom time.Time `json:"valid_from"`
ValidTo *time.Time `json:"valid_to,omitempty"`
IsActive bool `json:"is_active"`
Status PlanStatus `json:"status"`
Metadata map[string]interface{} `json:"metadata,omitempty" gorm:"serializer:json"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TableName returns the table name for RatePlan
func (RatePlan) TableName() string {
return "rate_plans"
}
// PlanType represents the type of rate plan
type PlanType string
const (
PlanTypeData PlanType = "data"
PlanTypeVoice PlanType = "voice"
PlanTypeSMS PlanType = "sms"
PlanTypeBundle PlanType = "bundle"
)
// PlanStatus represents the status of a rate plan
type PlanStatus string
const (
PlanStatusActive PlanStatus = "active"
PlanStatusInactive PlanStatus = "inactive"
PlanStatusDraft PlanStatus = "draft"
)
// DataAllowance represents data allowance configuration
type DataAllowance struct {
Amount int64 `json:"amount"`
Unit string `json:"unit"`
Unlimited bool `json:"unlimited"`
}
// VoiceAllowance represents voice allowance configuration
type VoiceAllowance struct {
Minutes int64 `json:"minutes"`
Unlimited bool `json:"unlimited"`
}
// SMSAllowance represents SMS allowance configuration
type SMSAllowance struct {
Messages int64 `json:"messages"`
Unlimited bool `json:"unlimited"`
}
// OverageRates represents overage rate configuration
type OverageRates struct {
DataRate float64 `json:"data_rate"`
VoiceRate float64 `json:"voice_rate"`
SMSRate float64 `json:"sms_rate"`
}
// Discount represents a discount configuration
type Discount struct {
ID string `json:"id"`
Type DiscountType `json:"type"`
Value float64 `json:"value"`
IsActive bool `json:"is_active"`
}
// DiscountType represents the type of discount
type DiscountType string
const (
DiscountTypePercentage DiscountType = "percentage"
DiscountTypeFixed DiscountType = "fixed"
)
// RatePlanFilter defines filtering options for rate plan queries
type RatePlanFilter struct {
CarrierID string `json:"carrier_id,omitempty"`
Region string `json:"region,omitempty"`
PlanType PlanType `json:"plan_type,omitempty"`
Status PlanStatus `json:"status,omitempty"`
IsActive *bool `json:"is_active,omitempty"`
MinPrice float64 `json:"min_price,omitempty"`
MaxPrice float64 `json:"max_price,omitempty"`
ValidFrom *time.Time `json:"valid_from,omitempty"`
ValidTo *time.Time `json:"valid_to,omitempty"`
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
SortBy string `json:"sort_by,omitempty"`
SortOrder string `json:"sort_order,omitempty"`
}
// UsageAnalyticsFilter defines filtering options for usage analytics
type UsageAnalyticsFilter struct {
RatePlanID string `json:"rate_plan_id,omitempty"`
CarrierID string `json:"carrier_id,omitempty"`
Region string `json:"region,omitempty"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
GroupBy string `json:"group_by,omitempty"`
}
// UsageAnalytics contains usage statistics
type UsageAnalytics struct {
TotalDataUsed int64 `json:"total_data_used"`
TotalVoiceUsed int64 `json:"total_voice_used"`
TotalSMSUsed int64 `json:"total_sms_used"`
ActiveUsers int `json:"active_users"`
AverageUsage map[string]float64 `json:"average_usage"`
UsageByPlan map[string]int64 `json:"usage_by_plan"`
UsageByRegion map[string]int64 `json:"usage_by_region"`
TimelineData []TimelineDataPoint `json:"timeline_data"`
}
// RevenueAnalyticsFilter defines filtering options for revenue analytics
type RevenueAnalyticsFilter struct {
RatePlanID string `json:"rate_plan_id,omitempty"`
CarrierID string `json:"carrier_id,omitempty"`
Region string `json:"region,omitempty"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
GroupBy string `json:"group_by,omitempty"`
}
// RevenueAnalytics contains revenue statistics
type RevenueAnalytics struct {
TotalRevenue float64 `json:"total_revenue"`
RevenueByPlan map[string]float64 `json:"revenue_by_plan"`
RevenueByCarrier map[string]float64 `json:"revenue_by_carrier"`
RevenueByRegion map[string]float64 `json:"revenue_by_region"`
AverageRevenue map[string]float64 `json:"average_revenue"`
TimelineData []TimelineDataPoint `json:"timeline_data"`
}
// TimelineDataPoint represents a data point in time series
type TimelineDataPoint struct {
Timestamp time.Time `json:"timestamp"`
Value float64 `json:"value"`
Label string `json:"label,omitempty"`
}
// SearchCriteria defines search criteria for rate plans
type SearchCriteria struct {
CarrierID string `json:"carrier_id,omitempty"`
Region string `json:"region,omitempty"`
PlanType PlanType `json:"plan_type,omitempty"`
MinPrice float64 `json:"min_price,omitempty"`
MaxPrice float64 `json:"max_price,omitempty"`
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
SortBy string `json:"sort_by,omitempty"`
SortOrder string `json:"sort_order,omitempty"`
}
// SubscribeRequest represents a request to subscribe to a rate plan
type SubscribeRequest struct {
ProfileID string `json:"profile_id"`
RatePlanID string `json:"rate_plan_id"`
AutoRenew bool `json:"auto_renew"`
AppliedDiscounts []string `json:"applied_discounts,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// RecordUsageRequest represents a request to record usage
type RecordUsageRequest struct {
ProfileID string `json:"profile_id"`
DataUsed int64 `json:"data_used"`
VoiceUsed int64 `json:"voice_used"`
SMSUsed int64 `json:"sms_used"`
}
// CalculateCostRequest represents a request to calculate cost
type CalculateCostRequest struct {
RatePlanID string `json:"rate_plan_id"`
DataUsed int64 `json:"data_used"`
VoiceUsed int64 `json:"voice_used"`
SMSUsed int64 `json:"sms_used"`
AppliedDiscounts []string `json:"applied_discounts,omitempty"`
}
// RatePlanCostCalculation represents the result of a cost calculation
type RatePlanCostCalculation struct {
RatePlanID string `json:"rate_plan_id"`
BaseCost float64 `json:"base_cost"`
OverageCost float64 `json:"overage_cost"`
DiscountCost float64 `json:"discount_cost"`
TotalCost float64 `json:"total_cost"`
Currency string `json:"currency"`
Breakdown map[string]interface{} `json:"breakdown"`
CalculatedAt time.Time `json:"calculated_at"`
}
@@ -194,9 +194,9 @@ func (s *AnalyticsServiceImpl) GetTopCurrenciesByRevenue(ctx context.Context, li
// Convert to slice and sort
var currencyRevenues []*currency.CurrencyRevenue
for currency, revenue := range revenueByCurrency {
for currCode, revenue := range revenueByCurrency {
currencyRevenues = append(currencyRevenues, &currency.CurrencyRevenue{
Currency: currency,
Currency: currCode,
Revenue: revenue,
})
}
@@ -6,19 +6,20 @@ import (
"time"
"github.com/google/uuid"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
"github.com/sirupsen/logrus"
)
// BillingServiceImpl handles multi-currency billing operations
type BillingServiceImpl struct {
repository Repository
exchangeService *ExchangeRateService
repository currency.Repository
exchangeService currency.ExchangeRateService
logger *logrus.Logger
baseCurrency string
}
// NewBillingService creates a new billing service
func NewBillingService(repository Repository, exchangeService *ExchangeRateService, logger *logrus.Logger, baseCurrency string) *BillingServiceImpl {
func NewBillingService(repository currency.Repository, exchangeService currency.ExchangeRateService, logger *logrus.Logger, baseCurrency string) *BillingServiceImpl {
return &BillingServiceImpl{
repository: repository,
exchangeService: exchangeService,
@@ -28,7 +29,7 @@ func NewBillingService(repository Repository, exchangeService *ExchangeRateServi
}
// ProcessBilling processes a billing request in multi-currency context
func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingRequest) (*BillingResponse, error) {
func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *currency.BillingRequest) (*currency.BillingResponse, error) {
// Validate request
if err := s.validateBillingRequest(req); err != nil {
return nil, fmt.Errorf("invalid billing request: %w", err)
@@ -49,18 +50,18 @@ func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingReq
}
// Create transaction
transaction := &Transaction{
transaction := &currency.Transaction{
ID: uuid.New().String(),
ProfileID: req.ProfileID,
SubscriptionID: req.SubscriptionID,
Type: TransactionTypeSubscription,
Type: currency.TransactionTypeSubscription,
Amount: req.Amount,
Currency: req.Currency,
BaseAmount: baseAmount,
BaseCurrency: s.baseCurrency,
ExchangeRate: exchangeRate,
Description: req.Description,
Status: TransactionStatusPending,
Status: currency.TransactionStatusPending,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
@@ -72,7 +73,7 @@ func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingReq
}
// Process payment (in real implementation, this would integrate with payment gateway)
transaction.Status = TransactionStatusCompleted
transaction.Status = currency.TransactionStatusCompleted
if err := s.repository.UpdateTransaction(ctx, transaction); err != nil {
s.logger.WithError(err).Error("Failed to update transaction status")
return nil, fmt.Errorf("failed to update transaction: %w", err)
@@ -87,7 +88,7 @@ func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingReq
"base_currency": s.baseCurrency,
}).Info("Billing processed successfully")
return &BillingResponse{
return &currency.BillingResponse{
TransactionID: transaction.ID,
Amount: transaction.Amount,
Currency: transaction.Currency,
@@ -100,14 +101,14 @@ func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingReq
}
// ConvertAmount converts an amount between currencies
func (s *BillingServiceImpl) ConvertAmount(ctx context.Context, req *CurrencyConversionRequest) (*CurrencyConversionResponse, error) {
func (s *BillingServiceImpl) ConvertAmount(ctx context.Context, req *currency.CurrencyConversionRequest) (*currency.CurrencyConversionResponse, error) {
return s.exchangeService.ConvertAmount(ctx, req.Amount, req.FromCurrency, req.ToCurrency)
}
// GetBillingHistory retrieves billing history for a profile
func (s *BillingServiceImpl) GetBillingHistory(ctx context.Context, profileID string, filter *TransactionFilter) ([]*Transaction, error) {
func (s *BillingServiceImpl) GetBillingHistory(ctx context.Context, profileID string, filter *currency.TransactionFilter) ([]*currency.Transaction, error) {
if filter == nil {
filter = &TransactionFilter{}
filter = &currency.TransactionFilter{}
}
filter.ProfileID = profileID
@@ -122,7 +123,7 @@ func (s *BillingServiceImpl) GetBillingHistory(ctx context.Context, profileID st
}
// validateBillingRequest validates a billing request
func (s *BillingServiceImpl) validateBillingRequest(req *BillingRequest) error {
func (s *BillingServiceImpl) validateBillingRequest(req *currency.BillingRequest) error {
if req.ProfileID == "" {
return fmt.Errorf("profile ID is required")
}
@@ -38,7 +38,7 @@ func NewRatePlanCurrencyIntegrator(
}
// SubscribeToPlanWithCurrency subscribes to a rate plan with currency conversion
func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.Context, profileID string, planID string, currency string) (*rateplan.RatePlanSubscription, error) {
func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.Context, profileID string, planID string, targetCurrency string) (*rateplan.RatePlanSubscription, error) {
// Get the rate plan
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
if err != nil {
@@ -49,8 +49,8 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
subscriptionPrice := plan.BasePrice
exchangeRate := 1.0
if currency != plan.Currency {
conversion, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, currency)
if targetCurrency != plan.Currency {
conversion, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, targetCurrency)
if err != nil {
rpci.logger.WithError(err).Error("Failed to convert rate plan price")
return nil, fmt.Errorf("currency conversion failed: %w", err)
@@ -67,7 +67,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
StartedAt: time.Now(),
Metadata: map[string]interface{}{
"original_currency": plan.Currency,
"subscription_currency": currency,
"subscription_currency": targetCurrency,
"original_price": plan.BasePrice,
"subscription_price": subscriptionPrice,
"exchange_rate": exchangeRate,
@@ -94,7 +94,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
ProfileID: profileID,
SubscriptionID: createdSubscription.ID,
Amount: subscriptionPrice,
Currency: currency,
Currency: targetCurrency,
Description: fmt.Sprintf("Initial subscription to %s", plan.Name),
BillingDate: time.Now(),
}
@@ -108,7 +108,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
rpci.logger.WithFields(logrus.Fields{
"profile_id": profileID,
"plan_id": planID,
"currency": currency,
"currency": targetCurrency,
"subscription_id": createdSubscription.ID,
}).Info("Rate plan subscription created with currency support")
@@ -116,7 +116,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
}
// CalculatePlanCostInCurrency calculates the cost of a rate plan in a specific currency
func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, currency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) {
func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, targetCurrency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) {
// Get the rate plan
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
if err != nil {
@@ -140,8 +140,8 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
convertedCost := baseCost
exchangeRate := 1.0
if currency != plan.Currency {
conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, currency)
if targetCurrency != plan.Currency {
conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, targetCurrency)
if err != nil {
return nil, fmt.Errorf("currency conversion failed: %w", err)
}
@@ -153,7 +153,7 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
summary := &currency.BillingSummary{
ProfileID: usageData.ProfileID,
TotalAmount: convertedCost,
Currency: currency,
Currency: targetCurrency,
BaseTotalAmount: baseCost,
BaseCurrency: plan.Currency,
TransactionCount: 1,
@@ -2,10 +2,12 @@ package services
import (
"context"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
)
// GetUsage retrieves usage for a profile
func (s *Service) GetUsage(ctx context.Context, profileID string) (*RatePlanUsage, error) {
func (s *Service) GetUsage(ctx context.Context, profileID string) (*repository.RatePlanUsage, error) {
usage, err := s.repo.GetCurrentUsage(ctx, profileID)
if err != nil {
s.logger.WithError(err).WithField("profile_id", profileID).Error("Failed to get usage")
@@ -14,8 +16,9 @@ func (s *Service) GetUsage(ctx context.Context, profileID string) (*RatePlanUsag
return usage, nil
}
// GetUsageHistory retrieves usage history for a profile
func (s *Service) GetUsageHistory(ctx context.Context, profileID string, limit int) ([]*RatePlanUsage, error) {
func (s *Service) GetUsageHistory(ctx context.Context, profileID string, limit int) ([]*repository.RatePlanUsage, error) {
usageHistory, err := s.repo.ListUsageHistory(ctx, profileID, limit)
if err != nil {
s.logger.WithError(err).Error("Failed to get usage history")
@@ -26,7 +29,7 @@ func (s *Service) GetUsageHistory(ctx context.Context, profileID string, limit i
}
// GetUsageAnalytics retrieves usage analytics
func (s *Service) GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsFilter) (*UsageAnalytics, error) {
func (s *Service) GetUsageAnalytics(ctx context.Context, filter *repository.UsageAnalyticsFilter) (*repository.UsageAnalytics, error) {
analytics, err := s.repo.GetUsageAnalytics(ctx, filter)
if err != nil {
s.logger.WithError(err).Error("Failed to get usage analytics")
@@ -37,7 +40,7 @@ func (s *Service) GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsF
}
// GetRevenueAnalytics retrieves revenue analytics
func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyticsFilter) (*RevenueAnalytics, error) {
func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *repository.RevenueAnalyticsFilter) (*repository.RevenueAnalytics, error) {
analytics, err := s.repo.GetRevenueAnalytics(ctx, filter)
if err != nil {
s.logger.WithError(err).Error("Failed to get revenue analytics")
@@ -48,7 +51,7 @@ func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyt
}
// GetPopularPlans retrieves the most popular rate plans
func (s *Service) GetPopularPlans(ctx context.Context, limit int) ([]*RatePlan, error) {
func (s *Service) GetPopularPlans(ctx context.Context, limit int) ([]*repository.RatePlan, error) {
plans, err := s.repo.GetPopularPlans(ctx, limit)
if err != nil {
s.logger.WithError(err).Error("Failed to get popular plans")
@@ -3,9 +3,11 @@ package services
import (
"fmt"
"time"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
)
func (s *Service) validateRatePlan(plan *RatePlan) error {
func (s *Service) validateRatePlan(plan *repository.RatePlan) error {
if plan.Name == "" {
return fmt.Errorf("rate plan name is required")
}
@@ -30,7 +32,7 @@ func (s *Service) validateRatePlan(plan *RatePlan) error {
return nil
}
func (s *Service) validateSubscribeRequest(req *SubscribeRequest) error {
func (s *Service) validateSubscribeRequest(req *repository.SubscribeRequest) error {
if req.ProfileID == "" {
return fmt.Errorf("profile ID is required")
}
@@ -40,37 +42,36 @@ func (s *Service) validateSubscribeRequest(req *SubscribeRequest) error {
return nil
}
func (s *Service) calculateNextBillingDate(cycle BillingCycle, from time.Time) time.Time {
func (s *Service) calculateNextBillingDate(cycle repository.BillingCycle, from time.Time) time.Time {
switch cycle {
case BillingCycleDaily:
case repository.BillingCycleDaily:
return from.AddDate(0, 0, 1)
case BillingCycleWeekly:
case repository.BillingCycleWeekly:
return from.AddDate(0, 0, 7)
case BillingCycleMonthly:
case repository.BillingCycleMonthly:
return from.AddDate(0, 1, 0)
case BillingCycleQuarterly:
case repository.BillingCycleQuarterly:
return from.AddDate(0, 3, 0)
case BillingCycleYearly:
case repository.BillingCycleYearly:
return from.AddDate(1, 0, 0)
default:
return from.AddDate(0, 1, 0) // Default to monthly
}
}
func (s *Service) calculateCycleEnd(cycle BillingCycle, cycleStart time.Time) time.Time {
func (s *Service) calculateCycleEnd(cycle repository.BillingCycle, cycleStart time.Time) time.Time {
switch cycle {
case BillingCycleDaily:
case repository.BillingCycleDaily:
return cycleStart.AddDate(0, 0, 1).Add(-time.Nanosecond)
case BillingCycleWeekly:
case repository.BillingCycleWeekly:
return cycleStart.AddDate(0, 0, 7).Add(-time.Nanosecond)
case BillingCycleMonthly:
case repository.BillingCycleMonthly:
return cycleStart.AddDate(0, 1, 0).Add(-time.Nanosecond)
case BillingCycleQuarterly:
case repository.BillingCycleQuarterly:
return cycleStart.AddDate(0, 3, 0).Add(-time.Nanosecond)
case BillingCycleYearly:
case repository.BillingCycleYearly:
return cycleStart.AddDate(1, 0, 0).Add(-time.Nanosecond)
default:
return cycleStart.AddDate(0, 1, 0).Add(-time.Nanosecond) // Default to monthly
}
}