Skip to main content

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

  1. Enum Definitions
  2. Status System Overview
  3. Client Creation Flow (NewBookCampaign)
  4. Status Transition Actions (User-Triggered)
  5. Nightly Status Recalculation (_RecalculateCampaignsStatus)
  6. Complete Lifecycle State Machine
  7. Active vs Deprecated Paths
  8. Key Data Fields
  9. Related Views

1. Enum Definitions

1.1 Client Status (ClientNewStatusEnum)

Display NameEnum ValueIntegerMeaning
"Active - Multichannel service"ActiveMultichannelService0Live client with active multichannel campaigns
"Active - Platform Only"PlatformFeeOnly1Downgraded to platform-fee-only package
"Inactive"Inactive2Terminated / fully deactivated
"Onboarding"Onboarding3New client being set up
"Suspended"Suspended4Temporarily paused
"Reactivation Onboarding"ReactivationOnboarding5Previously inactive client being re-onboarded

Source: Services/Services/Enums/ClientNewStatusEnum.cs

1.2 Client SubStatus (ClientNewSubStatusEnum)

Display NameEnum ValueIntegerMeaning
"Delayed"Delayed1Onboarding >60 days without going live
"MTC"MTC2Minimum Term Contract — within minimum commitment period
"Rolling"Rolling3Past minimum term, month-to-month
"Fixed Term"FixedTerm4Fixed-length contract
"Termination Notice"TerminatioNotice5Notice period served, waiting for ServiceEndDate
"" (empty)Empty6No substatus applicable (onboarding, inactive, suspended)
"Termination (credits left)"TerminationCreditsLeft7Old-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

ScenarioClient StatusClient SubStatusCampaign Status
New clientOnboarding"" (Empty)New_Status_Onboarding
Reactivating inactive clientReactivationOnboarding"" (Empty)New_Status_Reactivation_Onboarding
Existing client (adding campaign)UnchangedUnchangedNew_Status_Reactivation_Onboarding (resets FinanceApproved)

3.4 Go Live (Campaign Activation)

Method: CampaignController.GoLiveCampaignService.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 Status must be New_Status_Onboarding or New_Status_Reactivation_Onboarding
  • Campaign must be FinanceApproved == true

