diff --git a/apps/carrier-connector/internal/services/pricing_service.go b/apps/carrier-connector/internal/services/pricing_service.go index b5a5d28..111b513 100644 --- a/apps/carrier-connector/internal/services/pricing_service.go +++ b/apps/carrier-connector/internal/services/pricing_service.go @@ -9,7 +9,6 @@ import ( "github.com/sirupsen/logrus" ) -// PricingService implements the pricing business logic type PricingService struct { repository pricing.Repository engine pricing.RuleEngine @@ -17,7 +16,6 @@ type PricingService struct { logger *logrus.Logger } -// NewPricingService creates a new pricing service func NewPricingService( repository pricing.Repository, engine pricing.RuleEngine, @@ -32,182 +30,6 @@ func NewPricingService( } } -// CreateRule creates a new pricing rule -func (s *PricingService) CreateRule(ctx context.Context, rule *pricing.PricingRule) (*pricing.PricingRule, error) { - // Validate rule - if err := s.validator.ValidateRule(ctx, rule); err != nil { - return nil, fmt.Errorf("validation failed: %w", err) - } - - // Create rule - if err := s.repository.CreateRule(ctx, rule); err != nil { - s.logger.WithError(err).Error("Failed to create pricing rule") - return nil, fmt.Errorf("failed to create rule: %w", err) - } - - s.logger.WithFields(logrus.Fields{ - "rule_id": rule.ID, - "tenant_id": rule.TenantID, - "type": rule.Type, - }).Info("Pricing rule created successfully") - - return rule, nil -} - -// GetRule retrieves a pricing rule by ID -func (s *PricingService) GetRule(ctx context.Context, id string) (*pricing.PricingRule, error) { - rule, err := s.repository.GetRule(ctx, id) - if err != nil { - s.logger.WithError(err).WithField("rule_id", id).Error("Failed to get pricing rule") - return nil, err - } - - return rule, nil -} - -// UpdateRule updates an existing pricing rule -func (s *PricingService) UpdateRule(ctx context.Context, id string, rule *pricing.PricingRule) (*pricing.PricingRule, error) { - // Validate rule - if err := s.validator.ValidateRule(ctx, rule); err != nil { - return nil, fmt.Errorf("validation failed: %w", err) - } - - // Update rule - if err := s.repository.UpdateRule(ctx, rule); err != nil { - s.logger.WithError(err).WithField("rule_id", id).Error("Failed to update pricing rule") - return nil, fmt.Errorf("failed to update rule: %w", err) - } - - s.logger.WithFields(logrus.Fields{ - "rule_id": rule.ID, - "tenant_id": rule.TenantID, - "type": rule.Type, - }).Info("Pricing rule updated successfully") - - return rule, nil -} - -// DeleteRule deletes a pricing rule -func (s *PricingService) DeleteRule(ctx context.Context, id string) error { - // Get rule for logging - rule, err := s.repository.GetRule(ctx, id) - if err != nil { - return err - } - - // Delete rule - if err := s.repository.DeleteRule(ctx, id); err != nil { - s.logger.WithError(err).WithField("rule_id", id).Error("Failed to delete pricing rule") - return fmt.Errorf("failed to delete rule: %w", err) - } - - s.logger.WithFields(logrus.Fields{ - "rule_id": rule.ID, - "tenant_id": rule.TenantID, - "type": rule.Type, - }).Info("Pricing rule deleted successfully") - - return nil -} - -// ListRules lists pricing rules with filtering -func (s *PricingService) ListRules(ctx context.Context, filter *pricing.PricingFilter) ([]*pricing.PricingRule, error) { - rules, err := s.repository.ListRules(ctx, filter) - if err != nil { - s.logger.WithError(err).Error("Failed to list pricing rules") - return nil, fmt.Errorf("failed to list rules: %w", err) - } - - return rules, nil -} - -// CalculatePrice calculates the final price based on active rules -func (s *PricingService) CalculatePrice(ctx context.Context, pricingCtx *pricing.PricingContext) (*pricing.PricingResult, error) { - // Validate context - if err := s.validator.ValidateContext(ctx, pricingCtx); err != nil { - return nil, fmt.Errorf("invalid pricing context: %w", err) - } - - // Get active rules for tenant - rules, err := s.repository.GetActiveRules(ctx, pricingCtx.TenantID) - if err != nil { - s.logger.WithError(err).WithField("tenant_id", pricingCtx.TenantID).Error("Failed to get active rules") - return nil, fmt.Errorf("failed to get active rules: %w", err) - } - - // Apply rules to calculate final price - result, err := s.ApplyRules(ctx, pricingCtx, rules) - if err != nil { - return nil, err - } - - s.logger.WithFields(logrus.Fields{ - "tenant_id": pricingCtx.TenantID, - "product_id": pricingCtx.ProductID, - "original_price": result.OriginalPrice, - "final_price": result.FinalPrice, - "rules_applied": len(result.AppliedRules), - }).Info("Price calculated successfully") - - return result, nil -} - -// ApplyRules applies specific rules to a pricing context -func (s *PricingService) ApplyRules(ctx context.Context, pricingCtx *pricing.PricingContext, rules []*pricing.PricingRule) (*pricing.PricingResult, error) { - result := &pricing.PricingResult{ - OriginalPrice: pricingCtx.BasePrice, - AdjustedPrice: pricingCtx.BasePrice, - FinalPrice: pricingCtx.BasePrice, - Currency: pricingCtx.Currency, - AppliedRules: make([]pricing.AppliedRule, 0), - Metadata: make(map[string]any), - } - - currentPrice := pricingCtx.BasePrice - - // Apply rules in priority order - for _, rule := range rules { - shouldApply, err := s.engine.EvaluateRule(ctx, rule, pricingCtx) - if err != nil { - s.logger.WithError(err).WithField("rule_id", rule.ID).Error("Failed to evaluate rule") - continue - } - - if shouldApply { - adjustedPrice, err := s.engine.ApplyRule(ctx, rule, pricingCtx, currentPrice) - if err != nil { - s.logger.WithError(err).WithField("rule_id", rule.ID).Error("Failed to apply rule") - continue - } - - // Calculate discount amount - discount := currentPrice - adjustedPrice - - // Update result - currentPrice = adjustedPrice - result.AppliedRules = append(result.AppliedRules, pricing.AppliedRule{ - RuleID: rule.ID, - RuleName: rule.Name, - Type: string(rule.Type), - Adjustment: discount, - }) - - s.logger.WithFields(logrus.Fields{ - "rule_id": rule.ID, - "rule_name": rule.Name, - "adjustment": discount, - "new_price": adjustedPrice, - }).Debug("Rule applied") - } - } - - // Finalize result - result.FinalPrice = currentPrice - result.Discount = result.OriginalPrice - result.FinalPrice - - return result, nil -} - // ValidateRule validates a pricing rule func (s *PricingService) ValidateRule(ctx context.Context, rule *pricing.PricingRule) error { return s.validator.ValidateRule(ctx, rule) @@ -264,8 +86,7 @@ func (s *PricingService) GetAnalytics(ctx context.Context, tenantID string) (*pr // calculateDiscountStatistics calculates actual discount statistics from pricing history func (s *PricingService) calculateDiscountStatistics(ctx context.Context) (pricing.DiscountStatistics, error) { - // Get all rules to estimate discount usage - // Use empty filter to get all rules + // Retrieve all rules and compute discount statistics from their action values filter := &pricing.PricingFilter{} allRules, err := s.repository.ListRules(ctx, filter) if err != nil { @@ -299,8 +120,6 @@ func (s *PricingService) calculateDiscountStatistics(ctx context.Context) (prici var smallestDiscount float64 = -1 for _, rule := range activeRules { - // Extract discount value from rule actions - // This would be more sophisticated in a real implementation discountValue := extractDiscountValue(rule) if discountValue > 0 { @@ -338,29 +157,32 @@ func (s *PricingService) calculateDiscountStatistics(ctx context.Context) (prici // extractDiscountValue extracts discount value from rule actions func extractDiscountValue(rule *pricing.PricingRule) float64 { - // In a real implementation, this would parse the rule actions to extract discount values - // For now, we'll use a simplified approach based on rule type + // Extract the actual discount value from the rule's Actions field + actionValue := rule.Actions.Value + if actionValue == 0 { + return 0.0 + } - switch rule.Type { - case pricing.RuleTypePercentageDiscount: - // For percentage discount rules, assume a standard discount percentage - return 10.0 // 10% discount as example - case pricing.RuleTypeFixedDiscount: - // For fixed discount rules, assume a fixed amount - return 15.0 // $15 discount as example - case pricing.RuleTypeMultiplier: - // For multiplier rules, assume a discount factor - return 5.0 // 5% discount as example - case pricing.RuleTypeTieredPricing: - // For tiered pricing rules, assume variable discount - return 20.0 // 20% discount as example - case pricing.RuleTypeDynamicPricing: - // For dynamic pricing rules, assume market-based discount - return 12.5 // 12.5% discount as example - case pricing.RuleTypeConditionalPricing: - // For conditional pricing rules, assume conditional discount - return 8.0 // 8% discount as example + switch rule.Actions.AdjustmentType { + case pricing.AdjustmentTypePercentage: + // Value represents the percentage discount directly + return actionValue + case pricing.AdjustmentTypeFixed: + // Value represents a fixed monetary discount + return actionValue + case pricing.AdjustmentTypeMultiply: + // Multiplier < 1.0 implies a discount; convert to percentage + if actionValue < 1.0 { + return (1.0 - actionValue) * 100 + } + return 0.0 + case pricing.AdjustmentTypeOverride: + // Override replaces the price; if NewPrice is set, compute savings + if rule.Actions.NewPrice != nil && *rule.Actions.NewPrice < actionValue { + return actionValue - *rule.Actions.NewPrice + } + return 0.0 default: - return 0.0 // No discount for other rule types + return actionValue } } diff --git a/apps/carrier-connector/internal/services/pricing_service_crud.go b/apps/carrier-connector/internal/services/pricing_service_crud.go new file mode 100644 index 0000000..a6c027f --- /dev/null +++ b/apps/carrier-connector/internal/services/pricing_service_crud.go @@ -0,0 +1,186 @@ +package services + +import ( + "context" + "fmt" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/pricing" + "github.com/sirupsen/logrus" +) + + +// CreateRule creates a new pricing rule +func (s *PricingService) CreateRule(ctx context.Context, rule *pricing.PricingRule) (*pricing.PricingRule, error) { + // Validate rule + if err := s.validator.ValidateRule(ctx, rule); err != nil { + return nil, fmt.Errorf("validation failed: %w", err) + } + + // Create rule + if err := s.repository.CreateRule(ctx, rule); err != nil { + s.logger.WithError(err).Error("Failed to create pricing rule") + return nil, fmt.Errorf("failed to create rule: %w", err) + } + + s.logger.WithFields(logrus.Fields{ + "rule_id": rule.ID, + "tenant_id": rule.TenantID, + "type": rule.Type, + }).Info("Pricing rule created successfully") + + return rule, nil +} + +// GetRule retrieves a pricing rule by ID +func (s *PricingService) GetRule(ctx context.Context, id string) (*pricing.PricingRule, error) { + rule, err := s.repository.GetRule(ctx, id) + if err != nil { + s.logger.WithError(err).WithField("rule_id", id).Error("Failed to get pricing rule") + return nil, err + } + + return rule, nil +} + +// UpdateRule updates an existing pricing rule +func (s *PricingService) UpdateRule(ctx context.Context, id string, rule *pricing.PricingRule) (*pricing.PricingRule, error) { + // Validate rule + if err := s.validator.ValidateRule(ctx, rule); err != nil { + return nil, fmt.Errorf("validation failed: %w", err) + } + + // Update rule + if err := s.repository.UpdateRule(ctx, rule); err != nil { + s.logger.WithError(err).WithField("rule_id", id).Error("Failed to update pricing rule") + return nil, fmt.Errorf("failed to update rule: %w", err) + } + + s.logger.WithFields(logrus.Fields{ + "rule_id": rule.ID, + "tenant_id": rule.TenantID, + "type": rule.Type, + }).Info("Pricing rule updated successfully") + + return rule, nil +} + +// DeleteRule deletes a pricing rule +func (s *PricingService) DeleteRule(ctx context.Context, id string) error { + // Get rule for logging + rule, err := s.repository.GetRule(ctx, id) + if err != nil { + return err + } + + // Delete rule + if err := s.repository.DeleteRule(ctx, id); err != nil { + s.logger.WithError(err).WithField("rule_id", id).Error("Failed to delete pricing rule") + return fmt.Errorf("failed to delete rule: %w", err) + } + + s.logger.WithFields(logrus.Fields{ + "rule_id": rule.ID, + "tenant_id": rule.TenantID, + "type": rule.Type, + }).Info("Pricing rule deleted successfully") + + return nil +} + +// ListRules lists pricing rules with filtering +func (s *PricingService) ListRules(ctx context.Context, filter *pricing.PricingFilter) ([]*pricing.PricingRule, error) { + rules, err := s.repository.ListRules(ctx, filter) + if err != nil { + s.logger.WithError(err).Error("Failed to list pricing rules") + return nil, fmt.Errorf("failed to list rules: %w", err) + } + + return rules, nil +} + +// CalculatePrice calculates the final price based on active rules +func (s *PricingService) CalculatePrice(ctx context.Context, pricingCtx *pricing.PricingContext) (*pricing.PricingResult, error) { + // Validate context + if err := s.validator.ValidateContext(ctx, pricingCtx); err != nil { + return nil, fmt.Errorf("invalid pricing context: %w", err) + } + + // Get active rules for tenant + rules, err := s.repository.GetActiveRules(ctx, pricingCtx.TenantID) + if err != nil { + s.logger.WithError(err).WithField("tenant_id", pricingCtx.TenantID).Error("Failed to get active rules") + return nil, fmt.Errorf("failed to get active rules: %w", err) + } + + // Apply rules to calculate final price + result, err := s.ApplyRules(ctx, pricingCtx, rules) + if err != nil { + return nil, err + } + + s.logger.WithFields(logrus.Fields{ + "tenant_id": pricingCtx.TenantID, + "product_id": pricingCtx.ProductID, + "original_price": result.OriginalPrice, + "final_price": result.FinalPrice, + "rules_applied": len(result.AppliedRules), + }).Info("Price calculated successfully") + + return result, nil +} + +// ApplyRules applies specific rules to a pricing context +func (s *PricingService) ApplyRules(ctx context.Context, pricingCtx *pricing.PricingContext, rules []*pricing.PricingRule) (*pricing.PricingResult, error) { + result := &pricing.PricingResult{ + OriginalPrice: pricingCtx.BasePrice, + AdjustedPrice: pricingCtx.BasePrice, + FinalPrice: pricingCtx.BasePrice, + Currency: pricingCtx.Currency, + AppliedRules: make([]pricing.AppliedRule, 0), + Metadata: make(map[string]any), + } + + currentPrice := pricingCtx.BasePrice + + // Apply rules in priority order + for _, rule := range rules { + shouldApply, err := s.engine.EvaluateRule(ctx, rule, pricingCtx) + if err != nil { + s.logger.WithError(err).WithField("rule_id", rule.ID).Error("Failed to evaluate rule") + continue + } + + if shouldApply { + adjustedPrice, err := s.engine.ApplyRule(ctx, rule, pricingCtx, currentPrice) + if err != nil { + s.logger.WithError(err).WithField("rule_id", rule.ID).Error("Failed to apply rule") + continue + } + + // Calculate discount amount + discount := currentPrice - adjustedPrice + + // Update result + currentPrice = adjustedPrice + result.AppliedRules = append(result.AppliedRules, pricing.AppliedRule{ + RuleID: rule.ID, + RuleName: rule.Name, + Type: string(rule.Type), + Adjustment: discount, + }) + + s.logger.WithFields(logrus.Fields{ + "rule_id": rule.ID, + "rule_name": rule.Name, + "adjustment": discount, + "new_price": adjustedPrice, + }).Debug("Rule applied") + } + } + + // Finalize result + result.FinalPrice = currentPrice + result.Discount = result.OriginalPrice - result.FinalPrice + + return result, nil +}