/** * Services Domain - Schemas * * Zod schemas for runtime validation of services product data. */ import { z } from "zod"; import { addressSchema } from "../customer/index.js"; // ============================================================================ // Base Catalog Product Schema // ============================================================================ export const catalogProductBaseSchema = z.object({ id: z.string(), sku: z.string(), name: z.string(), description: z.string().optional(), displayOrder: z.number().optional(), billingCycle: z.string().optional(), monthlyPrice: z.number().optional(), oneTimePrice: z.number().optional(), unitPrice: z.number().optional(), catalogMetadata: z.record(z.string(), z.unknown()).optional(), }); // ============================================================================ // PricebookEntry Schema // ============================================================================ export const catalogPricebookEntrySchema = z.object({ id: z.string().optional(), name: z.string().optional(), unitPrice: z.number().optional(), pricebook2Id: z.string().optional(), product2Id: z.string().optional(), isActive: z.boolean().optional(), }); // ============================================================================ // Internet Product Schemas // ============================================================================ export const internetCatalogProductSchema = catalogProductBaseSchema.extend({ internetPlanTier: z.string().optional(), internetOfferingType: z.string().optional(), features: z.array(z.string()).optional(), }); export const internetPlanTemplateSchema = z.object({ tierDescription: z.string(), description: z.string().optional(), features: z.array(z.string()).optional(), }); export const internetPlanCatalogItemSchema = internetCatalogProductSchema.extend({ catalogMetadata: z .object({ tierDescription: z.string().optional(), features: z.array(z.string()).optional(), isRecommended: z.boolean().optional(), }) .optional(), }); export const internetInstallationCatalogItemSchema = internetCatalogProductSchema.extend({ catalogMetadata: z .object({ installationTerm: z.enum(["One-time", "12-Month", "24-Month"]), }) .optional(), }); export const internetAddonCatalogItemSchema = internetCatalogProductSchema.extend({ isBundledAddon: z.boolean().optional(), bundledAddonId: z.string().optional(), }); export const internetCatalogCollectionSchema = z.object({ plans: z.array(internetPlanCatalogItemSchema), installations: z.array(internetInstallationCatalogItemSchema), addons: z.array(internetAddonCatalogItemSchema), }); export const internetCatalogResponseSchema = internetCatalogCollectionSchema; // ============================================================================ // Internet Eligibility Schemas // ============================================================================ /** * Portal-facing internet eligibility status. * * NOTE: This is intentionally a small, stable enum used across BFF + Portal. * The raw Salesforce field value is returned separately as `eligibility`. */ export const internetEligibilityStatusSchema = z.enum([ "not_requested", "pending", "eligible", "ineligible", ]); export const internetEligibilityDetailsSchema = z.object({ status: internetEligibilityStatusSchema, /** Raw Salesforce value from Account.Internet_Eligibility__c (if present) */ eligibility: z.string().nullable(), /** Salesforce Case Id (eligibility request) */ requestId: z.string().nullable(), requestedAt: z.string().datetime().nullable(), checkedAt: z.string().datetime().nullable(), notes: z.string().nullable(), }); /** * Body for POST /services/internet/eligibility-request (BFF). * Lives in domain so Portal + BFF stay aligned. */ export const internetEligibilityRequestSchema = z.object({ notes: z.string().trim().max(2000).optional(), address: addressSchema.partial().optional(), }); export const internetEligibilityRequestResponseSchema = z.object({ requestId: z.string(), }); // ============================================================================ // SIM Product Schemas // ============================================================================ export const simCatalogProductSchema = catalogProductBaseSchema.extend({ simDataSize: z.string().optional(), simPlanType: z.string().optional(), simHasFamilyDiscount: z.boolean().optional(), isBundledAddon: z.boolean().optional(), bundledAddonId: z.string().optional(), }); export const simActivationFeeCatalogItemSchema = simCatalogProductSchema.extend({ catalogMetadata: z .object({ isDefault: z.boolean().optional(), }) .optional(), }); export const simCatalogCollectionSchema = z.object({ plans: z.array(simCatalogProductSchema), activationFees: z.array(simActivationFeeCatalogItemSchema), addons: z.array(simCatalogProductSchema), }); export const simCatalogResponseSchema = simCatalogCollectionSchema; // ============================================================================ // VPN Product Schema // ============================================================================ export const vpnCatalogProductSchema = catalogProductBaseSchema.extend({ vpnRegion: z.string().optional(), }); export const vpnCatalogCollectionSchema = z.object({ plans: z.array(vpnCatalogProductSchema), activationFees: z.array(vpnCatalogProductSchema), }); export const vpnCatalogResponseSchema = vpnCatalogCollectionSchema; // ============================================================================ // Catalog Filter Schema // ============================================================================ /** * Schema for catalog filtering options */ export const catalogFilterSchema = z.object({ category: z.string().optional(), priceMin: z.number().optional(), priceMax: z.number().optional(), search: z.string().optional(), }); // ============================================================================ // Inferred Types from Schemas (Schema-First Approach) // ============================================================================ export type CatalogProductBase = z.infer; export type CatalogPricebookEntry = z.infer; // Internet products export type InternetCatalogProduct = z.infer; export type InternetPlanTemplate = z.infer; export type InternetPlanCatalogItem = z.infer; export type InternetInstallationCatalogItem = z.infer; export type InternetAddonCatalogItem = z.infer; export type InternetCatalogCollection = z.infer; export type InternetEligibilityStatus = z.infer; export type InternetEligibilityDetails = z.infer; export type InternetEligibilityRequest = z.infer; export type InternetEligibilityRequestResponse = z.infer< typeof internetEligibilityRequestResponseSchema >; // SIM products export type SimCatalogProduct = z.infer; export type SimActivationFeeCatalogItem = z.infer; export type SimCatalogCollection = z.infer; // VPN products export type VpnCatalogProduct = z.infer; export type VpnCatalogCollection = z.infer; // Union type for all catalog products export type CatalogProduct = | InternetPlanCatalogItem | InternetInstallationCatalogItem | InternetAddonCatalogItem | SimCatalogProduct | SimActivationFeeCatalogItem | VpnCatalogProduct | CatalogProductBase;