Campaign changes:

  • StatusNew_Status_Active
  • DeliveryDateDateTime.Now (today's date)
  • SubStatus"" (cleared)

Client changes (only if this is the first active campaign, or client is in Onboarding/ReactivationOnboarding/Inactive):

  • StatusActiveMultichannelService ("Active - Multichannel service")
  • SubStatusMTC (if ContractTermType == "MTC") or FixedTerm (if ContractTermType == "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 ends
  • DeliveryEndDate — 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 == FixedTerm AND ContractTermType == 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 == MTC AND ContractTermType == 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 == ActiveMultichannelService AND SubStatus == 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 <= today AND Status != Inactive AND ClientContractualStatus 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 <= today AND Status != Inactive AND DeliveryEndDate != null AND SubStatus != TerminationCreditsLeft AND ClientContractualStatus in (ProtectedLegacy, ExceptionClients)
  • Action: SubStatus → Termination (credits left), clear TerminationDate
  • 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 <= today AND Status != Inactive AND DeliveryEndDate != 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 <= today AND Status != Inactive
  • Action:
    • Status → PlatformFeeOnly, SubStatus → "" (Empty)
    • Clear PlatformFeeDate
    • Assign platform fee package (from _packageService.GetPlatformFee())
    • Log TerminateSuspendClient entry with ActivePlatformOnly

Block 9 — Platform Only Termination (Req 3.2)

  • Query: Status == PlatformFeeOnly AND SubStatus == Termination Notice AND TerminationDate <= 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, set FinanceApprovedById = 2, FinanceApprovedDate = now

Block 11 — Batch Billing Removal

  • Query: Clients with BatchBilling == true AND having campaigns with DeliveryDate != null
  • Condition: max(DeliveryDate) + BatchBillingMonths (adjusted for Advance payment) <= today
  • Action: Set BatchBilling = false

Block 12 — Delayed Status

  • Query: SubStatus != Delayed AND Status 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, clear DeactivationDate

6. Complete Lifecycle State Machine


7. Active vs Deprecated Paths

Active Code Paths

MethodControllerStatus
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
_RecalculateCampaignsStatusHangFireJobServiceDB✅ Active — nightly Hangfire job

Deprecated / Commented-Out Paths

MethodControllerStatus
EditNew (GET/POST)ClientsController⚠️ Deprecated — superseded by NewBookCampaign
BookCampaign (GET/POST)ClientsController❌ Fully commented out
Platform Fee inline logic in PlatformFeeClientClientsController❌ 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)

FieldTypePurpose
StatusstringMajor lifecycle phase (enum display name from ClientNewStatusEnum)
SubStatusstringContract/operational detail (enum display name from ClientNewSubStatusEnum)
TerminationDateDateTime?When service ends (notice period expiration)
DeliveryEndDateDateTime?For old-term clients: final date credits can be used
PlatformFeeDateDateTime?Scheduled date to transition to PlatformFeeOnly
ClientContractualStatusintNetNew(0), MigratedLegacy(1), ProtectedLegacy(2), ExceptionClients(3)
ContractTermTypestring"MTC" or "Fixed"
Billablebool?Whether client is billable
BatchBillingboolWhether invoices are batched
BatchBillingMonthsintDuration of batch billing
IsPluginVerifiedboolWidget verification status
PaymentTermstring"Advance" or "Arrears"

Campaign Entity (Key Status Fields)

FieldTypePurpose
StatusstringNew_Status_Onboarding, New_Status_Reactivation_Onboarding, New_Status_Active, New_Status_Inactive
DeliveryDateDateTime?Go-live date
DeactivationDateDateTime?Scheduled campaign deactivation
FinanceApprovedbool?Finance sign-off (on CampaignExtended)
ContractTermTypestringSame as client level
TerminationNoticeMonthsintNotice period in months

Contract Entity (Key Fields)

FieldTypePurpose
MinimumTermMonthsintMinimum commitment before rolling
StartDateDateTime?Contract start date

All views are in Sodastream/Web/Web/Views/Clients/:

ViewPurposeActive?
NewBookCampaign.cshtmlMulti-step client + campaign creation form✅ Active
EditNew.cshtmlLegacy client edit form⚠️ Deprecated
TerminateClient.cshtmlTermination modal (select service end date, reasons)✅ Active
SuspendClient.cshtmlSuspend confirmation modal✅ Active
TerminateImmediatelyClient.cshtmlImmediate termination modal✅ Active
PlatformFeeSuspend.cshtmlPlatform fee suspend/reactivate modal✅ Active
ReactivateFromPlatformOnly.cshtmlFinance reactivation modal (select package)✅ Active
ReactivateLegacyClient.cshtmlLegacy client reactivation form✅ Active
BookCampaign.cshtmlOld booking form❌ Deprecated
Index.cshtmlClient listing (DataTables)✅ Active
Contracts.cshtmlClient contracts partial✅ Active
Documents.cshtmlClient documents✅ Active
CampaignDetails.cshtmlCampaign details for a client✅ Active
AddUsersToClient.cshtmlAssign users to client✅ Active
UploadDocument.cshtmlUpload document modal✅ Active
_MultiChannelFee.cshtmlMultichannel fee partial✅ Active
_PlatformFee.cshtmlPlatform fee partial✅ Active
_AddOnServicePlans.cshtmlAdd-on service plans partial✅ Active
_GenerateDocument.cshtmlDocument generation partial✅ Active

Appendix A: Quick Reference — Status Transition Triggers

FromToTriggerMethod
(new)Onboarding / ""New client createdNewBookCampaign POST
Inactive / ""ReactivationOnboarding / ""Inactive client reactivatedNewBookCampaign POST
TerminationCreditsLeftReactivationOnboarding / ""Previous term client rebookedNewBookCampaign POST
Onboarding / ""ActiveMultichannel / MTCCampaign goes live (MTC contract)GoLive POST
Onboarding / ""ActiveMultichannel / FixedTermCampaign goes live (Fixed contract)GoLive POST
ReactivationOnboarding / ""ActiveMultichannel / MTCReactivation campaign goes live (MTC)GoLive POST
ReactivationOnboarding / ""ActiveMultichannel / FixedTermReactivation campaign goes live (Fixed)GoLive POST
Onboarding / ""Onboarding / Delayed60+ days without live campaign_RecalculateCampaignsStatus Block 12
ReactivationOnboarding / ""ReactivationOnboarding / Delayed60+ days without live campaign_RecalculateCampaignsStatus Block 12
ActiveMultichannel / MTCActiveMultichannel / RollingMin term period passed_RecalculateCampaignsStatus Block 3
ActiveMultichannel / FixedTermInactive / ""Service end date passed_RecalculateCampaignsStatus Block 4
ActiveMultichannel / *ActiveMultichannel / Termination NoticeUser terminates (new term)TerminateClient POST
ActiveMultichannel / *ActiveMultichannel / TerminationCreditsLeftUser terminates (old term)TerminateClient POST
ActiveMultichannel / Termination NoticeActiveMultichannel / RollingUser cancels terminationCancelTermination POST
ActiveMultichannel / Termination NoticeInactive / ""TerminationDate reached (new term)_RecalculateCampaignsStatus Block 5
ActiveMultichannel / TerminationCreditsLeftInactive / ""DeliveryEndDate reached (old term)_RecalculateCampaignsStatus Block 7
ActiveMultichannel / *Suspended / ""User suspendsSuspendClient POST
ActiveMultichannel / *Inactive / ""User terminates immediatelyTerminateImmediatelyClient POST
ActiveMultichannel / *PlatformFeeOnly / ""PlatformFeeDate reached_RecalculateCampaignsStatus Block 8
PlatformFeeOnly / ""ActiveMultichannel / MTCFinance reactivation (back to active)PlatformFeeSuspend POST
PlatformFeeOnly / ""ActiveMultichannel / RollingFinance reactivationReactivateFromPlatformOnly POST
PlatformFeeOnly / ""Inactive / ""Platform terminatePlatformFeeSuspend POST (Terminate=true)
PlatformFeeOnly / Termination NoticeInactive / ""TerminationDate reached_RecalculateCampaignsStatus Block 9
Inactive / ""ActiveMultichannel / TerminationCreditsLeftLegacy reactivationReactivateLegacyClient POST

Documentation generated from source code analysis of ClientsController.cs, CampaignController.cs, CampaignService.cs, and HangFireJobServiceDB.cs.
For questions about specific status transitions or edge cases, consult the development team.