Client Statuses at a Glance

Every client has a Status (lifecycle phase) and a SubStatus (contract detail).

Status (what stage the client is in)

Onboarding Active — Multichannel service Active — Platform Only Suspended Inactive Reactivation Onboarding

SubStatus (what contract type or situation)

MTC Rolling Fixed Term Delayed Termination Notice Termination (credits left)

Which SubStatus goes with which Status?

StatusPossible SubStatuses
Onboarding— (empty) or Delayed
Reactivation Onboarding— (empty) or Delayed
Active — MultichannelMTC, Rolling, Fixed Term, Termination Notice, Termination (credits left)
Active — Platform Only— (empty) or Termination Notice
Suspended— (empty)
Inactive— (empty)

The Client Journey — End to End

How a client moves through the system, from creation to termination.
flowchart TD CREATE["New Client Created via NewBookCampaign form"] CREATE --> ONBOARD["Onboarding - Status Onboarding, SubStatus empty"] ONBOARD --> DELAYED{"60 plus days without going live?"} DELAYED -->|Yes| DELAY["Delayed - flags for CS attention"] DELAYED -->|No| WAIT["Waiting for Go Live"] DELAY --> WAIT WAIT --> GOLIVE["Go Live - Ops clicks button, Requires Finance Approved"] GOLIVE --> MTC["Active MTC - minimum term contract"] GOLIVE --> FIXED["Active Fixed Term - fixed-length contract"] MTC --> ROLLING["Active Rolling - min term completed, month-to-month"] FIXED --> INACTIVE["Inactive"] ROLLING --> TERMINATE{"Termination initiated?"} TERMINATE -->|New term client| TERM_NOTICE["Termination Notice - notice period active"] TERMINATE -->|Old term client| TERM_CREDITS["Termination credits left - credits available"] TERM_NOTICE -->|Notice period ends| INACTIVE TERM_NOTICE -->|Cancelled| ROLLING TERM_CREDITS -->|Credits exhausted| INACTIVE ROLLING --> SUSPEND["Suspended - temporarily paused"] SUSPEND --> ROLLING ROLLING --> PLATFORM["Platform Only - downgraded to platform fee"] PLATFORM -->|Reactivated| MTC PLATFORM -->|Terminated| INACTIVE INACTIVE -->|Client rebooked| REACTIVATE["Reactivation Onboarding - client rebooked via NewBookCampaign"] REACTIVATE --> GOLIVE style CREATE fill:#dbeafe,stroke:#2563eb style ONBOARD fill:#fef3c7,stroke:#d97706 style GOLIVE fill:#d1fae5,stroke:#059669 style MTC fill:#dbeafe,stroke:#2563eb style ROLLING fill:#dbeafe,stroke:#2563eb style FIXED fill:#dbeafe,stroke:#2563eb style TERM_NOTICE fill:#fed7aa,stroke:#ea580c style TERM_CREDITS fill:#e5e7eb,stroke:#6b7280 style PLATFORM fill:#fce7f3,stroke:#be185d style SUSPEND fill:#fee2e2,stroke:#dc2626 style INACTIVE fill:#f1f5f9,stroke:#94a3b8 style REACTIVATE fill:#fef3c7,stroke:#d97706 style DELAY fill:#fed7aa,stroke:#ea580c

Common Operations & What They Do

Create a New Client

Use the New Book Campaign form. This sets the client to Onboarding and creates the first campaign. A contract and package are also created if the client is billable.

Go Live

Once the campaign setup is complete and Finance has approved, Ops clicks Go Live. The client moves from Onboarding to Active — Multichannel with SubStatus MTC or Fixed Term.

Terminate a Client

From the client page, select Terminate. Choose a service end date. The client stays Active but moves to Termination Notice (new clients) or Termination (credits left) (legacy clients). The nightly job automatically moves them to Inactive when the date arrives.

Suspend a Client

Temporarily pauses the client. Status becomes Suspended. Widget is disabled. Can be manually unsuspended.

Cancel a Termination

If a termination was initiated but the client changes their mind, Cancel Termination moves SubStatus back to Rolling.

Move to Platform Only

