barsa b19c213931 Implement Unified Cancellation Features for SIM and Internet Services
- Added new methods in SalesforceOpportunityService to retrieve cancellation statuses for both Internet and SIM services by Opportunity ID, enhancing cancellation handling.
- Updated BFF module to include a new CancellationModule, improving service organization and modularity.
- Refactored portal routes and components to unify cancellation navigation, streamlining user experience.
- Introduced new domain schemas for unified cancellation previews, ensuring consistent data structure and validation across services.
- Removed deprecated cancellation components from the portal, promoting cleaner code and improved maintainability.
2026-01-05 17:09:10 +09:00

256 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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>;