From 3f8de6299d5737aaf6342e12d1711f40f9f818b2 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Fri, 1 May 2026 23:40:12 +0300 Subject: [PATCH] feat: Add rate plan type system with comprehensive domain models, allowances, subscriptions, and analytics support - Add RatePlan struct with carrier, region, pricing, allowances, features, discounts, and validity fields - Add PlanType enum with prepaid, postpaid, hybrid, pay-as-you-go, and unlimited options - Add PlanStatus enum with draft, active, inactive, archived, and suspended states - Add BillingCycle enum with daily, weekly, monthly, quarterly, and yearly options - Add DataAllowance, VoiceAllowance, and --- .../internal/rateplan/types.go | 224 ++++++++++++++++++ .../internal/rateplan/types_extended.go | 64 +++++ 2 files changed, 288 insertions(+) create mode 100644 apps/carrier-connector/internal/rateplan/types.go create mode 100644 apps/carrier-connector/internal/rateplan/types_extended.go diff --git a/apps/carrier-connector/internal/rateplan/types.go b/apps/carrier-connector/internal/rateplan/types.go new file mode 100644 index 0000000..19f6980 --- /dev/null +++ b/apps/carrier-connector/internal/rateplan/types.go @@ -0,0 +1,224 @@ +package rateplan + +import ( + "time" +) + +type RatePlan struct { + ID string `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Description string `json:"description" db:"description"` + CarrierID string `json:"carrier_id" db:"carrier_id"` + Region string `json:"region" db:"region"` + PlanType PlanType `json:"plan_type" db:"plan_type"` + Status PlanStatus `json:"status" db:"status"` + BasePrice float64 `json:"base_price" db:"base_price"` + Currency string `json:"currency" db:"currency"` + BillingCycle BillingCycle `json:"billing_cycle" db:"billing_cycle"` + DataAllowance *DataAllowance `json:"data_allowance,omitempty" db:"data_allowance"` + VoiceAllowance *VoiceAllowance `json:"voice_allowance,omitempty" db:"voice_allowance"` + SMSAllowance *SMSAllowance `json:"sms_allowance,omitempty" db:"sms_allowance"` + OverageRates *OverageRates `json:"overage_rates,omitempty" db:"overage_rates"` + Features []PlanFeature `json:"features,omitempty" db:"features"` + ActivationFee float64 `json:"activation_fee" db:"activation_fee"` + EarlyTermination *EarlyTermination `json:"early_termination,omitempty" db:"early_termination"` + Discounts []Discount `json:"discounts,omitempty" db:"discounts"` + ValidFrom time.Time `json:"valid_from" db:"valid_from"` + ValidTo *time.Time `json:"valid_to,omitempty" db:"valid_to"` + Priority int `json:"priority" db:"priority"` + IsActive bool `json:"is_active" db:"is_active"` + Metadata map[string]any `json:"metadata,omitempty" db:"metadata"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +type PlanType string + +const ( + PlanTypePrepaid PlanType = "prepaid" + PlanTypePostpaid PlanType = "postpaid" + PlanTypeHybrid PlanType = "hybrid" + PlanTypePayAsYouGo PlanType = "pay_as_you_go" + PlanTypeUnlimited PlanType = "unlimited" +) + +type PlanStatus string + +const ( + PlanStatusDraft PlanStatus = "draft" + PlanStatusActive PlanStatus = "active" + PlanStatusInactive PlanStatus = "inactive" + PlanStatusArchived PlanStatus = "archived" + PlanStatusSuspended PlanStatus = "suspended" +) + +type BillingCycle string + +const ( + BillingCycleDaily BillingCycle = "daily" + BillingCycleWeekly BillingCycle = "weekly" + BillingCycleMonthly BillingCycle = "monthly" + BillingCycleQuarterly BillingCycle = "quarterly" + BillingCycleYearly BillingCycle = "yearly" +) + +type DataAllowance struct { + Type DataAllowanceType `json:"type"` + Amount int64 `json:"amount"` // in MB or GB depending on type + Unit string `json:"unit"` // "MB", "GB", "TB" + Unlimited bool `json:"unlimited"` + SpeedLimit *int64 `json:"speed_limit,omitempty"` // in Kbps +} + +type DataAllowanceType string + +const ( + DataAllowanceTypeDaily DataAllowanceType = "daily" + DataAllowanceTypeMonthly DataAllowanceType = "monthly" + DataAllowanceTypeCycle DataAllowanceType = "cycle" + DataAllowanceTypeLifetime DataAllowanceType = "lifetime" +) + +type VoiceAllowance struct { + Type VoiceAllowanceType `json:"type"` + Minutes int64 `json:"minutes"` + Unlimited bool `json:"unlimited"` + Destinations []string `json:"destinations,omitempty"` // country codes +} + +type VoiceAllowanceType string + +const ( + VoiceAllowanceTypeDaily VoiceAllowanceType = "daily" + VoiceAllowanceTypeMonthly VoiceAllowanceType = "monthly" + VoiceAllowanceTypeCycle VoiceAllowanceType = "cycle" + VoiceAllowanceTypeUnlimited VoiceAllowanceType = "unlimited" +) + +type SMSAllowance struct { + Type SMSAllowanceType `json:"type"` + Messages int64 `json:"messages"` + Unlimited bool `json:"unlimited"` + Destinations []string `json:"destinations,omitempty"` // country codes +} + +type SMSAllowanceType string + +const ( + SMSAllowanceTypeDaily SMSAllowanceType = "daily" + SMSAllowanceTypeMonthly SMSAllowanceType = "monthly" + SMSAllowanceTypeCycle SMSAllowanceType = "cycle" + SMSAllowanceTypeUnlimited SMSAllowanceType = "unlimited" +) + +type OverageRates struct { + DataRate float64 `json:"data_rate"` // per MB + VoiceRate float64 `json:"voice_rate"` // per minute + SMSRate float64 `json:"sms_rate"` // per message + Currency string `json:"currency"` +} + +type PlanFeature struct { + Name string `json:"name"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + Config map[string]interface{} `json:"config,omitempty"` +} + +type EarlyTermination struct { + Enabled bool `json:"enabled"` + FeeType string `json:"fee_type"` // "fixed", "percentage", "remaining_months" + FeeAmount float64 `json:"fee_amount"` // for fixed fee type + FeePercentage float64 `json:"fee_percentage"` // for percentage fee type + MinMonths int `json:"min_months"` // minimum months before termination fee applies +} + +type Discount struct { + ID string `json:"id"` + Name string `json:"name"` + Type DiscountType `json:"type"` + Value float64 `json:"value"` + ValidFrom time.Time `json:"valid_from"` + ValidTo *time.Time `json:"valid_to,omitempty"` + Conditions string `json:"conditions,omitempty"` + IsActive bool `json:"is_active"` +} + +type DiscountType string + +const ( + DiscountTypePercentage DiscountType = "percentage" + DiscountTypeFixed DiscountType = "fixed" + DiscountTypeRecurring DiscountType = "recurring" +) + +// RatePlanUsage tracks actual usage against a rate plan +type RatePlanUsage struct { + ID string `json:"id" db:"id"` + RatePlanID string `json:"rate_plan_id" db:"rate_plan_id"` + ProfileID string `json:"profile_id" db:"profile_id"` + CycleStart time.Time `json:"cycle_start" db:"cycle_start"` + CycleEnd time.Time `json:"cycle_end" db:"cycle_end"` + DataUsed int64 `json:"data_used" db:"data_used"` // in MB + VoiceUsed int64 `json:"voice_used" db:"voice_used"` // in minutes + SMSUsed int64 `json:"sms_used" db:"sms_used"` // count + LastUpdated time.Time `json:"last_updated" db:"last_updated"` +} + +// RatePlanSubscription represents an active subscription to a rate plan +type RatePlanSubscription struct { + ID string `json:"id" db:"id"` + ProfileID string `json:"profile_id" db:"profile_id"` + RatePlanID string `json:"rate_plan_id" db:"rate_plan_id"` + Status SubscriptionStatus `json:"status" db:"status"` + StartedAt time.Time `json:"started_at" db:"started_at"` + EndedAt *time.Time `json:"ended_at,omitempty" db:"ended_at"` + BillingCycle BillingCycle `json:"billing_cycle" db:"billing_cycle"` + NextBillingDate time.Time `json:"next_billing_date" db:"next_billing_date"` + AutoRenew bool `json:"auto_renew" db:"auto_renew"` + CurrentCycle time.Time `json:"current_cycle" db:"current_cycle"` + AppliedDiscounts []string `json:"applied_discounts,omitempty" db:"applied_discounts"` + Metadata map[string]any `json:"metadata,omitempty" db:"metadata"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +// SubscriptionStatus defines the status of a subscription +type SubscriptionStatus string + +const ( + SubscriptionStatusActive SubscriptionStatus = "active" + SubscriptionStatusSuspended SubscriptionStatus = "suspended" + SubscriptionStatusCancelled SubscriptionStatus = "cancelled" + SubscriptionStatusExpired SubscriptionStatus = "expired" + SubscriptionStatusPending SubscriptionStatus = "pending" +) + +// 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"` + MinPrice float64 `json:"min_price,omitempty"` + MaxPrice float64 `json:"max_price,omitempty"` + IsActive *bool `json:"is_active,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"` +} + +// RatePlanCostCalculation represents the result of 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]any `json:"breakdown"` + CalculatedAt time.Time `json:"calculated_at"` +} diff --git a/apps/carrier-connector/internal/rateplan/types_extended.go b/apps/carrier-connector/internal/rateplan/types_extended.go new file mode 100644 index 0000000..6797c14 --- /dev/null +++ b/apps/carrier-connector/internal/rateplan/types_extended.go @@ -0,0 +1,64 @@ +package rateplan + +import ( + "time" +) + +// 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"` +} + +// 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"` // "day", "week", "month" +} + +// 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"` // "day", "week", "month" +} + +// 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"` +} + +// 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"` +}