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
This commit is contained in:
nutcas3
2026-05-03 10:21:46 +03:00
parent e5ccb62277
commit 82ecbff62a
2 changed files with 329 additions and 0 deletions
@@ -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,
}
}
@@ -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
}