mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-27 15:12:16 +00:00
- 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
192 lines
4.8 KiB
Go
192 lines
4.8 KiB
Go
package rateplan
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type PricingEngine struct {
|
|
repo Repository
|
|
logger Logger
|
|
}
|
|
|
|
type Logger interface {
|
|
WithError(err error) Logger
|
|
WithField(key string, value any) Logger
|
|
Error(msg string)
|
|
Info(msg string)
|
|
Warning(msg string)
|
|
}
|
|
|
|
func NewPricingEngine(repo Repository, logger Logger) *PricingEngine {
|
|
return &PricingEngine{
|
|
repo: repo,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (pe *PricingEngine) ValidateRatePlan(ctx context.Context, plan *RatePlan) error {
|
|
if err := pe.validateBasicFields(plan); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := pe.validateDates(plan); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := pe.validateAllowances(plan); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := pe.validateOverageRates(plan); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := pe.validateDiscounts(plan); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := pe.validateEarlyTermination(plan); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (pe *PricingEngine) CalculateOptimalPrice(ctx context.Context, plan *RatePlan) (*PriceOptimization, error) {
|
|
filter := &RatePlanFilter{
|
|
Region: plan.Region,
|
|
PlanType: plan.PlanType,
|
|
Status: PlanStatusActive,
|
|
IsActive: &[]bool{true}[0],
|
|
Limit: 10,
|
|
}
|
|
|
|
similarPlans, err := pe.repo.ListRatePlans(ctx, filter)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get similar plans: %w", err)
|
|
}
|
|
|
|
var totalBasePrice float64
|
|
for _, similarPlan := range similarPlans {
|
|
totalBasePrice += similarPlan.BasePrice
|
|
}
|
|
|
|
var marketAverage float64
|
|
if len(similarPlans) > 0 {
|
|
marketAverage = totalBasePrice / float64(len(similarPlans))
|
|
}
|
|
|
|
recommendedPrice := pe.calculateRecommendedPrice(plan, marketAverage)
|
|
|
|
optimization := &PriceOptimization{
|
|
CurrentPrice: plan.BasePrice,
|
|
MarketAverage: marketAverage,
|
|
RecommendedPrice: recommendedPrice,
|
|
PriceDifference: recommendedPrice - plan.BasePrice,
|
|
CompetitorCount: len(similarPlans),
|
|
OptimizedAt: time.Now(),
|
|
}
|
|
|
|
return optimization, nil
|
|
}
|
|
|
|
func (pe *PricingEngine) ValidateSubscription(ctx context.Context, req *SubscribeRequest) error {
|
|
plan, err := pe.repo.GetRatePlan(ctx, req.RatePlanID)
|
|
if err != nil {
|
|
return fmt.Errorf("rate plan not found: %w", err)
|
|
}
|
|
|
|
if !plan.IsActive || plan.Status != PlanStatusActive {
|
|
return fmt.Errorf("rate plan is not available for subscription")
|
|
}
|
|
|
|
now := time.Now()
|
|
if now.Before(plan.ValidFrom) {
|
|
return fmt.Errorf("rate plan is not yet available")
|
|
}
|
|
|
|
if plan.ValidTo != nil && now.After(*plan.ValidTo) {
|
|
return fmt.Errorf("rate plan has expired")
|
|
}
|
|
|
|
if len(req.AppliedDiscounts) > 0 {
|
|
if err := pe.validateSubscriptionDiscounts(plan, req.AppliedDiscounts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (pe *PricingEngine) CalculateSubscriptionCost(ctx context.Context, req *CalculateCostRequest) (*CostBreakdown, error) {
|
|
// Get the rate plan
|
|
plan, err := pe.repo.GetRatePlan(ctx, req.RatePlanID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rate plan not found: %w", err)
|
|
}
|
|
|
|
breakdown := &CostBreakdown{
|
|
RatePlanID: req.RatePlanID,
|
|
Currency: plan.Currency,
|
|
CalculatedAt: time.Now(),
|
|
BreakdownItems: []CostItem{},
|
|
}
|
|
|
|
baseCost := plan.BasePrice
|
|
breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{
|
|
Type: "base_price",
|
|
Description: "Base subscription cost",
|
|
Amount: baseCost,
|
|
Currency: plan.Currency,
|
|
})
|
|
|
|
dataOverageCost := pe.calculateDataOverage(plan, req.DataUsed)
|
|
if dataOverageCost > 0 {
|
|
breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{
|
|
Type: "data_overage",
|
|
Description: "Data usage over allowance",
|
|
Amount: dataOverageCost,
|
|
Currency: plan.Currency,
|
|
})
|
|
}
|
|
|
|
voiceOverageCost := pe.calculateVoiceOverage(plan, req.VoiceUsed)
|
|
if voiceOverageCost > 0 {
|
|
breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{
|
|
Type: "voice_overage",
|
|
Description: "Voice usage over allowance",
|
|
Amount: voiceOverageCost,
|
|
Currency: plan.Currency,
|
|
})
|
|
}
|
|
|
|
smsOverageCost := pe.calculateSMSOverage(plan, req.SMSUsed)
|
|
if smsOverageCost > 0 {
|
|
breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{
|
|
Type: "sms_overage",
|
|
Description: "SMS usage over allowance",
|
|
Amount: smsOverageCost,
|
|
Currency: plan.Currency,
|
|
})
|
|
}
|
|
|
|
discountAmount := pe.calculateDiscounts(plan, req.AppliedDiscounts, baseCost)
|
|
if discountAmount > 0 {
|
|
breakdown.BreakdownItems = append(breakdown.BreakdownItems, CostItem{
|
|
Type: "discount",
|
|
Description: "Applied discounts",
|
|
Amount: -discountAmount,
|
|
Currency: plan.Currency,
|
|
})
|
|
}
|
|
totalCost := baseCost + dataOverageCost + voiceOverageCost + smsOverageCost - discountAmount
|
|
breakdown.TotalCost = totalCost
|
|
breakdown.Subtotal = baseCost + dataOverageCost + voiceOverageCost + smsOverageCost
|
|
breakdown.DiscountTotal = discountAmount
|
|
|
|
return breakdown, nil
|
|
}
|
|
|