740 lines
21 KiB
TypeScript
740 lines
21 KiB
TypeScript
|
|
/**
|
|||
|
|
* Opportunity Domain - Contract
|
|||
|
|
*
|
|||
|
|
* Business types and constants for Salesforce Opportunity lifecycle management.
|
|||
|
|
* Used to track customer journeys from interest through service cancellation.
|
|||
|
|
*
|
|||
|
|
* IMPORTANT: Stage values match existing Salesforce picklist values.
|
|||
|
|
* See docs/salesforce/OPPORTUNITY-LIFECYCLE-GUIDE.md for complete documentation.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Opportunity Stage Constants (Existing Salesforce Values)
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Opportunity lifecycle stages - these match existing Salesforce picklist values
|
|||
|
|
*
|
|||
|
|
* Portal Flow: Introduction -> Ready -> Post Processing -> Active -> △Cancelling -> 〇Cancelled
|
|||
|
|
*/
|
|||
|
|
export const OPPORTUNITY_STAGE = {
|
|||
|
|
// Interest/Lead stages
|
|||
|
|
INTRODUCTION: "Introduction", // Initial interest / eligibility pending (30%)
|
|||
|
|
WIKI: "WIKI", // Low priority / informational (10%)
|
|||
|
|
|
|||
|
|
// Ready to proceed
|
|||
|
|
READY: "Ready", // Eligible / ready to order (60%)
|
|||
|
|
|
|||
|
|
// Order/Processing stages
|
|||
|
|
POST_PROCESSING: "Post Processing", // Order placed, processing (75%)
|
|||
|
|
|
|||
|
|
// Active service
|
|||
|
|
ACTIVE: "Active", // Service active (90%)
|
|||
|
|
|
|||
|
|
// Cancellation stages
|
|||
|
|
CANCELLING: "△Cancelling", // Cancellation pending (100%, Open)
|
|||
|
|
CANCELLED: "〇Cancelled", // Successfully cancelled (100%, Closed/Won)
|
|||
|
|
|
|||
|
|
// Completed/Closed stages
|
|||
|
|
COMPLETED: "Completed", // Service completed normally (100%, Closed/Won)
|
|||
|
|
VOID: "Void", // Lost / not eligible (0%, Closed/Lost)
|
|||
|
|
PENDING: "Pending", // On hold / abandoned (0%, Closed/Lost)
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type OpportunityStageValue = (typeof OPPORTUNITY_STAGE)[keyof typeof OPPORTUNITY_STAGE];
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Application Stage Constants (Existing Salesforce Values)
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Application stage picklist (Application_Stage__c)
|
|||
|
|
* Tracks application process step
|
|||
|
|
*
|
|||
|
|
* For portal, we use INTRO_1 (single-step intro, not multi-email legacy flow)
|
|||
|
|
*/
|
|||
|
|
export const APPLICATION_STAGE = {
|
|||
|
|
INTRO_1: "INTRO-1", // Portal default - simple introduction
|
|||
|
|
NA: "N/A", // Not applicable
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type ApplicationStageValue = (typeof APPLICATION_STAGE)[keyof typeof APPLICATION_STAGE];
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Cancellation Notice Constants (Existing Salesforce Values)
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cancellation notice status (CancellationNotice__c)
|
|||
|
|
* Tracks whether cancellation form has been received
|
|||
|
|
*/
|
|||
|
|
export const CANCELLATION_NOTICE = {
|
|||
|
|
RECEIVED: "有", // Form received
|
|||
|
|
NOT_YET: "未", // Not yet received (default)
|
|||
|
|
NOT_REQUIRED: "不要", // Not required
|
|||
|
|
TRANSFER: "移転", // Transferring (moving)
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type CancellationNoticeValue =
|
|||
|
|
(typeof CANCELLATION_NOTICE)[keyof typeof CANCELLATION_NOTICE];
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Line Return Status Constants (Existing Salesforce Values)
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Rental equipment return status (LineReturn__c)
|
|||
|
|
* Tracks equipment return process after cancellation
|
|||
|
|
*/
|
|||
|
|
export const LINE_RETURN_STATUS = {
|
|||
|
|
NOT_YET: "NotYet", // Not yet initiated (default)
|
|||
|
|
SENT_KIT: "SentKit", // Return kit sent to customer
|
|||
|
|
PICKUP_SCHEDULED: "AS/Pickup予定", // Pickup scheduled
|
|||
|
|
RETURNED_1: "Returned1", // Returned (variant 1)
|
|||
|
|
RETURNED: "Returned2", // Returned
|
|||
|
|
NTT_DISPATCH: "NTT派遣", // NTT dispatch
|
|||
|
|
COMPENSATED: "Compensated", // Compensation fee charged
|
|||
|
|
NA: "N/A", // Not applicable (no equipment)
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type LineReturnStatusValue = (typeof LINE_RETURN_STATUS)[keyof typeof LINE_RETURN_STATUS];
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Commodity Type Constants (Existing Salesforce Field: CommodityType)
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Commodity types - uses existing Salesforce CommodityType picklist
|
|||
|
|
* Maps to WHMCS product categories
|
|||
|
|
*/
|
|||
|
|
export const COMMODITY_TYPE = {
|
|||
|
|
// Internet types
|
|||
|
|
PERSONAL_HOME_INTERNET: "Personal SonixNet Home Internet",
|
|||
|
|
CORPORATE_HOME_INTERNET: "Corporate SonixNet Home Internet",
|
|||
|
|
// SIM
|
|||
|
|
SIM: "SIM",
|
|||
|
|
// VPN
|
|||
|
|
VPN: "VPN",
|
|||
|
|
// Other (not used by portal)
|
|||
|
|
TECH_SUPPORT: "Onsite Support",
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type CommodityTypeValue = (typeof COMMODITY_TYPE)[keyof typeof COMMODITY_TYPE];
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Simplified product types for portal logic
|
|||
|
|
* Maps multiple CommodityType values to simple categories
|
|||
|
|
*/
|
|||
|
|
export const OPPORTUNITY_PRODUCT_TYPE = {
|
|||
|
|
INTERNET: "Internet",
|
|||
|
|
SIM: "SIM",
|
|||
|
|
VPN: "VPN",
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type OpportunityProductTypeValue =
|
|||
|
|
(typeof OPPORTUNITY_PRODUCT_TYPE)[keyof typeof OPPORTUNITY_PRODUCT_TYPE];
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Map CommodityType to simplified product type
|
|||
|
|
*/
|
|||
|
|
export function getCommodityTypeProductType(
|
|||
|
|
commodityType: CommodityTypeValue
|
|||
|
|
): OpportunityProductTypeValue | null {
|
|||
|
|
switch (commodityType) {
|
|||
|
|
case COMMODITY_TYPE.PERSONAL_HOME_INTERNET:
|
|||
|
|
case COMMODITY_TYPE.CORPORATE_HOME_INTERNET:
|
|||
|
|
return OPPORTUNITY_PRODUCT_TYPE.INTERNET;
|
|||
|
|
case COMMODITY_TYPE.SIM:
|
|||
|
|
return OPPORTUNITY_PRODUCT_TYPE.SIM;
|
|||
|
|
case COMMODITY_TYPE.VPN:
|
|||
|
|
return OPPORTUNITY_PRODUCT_TYPE.VPN;
|
|||
|
|
default:
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get default CommodityType for a product type
|
|||
|
|
* Used when creating new Opportunities
|
|||
|
|
*
|
|||
|
|
* NOTE: Currently only Personal SonixNet Home Internet is used for Internet.
|
|||
|
|
* Corporate can be added later when needed.
|
|||
|
|
*/
|
|||
|
|
export function getDefaultCommodityType(
|
|||
|
|
productType: OpportunityProductTypeValue
|
|||
|
|
): CommodityTypeValue {
|
|||
|
|
switch (productType) {
|
|||
|
|
case OPPORTUNITY_PRODUCT_TYPE.INTERNET:
|
|||
|
|
// Always use Personal for now - Corporate support can be added later
|
|||
|
|
return COMMODITY_TYPE.PERSONAL_HOME_INTERNET;
|
|||
|
|
case OPPORTUNITY_PRODUCT_TYPE.SIM:
|
|||
|
|
return COMMODITY_TYPE.SIM;
|
|||
|
|
case OPPORTUNITY_PRODUCT_TYPE.VPN:
|
|||
|
|
return COMMODITY_TYPE.VPN;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Default commodity types used when creating opportunities from portal
|
|||
|
|
* These are the only values the portal will create - agents can use others
|
|||
|
|
*/
|
|||
|
|
export const PORTAL_DEFAULT_COMMODITY_TYPES = {
|
|||
|
|
INTERNET: COMMODITY_TYPE.PERSONAL_HOME_INTERNET,
|
|||
|
|
SIM: COMMODITY_TYPE.SIM,
|
|||
|
|
VPN: COMMODITY_TYPE.VPN,
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Opportunity Source Constants
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Sources from which Opportunities are created (Portal_Source__c)
|
|||
|
|
*/
|
|||
|
|
export const OPPORTUNITY_SOURCE = {
|
|||
|
|
INTERNET_ELIGIBILITY: "Portal - Internet Eligibility Request",
|
|||
|
|
SIM_CHECKOUT_REGISTRATION: "Portal - SIM Checkout Registration",
|
|||
|
|
ORDER_PLACEMENT: "Portal - Order Placement",
|
|||
|
|
AGENT_CREATED: "Agent Created",
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type OpportunitySourceValue = (typeof OPPORTUNITY_SOURCE)[keyof typeof OPPORTUNITY_SOURCE];
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Opportunity Matching Constants
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Stages considered "open" for matching purposes
|
|||
|
|
* Opportunities in these stages can be linked to new orders
|
|||
|
|
*/
|
|||
|
|
export const OPEN_OPPORTUNITY_STAGES: OpportunityStageValue[] = [
|
|||
|
|
OPPORTUNITY_STAGE.INTRODUCTION,
|
|||
|
|
OPPORTUNITY_STAGE.READY,
|
|||
|
|
OPPORTUNITY_STAGE.POST_PROCESSING,
|
|||
|
|
OPPORTUNITY_STAGE.ACTIVE,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Stages that indicate the Opportunity is closed
|
|||
|
|
*/
|
|||
|
|
export const CLOSED_OPPORTUNITY_STAGES: OpportunityStageValue[] = [
|
|||
|
|
OPPORTUNITY_STAGE.CANCELLED,
|
|||
|
|
OPPORTUNITY_STAGE.COMPLETED,
|
|||
|
|
OPPORTUNITY_STAGE.VOID,
|
|||
|
|
OPPORTUNITY_STAGE.PENDING,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Cancellation Deadline Constants
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Day of month by which cancellation form must be received
|
|||
|
|
* If after this day, cancellation applies to next month
|
|||
|
|
*/
|
|||
|
|
export const CANCELLATION_DEADLINE_DAY = 25;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Day of following month by which rental equipment must be returned
|
|||
|
|
*/
|
|||
|
|
export const RENTAL_RETURN_DEADLINE_DAY = 10;
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Business Types
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Opportunity record as returned from Salesforce
|
|||
|
|
*/
|
|||
|
|
export interface OpportunityRecord {
|
|||
|
|
id: string;
|
|||
|
|
name: string;
|
|||
|
|
accountId: string;
|
|||
|
|
stage: OpportunityStageValue;
|
|||
|
|
closeDate: string;
|
|||
|
|
/** CommodityType - existing Salesforce field for product categorization */
|
|||
|
|
commodityType?: CommodityTypeValue;
|
|||
|
|
/** Simplified product type - derived from commodityType */
|
|||
|
|
productType?: OpportunityProductTypeValue;
|
|||
|
|
source?: OpportunitySourceValue;
|
|||
|
|
applicationStage?: ApplicationStageValue;
|
|||
|
|
isClosed: boolean;
|
|||
|
|
|
|||
|
|
// Linked entities
|
|||
|
|
// Note: Cases and Orders link TO Opportunity (not stored here)
|
|||
|
|
// - Case.OpportunityId → for eligibility, ID verification, cancellation
|
|||
|
|
// - Order.OpportunityId → for order tracking
|
|||
|
|
whmcsServiceId?: number;
|
|||
|
|
|
|||
|
|
// Cancellation fields (updated by CS when processing cancellation Case)
|
|||
|
|
cancellationNotice?: CancellationNoticeValue;
|
|||
|
|
scheduledCancellationDate?: string;
|
|||
|
|
lineReturnStatus?: LineReturnStatusValue;
|
|||
|
|
// NOTE: alternativeContactEmail and cancellationComments are on the Cancellation Case
|
|||
|
|
|
|||
|
|
// Metadata
|
|||
|
|
createdDate: string;
|
|||
|
|
lastModifiedDate: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Request to create a new Opportunity
|
|||
|
|
*
|
|||
|
|
* Note: Opportunity Name is auto-generated by Salesforce workflow,
|
|||
|
|
* so we don't need to provide account name. The service will use
|
|||
|
|
* a placeholder that gets overwritten by Salesforce.
|
|||
|
|
*/
|
|||
|
|
export interface CreateOpportunityRequest {
|
|||
|
|
accountId: string;
|
|||
|
|
productType: OpportunityProductTypeValue;
|
|||
|
|
stage: OpportunityStageValue;
|
|||
|
|
source: OpportunitySourceValue;
|
|||
|
|
|
|||
|
|
/** Application stage, defaults to INTRO-1 */
|
|||
|
|
applicationStage?: ApplicationStageValue;
|
|||
|
|
|
|||
|
|
/** Expected close date, defaults to 30 days from now */
|
|||
|
|
closeDate?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Request to update Opportunity stage
|
|||
|
|
*/
|
|||
|
|
export interface UpdateOpportunityStageRequest {
|
|||
|
|
opportunityId: string;
|
|||
|
|
stage: OpportunityStageValue;
|
|||
|
|
|
|||
|
|
/** Optional: reason for stage change (for audit) */
|
|||
|
|
reason?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cancellation form data from customer
|
|||
|
|
*/
|
|||
|
|
export interface CancellationFormData {
|
|||
|
|
/**
|
|||
|
|
* Selected cancellation month (YYYY-MM format)
|
|||
|
|
* Service ends at end of this month
|
|||
|
|
*/
|
|||
|
|
cancellationMonth: string;
|
|||
|
|
|
|||
|
|
/** Customer confirms they have read cancellation terms */
|
|||
|
|
confirmTermsRead: boolean;
|
|||
|
|
|
|||
|
|
/** Customer confirms they understand month-end cancellation */
|
|||
|
|
confirmMonthEndCancellation: boolean;
|
|||
|
|
|
|||
|
|
/** Optional alternative email for post-cancellation communication */
|
|||
|
|
alternativeEmail?: string;
|
|||
|
|
|
|||
|
|
/** Optional customer comments/notes */
|
|||
|
|
comments?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cancellation data to populate on Opportunity (transformed from form)
|
|||
|
|
* Only core lifecycle fields - details go on Cancellation Case
|
|||
|
|
*/
|
|||
|
|
export interface CancellationOpportunityData {
|
|||
|
|
/** End of cancellation month (YYYY-MM-DD format) */
|
|||
|
|
scheduledCancellationDate: string;
|
|||
|
|
|
|||
|
|
/** Cancellation notice status (always 有 from portal) */
|
|||
|
|
cancellationNotice: CancellationNoticeValue;
|
|||
|
|
|
|||
|
|
/** Initial line return status (always NotYet from portal) */
|
|||
|
|
lineReturnStatus: LineReturnStatusValue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Data to populate on Cancellation Case
|
|||
|
|
* This contains all customer-provided details
|
|||
|
|
*/
|
|||
|
|
export interface CancellationCaseData {
|
|||
|
|
/** Account ID */
|
|||
|
|
accountId: string;
|
|||
|
|
|
|||
|
|
/** Linked Opportunity ID (if known) */
|
|||
|
|
opportunityId?: string;
|
|||
|
|
|
|||
|
|
/** WHMCS Service ID for reference */
|
|||
|
|
whmcsServiceId: number;
|
|||
|
|
|
|||
|
|
/** Product type (Internet, SIM, VPN) */
|
|||
|
|
productType: OpportunityProductTypeValue;
|
|||
|
|
|
|||
|
|
/** Selected cancellation month (YYYY-MM) */
|
|||
|
|
cancellationMonth: string;
|
|||
|
|
|
|||
|
|
/** End of cancellation month (YYYY-MM-DD) */
|
|||
|
|
cancellationDate: string;
|
|||
|
|
|
|||
|
|
/** Optional alternative contact email */
|
|||
|
|
alternativeEmail?: string;
|
|||
|
|
|
|||
|
|
/** Optional customer comments */
|
|||
|
|
comments?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cancellation eligibility check result
|
|||
|
|
*/
|
|||
|
|
export interface CancellationEligibility {
|
|||
|
|
/** Whether cancellation can be requested now */
|
|||
|
|
canCancel: boolean;
|
|||
|
|
|
|||
|
|
/** Earliest month available for cancellation (YYYY-MM) */
|
|||
|
|
earliestCancellationMonth: string;
|
|||
|
|
|
|||
|
|
/** Available cancellation months (up to 12 months ahead) */
|
|||
|
|
availableMonths: CancellationMonthOption[];
|
|||
|
|
|
|||
|
|
/** Deadline for requesting cancellation this month */
|
|||
|
|
currentMonthDeadline: string | null;
|
|||
|
|
|
|||
|
|
/** If cannot cancel, the reason why */
|
|||
|
|
reason?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* A month option for cancellation selection
|
|||
|
|
*/
|
|||
|
|
export interface CancellationMonthOption {
|
|||
|
|
/** Value in YYYY-MM format */
|
|||
|
|
value: string;
|
|||
|
|
|
|||
|
|
/** Display label (e.g., "January 2025") */
|
|||
|
|
label: string;
|
|||
|
|
|
|||
|
|
/** End date of the month (service end date) */
|
|||
|
|
serviceEndDate: string;
|
|||
|
|
|
|||
|
|
/** Rental return deadline (10th of following month) */
|
|||
|
|
rentalReturnDeadline: string;
|
|||
|
|
|
|||
|
|
/** Whether this is the current month (may have deadline) */
|
|||
|
|
isCurrentMonth: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cancellation status for display in portal
|
|||
|
|
*/
|
|||
|
|
export interface CancellationStatus {
|
|||
|
|
/** Current stage */
|
|||
|
|
stage: OpportunityStageValue;
|
|||
|
|
|
|||
|
|
/** Whether cancellation is pending */
|
|||
|
|
isPending: boolean;
|
|||
|
|
|
|||
|
|
/** Whether cancellation is complete */
|
|||
|
|
isComplete: boolean;
|
|||
|
|
|
|||
|
|
/** Scheduled end date */
|
|||
|
|
scheduledEndDate?: string;
|
|||
|
|
|
|||
|
|
/** Rental return status */
|
|||
|
|
rentalReturnStatus?: LineReturnStatusValue;
|
|||
|
|
|
|||
|
|
/** Rental return deadline */
|
|||
|
|
rentalReturnDeadline?: string;
|
|||
|
|
|
|||
|
|
/** Whether rental equipment needs to be returned */
|
|||
|
|
hasRentalEquipment: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Result of Opportunity matching/resolution
|
|||
|
|
*/
|
|||
|
|
export interface OpportunityMatchResult {
|
|||
|
|
/** The Opportunity ID (existing or newly created) */
|
|||
|
|
opportunityId: string;
|
|||
|
|
|
|||
|
|
/** Whether a new Opportunity was created */
|
|||
|
|
wasCreated: boolean;
|
|||
|
|
|
|||
|
|
/** Previous stage if updated */
|
|||
|
|
previousStage?: OpportunityStageValue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Lookup criteria for finding existing Opportunities
|
|||
|
|
*/
|
|||
|
|
export interface OpportunityLookupCriteria {
|
|||
|
|
accountId: string;
|
|||
|
|
productType: OpportunityProductTypeValue;
|
|||
|
|
|
|||
|
|
/** Only match Opportunities in these stages */
|
|||
|
|
allowedStages?: OpportunityStageValue[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Customer-Facing Service Display Model
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Customer-facing service phase
|
|||
|
|
* Only shows what's relevant to customers (no internal sales stages)
|
|||
|
|
*/
|
|||
|
|
export type CustomerServicePhase =
|
|||
|
|
| "order" // Order placed, awaiting activation (has SF Order, no WHMCS)
|
|||
|
|
| "active" // Service is live (has WHMCS service)
|
|||
|
|
| "cancelling" // Cancellation pending
|
|||
|
|
| "cancelled"; // Service ended
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Whether the WHMCS service is linked to an Opportunity
|
|||
|
|
*/
|
|||
|
|
export type ServiceLinkStatus =
|
|||
|
|
| "linked" // Opportunity has WHMCS_Service_ID__c (full features)
|
|||
|
|
| "unlinked"; // Legacy - WHMCS exists but Opp not linked
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Order tracking info (shown after order placed, before WHMCS activates)
|
|||
|
|
* Source: Salesforce Order
|
|||
|
|
*/
|
|||
|
|
export interface OrderTrackingInfo {
|
|||
|
|
/** Salesforce Order ID */
|
|||
|
|
orderId: string;
|
|||
|
|
|
|||
|
|
/** Order number for display */
|
|||
|
|
orderNumber: string;
|
|||
|
|
|
|||
|
|
/** Product being ordered */
|
|||
|
|
productName: string;
|
|||
|
|
|
|||
|
|
/** Order status */
|
|||
|
|
status: string;
|
|||
|
|
|
|||
|
|
/** When order was placed */
|
|||
|
|
orderDate: string;
|
|||
|
|
|
|||
|
|
/** Expected activation date (estimated) */
|
|||
|
|
expectedActivation?: string;
|
|||
|
|
|
|||
|
|
/** Progress steps */
|
|||
|
|
steps: Array<{
|
|||
|
|
label: string;
|
|||
|
|
status: "completed" | "current" | "upcoming";
|
|||
|
|
}>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Service details from WHMCS (shown when service is active)
|
|||
|
|
* This is the primary data source for customer service page
|
|||
|
|
*/
|
|||
|
|
export interface WhmcsServiceDetails {
|
|||
|
|
/** WHMCS Service ID */
|
|||
|
|
whmcsServiceId: number;
|
|||
|
|
|
|||
|
|
/** Product/plan name */
|
|||
|
|
productName: string;
|
|||
|
|
|
|||
|
|
/** WHMCS status (Active, Suspended, etc.) */
|
|||
|
|
status: string;
|
|||
|
|
|
|||
|
|
/** Service address (for Internet) */
|
|||
|
|
serviceAddress?: string;
|
|||
|
|
|
|||
|
|
/** Monthly amount */
|
|||
|
|
monthlyAmount?: number;
|
|||
|
|
|
|||
|
|
/** Currency code */
|
|||
|
|
currency?: string;
|
|||
|
|
|
|||
|
|
/** Next billing date */
|
|||
|
|
nextDueDate?: string;
|
|||
|
|
|
|||
|
|
/** Registration date */
|
|||
|
|
registrationDate?: string;
|
|||
|
|
|
|||
|
|
/** Linked Opportunity ID (from WHMCS custom field) */
|
|||
|
|
opportunityId?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cancellation info for display (shown when cancelling)
|
|||
|
|
*/
|
|||
|
|
export interface CancellationDisplayInfo {
|
|||
|
|
/** When the service will end (end of month) */
|
|||
|
|
serviceEndDate: string;
|
|||
|
|
|
|||
|
|
/** Deadline for returning equipment */
|
|||
|
|
equipmentReturnDeadline: string;
|
|||
|
|
|
|||
|
|
/** Current return kit status */
|
|||
|
|
returnKitStatus: LineReturnStatusValue;
|
|||
|
|
|
|||
|
|
/** Human-readable return status */
|
|||
|
|
returnKitStatusLabel: string;
|
|||
|
|
|
|||
|
|
/** Whether equipment needs to be returned */
|
|||
|
|
hasEquipmentToReturn: boolean;
|
|||
|
|
|
|||
|
|
/** Days remaining until service ends */
|
|||
|
|
daysUntilServiceEnd: number;
|
|||
|
|
|
|||
|
|
/** Days remaining until return deadline */
|
|||
|
|
daysUntilReturnDeadline: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Customer-facing service view for the portal
|
|||
|
|
*
|
|||
|
|
* Combines data from WHMCS (primary) and Opportunity (lifecycle/cancellation)
|
|||
|
|
* Handles both linked and legacy (unlinked) services
|
|||
|
|
*/
|
|||
|
|
export interface CustomerServiceView {
|
|||
|
|
// ---- Identification ----
|
|||
|
|
|
|||
|
|
/** Unique ID for this view (WHMCS Service ID as string) */
|
|||
|
|
id: string;
|
|||
|
|
|
|||
|
|
/** WHMCS Service ID */
|
|||
|
|
whmcsServiceId: number;
|
|||
|
|
|
|||
|
|
/** Linked Opportunity ID (null for legacy services) */
|
|||
|
|
opportunityId?: string;
|
|||
|
|
|
|||
|
|
/** Product type (Internet, SIM, VPN) */
|
|||
|
|
productType: OpportunityProductTypeValue;
|
|||
|
|
|
|||
|
|
// ---- Display State ----
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Customer-facing phase
|
|||
|
|
* - order: Waiting for activation
|
|||
|
|
* - active: Service is live
|
|||
|
|
* - cancelling: Cancellation pending
|
|||
|
|
* - cancelled: Service ended
|
|||
|
|
*/
|
|||
|
|
phase: CustomerServicePhase;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Whether this service has full Opportunity linking
|
|||
|
|
* - linked: Full features (can show cancellation status, etc.)
|
|||
|
|
* - unlinked: Legacy service (limited features)
|
|||
|
|
*/
|
|||
|
|
linkStatus: ServiceLinkStatus;
|
|||
|
|
|
|||
|
|
// ---- Service Data (from WHMCS) ----
|
|||
|
|
|
|||
|
|
/** Product/plan name */
|
|||
|
|
productName: string;
|
|||
|
|
|
|||
|
|
/** WHMCS status */
|
|||
|
|
status: string;
|
|||
|
|
|
|||
|
|
/** Monthly amount */
|
|||
|
|
monthlyAmount?: number;
|
|||
|
|
|
|||
|
|
/** Currency */
|
|||
|
|
currency?: string;
|
|||
|
|
|
|||
|
|
/** Next billing date */
|
|||
|
|
nextDueDate?: string;
|
|||
|
|
|
|||
|
|
// ---- Cancellation Data (from Opportunity, if linked) ----
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cancellation info (only when phase = 'cancelling' and linked)
|
|||
|
|
*/
|
|||
|
|
cancellation?: CancellationDisplayInfo;
|
|||
|
|
|
|||
|
|
// ---- Actions ----
|
|||
|
|
|
|||
|
|
/** Whether cancel button should be shown */
|
|||
|
|
canCancel: boolean;
|
|||
|
|
|
|||
|
|
/** Whether user can view invoices */
|
|||
|
|
canViewInvoices: boolean;
|
|||
|
|
|
|||
|
|
/** Message to show if features are limited (for legacy) */
|
|||
|
|
limitedFeaturesMessage?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Map Opportunity stage to customer-facing phase
|
|||
|
|
*/
|
|||
|
|
export function getCustomerPhaseFromStage(
|
|||
|
|
stage: OpportunityStageValue | undefined,
|
|||
|
|
whmcsStatus: string
|
|||
|
|
): CustomerServicePhase {
|
|||
|
|
// If no Opportunity or WHMCS is active, derive from WHMCS status
|
|||
|
|
if (!stage) {
|
|||
|
|
if (whmcsStatus === "Active") return "active";
|
|||
|
|
if (whmcsStatus === "Cancelled" || whmcsStatus === "Terminated") return "cancelled";
|
|||
|
|
return "active"; // Default for legacy
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
switch (stage) {
|
|||
|
|
case OPPORTUNITY_STAGE.INTRODUCTION:
|
|||
|
|
case OPPORTUNITY_STAGE.READY:
|
|||
|
|
case OPPORTUNITY_STAGE.POST_PROCESSING:
|
|||
|
|
return "order"; // Customer sees "order in progress"
|
|||
|
|
|
|||
|
|
case OPPORTUNITY_STAGE.ACTIVE:
|
|||
|
|
return "active";
|
|||
|
|
|
|||
|
|
case OPPORTUNITY_STAGE.CANCELLING:
|
|||
|
|
return "cancelling";
|
|||
|
|
|
|||
|
|
case OPPORTUNITY_STAGE.CANCELLED:
|
|||
|
|
case OPPORTUNITY_STAGE.COMPLETED:
|
|||
|
|
case OPPORTUNITY_STAGE.VOID:
|
|||
|
|
case OPPORTUNITY_STAGE.PENDING:
|
|||
|
|
return "cancelled";
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
return "active";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get order tracking progress steps
|
|||
|
|
* Used for customers who have placed an order but WHMCS is not yet active
|
|||
|
|
*/
|
|||
|
|
export function getOrderTrackingSteps(
|
|||
|
|
currentStage: OpportunityStageValue
|
|||
|
|
): OrderTrackingInfo["steps"] {
|
|||
|
|
const stages = [
|
|||
|
|
{ stage: OPPORTUNITY_STAGE.POST_PROCESSING, label: "Order Received" },
|
|||
|
|
{ stage: OPPORTUNITY_STAGE.POST_PROCESSING, label: "Processing" },
|
|||
|
|
{ stage: OPPORTUNITY_STAGE.ACTIVE, label: "Activating" },
|
|||
|
|
{ stage: OPPORTUNITY_STAGE.ACTIVE, label: "Active" },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// Simplify to 4 steps for customer clarity
|
|||
|
|
let currentStep = 0;
|
|||
|
|
if (currentStage === OPPORTUNITY_STAGE.POST_PROCESSING) {
|
|||
|
|
currentStep = 1; // Processing
|
|||
|
|
} else if (currentStage === OPPORTUNITY_STAGE.ACTIVE) {
|
|||
|
|
currentStep = 3; // Active
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return stages.map((s, index) => ({
|
|||
|
|
label: s.label,
|
|||
|
|
status: index < currentStep ? "completed" : index === currentStep ? "current" : "upcoming",
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Determine if a service can be cancelled
|
|||
|
|
*/
|
|||
|
|
export function canServiceBeCancelled(
|
|||
|
|
whmcsStatus: string,
|
|||
|
|
opportunityStage?: OpportunityStageValue
|
|||
|
|
): boolean {
|
|||
|
|
// Can't cancel if WHMCS is not active
|
|||
|
|
if (whmcsStatus !== "Active") return false;
|
|||
|
|
|
|||
|
|
// If linked to Opportunity, check stage
|
|||
|
|
if (opportunityStage) {
|
|||
|
|
// Can only cancel from Active stage
|
|||
|
|
return opportunityStage === OPPORTUNITY_STAGE.ACTIVE;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Legacy service - allow cancellation (will create Case for agent)
|
|||
|
|
return true;
|
|||
|
|
}
|