Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

8.7 KiB
Raw Permalink Blame History

PRD — Billing Cycle Anchors (Day-of-Month) + Exclusive End Dates

  • Slug: billing-cycle-anchors
  • Date: 2026-01-09
  • Status: Draft

Summary

Add per-client billing cycle anchors so MSPs can invoice clients on non-calendar boundaries (e.g., “bill on the 10th of each month”), while standardizing the billing domain on a single, consistent date-range convention: billing period end dates are exclusive.

Problem

Today Alga supports picking a billing cycle type per client (weekly/bi-weekly/monthly/quarterly/semi-annually/annually), but cycle generation is effectively aligned to calendar boundaries for monthly+ (and has inconsistent logic for weekly/bi-weekly), and the billing domain has mixed semantics for whether a period end date is inclusive or exclusive. This prevents common MSP workflows like:

  • “Invoice this client monthly on the 10th.”
  • “Invoice this client annually on their service anniversary.”
  • “Invoice this client every 2 weeks starting next Friday.”

Goals

  • Allow admins to configure a per-client billing cycle anchor appropriate to the clients billing frequency.
  • Ensure billing, proration, and overlap validation all treat billing periods as [start, end) (end is exclusive).
  • Ensure the design works across cycle types: weekly, bi-weekly, monthly, quarterly, semi-annually, annually.
  • Preserve existing behavior by default (clients without anchors keep the current “calendar-aligned” schedule for monthly+/annual and the current rolling schedule for weekly/bi-weekly).

Non-goals

  • Replacing client_billing_cycles with a new “bill run” system; we continue to use client_billing_cycles as the source of truth for concrete billing periods.
  • Retroactively rewriting already-invoiced billing periods.
  • Supporting arbitrary time-of-day boundaries; billing periods remain UTC-midnight dates.
  • V1: complex “29/30/31st of month” semantics unless explicitly requested (see Open Questions).

Users and Primary Flows

Personas

  • Billing Admin (MSP): configures billing cycles and anchors per client; generates invoices.
  • Ops/Admin: adjusts client billing settings during onboarding/renewal.

Primary flows

  1. Billing admin selects a client from the Clients menu.
  2. In the client detail view, navigates to the clients Billing tab.
  3. Chooses the billing cycle type and configures the anchor (cycle-type-aware).
  4. System uses the configured schedule to generate upcoming billing cycles (automated nightly + manual “create next cycle”).
  5. Billing admin generates invoices from billing cycles (existing).

Secondary flow:

  • Billing admin opens Billing → Billing Cycles to review a cross-client summary and click through to a specific clients Billing tab.

UX / UI Notes

UI placement

  • Billing schedule configuration (cycle type + anchor + preview) lives on the Client → Billing tab.
  • The Billing → Billing Cycles tab is summary-only (no schedule editing); it links to the clients Billing tab for changes.

Anchor UX should be cycle-type aware:

  • Weekly: choose “weekday” (MonSun). Optionally show the computed “next start date” preview.
  • Bi-weekly: choose a concrete “first cycle start date” (UTC date) so parity is stable.
  • Monthly: choose “day of month” (default 1). V1 is limited to 128 for predictability.
  • Quarterly: choose “start month” + day-of-month (128), with a preview of next cycle boundaries.
  • Semi-annually: choose “start month” + day-of-month (128), with a preview of next cycle boundaries.
  • Annually: choose “start month” + day-of-month (128), with a preview of next cycle boundaries.

UI should make the date semantics explicit in copy/tooltips: “Billing periods are [start, end); end date is the start of the next period.”

Requirements

Functional Requirements

Billing schedule configuration (cycle type + anchor)

  • Store a per-client anchor configuration.
  • Validate anchor values (ranges, required fields per cycle type).
  • Use anchor configuration when generating new client_billing_cycles rows.
  • Allow updating schedule configuration (cycle type and/or anchor), with guardrails to avoid breaking already-invoiced periods.

