barsa 0a5a33da98 Refactor conditional statements for improved readability and consistency
- Updated various components to use parentheses in conditional statements for clarity.
- Refactored `renderSubCardHeader` to use an options object for better parameter handling.
- Enhanced error handling messages across multiple components to provide clearer feedback.
- Adjusted query string handling in routing to improve readability.
- Made minor adjustments to ensure consistent formatting and style across the codebase.
2026-01-19 15:14:39 +09:00

306 lines
9.5 KiB
TypeScript
Raw Permalink 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.

/**
* 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
* NOTE: alternativeEmail and comments are captured in the Cancellation Case, not Opportunity
*/
export const cancellationOpportunityDataSchema = z.object({
scheduledCancellationDate: z.string(),
cancellationNotice: z.enum(CANCELLATION_NOTICE_VALUES),
lineReturnStatus: z.enum(LINE_RETURN_STATUS_VALUES),
});
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(),
}),
});