# Critical Issues Implementation Progress **Date:** 2025-10-29 **Status:** Phase 1 Complete | Phase 2 In Progress --- ## ✅ Phase 1 Complete: Type Flow Cleanup (C2, C7) ### C2: Consolidate Duplicate WHMCS Mapper Utilities ✅ **Files Created:** - ✅ `packages/domain/providers/whmcs/utils.ts` - Shared WHMCS utilities - ✅ `packages/domain/providers/whmcs/index.ts` - Module exports - ✅ `packages/domain/providers/index.ts` - Providers index **Files Modified:** - ✅ `packages/domain/billing/providers/whmcs/mapper.ts` - Now uses shared utils - ✅ `packages/domain/subscriptions/providers/whmcs/mapper.ts` - Now uses shared utils **Files Deleted:** - ✅ `packages/integrations/whmcs/src/utils/data-utils.ts` - Obsolete duplicate **Impact:** - Eliminated 50+ lines of duplicate code - Single source of truth for WHMCS data transformations - Consistent parsing across all mappers ### C7: Remove Redundant Frontend Validation ✅ **Files Modified:** - ✅ `apps/portal/src/features/catalog/services/catalog.service.ts` - Removed `parseInternetCatalog()`, `parseSimCatalog()`, `parseVpnCatalog()` calls - Now trusts BFF-validated responses - Added comments explaining BFF validation **Impact:** - Reduced redundant validation overhead - Faster catalog loading - Clear HTTP boundary validation responsibility **Lint Status:** All Phase 1 changes pass TypeScript compilation ✅ --- ## 🔄 Phase 2 In Progress: Business Logic Separation (C1, C6) ### C1: Remove Pricing Calculations from Frontend **Status:** Ready to implement **Required Changes:** #### 1. Create BFF Preview Pricing Service **File:** `apps/bff/src/modules/catalog/services/catalog-pricing.service.ts` (NEW) ```typescript import { Injectable, Inject } from "@nestjs/common"; import { Logger } from "nestjs-pino"; import { BaseCatalogService } from "./base-catalog.service"; import type { CatalogProductBase } from "@customer-portal/domain/catalog"; export interface PricingPreviewRequest { orderType: string; selections: string[]; addons?: string[]; } export interface PricingPreviewResponse { monthlyTotal: number; oneTimeTotal: number; items: Array<{ sku: string; name: string; monthlyPrice?: number; oneTimePrice?: number; }>; } @Injectable() export class CatalogPricingService { constructor( private readonly baseCatalog: BaseCatalogService, @Inject(Logger) private readonly logger: Logger ) {} async calculatePreviewPricing(request: PricingPreviewRequest): Promise { const allSkus = [...request.selections, ...(request.addons || [])]; this.logger.debug(`Calculating pricing preview for ${allSkus.length} items`, { orderType: request.orderType, skus: allSkus, }); // Fetch products from Salesforce const products = await this.baseCatalog.fetchProductsBySkus(allSkus); let monthlyTotal = 0; let oneTimeTotal = 0; const items = products.map(product => { const monthly = product.monthlyPrice || 0; const oneTime = product.oneTimePrice || 0; monthlyTotal += monthly; oneTimeTotal += oneTime; return { sku: product.sku, name: product.name, monthlyPrice: monthly > 0 ? monthly : undefined, oneTimePrice: oneTime > 0 ? oneTime : undefined, }; }); this.logger.log(`Pricing preview calculated`, { monthlyTotal, oneTimeTotal, itemCount: items.length, }); return { monthlyTotal, oneTimeTotal, items, }; } } ``` #### 2. Add Endpoint to Catalog Controller **File:** `apps/bff/src/modules/catalog/catalog.controller.ts` ```typescript // Add import import { CatalogPricingService } from "./services/catalog-pricing.service"; import { Body, Post } from "@nestjs/common"; // Add to constructor constructor( private internetCatalog: InternetCatalogService, private simCatalog: SimCatalogService, private vpnCatalog: VpnCatalogService, private pricingService: CatalogPricingService // ADD THIS ) {} // Add endpoint @Post("preview-pricing") @Throttle({ default: { limit: 30, ttl: 60 } }) // 30 requests per minute async previewPricing(@Body() body: { orderType: string; selections: string[]; addons?: string[]; }): Promise<{ monthlyTotal: number; oneTimeTotal: number; items: Array<{ sku: string; name: string; monthlyPrice?: number; oneTimePrice?: number; }>; }> { return this.pricingService.calculatePreviewPricing(body); } ``` #### 3. Register Service in Module **File:** `apps/bff/src/modules/catalog/catalog.module.ts` ```typescript import { CatalogPricingService } from "./services/catalog-pricing.service"; providers: [ BaseCatalogService, InternetCatalogService, SimCatalogService, VpnCatalogService, CatalogCacheService, CatalogPricingService, // ADD THIS ], ``` #### 4. Update Frontend Components **File:** `apps/portal/src/features/catalog/components/sim/SimConfigureView.tsx` Lines 52-64 - REPLACE calculation with API call: ```typescript // Remove local calculations - const monthlyTotal = (plan?.monthlyPrice ?? 0) + ...; - const oneTimeTotal = (plan?.oneTimePrice ?? 0) + ...; // Add API call using React Query const { data: pricingPreview, isLoading: pricingLoading } = useQuery({ queryKey: ['pricing-preview', 'SIM', plan?.sku, selectedAddons], queryFn: async () => { if (!plan) return null; const response = await apiClient.POST<{ monthlyTotal: number; oneTimeTotal: number; items: Array<{ sku: string; name: string; monthlyPrice?: number; oneTimePrice?: number; }>; }>('/api/catalog/preview-pricing', { body: { orderType: 'SIM', selections: [plan.sku], addons: selectedAddons, }, }); return response.data; }, enabled: !!plan, staleTime: 5 * 60 * 1000, // 5 minutes }); const monthlyTotal = pricingPreview?.monthlyTotal ?? 0; const oneTimeTotal = pricingPreview?.oneTimeTotal ?? 0; ``` **File:** `apps/portal/src/features/catalog/utils/catalog.utils.ts` Lines 51-56 - DELETE: ```typescript - export function calculateSavings(originalPrice: number, currentPrice: number): number { - if (originalPrice <= currentPrice) return 0; - return Math.round(((originalPrice - currentPrice) / originalPrice) * 100); - } ``` **File:** `apps/portal/src/features/orders/utils/order-presenters.ts` Lines 120-152 - DELETE: ```typescript - export function calculateOrderTotals(...) { ... } - export function normalizeBillingCycle(...) { ... } ``` Update components that use these deleted functions to use API values directly. ### C6: Remove Billing Cycle Normalization from Frontend **Status:** Ready to implement **Required Changes:** **File:** `apps/portal/src/features/subscriptions/components/SubscriptionCard.tsx` Lines 70-74 - DELETE: ```typescript - const getBillingCycleLabel = (cycle: string, name: string) => { - const looksLikeActivation = name.toLowerCase().includes("activation") || name.includes("setup"); - return looksLikeActivation ? "One-time" : cycle; - }; ``` Line 171 - UPDATE: ```typescript -

