Assist_Design/CRITICAL_ISSUES_IMPLEMENTATION_PROGRESS.md
barsa 0a3d5b1e3c Refactor WHMCS service methods and improve type handling
- Updated WHMCS service methods to return normalized product types, enhancing type consistency across services.
- Refactored product retrieval logic in WhmcsPaymentService and WhmcsService to streamline data handling.
- Removed deprecated utility functions and optimized custom field handling in WHMCS-related services.
- Enhanced error handling in subscription processing to improve reliability and clarity.
- Cleaned up imports and improved overall code organization for better maintainability.
2025-10-29 18:59:17 +09:00

11 KiB

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)

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<PricingPreviewResponse> {
    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

// 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

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:

// 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:

- 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:

- 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:

- const getBillingCycleLabel = (cycle: string, name: string) => {
-   const looksLikeActivation = name.toLowerCase().includes("activation") || name.includes("setup");
-   return looksLikeActivation ? "One-time" : cycle;
- };

Line 171 - UPDATE:

- <p className="text-gray-500">{getBillingCycleLabel(subscription.cycle)}</p>
+ <p className="text-gray-500">{subscription.cycle}</p>

📋 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