- Introduced Zod DTOs for request validation across multiple controllers, replacing inline validation with structured classes for improved maintainability and clarity. - Updated ESLint configuration to enforce a rule against importing Zod directly in BFF controllers, promoting the use of shared domain schemas for request validation. - Removed the SecureErrorMapperService to streamline the security module, as its functionality was deemed unnecessary. - Enhanced various controllers to utilize the new DTOs, ensuring consistent validation and response handling across the application.
217 lines
7.6 KiB
TypeScript
217 lines
7.6 KiB
TypeScript
/**
|
|
* 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(),
|
|
});
|
|
|
|
// ============================================================================
|
|
// 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<typeof catalogProductBaseSchema>;
|
|
export type CatalogPricebookEntry = z.infer<typeof catalogPricebookEntrySchema>;
|
|
|
|
// Internet products
|
|
export type InternetCatalogProduct = z.infer<typeof internetCatalogProductSchema>;
|
|
export type InternetPlanTemplate = z.infer<typeof internetPlanTemplateSchema>;
|
|
export type InternetPlanCatalogItem = z.infer<typeof internetPlanCatalogItemSchema>;
|
|
export type InternetInstallationCatalogItem = z.infer<typeof internetInstallationCatalogItemSchema>;
|
|
export type InternetAddonCatalogItem = z.infer<typeof internetAddonCatalogItemSchema>;
|
|
export type InternetCatalogCollection = z.infer<typeof internetCatalogCollectionSchema>;
|
|
export type InternetEligibilityStatus = z.infer<typeof internetEligibilityStatusSchema>;
|
|
export type InternetEligibilityDetails = z.infer<typeof internetEligibilityDetailsSchema>;
|
|
export type InternetEligibilityRequest = z.infer<typeof internetEligibilityRequestSchema>;
|
|
|
|
// SIM products
|
|
export type SimCatalogProduct = z.infer<typeof simCatalogProductSchema>;
|
|
export type SimActivationFeeCatalogItem = z.infer<typeof simActivationFeeCatalogItemSchema>;
|
|
export type SimCatalogCollection = z.infer<typeof simCatalogCollectionSchema>;
|
|
|
|
// VPN products
|
|
export type VpnCatalogProduct = z.infer<typeof vpnCatalogProductSchema>;
|
|
export type VpnCatalogCollection = z.infer<typeof vpnCatalogCollectionSchema>;
|
|
|
|
// Union type for all catalog products
|
|
export type CatalogProduct =
|
|
| InternetPlanCatalogItem
|
|
| InternetInstallationCatalogItem
|
|
| InternetAddonCatalogItem
|
|
| SimCatalogProduct
|
|
| SimActivationFeeCatalogItem
|
|
| VpnCatalogProduct
|
|
| CatalogProductBase;
|