Client Lifecycle & Status/SubStatus Flow Documentation
Audience: Developers & Operations Team
Last Updated: 2026-07-09
Files:ClientsController.cs+CampaignController.cs+CampaignService.cs+HangFireJobServiceDB.cs
Table of Contents
- Enum Definitions
- Status System Overview
- Client Creation Flow (NewBookCampaign)
- Status Transition Actions (User-Triggered)
- Nightly Status Recalculation (_RecalculateCampaignsStatus)
- Complete Lifecycle State Machine
- Active vs Deprecated Paths
- Key Data Fields
- Related Views
1. Enum Definitions
1.1 Client Status (ClientNewStatusEnum)
| Display Name | Enum Value | Integer | Meaning |
|---|---|---|---|
| "Active - Multichannel service" | ActiveMultichannelService | 0 | Live client with active multichannel campaigns |
| "Active - Platform Only" | PlatformFeeOnly | 1 | Downgraded to platform-fee-only package |
| "Inactive" | Inactive | 2 | Terminated / fully deactivated |
| "Onboarding" | Onboarding | 3 | New client being set up |
| "Suspended" | Suspended | 4 | Temporarily paused |
| "Reactivation Onboarding" | ReactivationOnboarding | 5 | Previously inactive client being re-onboarded |
Source: Services/Services/Enums/ClientNewStatusEnum.cs
1.2 Client SubStatus (ClientNewSubStatusEnum)
| Display Name | Enum Value | Integer | Meaning |
|---|---|---|---|
| "Delayed" | Delayed | 1 | Onboarding >60 days without going live |
| "MTC" | MTC | 2 | Minimum Term Contract — within minimum commitment period |
| "Rolling" | Rolling | 3 | Past minimum term, month-to-month |
| "Fixed Term" | FixedTerm | 4 | Fixed-length contract |
| "Termination Notice" | TerminatioNotice | 5 | Notice period served, waiting for ServiceEndDate |
| "" (empty) | Empty | 6 | No substatus applicable (onboarding, inactive, suspended) |
| "Termination (credits left)" | TerminationCreditsLeft | 7 | Old-term client: ServiceEndDate reached but credits remain |
Source: Services/Services/Enums/ClientNewSubStatusEnum.cs
2. Status System Overview
The current system uses a two-tier model: Client.Status (major lifecycle phase) + Client.SubStatus (contract/operational detail within that phase).
┌────────────────────────────────────────────────────────────────────┐
│ Client Status / SubStatus Matrix │
├──────────────────────┬─────────────────────────────────────────────┤
│ Status │ Valid SubStatuses │
├──────────────────────┼─────────────────────────────────────────────┤
│ Onboarding │ "" (Empty), "Delayed" │
│ ReactivationOnboarding│ "" (Empty), "Delayed" │
│ ActiveMultichannel │ "MTC", "Rolling", "Fixed Term", │
│ │ "Termination Notice", │
│ │ "Termination (credits left)" │
│ PlatformFeeOnly │ "" (Empty), "Termination Notice" │
│ Suspended │ "" (Empty) │
│ Inactive │ "" (Empty) │
└──────────────────────┴─────────────────────────────────────────────┘
3. Client Creation Flow (NewBookCampaign)
The NewBookCampaign method in ClientsController.cs is the primary and currently active path for creating clients and campaigns. The older EditNew and commented-out BookCampaign methods are deprecated/legacy.
3.1 GET: NewBookCampaign (View Preparation)
View: Views/Clients/NewBookCampaign.cshtml
The GET action prepares the multi-step booking form by loading:
- Client model (if existing client)
- Packages, currencies, taxes
- Payment terms, contract types
- Invoice-to options (Client, Soteq, G33K Holding)
- Lead sources, white label partners
- Campaign type (Internal, External, Incubator, Test)
3.2 POST: NewBookCampaign (Creation Logic)
Flow diagram:
3.3 Campaign Status Mapping in NewBookCampaign
| Scenario | Client Status | Client SubStatus | Campaign Status |
|---|---|---|---|
| New client | Onboarding | "" (Empty) | New_Status_Onboarding |
| Reactivating inactive client | ReactivationOnboarding | "" (Empty) | New_Status_Reactivation_Onboarding |
| Existing client (adding campaign) | Unchanged | Unchanged | New_Status_Reactivation_Onboarding (resets FinanceApproved) |
3.4 Go Live (Campaign Activation)
Method: CampaignController.GoLive → CampaignService.CampaignGoLive
View: Button on campaign status/edit page
Controller: CampaignController.cs (line ~2315) / Service: CampaignService.cs (line ~1596)
The Go Live action is the bridge between client Onboarding and Active status. It is triggered by Ops when a campaign is ready to start prospecting.
Preconditions:
- Campaign
Statusmust beNew_Status_OnboardingorNew_Status_Reactivation_Onboarding - Campaign must be
FinanceApproved == true
Campaign changes:
Status→New_Status_ActiveDeliveryDate→DateTime.Now(today's date)SubStatus→""(cleared)
Client changes (only if this is the first active campaign, or client is in Onboarding/ReactivationOnboarding/Inactive):
Status→ActiveMultichannelService("Active - Multichannel service")SubStatus→MTC(ifContractTermType == "MTC") orFixedTerm(ifContractTermType == "Fixed")MonthlyInvoiceDate→ set to the day-of-month of Go Live (capped at 28th)
Multi-campaign protection: If the client already has other active campaigns, the Go Live will NOT change the client's Status/SubStatus — the existing lifecycle is preserved. A warning is logged if the client is in FixedTerm/TerminationNotice/TerminationCreditsLeft/PlatformFeeOnly without a new contract.
Side effects:
- Creates monthly invoice for billable, non-batch-billing clients
- Sends Go Live email notification to client
- Sends campaign live survey (first campaign only, non-white-label)
- Syncs Planhat
4. Status Transition Actions (User-Triggered)
All of these are POST actions in ClientsController.cs.
4.1 Terminate Client
View: Views/Clients/TerminateClient.cshtml
POST: /Clients/TerminateClient
Key fields set:
TerminationDate— when service endsDeliveryEndDate— only for old-term contracts (90 days after TerminationDate default)PlatformFeeDate— set to null (cancels any pending platform fee transition)PrimaryTerminationReason/SecondaryTerminationReason/TerminationReasonOther— stored in log
4.2 Suspend Client
View: Views/Clients/SuspendClient.cshtml
POST: /Clients/SuspendClient
Status → Suspended
SubStatus → "" (Empty)
Side effects: DisableWidgetForClient, SendClientSuspensionNotification
4.3 Terminate Immediately
View: Views/Clients/TerminateImmediatelyClient.cshtml
POST: /Clients/TerminateImmediatelyClient
Status → Inactive
SubStatus → "" (Empty)
4.4 Cancel Termination
POST: /Clients/CancelTermination
Precondition: Status == ActiveMultichannelService AND SubStatus == Termination Notice
Action: SubStatus → Rolling, clear TerminationDate + DeliveryEndDate
4.5 Platform Fee Actions
4.5.1 Schedule Platform Fee (from Terminate view)
POST: /Clients/PlatformFeeClient
Sets PlatformFeeDate on the client. The actual status change happens in the nightly job when that date is reached.
PlatformFeeDate → user-selected date
TerminationDate → null (cancels termination)
DeliveryEndDate → null
4.5.2 Platform Fee Suspend — Back to Active Multichannel
View: Views/Clients/PlatformFeeSuspend.cshtml
POST: /Clients/PlatformFeeSuspend (with Terminate = false)
4.5.3 Platform Fee Suspend — Terminate Permanently
POST: /Clients/PlatformFeeSuspend (with Terminate = true)
Status → Inactive
SubStatus → "" (Empty)
4.6 Reactivate from Platform Only
View: Views/Clients/ReactivateFromPlatformOnly.cshtml
POST: /Clients/ReactivateFromPlatformOnly
Precondition: Status == PlatformFeeOnly
Action:
Status → ActiveMultichannelService
SubStatus → Rolling
Save new MultichannelFee package
4.7 Reactivate Legacy Client
View: Views/Clients/ReactivateLegacyClient.cshtml
POST: /Clients/ReactivateLegacyClient
Status → ActiveMultichannelService
SubStatus → Termination (credits left)
Set TerminationDate + DeliveryEndDate
Update package
5. Nightly Status Recalculation (_RecalculateCampaignsStatus)
Schedule: Daily at 01:12 AM (Hangfire recurring job)
Source: Services/Services/HangFireJobServiceDB.cs (line ~2564)
The method runs 13 logic blocks in sequence. All changes are tracked via ClientChangeStatuses and CampaingChangeStatuses lists.
5.1 Logic Blocks Overview
5.2 Detailed Block Descriptions
Block 1 — MTC Correction
- Query: Clients where
SubStatus == FixedTermANDContractTermType == MTC - Action: Set
SubStatus = MTC - Purpose: Fixes data inconsistency where a client with an MTC contract was incorrectly marked as Fixed Term.
Block 2 — Fixed Term Correction
- Query: Clients where
SubStatus == MTCANDContractTermType == Fixed - Action: Set
SubStatus = FixedTerm - Purpose: Mirrors Block 1 for the opposite inconsistency.
Block 3 — MTC → Rolling Transition (Req 2.1)
- Query: Clients with
Status == ActiveMultichannelServiceANDSubStatus == MTC - Condition:
today >= MIN(active campaign DeliveryDate) + contract.MinimumTermMonths - Action: Set
SubStatus = Rolling - Business meaning: Client has completed their minimum commitment period and is now on a flexible month-to-month basis.
Block 4 — Fixed Term Expiry (Req 2.2)
- Query: Clients with
SubStatus == FixedTerm - Condition:
today >= MAX(campaign DeliveryDate) + contract.MinimumTermMonths - Action:
Status → Inactive,SubStatus → ""(Empty)- Clear
TerminationDate,TerminationReason - Set
IsPluginVerified = false - Deactivate all non-inactive campaigns
- Remove widget verification
Block 5 — New Term Client Final Termination (Req 2.3)
- Query:
TerminationDate <= todayANDStatus != InactiveANDClientContractualStatus in (NetNew, MigratedLegacy) - Action:
Status → Inactive,SubStatus → ""(Empty)- Clear
TerminationDate,TerminationReason - Set
IsPluginVerified = false - Deactivate all non-inactive campaigns
- Remove widget verification
Block 6 — Old Term Client: Service End Date Reached (Req 2.4 part 1)
- Query:
TerminationDate <= todayANDStatus != InactiveANDDeliveryEndDate != nullANDSubStatus != TerminationCreditsLeftANDClientContractualStatus in (ProtectedLegacy, ExceptionClients) - Action:
SubStatus → Termination (credits left), clearTerminationDate - Business meaning: The notice period has ended but the client still has paid-for credits to use until
DeliveryEndDate.
Block 7 — Old Term Client: Delivery End Date Reached (Req 2.4 part 2)
- Query:
DeliveryEndDate <= todayANDStatus != InactiveANDDeliveryEndDate != null - Action:
Status → Inactive,SubStatus → ""(Empty)- Clear
TerminationDate,DeliveryEndDate,TerminationReason - Set
IsPluginVerified = false - Deactivate all non-inactive campaigns
- Remove widget verification
Block 8 — Platform Fee Activation
- Query:
PlatformFeeDate <= todayANDStatus != Inactive - Action:
Status → PlatformFeeOnly,SubStatus → ""(Empty)- Clear
PlatformFeeDate - Assign platform fee package (from
_packageService.GetPlatformFee()) - Log
TerminateSuspendCliententry withActivePlatformOnly
Block 9 — Platform Only Termination (Req 3.2)
- Query:
Status == PlatformFeeOnlyANDSubStatus == Termination NoticeANDTerminationDate <= today - Action:
Status → Inactive,SubStatus → ""(Empty)- Clear
TerminationDate,TerminationReason - Set
IsPluginVerified = false - Deactivate all non-inactive campaigns
- Remove widget verification (only primary domain)
Block 10 — Finance Approval
- Query: Campaigns where
CampaignExtended.FinanceApproved == false - Condition (billable): All client invoices in Xero are AUTHORISED or PAID with zero due
- Condition (non-billable): Always approved
- Action: Set
FinanceApproved = true, setFinanceApprovedById = 2,FinanceApprovedDate = now
Block 11 — Batch Billing Removal
- Query: Clients with
BatchBilling == trueAND having campaigns withDeliveryDate != null - Condition:
max(DeliveryDate) + BatchBillingMonths (adjusted for Advance payment) <= today - Action: Set
BatchBilling = false
Block 12 — Delayed Status
- Query:
SubStatus != DelayedANDStatus in (Onboarding, ReactivationOnboarding) - Condition:
max(Campaign.CreateDate) + 60 days <= today - Action: Set
SubStatus = Delayed - Business meaning: Client has been in onboarding for over 60 days without going live — flags for CS attention.
Block 13 — Campaign Deactivation
- Query: Campaigns with
DeactivationDate <= today - Action: Set
Status = New_Status_Inactive, clearDeactivationDate
6. Complete Lifecycle State Machine
7. Active vs Deprecated Paths
Active Code Paths
| Method | Controller | Status |
|---|---|---|
NewBookCampaign (GET/POST) | ClientsController | ✅ Active — primary client/campaign creation |
GoLive (POST) | CampaignController | ✅ Active — transitions Onboarding → Active |
TerminateClient (GET/POST) | ClientsController | ✅ Active |
SuspendClient (GET/POST) | ClientsController | ✅ Active |
TerminateImmediatelyClient (GET/POST) | ClientsController | ✅ Active |
CancelTermination (POST) | ClientsController | ✅ Active |
PlatformFeeClient (POST) | ClientsController | ✅ Active |
PlatformFeeSuspend (GET/POST) | ClientsController | ✅ Active |
ReactivateFromPlatformOnly (GET/POST) | ClientsController | ✅ Active |
ReactivateLegacyClient (GET/POST) | ClientsController | ✅ Active |
_RecalculateCampaignsStatus | HangFireJobServiceDB | ✅ Active — nightly Hangfire job |
Deprecated / Commented-Out Paths
| Method | Controller | Status |
|---|---|---|
EditNew (GET/POST) | ClientsController | ⚠️ Deprecated — superseded by NewBookCampaign |
BookCampaign (GET/POST) | ClientsController | ❌ Fully commented out |
Platform Fee inline logic in PlatformFeeClient | ClientsController | ❌ Commented out — now handled by nightly job |
| Old campaign substatus transitions (MTC/FixedTerm/Inactive) | HangFireJobServiceDB | ❌ Commented out — moved to client-level statuses |
Key Architectural Decision
The system migrated from campaign-level status management to client-level status management. Previously, campaign statuses like Active: MTC, Active: Fixed Term, Active: Rolling etc. were the source of truth. Now these are tracked at the Client.Status + Client.SubStatus level, and campaign-level substatuses are no longer actively managed by the nightly job.
8. Key Data Fields
Client Entity (Critical Status Fields)
| Field | Type | Purpose |
|---|---|---|
Status | string | Major lifecycle phase (enum display name from ClientNewStatusEnum) |
SubStatus | string | Contract/operational detail (enum display name from ClientNewSubStatusEnum) |
TerminationDate | DateTime? | When service ends (notice period expiration) |
DeliveryEndDate | DateTime? | For old-term clients: final date credits can be used |
PlatformFeeDate | DateTime? | Scheduled date to transition to PlatformFeeOnly |
ClientContractualStatus | int | NetNew(0), MigratedLegacy(1), ProtectedLegacy(2), ExceptionClients(3) |
ContractTermType | string | "MTC" or "Fixed" |
Billable | bool? | Whether client is billable |
BatchBilling | bool | Whether invoices are batched |
BatchBillingMonths | int | Duration of batch billing |
IsPluginVerified | bool | Widget verification status |
PaymentTerm | string | "Advance" or "Arrears" |
Campaign Entity (Key Status Fields)
| Field | Type | Purpose |
|---|---|---|
Status | string | New_Status_Onboarding, New_Status_Reactivation_Onboarding, New_Status_Active, New_Status_Inactive |
DeliveryDate | DateTime? | Go-live date |
DeactivationDate | DateTime? | Scheduled campaign deactivation |
FinanceApproved | bool? | Finance sign-off (on CampaignExtended) |
ContractTermType | string | Same as client level |
TerminationNoticeMonths | int | Notice period in months |
Contract Entity (Key Fields)
| Field | Type | Purpose |
|---|---|---|
MinimumTermMonths | int | Minimum commitment before rolling |
StartDate | DateTime? | Contract start date |
9. Related Views
All views are in Sodastream/Web/Web/Views/Clients/:
| View | Purpose | Active? |
|---|---|---|
NewBookCampaign.cshtml | Multi-step client + campaign creation form | ✅ Active |
EditNew.cshtml | Legacy client edit form | ⚠️ Deprecated |
TerminateClient.cshtml | Termination modal (select service end date, reasons) | ✅ Active |
SuspendClient.cshtml | Suspend confirmation modal | ✅ Active |
TerminateImmediatelyClient.cshtml | Immediate termination modal | ✅ Active |
PlatformFeeSuspend.cshtml | Platform fee suspend/reactivate modal | ✅ Active |
ReactivateFromPlatformOnly.cshtml | Finance reactivation modal (select package) | ✅ Active |
ReactivateLegacyClient.cshtml | Legacy client reactivation form | ✅ Active |
BookCampaign.cshtml | Old booking form | ❌ Deprecated |
Index.cshtml | Client listing (DataTables) | ✅ Active |
Contracts.cshtml | Client contracts partial | ✅ Active |
Documents.cshtml | Client documents | ✅ Active |
CampaignDetails.cshtml | Campaign details for a client | ✅ Active |
AddUsersToClient.cshtml | Assign users to client | ✅ Active |
UploadDocument.cshtml | Upload document modal | ✅ Active |
_MultiChannelFee.cshtml | Multichannel fee partial | ✅ Active |
_PlatformFee.cshtml | Platform fee partial | ✅ Active |
_AddOnServicePlans.cshtml | Add-on service plans partial | ✅ Active |
_GenerateDocument.cshtml | Document generation partial | ✅ Active |
Appendix A: Quick Reference — Status Transition Triggers
| From | To | Trigger | Method |
|---|---|---|---|
| (new) | Onboarding / "" | New client created | NewBookCampaign POST |
| Inactive / "" | ReactivationOnboarding / "" | Inactive client reactivated | NewBookCampaign POST |
| TerminationCreditsLeft | ReactivationOnboarding / "" | Previous term client rebooked | NewBookCampaign POST |
| Onboarding / "" | ActiveMultichannel / MTC | Campaign goes live (MTC contract) | GoLive POST |
| Onboarding / "" | ActiveMultichannel / FixedTerm | Campaign goes live (Fixed contract) | GoLive POST |
| ReactivationOnboarding / "" | ActiveMultichannel / MTC | Reactivation campaign goes live (MTC) | GoLive POST |
| ReactivationOnboarding / "" | ActiveMultichannel / FixedTerm | Reactivation campaign goes live (Fixed) | GoLive POST |
| Onboarding / "" | Onboarding / Delayed | 60+ days without live campaign | _RecalculateCampaignsStatus Block 12 |
| ReactivationOnboarding / "" | ReactivationOnboarding / Delayed | 60+ days without live campaign | _RecalculateCampaignsStatus Block 12 |
| ActiveMultichannel / MTC | ActiveMultichannel / Rolling | Min term period passed | _RecalculateCampaignsStatus Block 3 |
| ActiveMultichannel / FixedTerm | Inactive / "" | Service end date passed | _RecalculateCampaignsStatus Block 4 |
| ActiveMultichannel / * | ActiveMultichannel / Termination Notice | User terminates (new term) | TerminateClient POST |
| ActiveMultichannel / * | ActiveMultichannel / TerminationCreditsLeft | User terminates (old term) | TerminateClient POST |
| ActiveMultichannel / Termination Notice | ActiveMultichannel / Rolling | User cancels termination | CancelTermination POST |
| ActiveMultichannel / Termination Notice | Inactive / "" | TerminationDate reached (new term) | _RecalculateCampaignsStatus Block 5 |
| ActiveMultichannel / TerminationCreditsLeft | Inactive / "" | DeliveryEndDate reached (old term) | _RecalculateCampaignsStatus Block 7 |
| ActiveMultichannel / * | Suspended / "" | User suspends | SuspendClient POST |
| ActiveMultichannel / * | Inactive / "" | User terminates immediately | TerminateImmediatelyClient POST |
| ActiveMultichannel / * | PlatformFeeOnly / "" | PlatformFeeDate reached | _RecalculateCampaignsStatus Block 8 |
| PlatformFeeOnly / "" | ActiveMultichannel / MTC | Finance reactivation (back to active) | PlatformFeeSuspend POST |
| PlatformFeeOnly / "" | ActiveMultichannel / Rolling | Finance reactivation | ReactivateFromPlatformOnly POST |
| PlatformFeeOnly / "" | Inactive / "" | Platform terminate | PlatformFeeSuspend POST (Terminate=true) |
| PlatformFeeOnly / Termination Notice | Inactive / "" | TerminationDate reached | _RecalculateCampaignsStatus Block 9 |
| Inactive / "" | ActiveMultichannel / TerminationCreditsLeft | Legacy reactivation | ReactivateLegacyClient POST |
Documentation generated from source code analysis of
ClientsController.cs,CampaignController.cs,CampaignService.cs, andHangFireJobServiceDB.cs.
For questions about specific status transitions or edge cases, consult the development team.