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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
6.6 KiB
6.6 KiB
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.,< endDatefilters) 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_cyclesis the concrete audit log of periods; anchors are configuration. - (2026-01-09) V1 monthly day-of-month anchor supports 1–28 only (no 29–31). Rationale: avoids short-month ambiguity.
- (2026-01-09) Quarterly/semi-annual/annual anchors include start month selection (plus day-of-month 1–28). 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()usesclient_billing_cycles.period_start_dateandperiod_end_datedirectly 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 theeffectiveDateargument 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()setsperiod_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+1semantics, 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_settingshas required (non-null) invoice/credit settings columns, so anchor updates must upsert/ensure a settings row exists (can’t 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 cycle’seffective_date(duplicate) instead of starting fromperiod_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 button’s native
.click()to avoid losing the click event. - (2026-01-09) Knex may return timestamp columns as
Dateobjects; usingString(date)is locale/timezone-dependent and can break UTC-midnight validation. Normalize DB dates via.toISOString()before callingensureUtcMidnightIsoDate. File:server/src/lib/actions/billingCycleAnchorActions.ts. - (2026-01-09) The anchor editor UI uses constrained selects/inputs (e.g., monthly day-of-month 1–28), 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
Links / References
- 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 29–31 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”?