{getBillingCycleLabel(subscription.cycle)}

+

{subscription.cycle}

``` --- ## 📋 Phase 3 TODO: Structural Improvements (C3, C4, C5) ### C3: Remove Service Wrapper Anti-Pattern **Files to DELETE:** - `apps/portal/src/features/checkout/services/checkout.service.ts` - `apps/portal/src/features/account/services/account.service.ts` - `apps/portal/src/features/orders/services/orders.service.ts` - `apps/portal/src/features/subscriptions/services/sim-actions.service.ts` **Files to UPDATE:** - All hooks that use these services → Replace with direct apiClient calls ### C4: Extract Hardcoded Pricing to Salesforce **Prerequisite:** Create Salesforce Product: - **Product2:** - Name: "SIM Data Top-Up - 1GB Unit" - StockKeepingUnit: "SIM-TOPUP-1GB" - IsActive: true - Product2Categories1__c: "SIM" - Item_Class__c: "Add-on" - Billing_Cycle__c: "One-time" - Portal_Catalog__c: false - Portal_Accessible__c: true - Price__c: 500 - One_Time_Price__c: 500 **PricebookEntry:** - UnitPrice: 500 - IsActive: true - Link to standard pricebook **Files to CREATE:** - `apps/bff/src/modules/catalog/services/pricing.service.ts` **Files to UPDATE:** - `apps/bff/src/modules/subscriptions/sim-management/services/sim-topup.service.ts` - `apps/bff/src/modules/catalog/catalog.module.ts` - `apps/bff/src/modules/subscriptions/sim-management/sim-management.module.ts` ### C5: Consolidate Currency Formatting **Files to UPDATE:** - `packages/domain/toolkit/formatting/currency.ts` - Add caching - `apps/portal/src/features/dashboard/utils/dashboard.utils.ts` - Remove duplicate, use domain formatter --- ## 🧪 Testing Plan ### Phase 1 Tests ✅ - ✅ TypeScript compilation passes - ⏳ Unit tests for shared WHMCS utils - ⏳ Integration tests for mappers ### Phase 2 Tests - [ ] Pricing preview endpoint returns correct totals - [ ] Frontend displays API-calculated prices - [ ] Billing cycle normalization works consistently - [ ] Performance: Preview pricing response time < 500ms ### Phase 3 Tests - [ ] All hooks work with direct apiClient calls - [ ] SIM top-up pricing fetches from Salesforce - [ ] Currency formatting consistent across all views - [ ] No service wrapper imports remain --- ## 📊 Impact Summary ### Code Reduction: - **Phase 1:** ~60 lines of duplicate code removed - **Phase 2 (Projected):** ~80 lines of business logic removed from frontend - **Phase 3 (Projected):** ~200 lines of service wrappers removed - **Total:** ~340 lines removed, cleaner architecture ### Architecture Improvements: - Single source of truth for WHMCS data parsing ✅ - Clear HTTP boundary validation ✅ - Business logic centralized in BFF (in progress) - Clean Next.js pattern (hooks → API) (planned) - Dynamic pricing from Salesforce (planned) ### Performance: - Reduced redundant validation overhead ✅ - Cached pricing calculations (planned) - Faster catalog loading ✅ --- ## 🚀 Next Steps 1. **Continue Phase 2:** - Create `CatalogPricingService` - Add `/api/catalog/preview-pricing` endpoint - Update `SimConfigureView` to use API pricing - Delete frontend pricing utils - Remove billing cycle normalization 2. **Test Phase 2:** - Write unit tests for pricing service - Integration test the new endpoint - E2E test SIM configuration flow 3. **Begin Phase 3:** - Remove service wrappers - Create Salesforce pricing product - Implement dynamic pricing service - Consolidate currency formatting --- ## 📝 Notes - All Phase 1 changes are backward compatible - Phase 2 requires coordination with frontend team for UI updates - Phase 3 requires Salesforce admin access for product creation - Consider feature flags for gradual rollout **Last Updated:** 2025-10-29 **Next Review:** After Phase 2 completion