Schedule a Platform Fee start date. On that date (during nightly processing), the client transitions to Active — Platform Only with a platform-fee package.

Reactivate from Platform Only

Finance can reactivate a Platform Only client back to Active — Multichannel with a new package. SubStatus becomes Rolling or MTC.

What Happens Automatically (Nightly Job)

Every night at 1:12 AM, the system runs _RecalculateCampaignsStatus to handle time-based transitions.
What changesWhen
MTC → RollingMinimum contract term has passed
Fixed Term → InactiveFixed contract service end date reached
Termination Notice → InactiveThe termination service end date arrives
Termination (credits left) → InactiveThe delivery end date arrives (credits exhausted)
→ Platform OnlyScheduled Platform Fee date arrives
Onboarding → Delayed60+ days in Onboarding without going live
Campaign deactivatedScheduled deactivation date arrives
Finance ApprovedAll Xero invoices are paid/authorised
💡 Tip: If a client looks stuck in a status, check whether a nightly job transition is pending (e.g. waiting for a date to arrive). The nightly job only runs once per day.

Nightly Job Flow Diagram

Visual walkthrough of the automated nightly status recalculation process.
flowchart TD NIGHTLY["Nightly Job Runs at 1:12 AM"] NIGHTLY --> CORR["Contract Corrections"] CORR --> CB1["Fix MTC-FixedTerm mismatch"] CORR --> CB2["Fix Fixed-MTC mismatch"] NIGHTLY --> LIFE["Lifecycle Progression"] LIFE --> LB1["MTC to Rolling when min term passed"] LIFE --> LB2["Fixed Term to Inactive when service ended"] NIGHTLY --> TERM["Termination Processing"] TERM --> TB1["New term: TerminationDate becomes Inactive"] TERM --> TB2["Old term: TerminationDate becomes credits left"] TERM --> TB3["Old term: DeliveryEndDate becomes Inactive"] NIGHTLY --> PFEE["Platform Fee"] PFEE --> PB1["PlatformFeeDate becomes Platform Only"] PFEE --> PB2["Platform Only plus TerminationDate becomes Inactive"] NIGHTLY --> OTHR["Other Checks"] OTHR --> OB1["Finance Approval via Xero"] OTHR --> OB2["Remove BatchBilling when done"] OTHR --> OB3["Onboarding 60 plus days becomes Delayed"] OTHR --> OB4["DeactivationDate becomes Inactive"]

Enum Definitions

Source: Services/Services/Enums/ClientNewStatusEnum.cs and ClientNewSubStatusEnum.cs

ClientNewStatusEnum

Display NameEnum ValueIntMeaning
Active - Multichannel serviceActiveMultichannelService0Live client with active multichannel campaigns
Active - Platform OnlyPlatformFeeOnly1Downgraded to platform-fee-only package
InactiveInactive2Terminated / fully deactivated
OnboardingOnboarding3New client being set up
SuspendedSuspended4Temporarily paused
Reactivation OnboardingReactivationOnboarding5Previously inactive client being re-onboarded

ClientNewSubStatusEnum

Display NameEnum ValueIntMeaning
DelayedDelayed1Onboarding >60 days without going live
MTCMTC2Minimum Term Contract — within minimum commitment period
RollingRolling3Past minimum term, month-to-month
Fixed TermFixedTerm4Fixed-length contract
Termination NoticeTerminatioNotice5Notice period served, waiting for ServiceEndDate
(empty)Empty6No substatus applicable
Termination (credits left)TerminationCreditsLeft7Old-term client: ServiceEndDate reached but credits remain

Client Creation: NewBookCampaign

