2025-12-22 18:59:38 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 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
|
2026-01-19 15:14:39 +09:00
|
|
|
|
* NOTE: alternativeEmail and comments are captured in the Cancellation Case, not Opportunity
|
2025-12-22 18:59:38 +09:00
|
|
|
|
*/
|
|
|
|
|
|
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(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
});
|