- Refactored the SignupWorkflowService to throw a DomainHttpException for legacy account conflicts, improving error handling. - Updated the SignupForm component to include initialEmail and showFooterLinks props, enhancing user experience during account creation. - Improved the AccountStep in the SignupForm to allow users to add optional details, such as date of birth and gender, for a more personalized signup process. - Enhanced the PasswordStep to include terms acceptance and marketing consent options, ensuring compliance and user engagement. - Updated various catalog views to improve layout and user guidance, streamlining the onboarding process for new users.
307 lines
9.5 KiB
TypeScript
307 lines
9.5 KiB
TypeScript
/**
|
||
* Opportunity Domain - Schemas
|
||
*
|
||
* Zod schemas for runtime validation of Opportunity data.
|
||
* Includes cancellation form validation with deadline logic.
|
||
*/
|
||
|
||
import { z } from "zod";
|
||
|
||
// ============================================================================
|
||
// Enum Value Arrays (for Zod schemas)
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Opportunity stage values - match existing Salesforce picklist
|
||
*/
|
||
const OPPORTUNITY_STAGE_VALUES = [
|
||
"Introduction",
|
||
"WIKI",
|
||
"Ready",
|
||
"Post Processing",
|
||
"Active",
|
||
"△Cancelling",
|
||
"〇Cancelled",
|
||
"Completed",
|
||
"Void",
|
||
"Pending",
|
||
] as const;
|
||
|
||
/**
|
||
* Application stage values
|
||
*/
|
||
const APPLICATION_STAGE_VALUES = ["INTRO-1", "N/A"] as const;
|
||
|
||
/**
|
||
* Cancellation notice values
|
||
*/
|
||
const CANCELLATION_NOTICE_VALUES = ["有", "未", "不要", "移転"] as const;
|
||
|
||
/**
|
||
* Line return status values
|
||
*/
|
||
const LINE_RETURN_STATUS_VALUES = [
|
||
"NotYet",
|
||
"SentKit",
|
||
"AS/Pickup予定",
|
||
"Returned1",
|
||
"Returned2",
|
||
"NTT派遣",
|
||
"Compensated",
|
||
"N/A",
|
||
] as const;
|
||
|
||
const OPPORTUNITY_PRODUCT_TYPE_VALUES = ["Internet", "SIM", "VPN"] as const;
|
||
|
||
/**
|
||
* Commodity type values - existing Salesforce picklist
|
||
*/
|
||
const COMMODITY_TYPE_VALUES = [
|
||
"Personal SonixNet Home Internet",
|
||
"Corporate SonixNet Home Internet",
|
||
"SIM",
|
||
"VPN",
|
||
"Onsite Support",
|
||
] as const;
|
||
|
||
const OPPORTUNITY_SOURCE_VALUES = [
|
||
"Portal - Internet Eligibility Request",
|
||
"Portal - SIM Checkout Registration",
|
||
"Portal - Order Placement",
|
||
"Agent Created",
|
||
] as const;
|
||
|
||
// ============================================================================
|
||
// Opportunity Record Schema
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Schema for Opportunity record returned from Salesforce
|
||
*/
|
||
export const opportunityRecordSchema = z.object({
|
||
id: z.string(),
|
||
name: z.string(),
|
||
accountId: z.string(),
|
||
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
|
||
closeDate: z.string(),
|
||
commodityType: z.enum(COMMODITY_TYPE_VALUES).optional(),
|
||
productType: z.enum(OPPORTUNITY_PRODUCT_TYPE_VALUES).optional(), // Derived from commodityType
|
||
source: z.enum(OPPORTUNITY_SOURCE_VALUES).optional(),
|
||
applicationStage: z.enum(APPLICATION_STAGE_VALUES).optional(),
|
||
isClosed: z.boolean(),
|
||
|
||
// Linked entities
|
||
// Note: Cases and Orders link TO Opportunity via their OpportunityId field
|
||
whmcsServiceId: z.number().int().optional(),
|
||
|
||
// Cancellation fields (updated by CS when processing cancellation Case)
|
||
cancellationNotice: z.enum(CANCELLATION_NOTICE_VALUES).optional(),
|
||
scheduledCancellationDate: z.string().optional(),
|
||
lineReturnStatus: z.enum(LINE_RETURN_STATUS_VALUES).optional(),
|
||
// NOTE: alternativeContactEmail and cancellationComments are on the Cancellation Case
|
||
|
||
// Metadata
|
||
createdDate: z.string(),
|
||
lastModifiedDate: z.string(),
|
||
});
|
||
|
||
export type OpportunityRecord = z.infer<typeof opportunityRecordSchema>;
|
||
|
||
// ============================================================================
|
||
// Create Opportunity Request Schema
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Schema for creating a new Opportunity
|
||
*
|
||
* Note: Opportunity Name is auto-generated by Salesforce workflow,
|
||
* so we don't need to provide account name.
|
||
*/
|
||
export const createOpportunityRequestSchema = z.object({
|
||
accountId: z.string().min(15, "Salesforce Account ID must be at least 15 characters"),
|
||
productType: z.enum(OPPORTUNITY_PRODUCT_TYPE_VALUES),
|
||
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
|
||
source: z.enum(OPPORTUNITY_SOURCE_VALUES),
|
||
applicationStage: z.enum(APPLICATION_STAGE_VALUES).optional(),
|
||
closeDate: z.string().optional(),
|
||
// Note: Create Case separately with Case.OpportunityId = returned Opportunity ID
|
||
});
|
||
|
||
export type CreateOpportunityRequest = z.infer<typeof createOpportunityRequestSchema>;
|
||
|
||
// ============================================================================
|
||
// Update Stage Request Schema
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Schema for updating Opportunity stage
|
||
*/
|
||
export const updateOpportunityStageRequestSchema = z.object({
|
||
opportunityId: z.string().min(15, "Salesforce Opportunity ID must be at least 15 characters"),
|
||
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
|
||
reason: z.string().optional(),
|
||
});
|
||
|
||
export type UpdateOpportunityStageRequest = z.infer<typeof updateOpportunityStageRequestSchema>;
|
||
|
||
// ============================================================================
|
||
// Cancellation Form Schema
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Regex for YYYY-MM format
|
||
*/
|
||
const CANCELLATION_MONTH_REGEX = /^\d{4}-(0[1-9]|1[0-2])$/;
|
||
|
||
/**
|
||
* Schema for cancellation form data from customer
|
||
*
|
||
* Validates:
|
||
* - cancellationMonth is in YYYY-MM format
|
||
* - Both confirmations are checked
|
||
* - Email is valid if provided
|
||
*/
|
||
export const cancellationFormDataSchema = z
|
||
.object({
|
||
cancellationMonth: z
|
||
.string()
|
||
.regex(CANCELLATION_MONTH_REGEX, "Cancellation month must be in YYYY-MM format"),
|
||
confirmTermsRead: z.boolean(),
|
||
confirmMonthEndCancellation: z.boolean(),
|
||
alternativeEmail: z.string().email().optional().or(z.literal("")),
|
||
comments: z.string().max(2000, "Comments must be 2000 characters or less").optional(),
|
||
})
|
||
.refine(data => data.confirmTermsRead === true, {
|
||
message: "You must confirm you have read the cancellation terms",
|
||
path: ["confirmTermsRead"],
|
||
})
|
||
.refine(data => data.confirmMonthEndCancellation === true, {
|
||
message: "You must confirm you understand cancellation is at month end",
|
||
path: ["confirmMonthEndCancellation"],
|
||
});
|
||
|
||
export type CancellationFormData = z.infer<typeof cancellationFormDataSchema>;
|
||
|
||
/**
|
||
* Schema for cancellation data to populate on Opportunity
|
||
*/
|
||
export const cancellationOpportunityDataSchema = z.object({
|
||
scheduledCancellationDate: z.string(),
|
||
cancellationNotice: z.enum(CANCELLATION_NOTICE_VALUES),
|
||
lineReturnStatus: z.enum(LINE_RETURN_STATUS_VALUES),
|
||
alternativeEmail: z.string().email().optional(),
|
||
comments: z.string().max(2000).optional(),
|
||
});
|
||
|
||
export type CancellationOpportunityData = z.infer<typeof cancellationOpportunityDataSchema>;
|
||
|
||
// ============================================================================
|
||
// Cancellation Eligibility Schema
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Schema for a cancellation month option
|
||
*/
|
||
export const cancellationMonthOptionSchema = z.object({
|
||
value: z.string(),
|
||
label: z.string(),
|
||
serviceEndDate: z.string(),
|
||
rentalReturnDeadline: z.string(),
|
||
isCurrentMonth: z.boolean(),
|
||
});
|
||
|
||
export type CancellationMonthOption = z.infer<typeof cancellationMonthOptionSchema>;
|
||
|
||
/**
|
||
* Schema for cancellation eligibility check result
|
||
*/
|
||
export const cancellationEligibilitySchema = z.object({
|
||
canCancel: z.boolean(),
|
||
earliestCancellationMonth: z.string(),
|
||
availableMonths: z.array(cancellationMonthOptionSchema),
|
||
currentMonthDeadline: z.string().nullable(),
|
||
reason: z.string().optional(),
|
||
});
|
||
|
||
export type CancellationEligibility = z.infer<typeof cancellationEligibilitySchema>;
|
||
|
||
// ============================================================================
|
||
// Cancellation Status Schema
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Schema for cancellation status display
|
||
*/
|
||
export const cancellationStatusSchema = z.object({
|
||
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
|
||
isPending: z.boolean(),
|
||
isComplete: z.boolean(),
|
||
scheduledEndDate: z.string().optional(),
|
||
rentalReturnStatus: z.enum(LINE_RETURN_STATUS_VALUES).optional(),
|
||
rentalReturnDeadline: z.string().optional(),
|
||
hasRentalEquipment: z.boolean(),
|
||
});
|
||
|
||
export type CancellationStatus = z.infer<typeof cancellationStatusSchema>;
|
||
|
||
// ============================================================================
|
||
// Opportunity Lookup Schema
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Schema for Opportunity lookup criteria
|
||
*/
|
||
export const opportunityLookupCriteriaSchema = z.object({
|
||
accountId: z.string().min(15),
|
||
productType: z.enum(OPPORTUNITY_PRODUCT_TYPE_VALUES),
|
||
allowedStages: z.array(z.enum(OPPORTUNITY_STAGE_VALUES)).optional(),
|
||
});
|
||
|
||
export type OpportunityLookupCriteria = z.infer<typeof opportunityLookupCriteriaSchema>;
|
||
|
||
// ============================================================================
|
||
// Opportunity Match Result Schema
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Schema for Opportunity matching result
|
||
*/
|
||
export const opportunityMatchResultSchema = z.object({
|
||
opportunityId: z.string(),
|
||
wasCreated: z.boolean(),
|
||
previousStage: z.enum(OPPORTUNITY_STAGE_VALUES).optional(),
|
||
});
|
||
|
||
export type OpportunityMatchResult = z.infer<typeof opportunityMatchResultSchema>;
|
||
|
||
// ============================================================================
|
||
// API Response Schemas
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Schema for Opportunity API response
|
||
*/
|
||
export const opportunityResponseSchema = z.object({
|
||
opportunity: opportunityRecordSchema,
|
||
});
|
||
|
||
/**
|
||
* Schema for create Opportunity response
|
||
*/
|
||
export const createOpportunityResponseSchema = z.object({
|
||
opportunityId: z.string(),
|
||
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
|
||
});
|
||
|
||
/**
|
||
* Schema for cancellation preview response
|
||
*/
|
||
export const cancellationPreviewResponseSchema = z.object({
|
||
eligibility: cancellationEligibilitySchema,
|
||
terms: z.object({
|
||
deadlineDay: z.number(),
|
||
rentalReturnDeadlineDay: z.number(),
|
||
fullMonthCharge: z.boolean(),
|
||
}),
|
||
});
|