From 793f15006a10189ede7296a12217a871b9315030 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sat, 2 May 2026 00:12:13 +0300 Subject: [PATCH] feat: Refactor pricing engine by extracting calculation methods into separate file - Move calculateDataOverage, calculateVoiceOverage, calculateSMSOverage, calculateDiscounts, and calculateRecommendedPrice to price_calculation.go - Remove validation helper methods from pricing_engine.go - Remove PriceOptimization and CostBreakdown type definitions - Update Logger interface to use `any` instead of `interface{}` - Remove inline comments throughout pricing_engine.go - Add switch statement for discount type handling in calculateDiscounts --- .../internal/rateplan/price_calculation.go | 76 +++++ .../internal/rateplan/pricing_engine.go | 279 +----------------- .../internal/rateplan/pricing_validate.go | 154 ++++++++++ 3 files changed, 231 insertions(+), 278 deletions(-) create mode 100644 apps/carrier-connector/internal/rateplan/price_calculation.go create mode 100644 apps/carrier-connector/internal/rateplan/pricing_validate.go diff --git a/apps/carrier-connector/internal/rateplan/price_calculation.go b/apps/carrier-connector/internal/rateplan/price_calculation.go new file mode 100644 index 0000000..07a4ad5 --- /dev/null +++ b/apps/carrier-connector/internal/rateplan/price_calculation.go @@ -0,0 +1,76 @@ +package rateplan + +func (pe *PricingEngine) calculateDataOverage(plan *RatePlan, dataUsed int64) float64 { + if plan.DataAllowance == nil || plan.DataAllowance.Unlimited || plan.OverageRates == nil { + return 0 + } + + allowanceMB := plan.DataAllowance.Amount + if plan.DataAllowance.Unit == "GB" { + allowanceMB *= 1024 + } + + if dataUsed <= allowanceMB { + return 0 + } + + overageMB := dataUsed - allowanceMB + return float64(overageMB) * plan.OverageRates.DataRate +} + +func (pe *PricingEngine) calculateVoiceOverage(plan *RatePlan, voiceUsed int64) float64 { + if plan.VoiceAllowance == nil || plan.VoiceAllowance.Unlimited || plan.OverageRates == nil { + return 0 + } + + if voiceUsed <= plan.VoiceAllowance.Minutes { + return 0 + } + + overageMinutes := voiceUsed - plan.VoiceAllowance.Minutes + return float64(overageMinutes) * plan.OverageRates.VoiceRate +} + +func (pe *PricingEngine) calculateSMSOverage(plan *RatePlan, smsUsed int64) float64 { + if plan.SMSAllowance == nil || plan.SMSAllowance.Unlimited || plan.OverageRates == nil { + return 0 + } + + if smsUsed <= plan.SMSAllowance.Messages { + return 0 + } + + overageSMS := smsUsed - plan.SMSAllowance.Messages + return float64(overageSMS) * plan.OverageRates.SMSRate +} + +func (pe *PricingEngine) calculateDiscounts(plan *RatePlan, discountIDs []string, baseCost float64) float64 { + if plan.Discounts == nil || len(discountIDs) == 0 { + return 0 + } + + totalDiscount := 0.0 + for _, discountID := range discountIDs { + for _, discount := range plan.Discounts { + if discount.ID == discountID && discount.IsActive { + switch discount.Type { +case DiscountTypePercentage: + totalDiscount += baseCost * discount.Value / 100 + case DiscountTypeFixed: + totalDiscount += discount.Value + } + } + } + } + + return totalDiscount +} + +func (pe *PricingEngine) calculateRecommendedPrice(plan *RatePlan, marketAverage float64) float64 { + // Basic pricing strategy: position slightly below market average for competitive advantage + if marketAverage > 0 { + return marketAverage * 0.95 // 5% below market average + } + return plan.BasePrice +} + diff --git a/apps/carrier-connector/internal/rateplan/pricing_engine.go b/apps/carrier-connector/internal/rateplan/pricing_engine.go index f212433..db0fd55 100644 --- a/apps/carrier-connector/internal/rateplan/pricing_engine.go +++ b/apps/carrier-connector/internal/rateplan/pricing_engine.go @@ -6,22 +6,19 @@ import ( "time" ) -// PricingEngine handles rate plan pricing calculations and validations type PricingEngine struct { repo Repository logger Logger } -// Logger interface for pricing engine type Logger interface { WithError(err error) Logger - WithField(key string, value interface{}) Logger + WithField(key string, value any) Logger Error(msg string) Info(msg string) Warning(msg string) } -// NewPricingEngine creates a new pricing engine func NewPricingEngine(repo Repository, logger Logger) *PricingEngine { return &PricingEngine{ repo: repo, @@ -29,34 +26,27 @@ func NewPricingEngine(repo Repository, logger Logger) *PricingEngine { } } -// ValidateRatePlan performs comprehensive validation of a rate plan func (pe *PricingEngine) ValidateRatePlan(ctx context.Context, plan *RatePlan) error { - // Basic field validation if err := pe.validateBasicFields(plan); err != nil { return err } - // Validate dates if err := pe.validateDates(plan); err != nil { return err } - // Validate allowances if err := pe.validateAllowances(plan); err != nil { return err } - // Validate overage rates if err := pe.validateOverageRates(plan); err != nil { return err } - // Validate discounts if err := pe.validateDiscounts(plan); err != nil { return err } - // Validate early termination if err := pe.validateEarlyTermination(plan); err != nil { return err } @@ -64,9 +54,7 @@ func (pe *PricingEngine) ValidateRatePlan(ctx context.Context, plan *RatePlan) e return nil } -// CalculateOptimalPrice calculates the optimal price for a rate plan based on market conditions func (pe *PricingEngine) CalculateOptimalPrice(ctx context.Context, plan *RatePlan) (*PriceOptimization, error) { - // Get similar plans in the same region filter := &RatePlanFilter{ Region: plan.Region, PlanType: plan.PlanType, @@ -80,7 +68,6 @@ func (pe *PricingEngine) CalculateOptimalPrice(ctx context.Context, plan *RatePl return nil, fmt.Errorf("failed to get similar plans: %w", err) } - // Calculate market average var totalBasePrice float64 for _, similarPlan := range similarPlans { totalBasePrice += similarPlan.BasePrice @@ -91,7 +78,6 @@ func (pe *PricingEngine) CalculateOptimalPrice(ctx context.Context, plan *RatePl marketAverage = totalBasePrice / float64(len(similarPlans)) } - // Calculate recommended price recommendedPrice := pe.calculateRecommendedPrice(plan, marketAverage) optimization := &PriceOptimization{ @@ -106,20 +92,16 @@ func (pe *PricingEngine) CalculateOptimalPrice(ctx context.Context, plan *RatePl return optimization, nil } -// ValidateSubscription validates a subscription request func (pe *PricingEngine) ValidateSubscription(ctx context.Context, req *SubscribeRequest) error { - // Get the rate plan plan, err := pe.repo.GetRatePlan(ctx, req.RatePlanID) if err != nil { return fmt.Errorf("rate plan not found: %w", err) } - // Check if plan is available for subscription if !plan.IsActive || plan.Status != PlanStatusActive { return fmt.Errorf("rate plan is not available for subscription") } - // Check validity dates now := time.Now() if now.Before(plan.ValidFrom) { return fmt.Errorf("rate plan is not yet available") @@ -129,7 +111,6 @@ func (pe *PricingEngine) ValidateSubscription(ctx context.Context, req *Subscrib return fmt.Errorf("rate plan has expired") } - // Validate discounts if len(req.AppliedDiscounts) > 0 { if err := pe.validateSubscriptionDiscounts(plan, req.AppliedDiscounts); err != nil { return err @@ -139,7 +120,6 @@ func (pe *PricingEngine) ValidateSubscription(ctx context.Context, req *Subscrib return nil } -// CalculateSubscriptionCost calculates the total cost for a subscription func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *CalculateCostRequest) (*CostBreakdown, error) { // Get the rate plan plan, err := pe.repo.GetRatePlan(ctx, req.RatePlanID) @@ -154,7 +134,6 @@ func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *Cal BreakdownItems: []CostItem{}, } - // Base cost baseCost := plan.BasePrice breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{ Type: "base_price", @@ -163,7 +142,6 @@ func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *Cal Currency: plan.Currency, }) - // Calculate data overage dataOverageCost := pe.calculateDataOverage(plan, req.DataUsed) if dataOverageCost > 0 { breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{ @@ -174,7 +152,6 @@ func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *Cal }) } - // Calculate voice overage voiceOverageCost := pe.calculateVoiceOverage(plan, req.VoiceUsed) if voiceOverageCost > 0 { breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{ @@ -185,7 +162,6 @@ func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *Cal }) } - // Calculate SMS overage smsOverageCost := pe.calculateSMSOverage(plan, req.SMSUsed) if smsOverageCost > 0 { breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{ @@ -196,7 +172,6 @@ func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *Cal }) } - // Apply discounts discountAmount := pe.calculateDiscounts(plan, req.AppliedDiscounts, baseCost) if discountAmount > 0 { breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{ @@ -206,8 +181,6 @@ func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *Cal Currency: plan.Currency, }) } - - // Calculate total totalCost := baseCost + dataOverageCost + voiceOverageCost + smsOverageCost - discountAmount breakdown.TotalCost = totalCost breakdown.Subtotal = baseCost + dataOverageCost + voiceOverageCost + smsOverageCost @@ -216,253 +189,3 @@ func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *Cal return breakdown, nil } -// Helper methods - -func (pe *PricingEngine) validateBasicFields(plan *RatePlan) error { - if plan.Name == "" { - return fmt.Errorf("rate plan name is required") - } - if plan.CarrierID == "" { - return fmt.Errorf("carrier ID is required") - } - if plan.Region == "" { - return fmt.Errorf("region is required") - } - if plan.BasePrice < 0 { - return fmt.Errorf("base price cannot be negative") - } - if plan.Currency == "" { - return fmt.Errorf("currency is required") - } - if plan.BillingCycle == "" { - return fmt.Errorf("billing cycle is required") - } - return nil -} - -func (pe *PricingEngine) validateDates(plan *RatePlan) error { - if plan.ValidFrom.IsZero() { - return fmt.Errorf("valid from date is required") - } - - now := time.Now() - if plan.ValidFrom.After(now) { - pe.logger.Warning("Rate plan valid from date is in the future") - } - - if plan.ValidTo != nil && plan.ValidTo.Before(plan.ValidFrom) { - return fmt.Errorf("valid to date cannot be before valid from date") - } - - return nil -} - -func (pe *PricingEngine) validateAllowances(plan *RatePlan) error { - // Validate data allowance - if plan.DataAllowance != nil { - if plan.DataAllowance.Amount <= 0 && !plan.DataAllowance.Unlimited { - return fmt.Errorf("data allowance amount must be positive or unlimited") - } - if plan.DataAllowance.Unit == "" { - return fmt.Errorf("data allowance unit is required") - } - } - - // Validate voice allowance - if plan.VoiceAllowance != nil { - if plan.VoiceAllowance.Minutes <= 0 && !plan.VoiceAllowance.Unlimited { - return fmt.Errorf("voice allowance minutes must be positive or unlimited") - } - } - - // Validate SMS allowance - if plan.SMSAllowance != nil { - if plan.SMSAllowance.Messages <= 0 && !plan.SMSAllowance.Unlimited { - return fmt.Errorf("SMS allowance messages must be positive or unlimited") - } - } - - return nil -} - -func (pe *PricingEngine) validateOverageRates(plan *RatePlan) error { - if plan.OverageRates != nil { - if plan.OverageRates.DataRate < 0 { - return fmt.Errorf("data overage rate cannot be negative") - } - if plan.OverageRates.VoiceRate < 0 { - return fmt.Errorf("voice overage rate cannot be negative") - } - if plan.OverageRates.SMSRate < 0 { - return fmt.Errorf("SMS overage rate cannot be negative") - } - if plan.OverageRates.Currency == "" { - return fmt.Errorf("overage rates currency is required") - } - } - return nil -} - -func (pe *PricingEngine) validateDiscounts(plan *RatePlan) error { - if plan.Discounts != nil { - for _, discount := range plan.Discounts { - if discount.Name == "" { - return fmt.Errorf("discount name is required") - } - if discount.Value <= 0 { - return fmt.Errorf("discount value must be positive") - } - if discount.ValidFrom.IsZero() { - return fmt.Errorf("discount valid from date is required") - } - if discount.ValidTo != nil && discount.ValidTo.Before(discount.ValidFrom) { - return fmt.Errorf("discount valid to date cannot be before valid from date") - } - } - } - return nil -} - -func (pe *PricingEngine) validateEarlyTermination(plan *RatePlan) error { - if plan.EarlyTermination != nil && plan.EarlyTermination.Enabled { - if plan.EarlyTermination.FeeType == "" { - return fmt.Errorf("early termination fee type is required") - } - if plan.EarlyTermination.FeeType == "fixed" && plan.EarlyTermination.FeeAmount <= 0 { - return fmt.Errorf("early termination fee amount must be positive for fixed fee type") - } - if plan.EarlyTermination.FeeType == "percentage" && (plan.EarlyTermination.FeePercentage <= 0 || plan.EarlyTermination.FeePercentage > 100) { - return fmt.Errorf("early termination fee percentage must be between 0 and 100") - } - } - return nil -} - -func (pe *PricingEngine) calculateRecommendedPrice(plan *RatePlan, marketAverage float64) float64 { - // Basic pricing strategy: position slightly below market average for competitive advantage - if marketAverage > 0 { - return marketAverage * 0.95 // 5% below market average - } - return plan.BasePrice -} - -func (pe *PricingEngine) validateSubscriptionDiscounts(plan *RatePlan, discountIDs []string) error { - if plan.Discounts == nil { - return fmt.Errorf("no discounts available for this rate plan") - } - - for _, discountID := range discountIDs { - found := false - for _, discount := range plan.Discounts { - if discount.ID == discountID { - if !discount.IsActive { - return fmt.Errorf("discount %s is not active", discountID) - } - now := time.Now() - if now.Before(discount.ValidFrom) || (discount.ValidTo != nil && now.After(*discount.ValidTo)) { - return fmt.Errorf("discount %s is not currently valid", discountID) - } - found = true - break - } - } - if !found { - return fmt.Errorf("discount %s not found", discountID) - } - } - - return nil -} - -func (pe *PricingEngine) calculateDataOverage(plan *RatePlan, dataUsed int64) float64 { - if plan.DataAllowance == nil || plan.DataAllowance.Unlimited || plan.OverageRates == nil { - return 0 - } - - allowanceMB := plan.DataAllowance.Amount - if plan.DataAllowance.Unit == "GB" { - allowanceMB *= 1024 - } - - if dataUsed <= allowanceMB { - return 0 - } - - overageMB := dataUsed - allowanceMB - return float64(overageMB) * plan.OverageRates.DataRate -} - -func (pe *PricingEngine) calculateVoiceOverage(plan *RatePlan, voiceUsed int64) float64 { - if plan.VoiceAllowance == nil || plan.VoiceAllowance.Unlimited || plan.OverageRates == nil { - return 0 - } - - if voiceUsed <= plan.VoiceAllowance.Minutes { - return 0 - } - - overageMinutes := voiceUsed - plan.VoiceAllowance.Minutes - return float64(overageMinutes) * plan.OverageRates.VoiceRate -} - -func (pe *PricingEngine) calculateSMSOverage(plan *RatePlan, smsUsed int64) float64 { - if plan.SMSAllowance == nil || plan.SMSAllowance.Unlimited || plan.OverageRates == nil { - return 0 - } - - if smsUsed <= plan.SMSAllowance.Messages { - return 0 - } - - overageSMS := smsUsed - plan.SMSAllowance.Messages - return float64(overageSMS) * plan.OverageRates.SMSRate -} - -func (pe *PricingEngine) calculateDiscounts(plan *RatePlan, discountIDs []string, baseCost float64) float64 { - if plan.Discounts == nil || len(discountIDs) == 0 { - return 0 - } - - totalDiscount := 0.0 - for _, discountID := range discountIDs { - for _, discount := range plan.Discounts { - if discount.ID == discountID && discount.IsActive { - if discount.Type == DiscountTypePercentage { - totalDiscount += baseCost * discount.Value / 100 - } else if discount.Type == DiscountTypeFixed { - totalDiscount += discount.Value - } - } - } - } - - return totalDiscount -} - -// Supporting types - -type PriceOptimization struct { - CurrentPrice float64 `json:"current_price"` - MarketAverage float64 `json:"market_average"` - RecommendedPrice float64 `json:"recommended_price"` - PriceDifference float64 `json:"price_difference"` - CompetitorCount int `json:"competitor_count"` - OptimizedAt time.Time `json:"optimized_at"` -} - -type CostBreakdown struct { - RatePlanID string `json:"rate_plan_id"` - Currency string `json:"currency"` - TotalCost float64 `json:"total_cost"` - Subtotal float64 `json:"subtotal"` - DiscountTotal float64 `json:"discount_total"` - BreakdownItems []CostItem `json:"breakdown_items"` - CalculatedAt time.Time `json:"calculated_at"` -} - -type CostItem struct { - Type string `json:"type"` - Description string `json:"description"` - Amount float64 `json:"amount"` - Currency string `json:"currency"` -} diff --git a/apps/carrier-connector/internal/rateplan/pricing_validate.go b/apps/carrier-connector/internal/rateplan/pricing_validate.go new file mode 100644 index 0000000..21af0e4 --- /dev/null +++ b/apps/carrier-connector/internal/rateplan/pricing_validate.go @@ -0,0 +1,154 @@ +package rateplan + +import ( + "fmt" + "time" +) + +func (pe *PricingEngine) validateBasicFields(plan *RatePlan) error { + if plan.Name == "" { + return fmt.Errorf("rate plan name is required") + } + if plan.CarrierID == "" { + return fmt.Errorf("carrier ID is required") + } + if plan.Region == "" { + return fmt.Errorf("region is required") + } + if plan.BasePrice < 0 { + return fmt.Errorf("base price cannot be negative") + } + if plan.Currency == "" { + return fmt.Errorf("currency is required") + } + if plan.BillingCycle == "" { + return fmt.Errorf("billing cycle is required") + } + return nil +} + +func (pe *PricingEngine) validateDates(plan *RatePlan) error { + if plan.ValidFrom.IsZero() { + return fmt.Errorf("valid from date is required") + } + + now := time.Now() + if plan.ValidFrom.After(now) { + pe.logger.Warning("Rate plan valid from date is in the future") + } + + if plan.ValidTo != nil && plan.ValidTo.Before(plan.ValidFrom) { + return fmt.Errorf("valid to date cannot be before valid from date") + } + + return nil +} + +func (pe *PricingEngine) validateAllowances(plan *RatePlan) error { + // Validate data allowance + if plan.DataAllowance != nil { + if plan.DataAllowance.Amount <= 0 && !plan.DataAllowance.Unlimited { + return fmt.Errorf("data allowance amount must be positive or unlimited") + } + if plan.DataAllowance.Unit == "" { + return fmt.Errorf("data allowance unit is required") + } + } + + // Validate voice allowance + if plan.VoiceAllowance != nil { + if plan.VoiceAllowance.Minutes <= 0 && !plan.VoiceAllowance.Unlimited { + return fmt.Errorf("voice allowance minutes must be positive or unlimited") + } + } + + // Validate SMS allowance + if plan.SMSAllowance != nil { + if plan.SMSAllowance.Messages <= 0 && !plan.SMSAllowance.Unlimited { + return fmt.Errorf("SMS allowance messages must be positive or unlimited") + } + } + + return nil +} + +func (pe *PricingEngine) validateOverageRates(plan *RatePlan) error { + if plan.OverageRates != nil { + if plan.OverageRates.DataRate < 0 { + return fmt.Errorf("data overage rate cannot be negative") + } + if plan.OverageRates.VoiceRate < 0 { + return fmt.Errorf("voice overage rate cannot be negative") + } + if plan.OverageRates.SMSRate < 0 { + return fmt.Errorf("SMS overage rate cannot be negative") + } + if plan.OverageRates.Currency == "" { + return fmt.Errorf("overage rates currency is required") + } + } + return nil +} + +func (pe *PricingEngine) validateDiscounts(plan *RatePlan) error { + if plan.Discounts != nil { + for _, discount := range plan.Discounts { + if discount.Name == "" { + return fmt.Errorf("discount name is required") + } + if discount.Value <= 0 { + return fmt.Errorf("discount value must be positive") + } + if discount.ValidFrom.IsZero() { + return fmt.Errorf("discount valid from date is required") + } + if discount.ValidTo != nil && discount.ValidTo.Before(discount.ValidFrom) { + return fmt.Errorf("discount valid to date cannot be before valid from date") + } + } + } + return nil +} + +func (pe *PricingEngine) validateEarlyTermination(plan *RatePlan) error { + if plan.EarlyTermination != nil && plan.EarlyTermination.Enabled { + if plan.EarlyTermination.FeeType == "" { + return fmt.Errorf("early termination fee type is required") + } + if plan.EarlyTermination.FeeType == "fixed" && plan.EarlyTermination.FeeAmount <= 0 { + return fmt.Errorf("early termination fee amount must be positive for fixed fee type") + } + if plan.EarlyTermination.FeeType == "percentage" && (plan.EarlyTermination.FeePercentage <= 0 || plan.EarlyTermination.FeePercentage > 100) { + return fmt.Errorf("early termination fee percentage must be between 0 and 100") + } + } + return nil +} + +func (pe *PricingEngine) validateSubscriptionDiscounts(plan *RatePlan, discountIDs []string) error { + if plan.Discounts == nil { + return fmt.Errorf("no discounts available for this rate plan") + } + + for _, discountID := range discountIDs { + found := false + for _, discount := range plan.Discounts { + if discount.ID == discountID { + if !discount.IsActive { + return fmt.Errorf("discount %s is not active", discountID) + } + now := time.Now() + if now.Before(discount.ValidFrom) || (discount.ValidTo != nil && now.After(*discount.ValidTo)) { + return fmt.Errorf("discount %s is not currently valid", discountID) + } + found = true + break + } + } + if !found { + return fmt.Errorf("discount %s not found", discountID) + } + } + + return nil +}