Primary path — ClientsController.NewBookCampaign (POST)
flowchart TD START["POST NewBookCampaign"] --> GET["GetClientById"] GET -->|not found| NEW["Create New Client"] GET -->|found| CHK{"Inactive or TerminationCreditsLeft?"} NEW --> SN["Status Onboarding, SubStatus empty"] NEW --> FL["newClient true"] CHK -->|Yes| RA["Status ReactivationOnboarding"] CHK -->|No| EX["Keep Status, clear dates"] SN --> POP["Populate Client fields"] RA --> POP EX --> POP FL --> POP POP --> SAV["_clientService.AddOrUpdateClient"] SAV -->|newClient| SET["Create default ClientSettings"] SAV --> CP{"DontCreateCampaign?"} CP -->|No| NC["Create Campaign Onboarding"] NC --> SC["_campaignService.AddOrUpdate"] SC --> EX2{"DontCreateCampaign?"} EX2 -->|No| NE["Create CampaignExtended"] POP --> BL{"model.Billable?"} BL -->|Yes| CT{"DontCreateContract?"} CT -->|No| SCT["_contractService.AddOrUpdate"] BL -->|Yes| PK{"DontCreatePackage?"} PK -->|No| SPK["_clientMultichanelFeeService.Insert"] BL -->|Yes and GenerateInvoice| INV["_invoiceService.AddSetupFee"] SC --> PH["Sync Planhat"] PH --> RET["Return JSON with IDs"]

Campaign Status Mapping

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)

Go Live (Campaign Activation)

CampaignController.GoLiveCampaignService.CampaignGoLive
flowchart TD A["Campaign in Onboarding or Reactivation"] --> B{"FinanceApproved?"} B -->|No| REJECT["Error not Finance Approved"] B -->|Yes| C["Set Campaign Active, DeliveryDate Now"] C --> D{"Other active campaigns exist?"} D -->|No| E["Client ActiveMultichannel, SubStatus MTC or FixedTerm"] D -->|Yes| F{"Client in Onboarding, Reactivation, or Inactive?"} F -->|Yes| E F -->|No| G["Preserve existing Status, log warning"] E --> H["Set MonthlyInvoiceDate"] G --> H H --> I["Create monthly invoice if billable"] I --> J["Send notifications, sync Planhat"]
Multi-campaign protection: If the client already has other active campaigns, GoLive will NOT overwrite the existing Status/SubStatus. The lifecycle is governed by the original contract.

User-Triggered Status Transitions

All POST actions in ClientsController.cs

Terminate Client

flowchart LR A["Active Client"] -->|Terminate| B{"ContractualStatus?"} B -->|NetNew or MigratedLegacy| C["ActiveMultichannel, Termination Notice, set TerminationDate"] B -->|ProtectedLegacy or ExceptionClients| D["ActiveMultichannel, Termination credits left, set both dates"] C --> E["AddTerminateSuspendClient log"] D --> E E --> F["Send ClientTerminationNotification"] F --> G["Planhat CreateChurn"]

Platform Fee Suspend — Back to Active

flowchart TD A["Client on PlatformFeeOnly"] --> B["Select new Package"] B --> C["Update ClientMultiChanelFee"] C --> D["ActiveMultichannel, SubStatus MTC"] D --> E["Send SuspensionNotification"] E --> F["Log BackToActive"]

Other Actions

ActionPreconditionResult
Suspend ClientStatus → Suspended, SubStatus → ""
Terminate ImmediatelyStatus → Inactive, SubStatus → ""
Cancel TerminationActiveMultichannel + Termination NoticeSubStatus → Rolling
Platform Fee ScheduleSets PlatformFeeDate (nightly job executes)
Platform Fee TerminatePlatformFeeOnlyStatus → Inactive
Reactivate from Platform OnlyPlatformFeeOnlyStatus → ActiveMultichannelService, SubStatus → Rolling
Reactivate Legacy ClientStatus → ActiveMultichannelService, SubStatus → TerminationCreditsLeft

Nightly _RecalculateCampaignsStatus

