2025-12-22 18:59:38 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 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
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-05 16:32:45 +09:00
|
|
|
|
* Sources from which Opportunities are created (Opportunity_Source__c)
|
2025-12-22 18:59:38 +09:00
|
|
|
|
*/
|
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
2025-12-23 16:44:45 +09:00
|
|
|
|
// Opportunity Matching / Stage Sets
|
2025-12-22 18:59:38 +09:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-23 16:44:45 +09:00
|
|
|
|
* Stages considered "open" (i.e. not closed) in our lifecycle.
|
|
|
|
|
|
*
|
|
|
|
|
|
* IMPORTANT:
|
|
|
|
|
|
* - Do NOT use this list to match a *new* order or eligibility request to an existing Opportunity.
|
|
|
|
|
|
* For those flows we intentionally match a much smaller set of early-stage Opportunities.
|
|
|
|
|
|
* - Use the `OPPORTUNITY_MATCH_STAGES_*` constants below instead.
|
2025-12-22 18:59:38 +09:00
|
|
|
|
*/
|
|
|
|
|
|
export const OPEN_OPPORTUNITY_STAGES: OpportunityStageValue[] = [
|
|
|
|
|
|
OPPORTUNITY_STAGE.INTRODUCTION,
|
|
|
|
|
|
OPPORTUNITY_STAGE.READY,
|
|
|
|
|
|
OPPORTUNITY_STAGE.POST_PROCESSING,
|
|
|
|
|
|
OPPORTUNITY_STAGE.ACTIVE,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2025-12-23 16:44:45 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* Stages eligible for matching during an Internet eligibility request.
|
|
|
|
|
|
*
|
|
|
|
|
|
* We only ever reuse the initial "Introduction" opportunity; later stages indicate the
|
|
|
|
|
|
* customer journey has progressed beyond eligibility.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const OPPORTUNITY_MATCH_STAGES_INTERNET_ELIGIBILITY: OpportunityStageValue[] = [
|
|
|
|
|
|
OPPORTUNITY_STAGE.INTRODUCTION,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Stages eligible for matching during order placement.
|
|
|
|
|
|
*
|
|
|
|
|
|
* If a customer came from eligibility, the Opportunity will usually be in "Introduction" or "Ready".
|
|
|
|
|
|
* We must never match an "Active" Opportunity for a new order (that would corrupt lifecycle tracking).
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const OPPORTUNITY_MATCH_STAGES_ORDER_PLACEMENT: OpportunityStageValue[] = [
|
|
|
|
|
|
OPPORTUNITY_STAGE.INTRODUCTION,
|
|
|
|
|
|
OPPORTUNITY_STAGE.READY,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2025-12-22 18:59:38 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
2026-01-19 15:14:39 +09:00
|
|
|
|
// Cancellation Opportunity Data Types
|
2025-12-22 18:59:38 +09:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-05 16:32:45 +09:00
|
|
|
|
* Cancellation data to populate on Opportunity for Internet services
|
2025-12-22 18:59:38 +09:00
|
|
|
|
* Only core lifecycle fields - details go on Cancellation Case
|
|
|
|
|
|
*/
|
2026-01-05 16:32:45 +09:00
|
|
|
|
export interface InternetCancellationOpportunityData {
|
2025-12-22 18:59:38 +09:00
|
|
|
|
/** 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-05 16:32:45 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* SIM cancellation notice values (SIMCancellationNotice__c)
|
|
|
|
|
|
* Tracks whether SIM cancellation form has been received
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const SIM_CANCELLATION_NOTICE = {
|
|
|
|
|
|
RECEIVED: "有", // Form received
|
|
|
|
|
|
NOT_YET: "未", // Not yet received (default)
|
|
|
|
|
|
NOT_REQUIRED: "不要", // Not required
|
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type SimCancellationNoticeValue =
|
|
|
|
|
|
(typeof SIM_CANCELLATION_NOTICE)[keyof typeof SIM_CANCELLATION_NOTICE];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Cancellation data to populate on Opportunity for SIM services
|
|
|
|
|
|
* NOTE: Customer comments go on the Cancellation Case, not Opportunity (same as Internet)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface SimCancellationOpportunityData {
|
|
|
|
|
|
/** End of cancellation month (YYYY-MM-DD format) */
|
|
|
|
|
|
scheduledCancellationDate: string;
|
|
|
|
|
|
|
|
|
|
|
|
/** SIM Cancellation notice status (always 有 from portal) */
|
|
|
|
|
|
cancellationNotice: SimCancellationNoticeValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 18:59:38 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|