256 lines
8.2 KiB
TypeScript
Raw Normal View History

/**
* Subscriptions Domain - Schemas
*
* Zod validation schemas for subscription domain types.
*/
import { z } from "zod";
// Subscription Status Schema
export const subscriptionStatusSchema = z.enum([
"Active",
"Inactive",
"Pending",
"Cancelled",
"Suspended",
"Terminated",
"Completed",
]);
// Subscription Cycle Schema
export const subscriptionCycleSchema = z.enum([
"Monthly",
"Quarterly",
"Semi-Annually",
"Annually",
"Biennially",
"Triennially",
"One-time",
"Free",
]);
// Subscription Schema
export const subscriptionSchema = z.object({
id: z.number().int().positive("Subscription id must be positive"),
serviceId: z.number().int().positive("Service id must be positive"),
productName: z.string().min(1, "Product name is required"),
domain: z.string().optional(),
cycle: subscriptionCycleSchema,
status: subscriptionStatusSchema,
nextDue: z.string().optional(),
amount: z.number(),
currency: z.string().min(1, "Currency is required"),
currencySymbol: z.string().optional(),
registrationDate: z.string().min(1, "Registration date is required"),
notes: z.string().optional(),
customFields: z.record(z.string(), z.string()).optional(),
orderNumber: z.string().optional(),
groupName: z.string().optional(),
paymentMethod: z.string().optional(),
serverName: z.string().optional(),
});
export const subscriptionArraySchema = z.array(subscriptionSchema);
// Subscription List Schema
export const subscriptionListSchema = z.object({
subscriptions: z.array(subscriptionSchema),
totalCount: z.number().int().nonnegative(),
});
// ============================================================================
// Route Param Schemas (BFF)
// ============================================================================
export const subscriptionIdParamSchema = z.object({
id: z.coerce.number().int().positive("Subscription id must be positive"),
});
export type SubscriptionIdParam = z.infer<typeof subscriptionIdParamSchema>;
// ============================================================================
// Query Parameter Schemas
// ============================================================================
/**
* Schema for subscription query parameters
*/
export const subscriptionQueryParamsSchema = z.object({
page: z.coerce.number().int().positive().optional(),
limit: z.coerce.number().int().positive().max(100).optional(),
status: subscriptionStatusSchema.optional(),
type: z.string().optional(),
});
export type SubscriptionQueryParams = z.infer<typeof subscriptionQueryParamsSchema>;
export const subscriptionQuerySchema = subscriptionQueryParamsSchema;
export type SubscriptionQuery = SubscriptionQueryParams;
// ============================================================================
// Response Schemas
// ============================================================================
/**
* Schema for subscription statistics
*/
export const subscriptionStatsSchema = z.object({
total: z.number().int().nonnegative(),
active: z.number().int().nonnegative(),
completed: z.number().int().nonnegative(),
cancelled: z.number().int().nonnegative(),
});
/**
* Schema for SIM action responses (top-up, cancellation, feature updates)
*/
export const simActionResponseSchema = z.object({
message: z.string(),
data: z.unknown().optional(),
});
/**
* Schema for SIM plan change result with IP addresses
*/
export const simPlanChangeResultSchema = z.object({
message: z.string(),
ipv4: z.string().optional(),
ipv6: z.string().optional(),
scheduledAt: z
.string()
.regex(/^\d{8}$/, "Scheduled date must be in YYYYMMDD format")
.optional(),
});
// ============================================================================
// Inferred Types from Schemas (Schema-First Approach)
// ============================================================================
export type SubscriptionStatus = z.infer<typeof subscriptionStatusSchema>;
export type SubscriptionCycle = z.infer<typeof subscriptionCycleSchema>;
export type Subscription = z.infer<typeof subscriptionSchema>;
export type SubscriptionArray = z.infer<typeof subscriptionArraySchema>;
export type SubscriptionList = z.infer<typeof subscriptionListSchema>;
export type SubscriptionStats = z.infer<typeof subscriptionStatsSchema>;
export type SimActionResponse = z.infer<typeof simActionResponseSchema>;
export type SimPlanChangeResult = z.infer<typeof simPlanChangeResultSchema>;
// ============================================================================
// Internet Cancellation Schemas
// ============================================================================
/**
* Available cancellation month for the customer
*/
export const internetCancellationMonthSchema = z.object({
value: z.string(), // YYYY-MM format
label: z.string(), // Display label like "November 2025"
});
/**
* Internet cancellation preview response (service details + available months)
*/
export const internetCancellationPreviewSchema = z.object({
productName: z.string(),
billingAmount: z.number(),
nextDueDate: z.string().optional(),
registrationDate: z.string().optional(),
availableMonths: z.array(internetCancellationMonthSchema),
customerEmail: z.string(),
customerName: z.string(),
});
/**
* Internet cancellation request from customer
*/
export const internetCancelRequestSchema = z.object({
cancellationMonth: z
.string()
.regex(/^\d{4}-\d{2}$/, "Cancellation month must be in YYYY-MM format"),
confirmRead: z.boolean(),
confirmCancel: z.boolean(),
comments: z.string().max(1000).optional(),
});
export type InternetCancellationMonth = z.infer<typeof internetCancellationMonthSchema>;
export type InternetCancellationPreview = z.infer<typeof internetCancellationPreviewSchema>;
export type InternetCancelRequest = z.infer<typeof internetCancelRequestSchema>;
// ============================================================================
// Unified Cancellation Preview (SIM + Internet)
// ============================================================================
/**
* Service type for cancellation flows
*/
export const serviceTypeSchema = z.enum(["sim", "internet"]);
export type ServiceType = z.infer<typeof serviceTypeSchema>;
/**
* Structured notice/term content for cancellation pages
*/
export const cancellationNoticeSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
});
export type CancellationNotice = z.infer<typeof cancellationNoticeSchema>;
/**
* Cancellation status derived from Salesforce Opportunity (if available)
*
* Notes:
* - We only need a small subset for portal display.
* - This is nullable because some services may not have a linked Opportunity.
*/
export const cancellationStatusSchema = z
.object({
stage: z.enum(["Active", "△Cancelling", "Cancelled"]),
scheduledEndDate: z.string().optional(),
// Internet only
rentalReturnStatus: z.string().optional(),
})
.nullable();
export type CancellationStatus = z.infer<typeof cancellationStatusSchema>;
/**
* Unified cancellation preview response used by the generic cancellation page.
*
* Includes:
* - Service type + display fields
* - Terms and notices (service-type specific)
* - Cancellation status (derived from Opportunity when WHMCS isn't already cancelled)
*/
export const cancellationPreviewSchema = z.object({
serviceType: serviceTypeSchema,
serviceName: z.string().min(1),
/**
* Salesforce Opportunity ID read from WHMCS service custom fields (already stored in WHMCS).
* Optional because not all services are guaranteed to have it.
*/
opportunityId: z.string().min(15).max(18).optional(),
serviceInfo: z.array(
z.object({
label: z.string().min(1),
value: z.string().min(1),
mono: z.boolean().optional(),
})
),
terms: z.array(cancellationNoticeSchema),
warnings: z.array(cancellationNoticeSchema).default([]),
step3Notices: z.array(cancellationNoticeSchema).default([]),
cancellationStatus: cancellationStatusSchema,
availableMonths: z.array(internetCancellationMonthSchema),
customerEmail: z.string(),
customerName: z.string().min(1),
// SIM-specific (optional)
isWithinMinimumTerm: z.boolean().optional(),
minimumContractEndDate: z.string().optional(),
});
export type CancellationPreview = z.infer<typeof cancellationPreviewSchema>;