Hangfire recurring job, daily at 01:12 AM. Source: HangFireJobServiceDB.cs
flowchart TD subgraph CORRECT [Blocks 1 and 2: Contract Type Corrections] B1["B1: SubStatus is FixedTerm but Contract is MTC - Correct to MTC"] B2["B2: SubStatus is MTC but Contract is Fixed - Correct to FixedTerm"] end subgraph LIFE [Blocks 3 and 4: Lifecycle Progression] B3["B3: MTC to Rolling when min DeliveryDate plus MinimumTermMonths reached"] B4["B4: Fixed Term Expiry when max DeliveryDate plus MinimumTermMonths reached"] end subgraph TERM [Blocks 5, 6, 7: Termination Processing] B5["B5: TerminationDate reached for NetNew or MigratedLegacy becomes Inactive"] B6["B6: TerminationDate reached for ProtectedLegacy or ExceptionClients becomes TerminationCreditsLeft"] B7["B7: DeliveryEndDate reached becomes Inactive"] end subgraph PFEE [Blocks 8 and 9: Platform Fee] B8["B8: PlatformFeeDate reached becomes PlatformFeeOnly with platform package"] B9["B9: PlatformFeeOnly plus TerminationNotice plus TerminationDate reached becomes Inactive"] end subgraph OTHR [Blocks 10 to 13: Other] B10["B10: Finance Approval - invoices AUTHORISED or PAID in Xero"] B11["B11: Remove BatchBilling when delivery duration passed"] B12["B12: 60 plus days since last campaign in Onboarding becomes Delayed"] B13["B13: Campaign DeactivationDate reached becomes Inactive"] end B1 --> B2 --> B3 --> B4 --> B5 --> B6 --> B7 --> B8 --> B9 --> B10 --> B11 --> B12 --> B13

Complete Lifecycle State Machine

stateDiagram-v2 [*] --> Onboarding : NewBookCampaign (new client) Onboarding --> Onboarding_Delayed : 60 plus days (nightly) Onboarding_Delayed --> Onboarding : manual edit Onboarding --> Active_MTC : GoLive MTC contract Onboarding --> Active_FixedTerm : GoLive Fixed contract ReactOnboarding --> ReactOnboarding_Delayed : 60 plus days (nightly) ReactOnboarding_Delayed --> ReactOnboarding : manual edit ReactOnboarding --> Active_MTC : GoLive MTC contract ReactOnboarding --> Active_FixedTerm : GoLive Fixed contract Active_MTC --> Active_Rolling : Min term passed (nightly) Active_MTC --> Suspended : Suspend (user) Active_MTC --> Inactive : Terminate Immediately (user) Active_MTC --> PFO_Empty : PlatformFeeDate reached Active_FixedTerm --> Inactive : Service end reached (nightly) Active_FixedTerm --> Suspended : Suspend (user) Active_FixedTerm --> PFO_Empty : PlatformFeeDate reached Active_Rolling --> Active_TerminationNotice : Terminate new term (user) Active_Rolling --> Suspended : Suspend (user) Active_Rolling --> Inactive : Terminate Immediately (user) Active_Rolling --> PFO_Empty : PlatformFeeDate reached Active_TerminationNotice --> Active_Rolling : Cancel Termination (user) Active_TerminationNotice --> Inactive : TerminationDate reached Active_TerminationNotice --> Suspended : Suspend (user) Active_TerminationNotice --> PFO_Empty : PlatformFeeDate reached Active_TermCreditsLeft --> Inactive : DeliveryEndDate reached Active_TermCreditsLeft --> Suspended : Suspend (user) Active_TermCreditsLeft --> PFO_Empty : PlatformFeeDate reached Active_Rolling --> Active_TermCreditsLeft : Terminate old term (user) Active_MTC --> Active_TermCreditsLeft : Terminate old term (user) Active_FixedTerm --> Active_TermCreditsLeft : Terminate old term (user) Suspended --> Active_MTC : Unsuspend (manual) Suspended --> Active_FixedTerm : Unsuspend (manual) Suspended --> Active_Rolling : Unsuspend (manual) Suspended --> Active_TerminationNotice : Unsuspend (manual) Suspended --> Active_TermCreditsLeft : Unsuspend (manual) PFO_Empty --> PFO_TermNotice : Terminate Platform Only (user) PFO_Empty --> Active_MTC : PlatformFeeSuspend (user) PFO_Empty --> Active_Rolling : ReactivateFromPlatformOnly (user) PFO_TermNotice --> Inactive : TerminationDate reached Inactive --> ReactOnboarding : NewBookCampaign (reactivate) Inactive --> Active_TermCreditsLeft : ReactivateLegacyClient (user) note right of Active_TermCreditsLeft Old term clients only (ProtectedLegacy / ExceptionClients) end note note right of Active_TerminationNotice New term clients only (NetNew / MigratedLegacy) end note

