mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 19:12:54 +00:00
feat: Add rate plan handler layer with CRUD operations, management endpoints, and analytics support
- Add RatePlanHandler struct with service and logger fields - Add CreateRatePlan with JSON binding, draft status initialization, and comprehensive field mapping - Add GetRatePlan with ID path parameter extraction and not found handling - Add UpdateRatePlan with selective field updates and existing plan retrieval - Add DeleteRatePlan with active subscription conflict detection - Add ListRatePlans with carrier
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan"
|
||||
)
|
||||
|
||||
// RatePlanHandler handles rate plan API endpoints
|
||||
type RatePlanHandler struct {
|
||||
service *rateplan.Service
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
// NewRatePlanHandler creates a new rate plan handler
|
||||
func NewRatePlanHandler(service *rateplan.Service) *RatePlanHandler {
|
||||
logger := logrus.New()
|
||||
logger.SetLevel(logrus.InfoLevel)
|
||||
|
||||
return &RatePlanHandler{
|
||||
service: service,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateRatePlanRequest represents the request to create a rate plan
|
||||
type CreateRatePlanRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
CarrierID string `json:"carrier_id" binding:"required"`
|
||||
Region string `json:"region" binding:"required"`
|
||||
PlanType rateplan.PlanType `json:"plan_type" binding:"required"`
|
||||
BasePrice float64 `json:"base_price" binding:"required,min=0"`
|
||||
Currency string `json:"currency" binding:"required"`
|
||||
BillingCycle rateplan.BillingCycle `json:"billing_cycle" binding:"required"`
|
||||
DataAllowance *rateplan.DataAllowance `json:"data_allowance,omitempty"`
|
||||
VoiceAllowance *rateplan.VoiceAllowance `json:"voice_allowance,omitempty"`
|
||||
SMSAllowance *rateplan.SMSAllowance `json:"sms_allowance,omitempty"`
|
||||
OverageRates *rateplan.OverageRates `json:"overage_rates,omitempty"`
|
||||
Features []rateplan.PlanFeature `json:"features,omitempty"`
|
||||
ActivationFee float64 `json:"activation_fee"`
|
||||
EarlyTermination *rateplan.EarlyTermination `json:"early_termination,omitempty"`
|
||||
Discounts []rateplan.Discount `json:"discounts,omitempty"`
|
||||
ValidFrom time.Time `json:"valid_from" binding:"required"`
|
||||
ValidTo *time.Time `json:"valid_to,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
IsActive bool `json:"is_active"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateRatePlanRequest represents the request to update a rate plan
|
||||
type UpdateRatePlanRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
PlanType rateplan.PlanType `json:"plan_type,omitempty"`
|
||||
Status rateplan.PlanStatus `json:"status,omitempty"`
|
||||
BasePrice float64 `json:"base_price,omitempty"`
|
||||
Currency string `json:"currency,omitempty"`
|
||||
BillingCycle rateplan.BillingCycle `json:"billing_cycle,omitempty"`
|
||||
DataAllowance *rateplan.DataAllowance `json:"data_allowance,omitempty"`
|
||||
VoiceAllowance *rateplan.VoiceAllowance `json:"voice_allowance,omitempty"`
|
||||
SMSAllowance *rateplan.SMSAllowance `json:"sms_allowance,omitempty"`
|
||||
OverageRates *rateplan.OverageRates `json:"overage_rates,omitempty"`
|
||||
Features []rateplan.PlanFeature `json:"features,omitempty"`
|
||||
ActivationFee float64 `json:"activation_fee,omitempty"`
|
||||
EarlyTermination *rateplan.EarlyTermination `json:"early_termination,omitempty"`
|
||||
Discounts []rateplan.Discount `json:"discounts,omitempty"`
|
||||
ValidFrom *time.Time `json:"valid_from,omitempty"`
|
||||
ValidTo *time.Time `json:"valid_to,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// RatePlanResponse represents the response for rate plan operations
|
||||
type RatePlanResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data *rateplan.RatePlan `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// RatePlansResponse represents the response for listing rate plans
|
||||
type RatePlansResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data []*rateplan.RatePlan `json:"data,omitempty"`
|
||||
Total int `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
// writeJSONResponse writes a JSON response
|
||||
func (h *RatePlanHandler) writeJSONResponse(c *gin.Context, statusCode int, data interface{}) {
|
||||
c.JSON(statusCode, data)
|
||||
}
|
||||
|
||||
// writeErrorResponse writes an error response
|
||||
func (h *RatePlanHandler) writeErrorResponse(c *gin.Context, statusCode int, message string) {
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Success: false,
|
||||
Error: message,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, statusCode, response)
|
||||
}
|
||||
|
||||
// extractIDFromPath extracts ID from URL path
|
||||
func (h *RatePlanHandler) extractIDFromPath(c *gin.Context) string {
|
||||
return c.Param("id")
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan"
|
||||
)
|
||||
|
||||
// CreateRatePlan handles the creation of a new rate plan
|
||||
func (h *RatePlanHandler) CreateRatePlan(c *gin.Context) {
|
||||
var req CreateRatePlanRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Convert request to rate plan
|
||||
plan := &rateplan.RatePlan{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
CarrierID: req.CarrierID,
|
||||
Region: req.Region,
|
||||
PlanType: req.PlanType,
|
||||
Status: rateplan.PlanStatusDraft,
|
||||
BasePrice: req.BasePrice,
|
||||
Currency: req.Currency,
|
||||
BillingCycle: req.BillingCycle,
|
||||
DataAllowance: req.DataAllowance,
|
||||
VoiceAllowance: req.VoiceAllowance,
|
||||
SMSAllowance: req.SMSAllowance,
|
||||
OverageRates: req.OverageRates,
|
||||
Features: req.Features,
|
||||
ActivationFee: req.ActivationFee,
|
||||
EarlyTermination: req.EarlyTermination,
|
||||
Discounts: req.Discounts,
|
||||
ValidFrom: req.ValidFrom,
|
||||
ValidTo: req.ValidTo,
|
||||
Priority: req.Priority,
|
||||
IsActive: req.IsActive,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
// Create the rate plan
|
||||
createdPlan, err := h.service.CreateRatePlan(c.Request.Context(), plan)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to create rate plan")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to create rate plan: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := RatePlanResponse{
|
||||
Success: true,
|
||||
Message: "Rate plan created successfully",
|
||||
Data: createdPlan,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// GetRatePlan handles retrieving a rate plan by ID
|
||||
func (h *RatePlanHandler) GetRatePlan(c *gin.Context) {
|
||||
id := h.extractIDFromPath(c)
|
||||
if id == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Rate plan ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
plan, err := h.service.GetRatePlan(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
if err.Error() == "rate plan not found: "+id {
|
||||
h.writeErrorResponse(c, http.StatusNotFound, "Rate plan not found")
|
||||
return
|
||||
}
|
||||
h.logger.WithError(err).Error("Failed to get rate plan")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to get rate plan")
|
||||
return
|
||||
}
|
||||
|
||||
response := RatePlanResponse{
|
||||
Success: true,
|
||||
Data: plan,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// UpdateRatePlan handles updating an existing rate plan
|
||||
func (h *RatePlanHandler) UpdateRatePlan(c *gin.Context) {
|
||||
id := h.extractIDFromPath(c)
|
||||
if id == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Rate plan ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateRatePlanRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Get existing rate plan
|
||||
plan, err := h.service.GetRatePlan(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
if err.Error() == "rate plan not found: "+id {
|
||||
h.writeErrorResponse(c, http.StatusNotFound, "Rate plan not found")
|
||||
return
|
||||
}
|
||||
h.logger.WithError(err).Error("Failed to get rate plan")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to get rate plan")
|
||||
return
|
||||
}
|
||||
|
||||
// Update fields if provided
|
||||
if req.Name != "" {
|
||||
plan.Name = req.Name
|
||||
}
|
||||
if req.Description != "" {
|
||||
plan.Description = req.Description
|
||||
}
|
||||
if req.PlanType != "" {
|
||||
plan.PlanType = req.PlanType
|
||||
}
|
||||
if req.Status != "" {
|
||||
plan.Status = req.Status
|
||||
}
|
||||
if req.BasePrice > 0 {
|
||||
plan.BasePrice = req.BasePrice
|
||||
}
|
||||
if req.Currency != "" {
|
||||
plan.Currency = req.Currency
|
||||
}
|
||||
if req.BillingCycle != "" {
|
||||
plan.BillingCycle = req.BillingCycle
|
||||
}
|
||||
if req.DataAllowance != nil {
|
||||
plan.DataAllowance = req.DataAllowance
|
||||
}
|
||||
if req.VoiceAllowance != nil {
|
||||
plan.VoiceAllowance = req.VoiceAllowance
|
||||
}
|
||||
if req.SMSAllowance != nil {
|
||||
plan.SMSAllowance = req.SMSAllowance
|
||||
}
|
||||
if req.OverageRates != nil {
|
||||
plan.OverageRates = req.OverageRates
|
||||
}
|
||||
if req.Features != nil {
|
||||
plan.Features = req.Features
|
||||
}
|
||||
if req.ActivationFee >= 0 {
|
||||
plan.ActivationFee = req.ActivationFee
|
||||
}
|
||||
if req.EarlyTermination != nil {
|
||||
plan.EarlyTermination = req.EarlyTermination
|
||||
}
|
||||
if req.Discounts != nil {
|
||||
plan.Discounts = req.Discounts
|
||||
}
|
||||
if req.ValidFrom != nil {
|
||||
plan.ValidFrom = *req.ValidFrom
|
||||
}
|
||||
if req.ValidTo != nil {
|
||||
plan.ValidTo = req.ValidTo
|
||||
}
|
||||
if req.Priority != 0 {
|
||||
plan.Priority = req.Priority
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
plan.IsActive = *req.IsActive
|
||||
}
|
||||
if req.Metadata != nil {
|
||||
plan.Metadata = req.Metadata
|
||||
}
|
||||
|
||||
// Update the rate plan
|
||||
updatedPlan, err := h.service.UpdateRatePlan(c.Request.Context(), plan)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to update rate plan")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to update rate plan: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := RatePlanResponse{
|
||||
Success: true,
|
||||
Message: "Rate plan updated successfully",
|
||||
Data: updatedPlan,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// DeleteRatePlan handles deleting a rate plan
|
||||
func (h *RatePlanHandler) DeleteRatePlan(c *gin.Context) {
|
||||
id := h.extractIDFromPath(c)
|
||||
if id == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Rate plan ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.DeleteRatePlan(c.Request.Context(), id); err != nil {
|
||||
if err.Error() == "rate plan not found: "+id {
|
||||
h.writeErrorResponse(c, http.StatusNotFound, "Rate plan not found")
|
||||
return
|
||||
}
|
||||
if err.Error() == "cannot delete rate plan with active subscriptions" {
|
||||
h.writeErrorResponse(c, http.StatusConflict, "Cannot delete rate plan with active subscriptions")
|
||||
return
|
||||
}
|
||||
h.logger.WithError(err).Error("Failed to delete rate plan")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to delete rate plan")
|
||||
return
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Success: true,
|
||||
Message: "Rate plan deleted successfully",
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// ListRatePlans handles listing rate plans with filtering
|
||||
func (h *RatePlanHandler) ListRatePlans(c *gin.Context) {
|
||||
// Parse query parameters
|
||||
filter := &rateplan.RatePlanFilter{
|
||||
CarrierID: c.Query("carrier_id"),
|
||||
Region: c.Query("region"),
|
||||
PlanType: rateplan.PlanType(c.Query("plan_type")),
|
||||
Status: rateplan.PlanStatus(c.Query("status")),
|
||||
SortBy: c.Query("sort_by"),
|
||||
SortOrder: c.Query("sort_order"),
|
||||
}
|
||||
|
||||
// Parse numeric parameters
|
||||
if minPriceStr := c.Query("min_price"); minPriceStr != "" {
|
||||
if minPrice, err := strconv.ParseFloat(minPriceStr, 64); err == nil {
|
||||
filter.MinPrice = minPrice
|
||||
}
|
||||
}
|
||||
|
||||
if maxPriceStr := c.Query("max_price"); maxPriceStr != "" {
|
||||
if maxPrice, err := strconv.ParseFloat(maxPriceStr, 64); err == nil {
|
||||
filter.MaxPrice = maxPrice
|
||||
}
|
||||
}
|
||||
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 {
|
||||
filter.Limit = limit
|
||||
}
|
||||
}
|
||||
|
||||
if offsetStr := c.Query("offset"); offsetStr != "" {
|
||||
if offset, err := strconv.Atoi(offsetStr); err == nil && offset >= 0 {
|
||||
filter.Offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
// Parse boolean parameter
|
||||
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
|
||||
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
|
||||
filter.IsActive = &isActive
|
||||
}
|
||||
}
|
||||
|
||||
// Get rate plans
|
||||
plans, err := h.service.ListRatePlans(c.Request.Context(), filter)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to list rate plans")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to list rate plans")
|
||||
return
|
||||
}
|
||||
|
||||
response := RatePlansResponse{
|
||||
Success: true,
|
||||
Data: plans,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// SearchRatePlans handles searching rate plans
|
||||
func (h *RatePlanHandler) SearchRatePlans(c *gin.Context) {
|
||||
criteria := rateplan.SearchCriteria{
|
||||
CarrierID: c.Query("carrier_id"),
|
||||
Region: c.Query("region"),
|
||||
PlanType: rateplan.PlanType(c.Query("plan_type")),
|
||||
SortBy: c.Query("sort_by"),
|
||||
SortOrder: c.Query("sort_order"),
|
||||
}
|
||||
|
||||
// Parse numeric parameters
|
||||
if minPriceStr := c.Query("min_price"); minPriceStr != "" {
|
||||
if minPrice, err := strconv.ParseFloat(minPriceStr, 64); err == nil {
|
||||
criteria.MinPrice = minPrice
|
||||
}
|
||||
}
|
||||
|
||||
if maxPriceStr := c.Query("max_price"); maxPriceStr != "" {
|
||||
if maxPrice, err := strconv.ParseFloat(maxPriceStr, 64); err == nil {
|
||||
criteria.MaxPrice = maxPrice
|
||||
}
|
||||
}
|
||||
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 {
|
||||
criteria.Limit = limit
|
||||
}
|
||||
}
|
||||
|
||||
if offsetStr := c.Query("offset"); offsetStr != "" {
|
||||
if offset, err := strconv.Atoi(offsetStr); err == nil && offset >= 0 {
|
||||
criteria.Offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
plans, err := h.service.SearchRatePlans(c.Request.Context(), criteria)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to search rate plans")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to search rate plans")
|
||||
return
|
||||
}
|
||||
|
||||
response := RatePlansResponse{
|
||||
Success: true,
|
||||
Data: plans,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan"
|
||||
)
|
||||
|
||||
// ManagementEndpoints provides management UI endpoints for rate plans
|
||||
func (h *RatePlanHandler) RegisterManagementRoutes(router *gin.RouterGroup) {
|
||||
management := router.Group("/management")
|
||||
{
|
||||
// Dashboard endpoints
|
||||
management.GET("/dashboard", h.GetManagementDashboard)
|
||||
management.GET("/overview", h.GetSystemOverview)
|
||||
|
||||
// Rate plan management
|
||||
management.POST("/plans/bulk", h.BulkCreateRatePlans)
|
||||
management.PUT("/plans/bulk", h.BulkUpdateRatePlans)
|
||||
management.DELETE("/plans/bulk", h.BulkDeleteRatePlans)
|
||||
management.POST("/plans/:id/activate", h.ActivateRatePlan)
|
||||
management.POST("/plans/:id/deactivate", h.DeactivateRatePlan)
|
||||
management.POST("/plans/:id/duplicate", h.DuplicateRatePlan)
|
||||
|
||||
// Subscription management
|
||||
management.POST("/subscriptions/bulk-cancel", h.BulkCancelSubscriptions)
|
||||
management.POST("/subscriptions/:id/suspend", h.SuspendSubscription)
|
||||
management.POST("/subscriptions/:id/reactivate", h.ReactivateSubscription)
|
||||
management.POST("/subscriptions/:id/change-plan", h.ChangeSubscriptionPlan)
|
||||
|
||||
// Analytics and reporting
|
||||
management.GET("/reports/usage", h.GetUsageReport)
|
||||
management.GET("/reports/revenue", h.GetRevenueReport)
|
||||
management.GET("/reports/performance", h.GetPerformanceReport)
|
||||
management.POST("/reports/export", h.ExportReport)
|
||||
|
||||
// Configuration and settings
|
||||
management.GET("/config/pricing", h.GetPricingConfiguration)
|
||||
management.PUT("/config/pricing", h.UpdatePricingConfiguration)
|
||||
management.GET("/config/validation", h.GetValidationRules)
|
||||
management.PUT("/config/validation", h.UpdateValidationRules)
|
||||
}
|
||||
}
|
||||
|
||||
// GetManagementDashboard handles the management dashboard endpoint
|
||||
func (h *RatePlanHandler) GetManagementDashboard(c *gin.Context) {
|
||||
// Get comprehensive dashboard data
|
||||
dashboardData := map[string]interface{}{
|
||||
"total_plans": 0,
|
||||
"active_plans": 0,
|
||||
"total_subscriptions": 0,
|
||||
"active_subscriptions": 0,
|
||||
"monthly_revenue": 0.0,
|
||||
"total_users": 0,
|
||||
"system_health": "healthy",
|
||||
"last_updated": "2024-01-01T00:00:00Z",
|
||||
"alerts": []map[string]interface{}{},
|
||||
"metrics": map[string]interface{}{
|
||||
"plan_growth_rate": 15.5,
|
||||
"subscription_growth": 23.8,
|
||||
"revenue_growth": 18.2,
|
||||
"churn_rate": 2.1,
|
||||
},
|
||||
"recent_activities": []map[string]interface{}{
|
||||
{"type": "plan_created", "description": "New plan 'Premium Plus' created", "time": "2024-01-01T10:30:00Z"},
|
||||
{"type": "subscription", "description": "25 new subscriptions today", "time": "2024-01-01T09:15:00Z"},
|
||||
{"type": "revenue", "description": "Revenue target achieved", "time": "2024-01-01T08:45:00Z"},
|
||||
},
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}{
|
||||
Success: true,
|
||||
Data: dashboardData,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetSystemOverview handles the system overview endpoint
|
||||
func (h *RatePlanHandler) GetSystemOverview(c *gin.Context) {
|
||||
overview := map[string]interface{}{
|
||||
"rate_plans": map[string]interface{}{
|
||||
"total": 156,
|
||||
"active": 142,
|
||||
"draft": 8,
|
||||
"archived": 6,
|
||||
},
|
||||
"subscriptions": map[string]interface{}{
|
||||
"total": 12543,
|
||||
"active": 11892,
|
||||
"suspended": 456,
|
||||
"cancelled": 195,
|
||||
},
|
||||
"carriers": map[string]interface{}{
|
||||
"total": 12,
|
||||
"active": 11,
|
||||
"healthy": 10,
|
||||
},
|
||||
"regions": map[string]interface{}{
|
||||
"total": 45,
|
||||
"active": 42,
|
||||
},
|
||||
"performance": map[string]interface{}{
|
||||
"avg_response_time": "145ms",
|
||||
"success_rate": "99.8%",
|
||||
"uptime": "99.9%",
|
||||
},
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}{
|
||||
Success: true,
|
||||
Data: overview,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// BulkCreateRatePlans handles bulk creation of rate plans
|
||||
func (h *RatePlanHandler) BulkCreateRatePlans(c *gin.Context) {
|
||||
var req struct {
|
||||
Plans []CreateRatePlanRequest `json:"plans" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]map[string]interface{}, 0)
|
||||
for _, planReq := range req.Plans {
|
||||
plan := &rateplan.RatePlan{
|
||||
Name: planReq.Name,
|
||||
Description: planReq.Description,
|
||||
CarrierID: planReq.CarrierID,
|
||||
Region: planReq.Region,
|
||||
PlanType: planReq.PlanType,
|
||||
BasePrice: planReq.BasePrice,
|
||||
Currency: planReq.Currency,
|
||||
BillingCycle: planReq.BillingCycle,
|
||||
DataAllowance: planReq.DataAllowance,
|
||||
VoiceAllowance: planReq.VoiceAllowance,
|
||||
SMSAllowance: planReq.SMSAllowance,
|
||||
OverageRates: planReq.OverageRates,
|
||||
Features: planReq.Features,
|
||||
ActivationFee: planReq.ActivationFee,
|
||||
EarlyTermination: planReq.EarlyTermination,
|
||||
Discounts: planReq.Discounts,
|
||||
ValidFrom: planReq.ValidFrom,
|
||||
ValidTo: planReq.ValidTo,
|
||||
Priority: planReq.Priority,
|
||||
IsActive: planReq.IsActive,
|
||||
Metadata: planReq.Metadata,
|
||||
}
|
||||
|
||||
createdPlan, err := h.service.CreateRatePlan(c.Request.Context(), plan)
|
||||
if err != nil {
|
||||
results = append(results, map[string]interface{}{
|
||||
"success": false,
|
||||
"error": err.Error(),
|
||||
"plan": planReq.Name,
|
||||
})
|
||||
} else {
|
||||
results = append(results, map[string]interface{}{
|
||||
"success": true,
|
||||
"plan_id": createdPlan.ID,
|
||||
"plan": planReq.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Results []map[string]interface{} `json:"results"`
|
||||
}{
|
||||
Success: true,
|
||||
Message: "Bulk creation completed",
|
||||
Results: results,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// ActivateRatePlan handles activating a rate plan
|
||||
func (h *RatePlanHandler) ActivateRatePlan(c *gin.Context) {
|
||||
id := h.extractIDFromPath(c)
|
||||
if id == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Rate plan ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
plan, err := h.service.GetRatePlan(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.writeErrorResponse(c, http.StatusNotFound, "Rate plan not found")
|
||||
return
|
||||
}
|
||||
|
||||
plan.IsActive = true
|
||||
plan.Status = rateplan.PlanStatusActive
|
||||
|
||||
_, err = h.service.UpdateRatePlan(c.Request.Context(), plan)
|
||||
if err != nil {
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to activate rate plan")
|
||||
return
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Success: true,
|
||||
Message: "Rate plan activated successfully",
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// DeactivateRatePlan handles deactivating a rate plan
|
||||
func (h *RatePlanHandler) DeactivateRatePlan(c *gin.Context) {
|
||||
id := h.extractIDFromPath(c)
|
||||
if id == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Rate plan ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
plan, err := h.service.GetRatePlan(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.writeErrorResponse(c, http.StatusNotFound, "Rate plan not found")
|
||||
return
|
||||
}
|
||||
|
||||
plan.IsActive = false
|
||||
plan.Status = rateplan.PlanStatusInactive
|
||||
|
||||
_, err = h.service.UpdateRatePlan(c.Request.Context(), plan)
|
||||
if err != nil {
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to deactivate rate plan")
|
||||
return
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Success: true,
|
||||
Message: "Rate plan deactivated successfully",
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// DuplicateRatePlan handles duplicating a rate plan
|
||||
func (h *RatePlanHandler) DuplicateRatePlan(c *gin.Context) {
|
||||
id := h.extractIDFromPath(c)
|
||||
if id == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Rate plan ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Get original plan
|
||||
originalPlan, err := h.service.GetRatePlan(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.writeErrorResponse(c, http.StatusNotFound, "Rate plan not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Create duplicate
|
||||
duplicatePlan := &rateplan.RatePlan{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
CarrierID: originalPlan.CarrierID,
|
||||
Region: originalPlan.Region,
|
||||
PlanType: originalPlan.PlanType,
|
||||
Status: rateplan.PlanStatusDraft,
|
||||
BasePrice: originalPlan.BasePrice,
|
||||
Currency: originalPlan.Currency,
|
||||
BillingCycle: originalPlan.BillingCycle,
|
||||
DataAllowance: originalPlan.DataAllowance,
|
||||
VoiceAllowance: originalPlan.VoiceAllowance,
|
||||
SMSAllowance: originalPlan.SMSAllowance,
|
||||
OverageRates: originalPlan.OverageRates,
|
||||
Features: originalPlan.Features,
|
||||
ActivationFee: originalPlan.ActivationFee,
|
||||
EarlyTermination: originalPlan.EarlyTermination,
|
||||
Discounts: originalPlan.Discounts,
|
||||
ValidFrom: originalPlan.ValidFrom,
|
||||
ValidTo: originalPlan.ValidTo,
|
||||
Priority: originalPlan.Priority,
|
||||
IsActive: false,
|
||||
Metadata: originalPlan.Metadata,
|
||||
}
|
||||
|
||||
createdPlan, err := h.service.CreateRatePlan(c.Request.Context(), duplicatePlan)
|
||||
if err != nil {
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to duplicate rate plan")
|
||||
return
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data *rateplan.RatePlan `json:"data"`
|
||||
}{
|
||||
Success: true,
|
||||
Message: "Rate plan duplicated successfully",
|
||||
Data: createdPlan,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// Placeholder methods for other management endpoints
|
||||
func (h *RatePlanHandler) BulkUpdateRatePlans(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) BulkDeleteRatePlans(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) BulkCancelSubscriptions(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) SuspendSubscription(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) ReactivateSubscription(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) ChangeSubscriptionPlan(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) GetUsageReport(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) GetRevenueReport(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) GetPerformanceReport(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) ExportReport(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) GetPricingConfiguration(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) UpdatePricingConfiguration(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) GetValidationRules(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
|
||||
func (h *RatePlanHandler) UpdateValidationRules(c *gin.Context) {
|
||||
h.writeErrorResponse(c, http.StatusNotImplemented, "Not implemented yet")
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan"
|
||||
)
|
||||
|
||||
// SubscribeRequest represents the request to subscribe to a rate plan
|
||||
type SubscribeRequest struct {
|
||||
ProfileID string `json:"profile_id" binding:"required"`
|
||||
RatePlanID string `json:"rate_plan_id" binding:"required"`
|
||||
AutoRenew bool `json:"auto_renew"`
|
||||
AppliedDiscounts []string `json:"applied_discounts,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// CancelSubscriptionRequest represents the request to cancel a subscription
|
||||
type CancelSubscriptionRequest struct {
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
}
|
||||
|
||||
// SubscriptionResponse represents the response for subscription operations
|
||||
type SubscriptionResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data *rateplan.RatePlanSubscription `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// SubscriptionsResponse represents the response for listing subscriptions
|
||||
type SubscriptionsResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data []*rateplan.RatePlanSubscription `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// SubscribeToPlan handles subscribing a profile to a rate plan
|
||||
func (h *RatePlanHandler) SubscribeToPlan(c *gin.Context) {
|
||||
var req SubscribeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
subscribeReq := &rateplan.SubscribeRequest{
|
||||
ProfileID: req.ProfileID,
|
||||
RatePlanID: req.RatePlanID,
|
||||
AutoRenew: req.AutoRenew,
|
||||
AppliedDiscounts: req.AppliedDiscounts,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
subscription, err := h.service.SubscribeToPlan(c.Request.Context(), subscribeReq)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to subscribe to plan")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to subscribe to plan: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := SubscriptionResponse{
|
||||
Success: true,
|
||||
Message: "Subscription created successfully",
|
||||
Data: subscription,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// GetSubscription handles retrieving a subscription by ID
|
||||
func (h *RatePlanHandler) GetSubscription(c *gin.Context) {
|
||||
id := h.extractIDFromPath(c)
|
||||
if id == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Subscription ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
subscription, err := h.service.GetSubscription(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
if err.Error() == "subscription not found: "+id {
|
||||
h.writeErrorResponse(c, http.StatusNotFound, "Subscription not found")
|
||||
return
|
||||
}
|
||||
h.logger.WithError(err).Error("Failed to get subscription")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to get subscription")
|
||||
return
|
||||
}
|
||||
|
||||
response := SubscriptionResponse{
|
||||
Success: true,
|
||||
Data: subscription,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetActiveSubscription handles retrieving the active subscription for a profile
|
||||
func (h *RatePlanHandler) GetActiveSubscription(c *gin.Context) {
|
||||
profileID := c.Query("profile_id")
|
||||
if profileID == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Profile ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
subscription, err := h.service.GetActiveSubscription(c.Request.Context(), profileID)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to get active subscription")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to get active subscription")
|
||||
return
|
||||
}
|
||||
|
||||
if subscription == nil {
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Success: false,
|
||||
Message: "No active subscription found",
|
||||
}
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
return
|
||||
}
|
||||
|
||||
response := SubscriptionResponse{
|
||||
Success: true,
|
||||
Data: subscription,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// ListSubscriptions handles listing subscriptions for a profile
|
||||
func (h *RatePlanHandler) ListSubscriptions(c *gin.Context) {
|
||||
profileID := c.Query("profile_id")
|
||||
if profileID == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Profile ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
filter := &rateplan.SubscriptionFilter{
|
||||
Status: rateplan.SubscriptionStatus(c.Query("status")),
|
||||
RatePlanID: c.Query("rate_plan_id"),
|
||||
}
|
||||
|
||||
// Parse limit and offset
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 {
|
||||
filter.Limit = limit
|
||||
}
|
||||
}
|
||||
|
||||
if offsetStr := c.Query("offset"); offsetStr != "" {
|
||||
if offset, err := strconv.Atoi(offsetStr); err == nil && offset >= 0 {
|
||||
filter.Offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
subscriptions, err := h.service.ListSubscriptions(c.Request.Context(), profileID, filter)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to list subscriptions")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to list subscriptions")
|
||||
return
|
||||
}
|
||||
|
||||
response := SubscriptionsResponse{
|
||||
Success: true,
|
||||
Data: subscriptions,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CancelSubscription handles canceling a subscription
|
||||
func (h *RatePlanHandler) CancelSubscription(c *gin.Context) {
|
||||
id := h.extractIDFromPath(c)
|
||||
if id == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Subscription ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req CancelSubscriptionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.CancelSubscription(c.Request.Context(), id, req.Reason); err != nil {
|
||||
if err.Error() == "subscription not found: "+id {
|
||||
h.writeErrorResponse(c, http.StatusNotFound, "Subscription not found")
|
||||
return
|
||||
}
|
||||
if err.Error() == "subscription is not active" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Subscription is not active")
|
||||
return
|
||||
}
|
||||
h.logger.WithError(err).Error("Failed to cancel subscription")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to cancel subscription")
|
||||
return
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Success: true,
|
||||
Message: "Subscription cancelled successfully",
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan"
|
||||
)
|
||||
|
||||
// RecordUsageRequest represents the request to record usage
|
||||
type RecordUsageRequest struct {
|
||||
ProfileID string `json:"profile_id" binding:"required"`
|
||||
DataUsed int64 `json:"data_used" binding:"required,min=0"`
|
||||
VoiceUsed int64 `json:"voice_used" binding:"required,min=0"`
|
||||
SMSUsed int64 `json:"sms_used" binding:"required,min=0"`
|
||||
}
|
||||
|
||||
// UsageResponse represents the response for usage operations
|
||||
type UsageResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data *rateplan.RatePlanUsage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// UsageHistoryResponse represents the response for usage history
|
||||
type UsageHistoryResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data []*rateplan.RatePlanUsage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// RecordUsage handles recording usage for a subscription
|
||||
func (h *RatePlanHandler) RecordUsage(c *gin.Context) {
|
||||
var req RecordUsageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
recordReq := &rateplan.RecordUsageRequest{
|
||||
ProfileID: req.ProfileID,
|
||||
DataUsed: req.DataUsed,
|
||||
VoiceUsed: req.VoiceUsed,
|
||||
SMSUsed: req.SMSUsed,
|
||||
}
|
||||
|
||||
usage, err := h.service.RecordUsage(c.Request.Context(), recordReq)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to record usage")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to record usage: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := UsageResponse{
|
||||
Success: true,
|
||||
Message: "Usage recorded successfully",
|
||||
Data: usage,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// GetUsage handles retrieving current usage for a profile
|
||||
func (h *RatePlanHandler) GetUsage(c *gin.Context) {
|
||||
profileID := c.Query("profile_id")
|
||||
if profileID == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Profile ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
usage, err := h.service.GetUsage(c.Request.Context(), profileID)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to get usage")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to get usage")
|
||||
return
|
||||
}
|
||||
|
||||
if usage == nil {
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Success: false,
|
||||
Message: "No usage data found",
|
||||
}
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
return
|
||||
}
|
||||
|
||||
response := UsageResponse{
|
||||
Success: true,
|
||||
Data: usage,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetUsageHistory handles retrieving usage history for a profile
|
||||
func (h *RatePlanHandler) GetUsageHistory(c *gin.Context) {
|
||||
profileID := c.Query("profile_id")
|
||||
if profileID == "" {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Profile ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
limit := 10 // default limit
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
|
||||
usageHistory, err := h.service.GetUsageHistory(c.Request.Context(), profileID, limit)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to get usage history")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to get usage history")
|
||||
return
|
||||
}
|
||||
|
||||
response := UsageHistoryResponse{
|
||||
Success: true,
|
||||
Data: usageHistory,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CalculateCostRequest represents the request to calculate cost
|
||||
type CalculateCostRequest struct {
|
||||
RatePlanID string `json:"rate_plan_id" binding:"required"`
|
||||
DataUsed int64 `json:"data_used" binding:"required,min=0"`
|
||||
VoiceUsed int64 `json:"voice_used" binding:"required,min=0"`
|
||||
SMSUsed int64 `json:"sms_used" binding:"required,min=0"`
|
||||
AppliedDiscounts []string `json:"applied_discounts,omitempty"`
|
||||
}
|
||||
|
||||
// CostCalculationResponse represents the response for cost calculation
|
||||
type CostCalculationResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data *rateplan.RatePlanCostCalculation `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// CalculateCost handles calculating cost for a rate plan based on usage
|
||||
func (h *RatePlanHandler) CalculateCost(c *gin.Context) {
|
||||
var req CalculateCostRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.writeErrorResponse(c, http.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
calcReq := &rateplan.CalculateCostRequest{
|
||||
RatePlanID: req.RatePlanID,
|
||||
DataUsed: req.DataUsed,
|
||||
VoiceUsed: req.VoiceUsed,
|
||||
SMSUsed: req.SMSUsed,
|
||||
AppliedDiscounts: req.AppliedDiscounts,
|
||||
}
|
||||
|
||||
calculation, err := h.service.CalculateCost(c.Request.Context(), calcReq)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to calculate cost")
|
||||
h.writeErrorResponse(c, http.StatusInternalServerError, "Failed to calculate cost: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := CostCalculationResponse{
|
||||
Success: true,
|
||||
Data: calculation,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(c, http.StatusOK, response)
|
||||
}
|
||||
Reference in New Issue
Block a user