From 82ecbff62a01a101f6bfe365360dc07ffd9f63eb Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sun, 3 May 2026 10:21:46 +0300 Subject: [PATCH] feat: Add RatePlanAdapter with CRUD operations, subscription management, usage tracking, and analytics - Add RatePlanAdapter struct wrapping Service to implement rateplan.Service interface - Add NewRatePlanAdapter constructor returning rateplan.Service - Add CreateRatePlan, GetRatePlan, UpdateRatePlan, DeleteRatePlan, ListRatePlans, SearchRatePlans, and GetPopularPlans methods with type conversions - Add SubscribeToPlan, GetSubscription, UpdateSubscription, CancelSubscription, GetActiveSubscription --- .../internal/services/rateplan_adapter.go | 152 +++++++++++++++ .../services/rateplan_adapter_subs.go | 177 ++++++++++++++++++ 2 files changed, 329 insertions(+) create mode 100644 apps/carrier-connector/internal/services/rateplan_adapter.go create mode 100644 apps/carrier-connector/internal/services/rateplan_adapter_subs.go diff --git a/apps/carrier-connector/internal/services/rateplan_adapter.go b/apps/carrier-connector/internal/services/rateplan_adapter.go new file mode 100644 index 0000000..99a755c --- /dev/null +++ b/apps/carrier-connector/internal/services/rateplan_adapter.go @@ -0,0 +1,152 @@ +package services + +import ( + "context" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan" + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" +) + +// RatePlanAdapter adapts services.Service to implement rateplan.Service interface +type RatePlanAdapter struct { + service *Service +} + +// NewRatePlanAdapter creates a new rate plan adapter +func NewRatePlanAdapter(service *Service) rateplan.Service { + return &RatePlanAdapter{service: service} +} + +func (a *RatePlanAdapter) CreateRatePlan(ctx context.Context, plan *rateplan.RatePlan) (*rateplan.RatePlan, error) { + result, err := a.service.CreateRatePlan(ctx, toRepoPlan(plan)) + if err != nil { + return nil, err + } + return toRatePlan(result), nil +} + +func (a *RatePlanAdapter) GetRatePlan(ctx context.Context, id string) (*rateplan.RatePlan, error) { + plan, err := a.service.GetRatePlan(ctx, id) + if err != nil { + return nil, err + } + return toRatePlan(plan), nil +} + +func (a *RatePlanAdapter) UpdateRatePlan(ctx context.Context, plan *rateplan.RatePlan) (*rateplan.RatePlan, error) { + result, err := a.service.UpdateRatePlan(ctx, toRepoPlan(plan)) + if err != nil { + return nil, err + } + return toRatePlan(result), nil +} + +func (a *RatePlanAdapter) DeleteRatePlan(ctx context.Context, id string) error { + return a.service.DeleteRatePlan(ctx, id) +} + +func (a *RatePlanAdapter) ListRatePlans(ctx context.Context, filter *rateplan.RatePlanFilter) ([]*rateplan.RatePlan, error) { + repoFilter := &repository.RatePlanFilter{ + CarrierID: filter.CarrierID, + Region: filter.Region, + PlanType: repository.PlanType(filter.PlanType), + IsActive: filter.IsActive, + MinPrice: filter.MinPrice, + MaxPrice: filter.MaxPrice, + Limit: filter.Limit, + Offset: filter.Offset, + } + plans, err := a.service.ListRatePlans(ctx, repoFilter) + if err != nil { + return nil, err + } + return toRatePlanSlice(plans), nil +} + +func (a *RatePlanAdapter) SearchRatePlans(ctx context.Context, criteria rateplan.SearchCriteria) ([]*rateplan.RatePlan, error) { + filter := &rateplan.RatePlanFilter{ + CarrierID: criteria.CarrierID, + Region: criteria.Region, + PlanType: criteria.PlanType, + } + return a.ListRatePlans(ctx, filter) +} + +func (a *RatePlanAdapter) GetPopularPlans(ctx context.Context, limit int) ([]*rateplan.RatePlan, error) { + plans, err := a.service.GetPopularPlans(ctx, limit) + if err != nil { + return nil, err + } + return toRatePlanSlice(plans), nil +} + +// Conversion helpers + +func toRepoPlan(p *rateplan.RatePlan) *repository.RatePlan { + return &repository.RatePlan{ + ID: p.ID, + Name: p.Name, + Description: p.Description, + CarrierID: p.CarrierID, + Region: p.Region, + PlanType: repository.PlanType(p.PlanType), + BasePrice: p.BasePrice, + BillingCycle: repository.BillingCycle(p.BillingCycle), + ValidFrom: p.ValidFrom, + ValidTo: p.ValidTo, + IsActive: p.IsActive, + CreatedAt: p.CreatedAt, + UpdatedAt: p.UpdatedAt, + } +} + +func toRatePlan(p *repository.RatePlan) *rateplan.RatePlan { + return &rateplan.RatePlan{ + ID: p.ID, + Name: p.Name, + Description: p.Description, + CarrierID: p.CarrierID, + Region: p.Region, + PlanType: rateplan.PlanType(p.PlanType), + BasePrice: p.BasePrice, + BillingCycle: rateplan.BillingCycle(p.BillingCycle), + ValidFrom: p.ValidFrom, + ValidTo: p.ValidTo, + IsActive: p.IsActive, + CreatedAt: p.CreatedAt, + UpdatedAt: p.UpdatedAt, + } +} + +func toRatePlanSlice(plans []*repository.RatePlan) []*rateplan.RatePlan { + result := make([]*rateplan.RatePlan, len(plans)) + for i, p := range plans { + result[i] = toRatePlan(p) + } + return result +} + +func toRatePlanSub(s *repository.RatePlanSubscription) *rateplan.RatePlanSubscription { + return &rateplan.RatePlanSubscription{ + ID: s.ID, + ProfileID: s.ProfileID, + RatePlanID: s.RatePlanID, + Status: rateplan.SubscriptionStatus(s.Status), + CreatedAt: s.CreatedAt, + UpdatedAt: s.UpdatedAt, + } +} + +func convertRepoUsage(u *repository.RatePlanUsage) *rateplan.RatePlanUsage { + return &rateplan.RatePlanUsage{ + ID: u.ID, + RatePlanID: u.RatePlanID, + ProfileID: u.ProfileID, + CycleStart: u.CycleStart, + CycleEnd: u.CycleEnd, + DataUsed: u.DataUsed, + VoiceUsed: u.VoiceUsed, + SMSUsed: u.SMSUsed, + LastUpdated: u.LastUpdated, + } +} diff --git a/apps/carrier-connector/internal/services/rateplan_adapter_subs.go b/apps/carrier-connector/internal/services/rateplan_adapter_subs.go new file mode 100644 index 0000000..cf7cd5f --- /dev/null +++ b/apps/carrier-connector/internal/services/rateplan_adapter_subs.go @@ -0,0 +1,177 @@ +package services + +import ( + "context" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan" + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" +) + +func (a *RatePlanAdapter) SubscribeToPlan(ctx context.Context, req *rateplan.SubscribeRequest) (*rateplan.RatePlanSubscription, error) { + repoReq := &repository.SubscribeRequest{ + ProfileID: req.ProfileID, + RatePlanID: req.RatePlanID, + } + subscription, err := a.service.SubscribeToPlan(ctx, repoReq) + if err != nil { + return nil, err + } + return toRatePlanSub(subscription), nil +} + +func (a *RatePlanAdapter) GetSubscription(ctx context.Context, id string) (*rateplan.RatePlanSubscription, error) { + subscription, err := a.service.GetSubscription(ctx, id) + if err != nil { + return nil, err + } + return toRatePlanSub(subscription), nil +} + +func (a *RatePlanAdapter) UpdateSubscription(ctx context.Context, subscription *rateplan.RatePlanSubscription) (*rateplan.RatePlanSubscription, error) { + repoSub := &repository.RatePlanSubscription{ + ID: subscription.ID, + ProfileID: subscription.ProfileID, + RatePlanID: subscription.RatePlanID, + Status: repository.SubscriptionStatus(subscription.Status), + CreatedAt: subscription.CreatedAt, + UpdatedAt: subscription.UpdatedAt, + } + result, err := a.service.UpdateSubscription(ctx, repoSub) + if err != nil { + return nil, err + } + return toRatePlanSub(result), nil +} + +func (a *RatePlanAdapter) CancelSubscription(ctx context.Context, subscriptionID string, reason string) error { + return a.service.CancelSubscription(ctx, subscriptionID, reason) +} + +func (a *RatePlanAdapter) GetActiveSubscription(ctx context.Context, profileID string) (*rateplan.RatePlanSubscription, error) { + subscription, err := a.service.GetActiveSubscription(ctx, profileID) + if err != nil { + return nil, err + } + return toRatePlanSub(subscription), nil +} + +func (a *RatePlanAdapter) ListSubscriptions(ctx context.Context, profileID string, filter *rateplan.SubscriptionFilter) ([]*rateplan.RatePlanSubscription, error) { + repoFilter := &repository.SubscriptionFilter{ + Status: repository.SubscriptionStatus(filter.Status), + Limit: filter.Limit, + Offset: filter.Offset, + } + subscriptions, err := a.service.ListSubscriptions(ctx, profileID, repoFilter) + if err != nil { + return nil, err + } + result := make([]*rateplan.RatePlanSubscription, len(subscriptions)) + for i, sub := range subscriptions { + result[i] = toRatePlanSub(sub) + } + return result, nil +} + +func (a *RatePlanAdapter) RecordUsage(ctx context.Context, req *rateplan.RecordUsageRequest) (*rateplan.RatePlanUsage, error) { + repoReq := &repository.RecordUsageRequest{ + ProfileID: req.ProfileID, + DataUsed: req.DataUsed, + VoiceUsed: req.VoiceUsed, + SMSUsed: req.SMSUsed, + } + usage, err := a.service.RecordUsage(ctx, repoReq) + if err != nil { + return nil, err + } + return convertRepoUsage(usage), nil +} + +func (a *RatePlanAdapter) GetUsage(ctx context.Context, profileID string) (*rateplan.RatePlanUsage, error) { + usage, err := a.service.GetUsage(ctx, profileID) + if err != nil { + return nil, err + } + return convertRepoUsage(usage), nil +} + +func (a *RatePlanAdapter) GetUsageHistory(ctx context.Context, profileID string, limit int) ([]*rateplan.RatePlanUsage, error) { + usageHistory, err := a.service.GetUsageHistory(ctx, profileID, limit) + if err != nil { + return nil, err + } + result := make([]*rateplan.RatePlanUsage, len(usageHistory)) + for i, u := range usageHistory { + result[i] = convertRepoUsage(u) + } + return result, nil +} + +func (a *RatePlanAdapter) CalculateCost(ctx context.Context, req *rateplan.CalculateCostRequest) (*rateplan.RatePlanCostCalculation, error) { + repoReq := &repository.CalculateCostRequest{ + RatePlanID: req.RatePlanID, + DataUsed: req.DataUsed, + VoiceUsed: req.VoiceUsed, + SMSUsed: req.SMSUsed, + AppliedDiscounts: req.AppliedDiscounts, + } + calc, err := a.service.CalculateCost(ctx, repoReq) + if err != nil { + return nil, err + } + return &rateplan.RatePlanCostCalculation{ + RatePlanID: calc.RatePlanID, + BaseCost: calc.BaseCost, + OverageCost: calc.OverageCost, + DiscountCost: calc.DiscountCost, + TotalCost: calc.TotalCost, + Currency: calc.Currency, + Breakdown: calc.Breakdown, + CalculatedAt: calc.CalculatedAt, + }, nil +} + +func (a *RatePlanAdapter) GetUsageAnalytics(ctx context.Context, filter *rateplan.UsageAnalyticsFilter) (*rateplan.UsageAnalytics, error) { + repoFilter := &repository.UsageAnalyticsFilter{ + RatePlanID: filter.RatePlanID, + CarrierID: filter.CarrierID, + Region: filter.Region, + StartDate: filter.StartDate, + EndDate: filter.EndDate, + GroupBy: filter.GroupBy, + } + analytics, err := a.service.GetUsageAnalytics(ctx, repoFilter) + if err != nil { + return nil, err + } + return &rateplan.UsageAnalytics{ + TotalDataUsed: analytics.TotalDataUsed, + TotalVoiceUsed: analytics.TotalVoiceUsed, + TotalSMSUsed: analytics.TotalSMSUsed, + ActiveUsers: analytics.ActiveUsers, + AverageUsage: analytics.AverageUsage, + UsageByPlan: analytics.UsageByPlan, + UsageByRegion: analytics.UsageByRegion, + }, nil +} + +func (a *RatePlanAdapter) GetRevenueAnalytics(ctx context.Context, filter *rateplan.RevenueAnalyticsFilter) (*rateplan.RevenueAnalytics, error) { + repoFilter := &repository.RevenueAnalyticsFilter{ + RatePlanID: filter.RatePlanID, + CarrierID: filter.CarrierID, + Region: filter.Region, + StartDate: filter.StartDate, + EndDate: filter.EndDate, + GroupBy: filter.GroupBy, + } + analytics, err := a.service.GetRevenueAnalytics(ctx, repoFilter) + if err != nil { + return nil, err + } + return &rateplan.RevenueAnalytics{ + TotalRevenue: analytics.TotalRevenue, + RevenueByPlan: analytics.RevenueByPlan, + RevenueByCarrier: analytics.RevenueByCarrier, + RevenueByRegion: analytics.RevenueByRegion, + AverageRevenue: analytics.AverageRevenue, + }, nil +}