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

129 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.