Cutover
Cutover is the moment a company's billing flips from at_mirrored (AT generates the invoice) to concorbit_native (concorbit generates the invoice the customer pays). By design the flip is anti-climactic: by the time a company is eligible, billing parity has been green for at least two periods, so the flip confirms the maths rather than gambling on it.
The flip lives on Settings, Autotask, Cutover.
What "eligible" means
AutotaskCoverageService::eligibilityForCutover returns a row for every company on the tenant, recomputed live from the current parity read each time. A company is blocked when any of these six reasons applies:
| Reason | What it means |
|---|---|
| at_governed_contracts_present | At least one active support contract on the company is still AT-governed. Every active contract must be concorbit-governed first, see Governance. A mixed-governance company is never auto-flippable. |
| insufficient_periods | Fewer than two closed periods have been assessed in the parity window. There simply isn't enough history yet. |
| insufficient_green_streak | The trailing run of consecutive green periods is shorter than the tenant's configured threshold (billing.cutover.min_green_periods, default 2). |
| unenumerable_lines_present | At least one active recurring line has no known billing cadence or start date, so its over-bill risk can't be assessed. |
| unmapped_recurring_charges_present | At least one recurring AT charge could not be resolved to a billing period, real AT revenue that parity can't currently see. |
| non_recurring_billing_present | The company has non-recurring AT billing (time entries, blocks, products, milestones) inside the parity window. Parity only assesses recurring lines, so this is a "make sure you've checked" flag, not necessarily unsafe. |
Companies that fail one or more of these surface on the Blocked list with their reasons, so the operator knows what to fix.
Acknowledging non-recurring billing
non_recurring_billing_present is the one reason an operator can waive. If a company is blocked only by that reason (every other reason clear, the green-streak threshold met), the flip form shows an acknowledgement checkbox: "I confirm ad-hoc labour is handled outside recurring billing." Ticking it and flipping:
Waives
non_recurring_billing_presentfor this flip only. Every other reason, including the tenant's threshold, is never overridable this way.Is recorded on the cutover ledger row (
acknowledged_non_recurring,acknowledged_by_user_id,acknowledged_at) regardless of whether the waiver ended up mattering, so the sign-off is always visible on the audit trail.Applies identically whether flipping a whole company or a single contract with the finer
flipContracttool.
The finding itself is windowed to the same period the parity report reads, not the company's entire billing history, so a one-off adjustment from a year ago doesn't block a company forever.
The master flag
The whole Cutover tab is gated on a feature flag:
BILLING_CUTOVER_NATIVE_GENERATION_ENABLED=true
This is set in the application's own .env, it is a single, app-wide switch shared by every tenant on the platform, not a per-tenant setting. Until it's on, the Cutover tab is read-only, the flip button is disabled, and an amber banner explains that native generation is dormant. Flipping the flag activates both:
The generation guard,
Charge::scopeNativeBillable, which decides per-charge whetherBillingRunServicemay bill it.The sync skip, where AT invoice and contract-charge syncs skip companies and contracts that are now
concorbit_native.
The flag flip is the cutover master switch. It is deliberately not under Settings, Autotask, it is an .env change, audited, and reserved for the operator who is signing off the go-live for the whole platform.
The pre-enable backfill
Before flipping the flag, run the pre-enable safety backfill:
php artisan concorbit:billing-cutover-backfill-modes [--tenant=<uuid>] [--dry-run]
This stamps at_mirrored on AT-linked companies and contracts that are still defaulting concorbit_native because they were linked before the first-link stamp was in place. Without it, a newly AT-linked company would silently bill natively the moment the flag flips, while AT also still bills the customer (double-bill). Idempotent, run it with --dry-run first to see the count.
Flipping a company
From the Cutover tab, for a company in the Eligible list:
Click Flip to concorbit native.
Confirm the wizard checklist. The answers are frozen on the cutover ledger row.
Optionally set
cutover_period_start(the period boundary AT covered up to). Defaults to the most recent AT-invoiced period.If the company is blocked solely by
non_recurring_billing_present, tick the acknowledgement checkbox (see above).Confirm.
BillingCutoverService::flipCompany runs inside a single DB transaction with lockForUpdate on the company and every active support contract, in that lock order. Eligibility is re-checked inside the lock against a fresh parity read, the UI's "ready" read is advisory, only the locked re-check authorises the mutation. On commit:
The company flips to
concorbit_native.Every active contract on the company flips with it (the cascade).
billing_revert_until = now + 7 daysis stamped.A cutover ledger row records the operator, the period start, the anchor AT invoice id, the checklist, the bulk id, and the acknowledgement (if used).
Mixed-mode companies are reachable only via the deliberate single-contract flipContract action, never as a side effect of a company flip.
The 7-day revert window
While billing_revert_until is in the future:
Native generation is suppressed for that scope (
Charge::scopeNativeBillableexcludes it).Revert is a clean no-op, nothing to un-send because nothing was generated yet.
A revert restores
autotask_billing_mode = at_mirroredand reopens AT as the system of record.
To revert, the operator clicks Revert on the same cutover ledger row in the tab. The revert action refuses when the connection is wound down (see Wind-down), so a stranded scope is impossible.
After seven days, native generation activates for the scope. The first real native invoice lands on the next scheduled billing run.
When to flip
The recommended order is conservative:
Pick one pilot company that has been parity-green for at least the default two periods.
Confirm Coverage is clean and the AT mirror is faithful.
Verify the Cutover tab in dark and light mode in a browser.
Run
concorbit:billing-cutover-backfill-modes --dry-runand confirm 0+0.Set
BILLING_CUTOVER_NATIVE_GENERATION_ENABLED=truein.env.Flip the pilot company on the Cutover tab.
Wait the 7-day revert window. The next AT invoice should not arrive for this company (the sync skip is now active), the next concorbit native invoice arrives instead.
Roll forward to the rest of the eligible list as confidence grows.
After the flip
For a concorbit_native scope:
concorbit's
BillingRunServicegenerates the customer's invoice from approved charges.The AT pull skips this company's invoices and contract charges permanently, there is no bypass. AT mirror data already pulled stays for history.
The Coverage tab's on-demand re-pull (
autotask:sync --allow-wound-down) still refreshes this company's other entities (tickets, contacts, and so on), but its AT invoices specifically always returnskipped_native, they are never refreshed again once the company is native. Parity for this company is likewise frozen at whatever it was on the last periods before the flip.Once the whole tenant is native and the cooling window has elapsed, the connection becomes eligible for Wind-down.
Until the wind-down tab takes the connection to off, the AT credentials stay active. Reverting a flip remains possible only inside the 7-day window per company.