# Business Rules and Logic Patterns - Centron Enterprise Application ## Overview This document provides comprehensive coverage of business logic patterns and implementations within the Centron .NET 8 enterprise application. Business rules encompass decision logic, workflow patterns, process orchestration, state management, and calculation patterns that enforce business constraints and processes. ## Business Logic Architecture ### **Core Business Logic Patterns** #### **1. Business Logic Layer (BL) Pattern** The foundation of all business rule implementation follows a consistent 2-layer architecture: ```csharp // Base BL Class - Core Business Logic public class AccountBL : BaseBL { public Result SaveAccount(Account account) { // Business rule validation if (account.CustomerNumber <= 0) return Result.AsError("Customer number must be positive"); // Business logic execution var validationResult = ValidateAccountBusinessRules(account); if (validationResult.Status != ResultStatus.Success) return Result.FromResult(validationResult); // Data persistence return _accountDAO.SaveAccount(account); } private Result ValidateAccountBusinessRules(Account account) { // Complex business rule validation logic if (account.CreditLimit < 0 && !account.IsSpecialCustomer) return Result.AsError("Negative credit limit only allowed for special customers"); return Result.AsSuccess(); } } // WebService BL Class - DTO Conversion + Business Logic public class AccountWebServiceBL : BaseBL { private readonly AccountBL _accountBL; public AccountWebServiceBL() { _accountBL = new AccountBL(); } public Result SaveAccount(AccountDTO accountDTO) { // Convert DTO to Entity var account = ConvertAccountDTOToAccount(accountDTO); // Execute business logic var result = _accountBL.SaveAccount(account); // Convert back to DTO return result.Status == ResultStatus.Success ? Result.AsSuccess(ObjectMapper.Map(result.Data)) : Result.FromResult(result); } } ``` ## Decision Logic and Rule Implementations ### **1. Rule Engine Pattern** #### **Business Rule Validation Chain** ```csharp public class BusinessRuleValidator { private readonly List> _rules = new(); public BusinessRuleValidator AddRule(IBusinessRule rule) { _rules.Add(rule); return this; } public Result ValidateAll(T entity) { foreach (var rule in _rules) { var result = rule.Validate(entity); if (result.Status != ResultStatus.Success) return result; } return Result.AsSuccess(); } } // Example Business Rule Implementation public class CustomerCreditLimitRule : IBusinessRule { public Result Validate(Account account) { if (account.CreditLimit > 100000 && account.PaymentTerms > 30) return Result.AsError("High credit limit requires payment terms <= 30 days"); return Result.AsSuccess(); } } // Usage in Business Logic public Result ProcessAccountApproval(Account account) { var validator = new BusinessRuleValidator() .AddRule(new CustomerCreditLimitRule()) .AddRule(new CustomerTypeValidationRule()) .AddRule(new RegionalComplianceRule()); var validationResult = validator.ValidateAll(account); if (validationResult.Status != ResultStatus.Success) return Result.FromResult(validationResult); return ApproveAccount(account); } ``` ### **2. Conditional Business Logic Pattern** #### **Strategy Pattern for Business Decisions** ```csharp public interface IPricingStrategy { Result CalculatePrice(PricingContext context); } public class StandardPricingStrategy : IPricingStrategy { public Result CalculatePrice(PricingContext context) { var basePrice = context.Article.BasePrice; var discount = CalculateStandardDiscount(context.Customer); return Result.AsSuccess(basePrice * (1 - discount)); } } public class VipPricingStrategy : IPricingStrategy { public Result CalculatePrice(PricingContext context) { var basePrice = context.Article.BasePrice; var vipDiscount = CalculateVipDiscount(context.Customer, context.Volume); return Result.AsSuccess(basePrice * (1 - vipDiscount)); } } // Business Logic Using Strategy public class PricingBL : BaseBL { public Result CalculateArticlePrice(int customerId, int articleId, int quantity) { var context = GetPricingContext(customerId, articleId, quantity); var strategy = GetPricingStrategy(context.Customer.CustomerType); return strategy.CalculatePrice(context); } private IPricingStrategy GetPricingStrategy(CustomerType customerType) { return customerType switch { CustomerType.Vip => new VipPricingStrategy(), CustomerType.Corporate => new CorporatePricingStrategy(), _ => new StandardPricingStrategy() }; } } ``` ## Business Workflow Patterns ### **1. Process Orchestration Pattern** #### **Receipt Processing Workflow** ```csharp public class ReceiptProcessingWorkflow { private readonly IReceiptLogic _receiptLogic; private readonly IInventoryLogic _inventoryLogic; private readonly IFinancialLogic _financialLogic; public async Task> ProcessReceipt(ReceiptDTO receipt) { // Step 1: Validate Receipt var validationResult = await ValidateReceiptBusinessRules(receipt); if (validationResult.Status != ResultStatus.Success) return Result.FromResult(validationResult); // Step 2: Reserve Inventory var reservationResult = await _inventoryLogic.ReserveInventory(receipt.Items); if (reservationResult.Status != ResultStatus.Success) { await CompensateReservation(reservationResult.Data); return Result.FromResult(reservationResult); } // Step 3: Process Payment var paymentResult = await _financialLogic.ProcessPayment(receipt.PaymentInfo); if (paymentResult.Status != ResultStatus.Success) { await CompensateInventory(reservationResult.Data); return Result.FromResult(paymentResult); } // Step 4: Finalize Receipt var finalResult = await _receiptLogic.FinalizeReceipt(receipt, paymentResult.Data); return Result.AsSuccess(new ProcessedReceipt { Receipt = finalResult.Data, ReservationId = reservationResult.Data.ReservationId, PaymentId = paymentResult.Data.PaymentId }); } private async Task ValidateReceiptBusinessRules(ReceiptDTO receipt) { // Business rule: Receipt total must match item totals var calculatedTotal = receipt.Items.Sum(i => i.Quantity * i.Price); if (Math.Abs(receipt.Total - calculatedTotal) > 0.01m) return Result.AsError("Receipt total does not match item totals"); // Business rule: Customer credit limit check var customerResult = await _receiptLogic.GetCustomer(receipt.CustomerId); if (customerResult.Status == ResultStatus.Success) { var creditCheck = await CheckCreditLimit(customerResult.Data, receipt.Total); if (creditCheck.Status != ResultStatus.Success) return creditCheck; } return Result.AsSuccess(); } } ``` ### **2. State Machine Implementation Pattern** #### **Helpdesk Ticket State Management** ```csharp public enum HelpdeskState { Created, Assigned, InProgress, WaitingForCustomer, Resolved, Closed, Cancelled } public class HelpdeskStateMachine { private static readonly Dictionary> _validTransitions = new() { { HelpdeskState.Created, new() { HelpdeskState.Assigned, HelpdeskState.Cancelled } }, { HelpdeskState.Assigned, new() { HelpdeskState.InProgress, HelpdeskState.Cancelled } }, { HelpdeskState.InProgress, new() { HelpdeskState.WaitingForCustomer, HelpdeskState.Resolved, HelpdeskState.Cancelled } }, { HelpdeskState.WaitingForCustomer, new() { HelpdeskState.InProgress, HelpdeskState.Cancelled } }, { HelpdeskState.Resolved, new() { HelpdeskState.Closed, HelpdeskState.InProgress } }, { HelpdeskState.Closed, new() { HelpdeskState.InProgress } }, // Reopen capability { HelpdeskState.Cancelled, new() { HelpdeskState.Created } } // Reinstate capability }; public Result TransitionState(HelpdeskState currentState, HelpdeskState targetState, HelpdeskTransitionContext context) { // Validate state transition if (!_validTransitions.ContainsKey(currentState) || !_validTransitions[currentState].Contains(targetState)) { return Result.AsError($"Invalid state transition from {currentState} to {targetState}"); } // Apply business rules for transition var businessRuleResult = ValidateTransitionBusinessRules(currentState, targetState, context); if (businessRuleResult.Status != ResultStatus.Success) return Result.FromResult(businessRuleResult); // Execute transition logic ExecuteTransitionLogic(currentState, targetState, context); return Result.AsSuccess(targetState); } private Result ValidateTransitionBusinessRules(HelpdeskState from, HelpdeskState to, HelpdeskTransitionContext context) { switch (to) { case HelpdeskState.Resolved: if (string.IsNullOrEmpty(context.ResolutionNotes)) return Result.AsError("Resolution notes are required when resolving a ticket"); break; case HelpdeskState.Assigned: if (context.AssignedToUserId <= 0) return Result.AsError("Must specify assigned user when assigning ticket"); break; case HelpdeskState.Closed: if (context.CustomerApproval == null || !context.CustomerApproval.Value) return Result.AsError("Customer approval required before closing ticket"); break; } return Result.AsSuccess(); } } // Usage in Business Logic public class HelpdeskBL : BaseBL { private readonly HelpdeskStateMachine _stateMachine = new(); public Result UpdateHelpdeskState(int helpdeskId, HelpdeskState targetState, HelpdeskTransitionContext context) { var helpdesk = GetHelpdesk(helpdeskId); if (helpdesk.Status != ResultStatus.Success) return Result.FromResult(helpdesk); var transitionResult = _stateMachine.TransitionState(helpdesk.Data.State, targetState, context); if (transitionResult.Status != ResultStatus.Success) return Result.FromResult(transitionResult); helpdesk.Data.State = transitionResult.Data; helpdesk.Data.LastModified = DateTime.UtcNow; return SaveHelpdesk(helpdesk.Data); } } ``` ## Business Calculation Patterns ### **1. Financial Calculation Engine** #### **Complex Pricing Calculation Pattern** ```csharp public class PricingCalculationEngine { public Result CalculateComplexPricing(PricingRequest request) { var calculator = new PricingCalculator() .WithBasePrice(request.Article.BasePrice) .WithQuantityDiscounts(request.QuantityBreaks) .WithCustomerDiscounts(request.Customer.DiscountTiers) .WithRegionalPricing(request.DeliveryRegion) .WithSeasonalAdjustments(request.CalculationDate) .WithVatCalculation(request.VatRate); return calculator.Calculate(); } } public class PricingCalculator { private decimal _basePrice; private List _quantityDiscounts = new(); private List _customerDiscounts = new(); private RegionalPricing _regionalPricing; private SeasonalAdjustment _seasonalAdjustment; private decimal _vatRate; public PricingCalculator WithBasePrice(decimal basePrice) { _basePrice = basePrice; return this; } public Result Calculate() { try { // Step 1: Apply quantity discounts var netPrice = ApplyQuantityDiscounts(_basePrice); // Step 2: Apply customer discounts netPrice = ApplyCustomerDiscounts(netPrice); // Step 3: Apply regional pricing adjustments netPrice = ApplyRegionalAdjustments(netPrice); // Step 4: Apply seasonal adjustments netPrice = ApplySeasonalAdjustments(netPrice); // Step 5: Calculate VAT var vatAmount = netPrice * _vatRate; var grossPrice = netPrice + vatAmount; return Result.AsSuccess(new PricingResult { BasePrice = _basePrice, NetPrice = netPrice, VatAmount = vatAmount, GrossPrice = grossPrice, DiscountApplied = _basePrice - netPrice, CalculationBreakdown = GetCalculationBreakdown() }); } catch (Exception ex) { return Result.AsError($"Pricing calculation failed: {ex.Message}"); } } } ``` ### **2. Inventory Management Business Rules** #### **Stock Level and Allocation Logic** ```csharp public class InventoryBL : BaseBL { public Result AllocateInventory(AllocationRequest request) { // Business Rule: Check available stock var stockResult = GetAvailableStock(request.ArticleId, request.WarehouseId); if (stockResult.Status != ResultStatus.Success) return Result.FromResult(stockResult); var availableStock = stockResult.Data; // Business Rule: Priority allocation logic var allocationStrategy = DetermineAllocationStrategy(request.Priority); var allocationResult = allocationStrategy.Allocate(availableStock, request); if (allocationResult.AllocatedQuantity < request.RequestedQuantity) { // Business Rule: Handle partial allocation var partialResult = HandlePartialAllocation(request, allocationResult); if (partialResult.Status != ResultStatus.Success) return Result.FromResult(partialResult); } // Business Rule: Update stock levels var updateResult = UpdateStockLevels(request.ArticleId, request.WarehouseId, allocationResult.AllocatedQuantity); if (updateResult.Status != ResultStatus.Success) { // Compensate allocation CompensateAllocation(allocationResult); return Result.FromResult(updateResult); } return Result.AsSuccess(allocationResult); } private IAllocationStrategy DetermineAllocationStrategy(AllocationPriority priority) { return priority switch { AllocationPriority.VipCustomer => new VipAllocationStrategy(), AllocationPriority.LargeOrder => new BulkAllocationStrategy(), AllocationPriority.Express => new ExpressAllocationStrategy(), _ => new StandardAllocationStrategy() }; } } ``` ## Process Orchestration Patterns ### **1. Saga Pattern for Distributed Transactions** #### **Order Processing Saga** ```csharp public class OrderProcessingSaga { private readonly List _steps = new(); private readonly List _executedSteps = new(); public OrderProcessingSaga() { _steps.Add(new ValidateOrderStep()); _steps.Add(new ReserveInventoryStep()); _steps.Add(new ProcessPaymentStep()); _steps.Add(new CreateShipmentStep()); _steps.Add(new SendConfirmationStep()); } public async Task> ExecuteAsync(OrderProcessingContext context) { foreach (var step in _steps) { try { var stepResult = await step.ExecuteAsync(context); if (stepResult.Status != ResultStatus.Success) { await CompensateAsync(); return Result.FromResult(stepResult); } _executedSteps.Add(step); context.AddStepResult(step.Name, stepResult.Data); } catch (Exception ex) { await CompensateAsync(); return Result.AsError($"Saga failed at step {step.Name}: {ex.Message}"); } } return Result.AsSuccess(new OrderProcessingResult { OrderId = context.OrderId, ProcessingSteps = _executedSteps.Select(s => s.Name).ToList(), CompletedAt = DateTime.UtcNow }); } private async Task CompensateAsync() { // Execute compensation in reverse order var stepsToCompensate = _executedSteps.AsEnumerable().Reverse(); foreach (var step in stepsToCompensate) { try { await step.CompensateAsync(); } catch (Exception ex) { // Log compensation failure but continue LogCompensationFailure(step.Name, ex); } } } } // Example Saga Step Implementation public class ProcessPaymentStep : ISagaStep { public string Name => "ProcessPayment"; public async Task ExecuteAsync(OrderProcessingContext context) { var paymentResult = await ProcessPayment(context.Order.PaymentInfo); if (paymentResult.Status == ResultStatus.Success) { context.PaymentId = paymentResult.Data.PaymentId; } return paymentResult; } public async Task CompensateAsync() { // Reverse payment if possible if (context.PaymentId.HasValue) { await RefundPayment(context.PaymentId.Value); } } } ``` ## Rule Engine and Policy Patterns ### **1. Configurable Business Rules** #### **Dynamic Rule Configuration** ```csharp public class ConfigurableBusinessRuleEngine { private readonly Dictionary _rules = new(); public void RegisterRule(string ruleId, IBusinessRule rule) { _rules[ruleId] = rule; } public Result ApplyRules(string context, T entity) where T : class { var applicableRules = GetApplicableRules(context); foreach (var rule in applicableRules) { var result = rule.Apply(entity); if (result.Status != ResultStatus.Success) return Result.FromResult(result); entity = (T)result.Data; } return Result.AsSuccess(entity); } private IEnumerable GetApplicableRules(string context) { return _rules.Values.Where(r => r.AppliesTo(context)); } } // Example: Customer Validation Rules public class CustomerCreditCheckRule : IBusinessRule { private readonly decimal _minimumCreditScore; public CustomerCreditCheckRule(decimal minimumCreditScore) { _minimumCreditScore = minimumCreditScore; } public bool AppliesTo(string context) => context == "CustomerApproval"; public Result Apply(Customer customer) { if (customer.CreditScore < _minimumCreditScore) { customer.ApprovalStatus = ApprovalStatus.RequiresManualReview; customer.ApprovalNotes = $"Credit score {customer.CreditScore} below minimum {_minimumCreditScore}"; } else { customer.ApprovalStatus = ApprovalStatus.AutoApproved; } return Result.AsSuccess(customer); } } ``` ## Business Rule Implementation Best Practices ### **1. Rule Separation and Organization** - **Single Responsibility**: Each rule class handles one specific business constraint - **Clear Naming**: Rule names clearly indicate their purpose and scope - **Context-Aware**: Rules know when and where they should be applied - **Composable**: Rules can be combined to create complex validation chains ### **2. Error Handling and Messaging** - **Descriptive Messages**: Business rule violations provide clear, actionable error messages - **Localization**: Error messages support multiple languages - **User-Friendly**: Technical errors are translated to business-friendly language - **Audit Trail**: All rule evaluations are logged for compliance and debugging ### **3. Performance Considerations** - **Rule Caching**: Expensive rule evaluations are cached when appropriate - **Lazy Evaluation**: Rules are evaluated only when necessary - **Batch Processing**: Multiple entities can be validated in batches - **Asynchronous Processing**: Long-running rule evaluations use async patterns ## Conclusion The Centron application implements comprehensive business logic patterns that ensure: - **Consistency**: All business rules are applied uniformly across the application - **Maintainability**: Business logic is separated from infrastructure concerns - **Testability**: Business rules can be tested in isolation - **Flexibility**: New rules can be added without modifying existing code - **Performance**: Rule evaluation is optimized for production use - **Compliance**: All business operations are properly validated and audited These patterns provide the foundation for reliable, scalable business logic that can evolve with changing business requirements while maintaining architectural integrity.