# Consolidated Type System - Complete Solution ## Problem Analysis You identified a critical issue: **order items, catalog items, and product types were all trying to encapsulate the same Salesforce Product2 data**, leading to: 1. **Type duplication** across contexts 2. **Inconsistent pricing models** (sometimes `price`, sometimes `monthlyPrice`/`oneTimePrice`) 3. **Enhanced Order Summary** creating its own conflicting `OrderItem` interface 4. **Missing PricebookEntry representation** in the type system ## Solution: Unified Type Architecture ### Core Principle **One Salesforce Object = One TypeScript Type Structure** Since both catalog items and order items ultimately represent Salesforce `Product2` objects (with `PricebookEntry` for pricing), we created a unified type system that directly maps to the Salesforce/WHMCS object structure. ## New Type Hierarchy ### 1. Base Product Structure ```typescript // packages/domain/src/entities/product.ts // Maps directly to Salesforce Product2 fields interface BaseProduct { // Standard Salesforce fields id: string; // Product2.Id name: string; // Product2.Name sku: string; // Product2.StockKeepingUnit // Custom portal fields category: ProductCategory; // Product2Categories1__c itemClass: ItemClass; // Item_Class__c billingCycle: BillingCycle; // Billing_Cycle__c portalCatalog: boolean; // Portal_Catalog__c portalAccessible: boolean; // Portal_Accessible__c // WHMCS integration whmcsProductId: number; // WH_Product_ID__c whmcsProductName: string; // WH_Product_Name__c } // Represents Salesforce PricebookEntry structure interface PricebookEntry { id: string; // PricebookEntry.Id name: string; // PricebookEntry.Name unitPrice: number; // PricebookEntry.UnitPrice pricebook2Id: string; // PricebookEntry.Pricebook2Id product2Id: string; // PricebookEntry.Product2Id isActive: boolean; // PricebookEntry.IsActive } // Product with proper pricing structure interface ProductWithPricing extends BaseProduct { pricebookEntry?: PricebookEntry; // Convenience fields derived from pricebookEntry and billingCycle unitPrice?: number; // PricebookEntry.UnitPrice monthlyPrice?: number; // UnitPrice if billingCycle === "Monthly" oneTimePrice?: number; // UnitPrice if billingCycle === "Onetime" } ``` ### 2. Specialized Product Types ```typescript // Category-specific extensions interface InternetProduct extends ProductWithPricing { category: "Internet"; internetPlanTier?: "Silver" | "Gold" | "Platinum"; internetOfferingType?: string; internetMonthlyPrice?: number; } interface SimProduct extends ProductWithPricing { category: "SIM"; simDataSize?: string; simPlanType?: "DataOnly" | "DataSmsVoice" | "VoiceOnly"; simHasFamilyDiscount?: boolean; } // Union type for all products type Product = InternetProduct | SimProduct | VpnProduct | ProductWithPricing; ``` ### 3. Order Item Types ```typescript // For new orders (before Salesforce creation) interface OrderItemRequest extends ProductWithPricing { quantity: number; autoAdded?: boolean; } // Actual Salesforce OrderItem structure interface SalesforceOrderItem { id: string; // OrderItem.Id orderId: string; // OrderItem.OrderId quantity: number; // OrderItem.Quantity unitPrice: number; // OrderItem.UnitPrice totalPrice: number; // OrderItem.TotalPrice pricebookEntry: PricebookEntry & { product2: Product; // Full product data }; whmcsServiceId?: string; // WHMCS_Service_ID__c billingCycle?: BillingCycle; // Derived from Product2 } ``` ### 4. Order Types ```typescript // Salesforce Order structure interface SalesforceOrder { id: string; // Order.Id orderNumber: string; // Order.OrderNumber status: string; // Order.Status type: string; // Order.Type effectiveDate: string; // Order.EffectiveDate totalAmount: number; // Order.TotalAmount accountId: string; // Order.AccountId pricebook2Id: string; // Order.Pricebook2Id // Custom fields orderType?: string; // Order_Type__c activationStatus?: string; // Activation_Status__c whmcsOrderId?: string; // WHMCS_Order_ID__c orderItems?: SalesforceOrderItem[]; } // WHMCS Order structure (separate for existing orders) interface WhmcsOrder extends WhmcsEntity { orderNumber: string; status: OrderStatus; items: WhmcsOrderItem[]; } // Union types for different contexts type Order = WhmcsOrder | SalesforceOrder; type OrderItem = WhmcsOrderItem | OrderItemRequest | SalesforceOrderItem; ``` ## Key Benefits ### 1. **Single Source of Truth** - All types map directly to Salesforce object structure - No more guessing which type to use in which context - Consistent field names across the application ### 2. **Proper PricebookEntry Representation** - Pricing is now properly modeled as PricebookEntry structure - Convenience fields (`monthlyPrice`, `oneTimePrice`) derived from `unitPrice` and `billingCycle` - No more confusion between `price` vs `monthlyPrice` vs `unitPrice` ### 3. **Type Safety** - TypeScript discrimination based on `category` field - Proper type guards for business logic - Compile-time validation of field access ### 4. **Maintainability** - Changes to Salesforce fields only need updates in one place - Clear transformation functions between Salesforce API and TypeScript types - Backward compatibility through type aliases ## Transformation Functions ```typescript // Transform Salesforce Product2 + PricebookEntry to unified Product function fromSalesforceProduct2(sfProduct: any, pricebookEntry?: any): Product { const billingCycle = sfProduct.Billing_Cycle__c || "Onetime"; const unitPrice = pricebookEntry?.UnitPrice; return { id: sfProduct.Id, name: sfProduct.Name, sku: sfProduct.StockKeepingUnit, category: sfProduct.Product2Categories1__c, itemClass: sfProduct.Item_Class__c, billingCycle: billingCycle, portalCatalog: sfProduct.Portal_Catalog__c, portalAccessible: sfProduct.Portal_Accessible__c, whmcsProductId: sfProduct.WH_Product_ID__c, whmcsProductName: sfProduct.WH_Product_Name__c, // PricebookEntry structure pricebookEntry: pricebookEntry ? { id: pricebookEntry.Id, name: pricebookEntry.Name, unitPrice: pricebookEntry.UnitPrice, pricebook2Id: pricebookEntry.Pricebook2Id, product2Id: sfProduct.Id, isActive: pricebookEntry.IsActive !== false, } : undefined, // Convenience pricing fields unitPrice: unitPrice, monthlyPrice: billingCycle === "Monthly" ? unitPrice : undefined, oneTimePrice: billingCycle === "Onetime" ? unitPrice : undefined, // Include all other fields for dynamic access ...sfProduct, }; } // Transform Salesforce OrderItem to unified structure function fromSalesforceOrderItem(sfOrderItem: any): SalesforceOrderItem { const product = fromSalesforceProduct2( sfOrderItem.PricebookEntry?.Product2, sfOrderItem.PricebookEntry ); return { id: sfOrderItem.Id, orderId: sfOrderItem.OrderId, quantity: sfOrderItem.Quantity, unitPrice: sfOrderItem.UnitPrice, totalPrice: sfOrderItem.TotalPrice, pricebookEntry: { ...product.pricebookEntry!, product2: product, }, whmcsServiceId: sfOrderItem.WHMCS_Service_ID__c, billingCycle: product.billingCycle, ...sfOrderItem, }; } ``` ## Usage Examples ### Catalog Context ```typescript const product = fromSalesforceProduct2(salesforceProduct, pricebookEntry); if (isCatalogVisible(product)) { displayInCatalog(product); } ``` ### Order Context ```typescript const orderItem: OrderItemRequest = { ...product, quantity: 1, autoAdded: false, }; ``` ### Enhanced Order Summary ```typescript // Now uses consistent unified types interface OrderItem extends OrderItemRequest { id?: string; description?: string; } // Access unified pricing fields const price = item.billingCycle === "Monthly" ? item.monthlyPrice || item.unitPrice || 0 : item.oneTimePrice || item.unitPrice || 0; ``` ## Migration Strategy 1. **Backward Compatibility**: Legacy type aliases maintained ```typescript export type InternetPlan = InternetProduct; export type CatalogItem = Product; ``` 2. **Gradual Adoption**: Existing code continues to work while new code uses unified types 3. **Clear Documentation**: This document explains the new structure and migration path ## Files Modified - `packages/domain/src/entities/product.ts` - New unified product types - `packages/domain/src/entities/order.ts` - Updated order types - `packages/domain/src/entities/catalog.ts` - Re-exports with legacy aliases - `apps/portal/src/features/catalog/components/base/EnhancedOrderSummary.tsx` - Updated to use unified types ## Enhanced Order Summary The **Enhanced Order Summary** was a UI component that created its own `OrderItem` interface, leading to type conflicts. It has been updated to: 1. **Extend unified types**: Now extends `OrderItemRequest` instead of creating conflicting interfaces 2. **Use consistent pricing**: Uses `monthlyPrice`/`oneTimePrice`/`unitPrice` from unified structure 3. **Proper field access**: Uses `itemClass`, `billingCycle` from unified product structure This eliminates the confusion about "which OrderItem type should I use?" because there's now a clear hierarchy: - `OrderItemRequest` - for new orders in the portal - `SalesforceOrderItem` - for existing orders from Salesforce - `WhmcsOrderItem` - for existing orders from WHMCS The Enhanced Order Summary now properly represents the unified product structure while adding UI-specific fields like `description` and optional `id`. ## Conclusion This consolidated type system eliminates the original problem of multiple types trying to represent the same Salesforce data. Now there's **one unified type system** that properly maps to your Salesforce/WHMCS object structure, with clear transformation functions and proper PricebookEntry representation. The solution maintains backward compatibility while providing a clear path forward for consistent type usage across your entire application.