Key Data Fields

EntityFieldTypePurpose
ClientStatusstringMajor lifecycle phase (friendly name from ClientNewStatusEnum)
ClientSubStatusstringContract/operational detail (friendly name from ClientNewSubStatusEnum)
ClientTerminationDateDateTime?When service ends (notice period expiration)
ClientDeliveryEndDateDateTime?Old-term clients: final date credits can be used
ClientPlatformFeeDateDateTime?Scheduled date to transition to PlatformFeeOnly
ClientClientContractualStatusintNetNew(0), MigratedLegacy(1), ProtectedLegacy(2), ExceptionClients(3)
ClientContractTermTypestring"MTC" or "Fixed"
CampaignStatusstringNew_Status_Onboarding, New_Status_Reactivation_Onboarding, New_Status_Active, New_Status_Inactive
CampaignDeliveryDateDateTime?Go-live date
CampaignDeactivationDateDateTime?Scheduled campaign deactivation
CampaignExtendedFinanceApprovedbool?Finance sign-off (required for GoLive)
ContractMinimumTermMonthsintMinimum commitment before rolling

Quick Reference: All Status Transitions

FromToTriggerMethod
(new)Onboarding / ""New client createdNewBookCampaign
Inactive / ""ReactivationOnboarding / ""Inactive client reactivatedNewBookCampaign
TerminationCreditsLeftReactivationOnboarding / ""Previous term client rebookedNewBookCampaign
Onboarding / ""Active / MTCCampaign goes live (MTC)GoLive
Onboarding / ""Active / FixedTermCampaign goes live (Fixed)GoLive
ReactivationOnboarding / ""Active / MTCReactivation goes live (MTC)GoLive
ReactivationOnboarding / ""Active / FixedTermReactivation goes live (Fixed)GoLive
Onboarding / ""Onboarding / Delayed60+ days without going liveNightly Block 12
Active / MTCActive / RollingMin term period passedNightly Block 3
Active / FixedTermInactive / ""Service end date passedNightly Block 4
Active / *Active / Termination NoticeUser terminates (new term)TerminateClient
Active / *Active / TerminationCreditsLeftUser terminates (old term)TerminateClient
Active / Termination NoticeActive / RollingUser cancels terminationCancelTermination
Active / Termination NoticeInactive / ""TerminationDate reached (new term)Nightly Block 5
Active / TerminationCreditsLeftInactive / ""DeliveryEndDate reached (old term)Nightly Block 7
Active / *Suspended / ""User suspendsSuspendClient
Active / *Inactive / ""User terminates immediatelyTerminateImmediatelyClient
Active / *PlatformFeeOnly / ""PlatformFeeDate reachedNightly Block 8
PlatformFeeOnly / ""Active / MTCFinance reactivationPlatformFeeSuspend
PlatformFeeOnly / ""Active / RollingFinance reactivationReactivateFromPlatformOnly
PlatformFeeOnly / ""Inactive / ""Platform terminatePlatformFeeSuspend (Terminate=true)
PlatformFeeOnly / Termination NoticeInactive / ""TerminationDate reachedNightly Block 9
Inactive / ""Active / TerminationCreditsLeftLegacy reactivationReactivateLegacyClient

Active vs Deprecated Code Paths

MethodControllerStatus
NewBookCampaignClientsController✅ Active — primary client/campaign creation
GoLiveCampaignController✅ Active — Onboarding → Active transition
TerminateClientClientsController✅ Active
SuspendClientClientsController✅ Active
TerminateImmediatelyClientClientsController✅ Active
CancelTerminationClientsController✅ Active
PlatformFeeClientClientsController✅ Active
PlatformFeeSuspendClientsController✅ Active
ReactivateFromPlatformOnlyClientsController✅ Active
ReactivateLegacyClientClientsController✅ Active
_RecalculateCampaignsStatusHangFireJobServiceDB✅ Active — nightly Hangfire job
EditNewClientsController⚠️ Deprecated
BookCampaignClientsController❌ Fully commented out