Cycle generation

  • Initial cycle creation uses anchor logic to pick the correct current cycle start.
  • Subsequent cycle creation advances deterministically by cycle type from the last cycles boundary.
  • Manual “Create Next Cycle” uses the same logic as the scheduled job and respects anchor configuration.
  • Schedule updates (cycle type and/or anchor) take effect after the last invoiced cycle:
    • Compute cutoverStart = lastInvoiced.period_end_date (exclusive end = next start).
    • Create a transition period [cutoverStart, nextAnchorBoundary) (if cutoverStart is not already aligned).
    • Create subsequent full periods aligned to the configured anchor.
  • Transition periods require deterministic proration of fixed recurring charges based on actual period length vs canonical cycle length.

Exclusive end date consistency

  • All billing periods are treated as [period_start_date, period_end_date).
  • Proration factors and “days in period” calculations must be consistent with exclusive end semantics.
  • Overlap detection and “cannot span cycle change” validation must use exclusive end semantics.

Non-functional Requirements

  • Deterministic and timezone-safe: all persisted billing boundary dates are UTC-midnight ISO8601 strings.
  • Backward compatible: existing clients with no anchor configured continue behaving as today.

Data / API / Integrations

Data model

Add anchor fields to client_billing_settings (preferred, since it already stores billing knobs) rather than adding new columns to clients or client_billing_cycles.

Proposed shape (final schema is a design decision in implementation):

  • client_billing_settings.billing_cycle_anchor_day_of_month (int, nullable)
  • client_billing_settings.billing_cycle_anchor_month_of_year (int, nullable; for annual/semi/quarterly where applicable)
  • client_billing_settings.billing_cycle_anchor_day_of_week (int, nullable; ISO 1=Mon..7=Sun)
  • client_billing_settings.billing_cycle_anchor_reference_date (date/timestamp UTC-midnight, nullable; for bi-weekly parity / “first cycle start”)

API / server actions

  • Add server actions for reading/updating the anchor configuration.
  • Add a server action for updating a clients billing schedule (cycle type + anchor) that applies the same cutover behavior as anchor updates.
  • Ensure invoice generation continues to rely on client_billing_cycles.period_start_date/period_end_date (no anchor logic in invoice generation).

Security / Permissions

  • Same permissions as existing billing configuration: only tenant users with billing/admin privileges can update anchors and create cycles.

Observability

  • Not in scope for V1 (no new dashboards/metrics); rely on existing logs and error surfaces.

Rollout / Migration

  1. Ship schema changes for anchor fields on client_billing_settings.
  2. Backfill default anchor values for existing clients:
    • monthly/quarterly/semi/annually: default day-of-month = 1, default start-month = current calendar convention (Jan/Apr/Jul/Oct; Jan/Jul; Jan).
    • weekly/bi-weekly: preserve current rolling behavior unless the admin sets an anchor.
  3. Update cycle generation to use anchor settings for newly created cycles.
  4. Update date-range semantics and fix inconsistent end-date usage.
  5. Document the semantics and migration notes in billing docs.

Open Questions

  1. For weekly: do we also want a “first cycle start date” option (like bi-weekly) to make weekday-only anchors less ambiguous for initial creation?
  2. When changing schedule (cycle type and/or anchor), should the UI show an explicit warning that future non-invoiced billing cycles will be regenerated under the new schedule?

Acceptance Criteria (Definition of Done)

  • A billing admin can set a monthly anchor day (e.g., 10) for a client and the system generates monthly billing cycles starting/ending on the 10th.
  • Weekly, bi-weekly, quarterly, semi-annual, and annual cycle generation continues to work and respects anchors when configured.
  • Billing, proration, and overlap validation treat billing periods as [start, end) with end exclusive.
  • Manual “Create Next Cycle” respects anchor configuration and no longer ignores provided anchor/effective date inputs.
  • Billing cycle type changes follow the same cutover behavior as anchor changes (no retroactive modification of invoiced cycles; future non-invoiced cycles are regenerated under the new schedule).
  • Existing clients without anchors are not behaviorally broken; already-invoiced cycles are not modified.