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

6.6 KiB
Raw Permalink Blame History

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

  • Plan slug: billing-cycle-anchors
  • Created: 2026-01-09

What This Is

Keep a lightweight, continuously-updated log of discoveries and decisions made while implementing this plan.

Prefer short bullets. Append new entries as you learn things, and also update earlier notes when a decision changes or an open question is resolved.

Decisions

  • (2026-01-09) Billing periods are treated as [start, end) (end exclusive) throughout billing, invoicing, and validation. Rationale: aligns with most query patterns already used in billing engine (e.g., < endDate filters) and avoids double-billing boundary events.
  • (2026-01-09) Billing anchors are stored per-client in billing settings (not on client_billing_cycles), and only influence creation of future cycles. Rationale: client_billing_cycles is the concrete audit log of periods; anchors are configuration.
  • (2026-01-09) V1 monthly day-of-month anchor supports 128 only (no 2931). Rationale: avoids short-month ambiguity.
  • (2026-01-09) Quarterly/semi-annual/annual anchors include start month selection (plus day-of-month 128). Rationale: supports common anniversary/fiscal cycles.
  • (2026-01-09) Bi-weekly anchor uses a concrete first cycle start date to establish stable parity. Rationale: simplest deterministic model.
  • (2026-01-09) Anchor changes apply after the last invoiced cycle and may create a transition period to reach the next anchor boundary; transition periods require fixed-charge proration by period length.
  • (2026-01-11) Billing schedule configuration UX lives on Client → Billing; Billing → Billing Cycles becomes a summary-only view that links to the client for edits. Rationale: aligns with mental model “edit per client” and avoids bulk edits that can unintentionally affect multiple clients.
  • (2026-01-11) Billing cycle type changes should follow the same cutover behavior as anchor changes (no retroactive edits; deactivate future non-invoiced cycles so regeneration uses the new schedule).

Discoveries / Constraints

  • (2026-01-09) BillingEngine.calculateBilling() uses client_billing_cycles.period_start_date and period_end_date directly when present. This is the primary integration point for anchors (anchors only need to affect cycle generation). File: server/src/lib/billing/billingEngine.ts:188.
  • (2026-01-09) Cycle generation currently hard-codes calendar boundaries for monthly+ in getStartOfCurrentCycle() (e.g., startOfMonth, startOfYear). File: server/src/lib/billing/createBillingCycles.ts:141.
  • (2026-01-09) UI calls createNextBillingCycle(clientId, selectedDate?.toISOString()), but backend ignores the effectiveDate argument and always uses { manual: true } with no effective date. Files: server/src/components/billing-dashboard/BillingCycles.tsx:164, server/src/lib/actions/billingCycleActions.ts:116, server/src/lib/actions/billingCycleActions.ts:144.
  • (2026-01-09) Current cycle boundaries stored in DB appear to already be “end = next period start” (exclusive end) because getNextCycleDate() sets period_end_date = effective + period. File: server/src/lib/billing/createBillingCycles.ts:191.
  • (2026-01-09) There is inconsistent end-date treatment inside billing engine: some logic uses end as exclusive, but calculateInclusiveDays() uses +1 semantics, which can skew proration. File: server/src/lib/billing/billingEngine.ts:2327.
  • (2026-01-09) Contract assignment overlap validation currently uses inclusive comparisons and should be updated to [start, end) to avoid false overlaps at boundaries. File: server/src/lib/actions/client-actions/clientContractActions.ts:120.
  • (2026-01-09) Scheduled job runs nightly to create cycles for all clients/tenants. File: server/src/lib/initializeApp.ts:307.
  • (2026-01-09) client_billing_settings has required (non-null) invoice/credit settings columns, so anchor updates must upsert/ensure a settings row exists (cant blindly insert only anchor fields). File: server/src/lib/actions/billingCycleAnchorActions.ts.
  • (2026-01-09) createClientContractLineCycles “manual” mode was effectively broken because it attempted to create the next cycle at the last cycles effective_date (duplicate) instead of starting from period_end_date (next boundary). File: server/src/lib/billing/createBillingCycles.ts.
  • (2026-01-09) Transition-period proration is best modeled as activeDays / canonicalCycleDays (canonical cycle anchored at the next aligned boundary) so that non-canonical transition periods do not bill a full cycle amount. File: server/src/lib/billing/billingEngine.ts.
  • (2026-01-09) Playwright UI clicks on the Billing Cycles table can be flaky because background server actions (e.g., notifications/metrics) can re-render the page between mouse down/up; in the anchor E2E test we trigger the buttons native .click() to avoid losing the click event.
  • (2026-01-09) Knex may return timestamp columns as Date objects; using String(date) is locale/timezone-dependent and can break UTC-midnight validation. Normalize DB dates via .toISOString() before calling ensureUtcMidnightIsoDate. File: server/src/lib/actions/billingCycleAnchorActions.ts.
  • (2026-01-09) The anchor editor UI uses constrained selects/inputs (e.g., monthly day-of-month 128), so “invalid anchor value” scenarios are not user-reachable via UI; validation messaging is best covered with unit tests for validateAnchorSettingsForCycle.

Commands / Runbooks

  • (2026-01-09) Find cycle generation logic: rg -n "createClientContractLineCycles|getStartOfCurrentCycle|getNextCycleDate" server/src/lib/billing -S
  • (2026-01-09) Find end-date boundary usage: rg -n "billingPeriod\\.endDate|calculateInclusiveDays|<\\s*billingPeriod\\.endDate" server/src/lib/billing/billingEngine.ts -S
  • Docs: docs/billing/billing.md, docs/billing/billing_cycles.md
  • Cycle generation: server/src/lib/billing/createBillingCycles.ts
  • Billing engine period usage: server/src/lib/billing/billingEngine.ts
  • Billing cycle actions/UI: server/src/lib/actions/billingCycleActions.ts, server/src/components/billing-dashboard/BillingCycles.tsx

Open Questions

  • (2026-01-09) Do we need day-of-month 2931 support in V1? If yes, how should short-month handling work (clamp vs roll-forward)?
  • (2026-01-09) For quarterly/semi/annual: should anchor include start month selection, or keep existing calendar start months for V1?
  • (2026-01-09) For bi-weekly: should anchor be “first cycle start date” (recommended) or “weekday + parity”?