Assist_Design/docs/integrations/salesforce/opportunity-lifecycle.md

552 lines
37 KiB
Markdown
Raw Normal View History

# Opportunity Lifecycle Management Guide
This guide documents the Salesforce Opportunity integration for service lifecycle tracking.
## Table of Contents
1. [Overview](#overview)
2. [Existing Field Architecture](#existing-field-architecture)
3. [Opportunity Matching Rules](#opportunity-matching-rules)
4. [Internet Eligibility Flow](#internet-eligibility-flow)
5. [ID Verification Flow](#id-verification-flow)
6. [Order Placement Flow](#order-placement-flow)
7. [Service Activation Flow](#service-activation-flow)
8. [Cancellation Flow](#cancellation-flow)
9. [Implementation Checklist](#implementation-checklist)
---
## Overview
### What Customers See vs What's Internal
```
┌─────────────────────────────────────────────────────────────────────────┐
│ INTERNAL ONLY (Sales/CS Pipeline) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Opportunity Stages: Introduction, Ready (before Order placed) │
│ Application Stages: INTRO-1, UNRESPONSIVE-1, etc. (CS workflow) │
│ Eligibility fields on Account (eligibility status) │
│ ID Verification fields on Account (verification status) │
│ │
│ These are for internal tracking - customer sees results, not fields. │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ CUSTOMER-FACING (Portal) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. ELIGIBILITY STATUS (from Account field) │
│ └─ "Checking...", "Eligible", "Not Eligible" │
│ │
│ 2. ID VERIFICATION STATUS (from Account field) │
│ └─ "Pending", "Verified", "Rejected" │
│ │
│ 3. ORDER TRACKING (from Salesforce Order) │
│ └─ After order placed, before WHMCS activates │
│ │
│ 4. SERVICE PAGE (from WHMCS) │
│ └─ After service is active │
│ │
│ 5. CANCELLATION STATUS (from Opportunity) │
│ └─ End date, return deadline, kit status │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## Existing Field Architecture
### Account Fields (Already Exist)
**Internet Eligibility Fields:**
| Field | API Name | Purpose |
| ----------------- | ------------------------------------------- | ----------------------------- |
| Eligibility Value | `Internet_Eligibility__c` | The actual eligibility result |
| Status | `Internet_Eligibility_Status__c` | Pending, Checked |
| Requested At | `Internet_Eligibility_Request_Date_Time__c` | When request was made |
| Checked At | `Internet_Eligibility_Checked_Date_Time__c` | When eligibility was checked |
**Notes:**
- The portal does **not** store the Case ID or agent notes on the Account in our current Salesforce environment.
- Agent notes should live inside the **Case Description** / Case activity history.
**ID Verification Fields:**
| Field | API Name | Purpose |
| ----------------- | ---------------------------------------- | --------------------------- |
| Status | `Id_Verification_Status__c` | Pending, Verified, Rejected |
| Submitted At | `Id_Verification_Submitted_Date_Time__c` | When submitted |
| Verified At | `Id_Verification_Verified_Date_Time__c` | When verified |
| Notes | `Id_Verification_Note__c` | Agent notes |
| Rejection Message | `Id_Verification_Rejection_Message__c` | Reason for rejection |
### Opportunity Fields (Existing)
**Core Fields:**
| Field | API Name | Purpose |
| ----------------- | ---------------------- | --------------------------------------------------------------- |
| Stage | `StageName` | Introduction, Ready, Post Processing, Active, △Cancelling, etc. |
| Commodity Type | `CommodityType` | Personal SonixNet Home Internet, SIM, VPN |
| Application Stage | `Application_Stage__c` | INTRO-1 (for portal) |
**Internet Cancellation Fields:**
| Field | API Name | Purpose |
| ---------------------- | ------------------------------------- | ------------------------------ |
| Cancellation Notice | `CancellationNotice__c` | 有, 未, 不要, 移転 |
| Scheduled Cancellation | `ScheduledCancellationDateAndTime__c` | End of cancellation month |
| Line Return Status | `LineReturn__c` | NotYet, SentKit, Returned, etc |
**SIM Cancellation Fields:**
| Field | API Name | Purpose |
| -------------------------- | ---------------------------------------- | ----------------------- |
| SIM Cancellation Notice | `SIMCancellationNotice__c` | 有, 未, 不要 |
| SIM Scheduled Cancellation | `SIMScheduledCancellationDateAndTime__c` | End of SIM cancellation |
_Note: SIM customer comments are stored on the Cancellation Case, same as Internet._
### Portal Integration Fields (Existing)
| Field | API Name | Type | Purpose |
| ------------------ | ----------------------- | -------- | -------------------------------- |
| Opportunity Source | `Opportunity_Source__c` | Picklist | How Opportunity was created |
| WHMCS Service ID | `WHMCS_Service_ID__c` | Number | Link to WHMCS after provisioning |
### Order Fields (Existing)
| Field | API Name | Purpose |
| ----------- | --------------- | -------------------------- |
| Opportunity | `OpportunityId` | Links Order TO Opportunity |
### Case Fields (Existing)
| Field | API Name | Purpose |
| ----------- | --------------- | ------------------------- |
| Opportunity | `OpportunityId` | Links Case TO Opportunity |
**Key Principle:** Cases and Orders link TO Opportunity, not vice versa.
---
## Opportunity Matching Rules
### When to Find vs Create Opportunity
```
┌─────────────────────────────────────────────────────────────────────────┐
│ OPPORTUNITY MATCHING RULES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ INTERNET ELIGIBILITY REQUEST: │
│ ───────────────────────────── │
│ 1. Check if Account already has eligibility status set │
│ └─ If already "Eligible" or "Not Eligible" → Don't create Opp │
│ │
│ 2. Find existing Opportunity: │
│ └─ WHERE AccountId = ? │
│ AND CommodityType IN ('Personal SonixNet Home Internet', │
│ 'Corporate SonixNet Home Internet') │
│ AND StageName = 'Introduction' │
│ AND IsClosed = false │
│ │
│ 3. If found → Use existing, create Case linked to it │
│ If not → Create new Opportunity, then create Case linked to it │
│ │
│ ORDER PLACEMENT: │
│ ───────────────── │
│ 1. Find existing Opportunity: │
│ └─ WHERE AccountId = ? │
│ AND CommodityType = ? (based on order type) │
│ AND StageName IN ('Introduction', 'Ready') │
│ AND IsClosed = false │
│ │
│ 2. If found → Use existing, update stage to 'Post Processing' │
│ If not → Create new Opportunity with stage 'Post Processing' │
│ │
│ 3. Create Order with Order.OpportunityId = Opportunity.Id │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### Stage Matching by Flow
| Flow | Match Stages | If Not Found |
| -------------------- | ------------------- | ------------------------------- |
| Internet Eligibility | Introduction only | Create with Introduction |
| Order Placement | Introduction, Ready | Create with Post Processing |
| Provisioning | Post Processing | Error (should exist from order) |
| Cancellation | Active | Error (service must be active) |
---
## Internet Eligibility Flow
### Complete Flow Diagram
```
┌─────────────────────────────────────────────────────────────────────────┐
│ INTERNET ELIGIBILITY FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. CUSTOMER ENTERS ADDRESS │
│ └─ Portal: POST /api/services/internet/eligibility-request │
│ Portal UI: shows confirmation at /account/services/internet/request-submitted │
│ │
│ 2. CHECK IF ELIGIBILITY ALREADY KNOWN │
│ └─ Query: SELECT Internet_Eligibility__c FROM Account │
│ └─ If already set → Return cached result, no action needed │
│ │
│ 3. FIND OR CREATE OPPORTUNITY │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Query: │ │
│ │ SELECT Id FROM Opportunity │ │
│ │ WHERE AccountId = ? │ │
│ │ AND CommodityType IN ('Personal SonixNet Home Internet', │ │
│ │ 'Corporate SonixNet Home Internet') │ │
│ │ AND StageName = 'Introduction' │ │
│ │ AND IsClosed = false │ │
│ │ │ │
│ │ If found → Use existing │ │
│ │ If not → Create new: │ │
│ │ - Stage: Introduction │ │
│ │ - CommodityType: Personal SonixNet Home Internet │ │
│ │ - Opportunity_Source__c: Portal - Internet Eligibility Request│ │
│ │ - Application_Stage__c: INTRO-1 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 4. CREATE CASE (linked to Opportunity) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Case.Type = "Eligibility Check" │ │
│ │ Case.AccountId = customer's account │ │
│ │ Case.OpportunityId = opportunity from step 3 ←── THE LINK │ │
│ │ Case.Subject = "Internet Eligibility - {Address}" │ │
│ │ Case.Description = address details, postal code, etc. │ │
│ │ Case.Status = "New" │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 5. UPDATE ACCOUNT │
│ └─ Internet_Eligibility_Status__c = "Pending" │
│ └─ Internet_Eligibility_Request_Date_Time__c = now() │
│ │
│ 6. CS PROCESSES CASE │
│ └─ Checks with NTT / provider │
│ └─ Updates Account: │
│ - Internet_Eligibility__c = result │
│ - Internet_Eligibility_Status__c = "Checked" │
│ - Internet_Eligibility_Checked_Date_Time__c = now() │
│ └─ Opportunity stages are not automatically moved to "Ready" by │
│ the eligibility check. CS updates stages later as part of the │
│ order review / lifecycle workflow. │
│ │
│ 7. PORTAL DETECTS CHANGE (via CDC or polling) │
│ └─ Shows eligibility result to customer │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### What Customer Sees
| Account Field State | Portal Shows |
| ---------------------------- | -------------------------------------- |
| Status = null | Address input form |
| Status = "Pending" | "Checking your address..." spinner |
| Eligibility = eligible value | Plan selection enabled |
| Eligibility = not eligible | "Sorry, service not available" message |
---
## ID Verification Flow
### Complete Flow Diagram
```
┌─────────────────────────────────────────────────────────────────────────┐
│ ID VERIFICATION (eKYC) FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ WHEN: During checkout registration or before order placement │
│ │
│ 1. CHECK IF ALREADY VERIFIED │
│ └─ Query: SELECT Id_Verification_Status__c FROM Account │
│ └─ If "Verified" → Skip verification, proceed to checkout │
│ │
│ 2. CUSTOMER UPLOADS ID DOCUMENTS │
│ └─ Portal: POST /api/verification/residence-card │
│ └─ BFF uploads file to Salesforce Files (ContentVersion) │
│ │
│ 3. UPDATE ACCOUNT │
│ └─ Id_Verification_Status__c = "Submitted" (portal maps to pending) │
│ └─ Id_Verification_Submitted_Date_Time__c = now() │
│ │
│ 4. CS REVIEWS IN SALESFORCE │
│ └─ Id_Verification_Status__c = "Verified" or "Rejected" │
│ └─ Id_Verification_Verified_Date_Time__c = now() (on verify) │
│ └─ Id_Verification_Rejection_Message__c = reason (on reject) │
│ │
│ 5. CUSTOMER RESUBMITS IF NEEDED │
│ └─ Portal shows feedback + upload UI │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### What Customer Sees
| Account Field State | Portal Shows |
| ------------------- | ------------------------------- |
| Status = null | ID upload form |
| Status = "Pending" | "Verifying your identity..." |
| Status = "Verified" | Proceed to checkout enabled |
| Status = "Rejected" | Error message + resubmit option |
---
## Order Placement Flow
```
┌─────────────────────────────────────────────────────────────────────────┐
│ ORDER PLACEMENT FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ PREREQUISITES: │
│ - Internet: Eligibility = Eligible (Account field) │
│ - SIM: ID Verification = Verified (Account field) │
│ │
│ 1. CUSTOMER SUBMITS ORDER │
│ └─ Portal: POST /api/orders │
│ │
│ 2. FIND OR CREATE OPPORTUNITY │
│ └─ Query for existing (Introduction or Ready stage) │
│ └─ If found → Use existing │
│ └─ If not → Create with stage 'Post Processing' │
│ │
│ 3. CREATE SALESFORCE ORDER │
│ └─ Order.OpportunityId = Opportunity.Id ←── THE LINK │
│ └─ Order.Status = "Pending Review" │
│ │
│ 4. UPDATE OPPORTUNITY │
│ └─ Stage = "Post Processing" │
│ │
│ 5. CUSTOMER SEES ORDER TRACKING │
│ └─ "Your order is being processed" │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## Service Activation Flow
```
┌─────────────────────────────────────────────────────────────────────────┐
│ SERVICE ACTIVATION FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. AGENT APPROVES ORDER │
│ └─ Order.Status = "Approved" (triggers CDC) │
│ │
│ 2. BFF PROVISIONS TO WHMCS │
│ └─ Calls WHMCS AddOrder API │
│ └─ Passes OpportunityId as custom field ←── WHMCS GETS OPP ID │
│ └─ WHMCS returns serviceId │
│ │
│ 3. UPDATE OPPORTUNITY │
│ └─ WHMCS_Service_ID__c = serviceId ←── OPP GETS WHMCS ID │
│ └─ Stage = "Active" │
│ │
│ 4. BIDIRECTIONAL LINK COMPLETE │
│ └─ Opportunity.WHMCS_Service_ID__c → WHMCS Service │
│ └─ WHMCS Service.OpportunityId → Opportunity (optional; helpful for ops/debugging) │
│ │
│ 5. CUSTOMER SEES SERVICE PAGE │
│ └─ Portal shows active services from WHMCS; lifecycle/cancellation tracking lives on the linked Salesforce Opportunity (via WHMCS_Service_ID__c) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## Cancellation Flow
The cancellation process differs between Internet and SIM services:
### Internet Cancellation Flow
For Internet cancellation, we create a Case for CS workflow and update the Opportunity:
```
┌─────────────────────────────────────────────────────────────────────────┐
│ INTERNET CANCELLATION FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. CUSTOMER SUBMITS CANCELLATION FORM │
│ └─ Selects month (25th rule applies) │
│ └─ Enters comments, alternative email (optional) │
│ │
│ 2. CREATE CANCELLATION CASE │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Case.Type = "Cancellation Request" │ │
│ │ Case.AccountId = customer's account │ │
│ │ Case.Subject = "Cancellation Request - Internet ({month})" │ │
│ │ Case.Description = ALL form data │ │
│ │ Case.OpportunityId = linked Opportunity (if found) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. IF OPPORTUNITY IS LINKED (via WHMCS_Service_ID__c) │
│ └─ Update Opportunity: │
│ - Stage = "△Cancelling" │
│ - ScheduledCancellationDateAndTime__c = end of month │
│ - CancellationNotice__c = "有" │
│ - LineReturn__c = "NotYet" │
│ │
│ 4. SEND CONFIRMATION EMAIL │
│ └─ Customer receives confirmation with cancellation details │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### SIM Cancellation Flow
For SIM cancellation, we call Freebit API, create a Case, and update the Opportunity:
```
┌─────────────────────────────────────────────────────────────────────────┐
│ SIM CANCELLATION FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. CUSTOMER SUBMITS CANCELLATION FORM │
│ └─ Selects month (25th rule applies) │
│ └─ Sees minimum contract warning if applicable │
│ │
│ 2. CALL FREEBIT PA02-04 API │
│ └─ Cancel account with runDate = 1st of next month │
│ └─ Cancellation takes effect at end of selected month │
│ │
│ 3. CREATE CANCELLATION CASE (same as Internet) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Case.Type = "Cancellation Request" │ │
│ │ Case.AccountId = customer's account │ │
│ │ Case.Subject = "Cancellation Request - SIM ({month})" │ │
│ │ Case.Description = ALL form data (SIM #, comments, etc.) │ │
│ │ Case.OpportunityId = linked Opportunity (if found) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 4. IF OPPORTUNITY IS LINKED (via WHMCS_Service_ID__c) │
│ └─ Update Opportunity: │
│ - Stage = "△Cancelling" │
│ - SIMScheduledCancellationDateAndTime__c = end of month │
│ - SIMCancellationNotice__c = "有" │
│ │
│ 5. SEND CONFIRMATION EMAILS │
│ └─ Admin notification with API results │
│ └─ Customer confirmation with cancellation details │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### The 25th Rule
```
TODAY'S DATE AVAILABLE CANCELLATION MONTHS
─────────────────────────────────────────────────────────
Before 25th of month → Can select THIS month or later
On/After 25th → Must select NEXT month or later
```
### Cancellation Data Location
**Internet Cancellation:**
| Data | Where It Goes | Why |
| ----------------- | ------------- | ------------------------ |
| Scheduled date | Opportunity | Lifecycle tracking |
| Notice status | Opportunity | Lifecycle tracking |
| Return status | Opportunity | Equipment tracking (ONU) |
| Customer comments | **Case** | Details for CS |
| WHMCS Service ID | Case | Service identification |
**SIM Cancellation:**
| Data | Where It Goes | Why |
| ----------------- | ------------- | ------------------------- |
| Scheduled date | Opportunity | Lifecycle tracking |
| Notice status | Opportunity | Lifecycle tracking |
| Customer comments | **Case** | Same as Internet |
| SIM #, Serial # | Case | Service identification |
| WHMCS Service ID | Case | Helps CS identify service |
---
## Implementation Checklist
### Salesforce Admin Tasks
**Existing Fields (No Changes):**
- [x] Opportunity Stage picklist
- [x] CommodityType field
- [x] Application_Stage\_\_c
- [x] Account Internet eligibility fields
- [x] Account ID verification fields
- [x] Case.OpportunityId (standard lookup)
- [x] Order.OpportunityId (standard lookup)
**Internet Cancellation Fields (Existing):**
- [x] `CancellationNotice__c` - Internet cancellation notice status
- [x] `LineReturn__c` - Equipment return status
- [x] `ScheduledCancellationDateAndTime__c` - Internet cancellation date
**SIM Cancellation Fields (Existing):**
- [x] `SIMCancellationNotice__c` - SIM cancellation notice status
- [x] `SIMScheduledCancellationDateAndTime__c` - SIM cancellation date
- _Note: Customer comments stored on Case, same as Internet_
**Portal Integration Fields (Existing):**
- [x] `Opportunity_Source__c` picklist (tracks how Opportunity was created)
- [x] `WHMCS_Service_ID__c` number field (links WHMCS → Salesforce for cancellations)
### WHMCS Admin Tasks
- [ ] Create an `OpportunityId` custom field on Services/Hosting (optional but recommended for ops/debugging)
- [ ] Confirm whether your WHMCS expects `customfields[]` keys by **name** (`OpportunityId`) or by **numeric field id**, and configure accordingly
### BFF Development Tasks
**Completed:**
- [x] Domain types in `packages/domain/opportunity/`
- [x] Field map configuration
- [x] `SalesforceOpportunityService`
- [x] Cancellation deadline helpers (25th rule)
- [x] Eligibility request creates/reuses Opportunity (Stage `Introduction`) and links Case (`Case.OpportunityId`)
- [x] Order placement reuses Opportunity in `Introduction`/`Ready` (otherwise creates new `Post Processing`)
- [x] Fulfillment passes `OpportunityId` to WHMCS and links `WHMCS_Service_ID__c` back to the Opportunity
- [x] Cancellation Case creation + Opportunity cancellation field updates (Internet cancellations)
**Optional / Future:**
- [ ] Read `OpportunityId` back from WHMCS service custom fields (portal currently relies on Salesforce `WHMCS_Service_ID__c` for linking)
### Frontend Tasks
- [ ] Order tracking page (from Salesforce Order)
- [ ] Service page with cancellation status
- [x] Cancellation forms (Internet + SIM) with 25th deadline logic
---
## Related Documentation
- [Salesforce-WHMCS Mapping](./SALESFORCE-WHMCS-MAPPING-REFERENCE.md)
- [Order Provisioning](../orders/PORTAL-ORDERING-PROVISIONING.md)