# Domain Type System

This document describes the unified type system for the Customer Portal, focusing on how types map to Salesforce and WHMCS data structures.

---

## 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 have a unified type system that directly maps to the Salesforce/WHMCS object structure.

---

## Product Types

### Base Product Structure

```typescript
// 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;
  unitPrice?: number; // PricebookEntry.UnitPrice
  monthlyPrice?: number; // UnitPrice if billingCycle === "Monthly"
  oneTimePrice?: number; // UnitPrice if billingCycle === "Onetime"
}
```

### 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;
```

---

## Order Types

### 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
}
```

### 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;
```

---

## Transformation Functions

### Salesforce to Domain

```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,
  };
}
```

### Type Guards

```typescript
// Type-safe product checking
function isInternetProduct(product: Product): product is InternetProduct {
  return product.category === "Internet";
}

function isSimProduct(product: Product): product is SimProduct {
  return product.category === "SIM";
}

function isVpnProduct(product: Product): product is VpnProduct {
  return product.category === "VPN";
}

// Business logic helpers
function isCatalogVisible(product: Product): boolean {
  return product.portalCatalog && product.portalAccessible;
}

function isServiceProduct(product: Product): boolean {
  return product.itemClass === "Service";
}

function isAddonProduct(product: Product): boolean {
  return product.itemClass === "Add-on";
}
```

---

## Usage Examples

### Catalog Context

```typescript
const product = fromSalesforceProduct2(salesforceProduct, pricebookEntry);

if (isCatalogVisible(product)) {
  displayInServices(product);
}

// Type-safe access to specific fields
if (isInternetProduct(product)) {
  console.log(product.internetPlanTier); // TypeScript knows this exists
}
```

### Order Context

```typescript
const orderItem: OrderItemRequest = {
  ...product,
  quantity: 1,
  autoAdded: false,
};

// Access unified pricing fields
const price =
  item.billingCycle === "Monthly"
    ? item.monthlyPrice || item.unitPrice || 0
    : item.oneTimePrice || item.unitPrice || 0;
```

---

## Benefits

1. **Single Source of Truth**: All types map directly to Salesforce object structure
2. **No Duplication**: Same type used in catalog, orders, and business logic
3. **Type Safety**: Proper TypeScript discrimination based on `category` field
4. **Maintainability**: Changes to Salesforce fields only need updates in one place
5. **Consistency**: Same field names and structure across all contexts

---

## Backward Compatibility

Legacy type aliases are maintained for existing code:

```typescript
export type InternetPlan = InternetProduct;
export type CatalogItem = Product;
```

This allows gradual migration while new code uses unified types.

---

## Implementation Files

- **Note**: The domain package uses a provider-aware structure. The older `packages/domain/src/entities/*` paths referenced by earlier drafts are no longer accurate.

Current home for these concerns (examples):

- `packages/domain/services/contract.ts`: catalog/service product contracts
- `packages/domain/services/schema.ts`: Zod schemas for service catalog types
- `packages/domain/services/providers/salesforce/raw.types.ts`: Salesforce raw response shapes
- `packages/domain/services/providers/salesforce/mapper.ts`: Salesforce → domain transformations

For the overall structure and import rules, see:

- `docs/architecture/domain-layer.md`
- `docs/development/domain/structure.md`
