PSA/ee/docs/plans/bucket-overlays-migration-plan.md
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

293 lines
17 KiB
Markdown
Raw Permalink 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.

# Bucket Overlays Migration Plan
This plan transitions “Bucket” from a standalone plan type into an overlay on top of Hourly and Usage contract lines. The outcome is a simpler UX, clearer billing semantics, and reduced disambiguation complexity while preserving bucket usage tracking and overage billing.
This is a clean cutover and redesign without any requirement to preserve backward compatibility or legacy behavior.
The plan is phased to minimize risk and to keep tests green throughout. Each phase lists: goals, tasks, code touchpoints, acceptance criteria, and rollout notes.
---
## Phase 1 — Foundations
Goals
- Align interfaces and test utilities on the new overlay model.
Tasks
- Confirm current tables used by buckets (no schema change now):
- `plan_service_configuration` (with `configuration_type = 'Bucket'`)
- `plan_service_bucket_config` (stores per-line bucket config: total_minutes/units, overage_rate, allow_rollover, billing_period, etc.)
- `bucket_usage` (per period usage results, minutes/units used and overage)
- Establish coding conventions for overlay editor reuse and per-line validation.
Schema verification (2025-02-14)
- `plan_service_bucket_config` exists with `(tenant, config_id)` PK, FK back to `plan_service_configuration`, and bucket fields: `total_minutes`, `billing_period`, `overage_rate`, `allow_rollover`, timestamps.
- `contract_line_service_configuration` exists with `(tenant, config_id)` PK but currently lacks FKs to `contract_lines` and `service_catalog`, and has no supporting indexes on `contract_line_id` / `service_id`.
- `contract_line_service_bucket_config` mirrors the plan table structure (`total_minutes`, `billing_period`, `overage_rate`, `allow_rollover`, timestamps) but is missing the `(tenant, config_id)` FK back to `contract_line_service_configuration`.
- Recommended follow-up migrations for Phase 2+: add `(tenant, contract_line_id)` FK + index, `(tenant, service_id)` FK + index, and unique `(tenant, contract_line_id, service_id)` constraint on `contract_line_service_configuration`; add `(tenant, config_id)` FK on `contract_line_service_bucket_config`; audit `bucket_usage` to ensure it will link to contract-line overlays post-cutover.
Acceptance
- Documentation notes captured in this plan; team aligned on scope.
Rollout
- N/A
---
## Phase 2 — Test Helpers & Unit Tests First
Goals
- Migrate shared test helpers to create “bucket overlays” on Hourly/Usage lines instead of a dedicated `Bucket` plan type.
Tasks
- Update helper in `server/test-utils/billingTestHelpers.ts`:
- Replace `createBucketPlanAssignment(...)` with `createBucketOverlayForPlan(...)` that:
- Ensures an Hourly or Usage plan exists (create if needed).
- Inserts `plan_services` entries for target services (if missing).
- Upserts `plan_service_configuration` with `configuration_type = 'Bucket'` for each service.
- Inserts `plan_service_bucket_config` with: `total_minutes` (for hours) or units as “minutes” generic quantity for usage, `overage_rate`, `allow_rollover`, `billing_period`.
- Remove the old function usage across the suite; do not maintain a shim.
- Update/adjust unit tests that explicitly assert `plan_type = 'Bucket'`:
- `server/src/test/unit/contractLineDisambiguation.test.ts`: remove bucket-plan special cases; add overlay preference tests (prefer lines with active bucket balance).
- `server/src/test/unit/timeEntryBillingPlanSelection.test.tsx`: mock overlay presence (not plan type) and assert default selection preference.
- `server/src/test/unit/billingPlanSelection.test.ts`: same as above.
- `server/src/test/unit/billingEngine.test.ts`: replace “calculateBucketPlanCharges” expectations with overlay consumption assertions (time/usage paths).
- `server/src/test/unit/bucketUsageService.test.ts`: confirm logic still works with overlays.
- Progress (2025-02-14):
- Helper rewritten as `createBucketOverlayForPlan` and wired to both contract-line and legacy plan tables; usage helper now records `contract_line_id`.
- Invoice infrastructure test (`usageBucketAndFinalization`) migrated to build overlays on top of fixed lines and seed usage via overlay helper.
- Selector/disambiguation unit tests now mock `has_bucket_overlay` metadata instead of `contract_line_type === 'Bucket'`; pending updates to the production disambiguation logic and remaining billing engine/unit suites.
- Billing engine unit coverage updated to treat overlays as the source for bucket charges (`calculateBucketPlanCharges` test now stubs overlay rows and bucket usage, and `calculateBilling` aggregates the overlay charge).
- UI/service layers now consume overlay metadata: contract-line selectors prefer overlays, usage/time-entry actions update bucket usage when overlays exist (rather than relying on `contract_line_type`), and API imports point at `contractLineDisambiguation`.
- Follow-up work to finish ripping out bucket plans
1. **Contract wizard + Playwright flows**
- Update `contractWizardActions` so bucket configuration toggles add overlays to the existing fixed contract line (never create a `contract_line_type = 'Bucket'` row).
- Remove bucket-plan assertions from the Playwright suite (`ee/server/src/__tests__/integration/contract-wizard-happy-path.playwright.test.ts`) and replace them with overlay verifications (`contract_line_service_configuration` + `contract_line_service_bucket_config` joins).
- Adjust wizard helpers/seeders to account for overlay toggles and ensure monthly-fee behaviour still covered.
- Drop `bucket_contract_lines` from test cleanup lists once the wizard no longer creates them.
2. **Reporting/analytics surfaces**
- Switch `getRemainingBucketUnits`, client-portal billing metrics, and account dashboards to join `contract_line_service_configuration` + bucket overlays (remove `.where(plan_type = 'Bucket')` or `contract_line_type === 'Bucket'` filters).
- Update any UI components (e.g. `BucketContractLineConfiguration`) that guard on plan type so they inspect overlay metadata instead.
3. **Service APIs & helpers**
- Finish migrating remaining server actions and utilities that still query `contract_line_type` (search codebase for `'Bucket'` checks) specifically `ContractLineServiceConfigurationService`, report actions, constants, and interfaces in `billing.interfaces.ts`.
- Update TypeScript enums/interfaces to drop `'Bucket'` from `contract_line_type` once overlay consumers are in place.
4. **Database/tests cleanup**
- Remove dependencies on the `bucket_contract_lines` table from tests; plan a follow-up migration to archive/drop the table after the data migration script (Phase 8) runs.
- Sweep remaining tests/integration helpers that reference legacy plan tables (e.g. `bundle_billing_plans` lookups) and ensure they validate overlays instead.
Acceptance
- Unit tests compile and run; bucket assertions are overlay-based.
Rollout
- N/A
---
## Phase 3 — Contract Wizard: Per-Line Bucket Overlays
Goals
- Replace the separate “Bucket Services” step with per-line overlays inside Hourly and Usage steps.
Tasks
- Remove the dedicated wizard step (UI only change):
- `server/src/components/billing-dashboard/contracts/ContractWizard.tsx`
- Update `STEPS` to drop “Bucket Services” and adjust navigation/validation.
- Update `validateStep` logic to stop validating the removed step.
- Introduce per-line bucket overlay in:
- `FixedFeeServicesStep.tsx` and `HourlyServicesStep.tsx`: Add a toggle and fields per service for included hours (stored as minutes), overage rate (cents), and rollover support.
- `UsageBasedServicesStep.tsx`: Provide the same overlay toggle with included units (stored as generic minutes), overage rate, and rollover toggle.
- Create a reusable `BucketOverlayFields` component that renders the shared overlay inputs and automation hooks; reuse it across the fixed, hourly, and usage steps.
- Update the review step to summarize overlays under each line item.
Acceptance
- Wizard lets users add bucket overlays per Hourly/Usage line with proper validation.
- No separate bucket step shown when the flag is ON.
Rollout
- N/A
---
## Phase 4 — Wizard Submission & Persistence
Goals
- Persist per-line overlays instead of creating a `Bucket` plan.
Tasks
- Update `ContractWizardData` and the submission payload to capture a `bucket_overlay` object per fixed/hourly/usage service with fields:
- `{ total_minutes?: number, overage_rate?: number, allow_rollover?: boolean, billing_period?: 'monthly' | 'weekly' }`
- Modify `server/src/lib/actions/contractWizardActions.ts`:
- Remove creation of a dedicated `Bucket` plan.
- After creating each contract-line service configuration, upsert a matching `contract_line_service_configuration` + `contract_line_service_bucket_config` row when `bucket_overlay` is provided, using a shared helper to insert/update the overlay metadata.
- Monthly fee handling (decision):
- Option A (preferred for now): bill the “bucket monthly fee” by adding/allocating a Fixed Fee line scoped to that service (documented on the plan). No schema change needed; engine already handles fixed fees.
- Option B (future): add `monthly_fee` to `plan_service_bucket_config` (requires migration and engine update). Leave as a future enhancement.
Acceptance
- New contracts with overlays persist correctly and no `Bucket` plan is created.
Rollout
- N/A
---
## Phase 5 — Billing Engine: Consume Overlays
Goals
- Calculate included minutes/units and overage from overlay configs during invoice generation; eliminate reliance on a `Bucket` plan type.
Tasks
- In `server/src/lib/billing/billingEngine.ts`:
- Remove/retire `calculateBucketPlanCharges` (or convert into an internal helper that aggregates overlay consumption per line).
- For time-based charges:
- Fetch applicable overlay configs for the Hourly lines plan/service during the billing period.
- Compute available included minutes from `plan_service_bucket_config` minus previously used (`bucket_usage`) for that period.
- Allocate billable time first against remaining bucket minutes; compute overage minutes and bill at `overage_rate`.
- For usage-based charges:
- Same pattern using “units” (represented as minutes in `total_minutes` for parity).
- Write/update `bucket_usage` for the period with new consumption and overage.
- Ensure tax logic remains consistent; overage lines should apply the services tax config.
Acceptance
- Engine produces correct totals for lines with bucket overlays (included + overage).
- Infrastructure invoice tests pass with overlays.
Rollout
- N/A
---
## Phase 6 — Plan Disambiguation Update
Goals
- Simplify plan selection by preferring plans with active overlays (balance available), removing bucket plan special-cases.
Tasks
- In `server/src/lib/utils/contractLineDisambiguation.ts`:
- `determineDefaultBillingPlan(clientId, serviceId)`:
- If a single eligible plan → select it.
- If multiple: prefer the plan where the overlay exists and has remaining balance in the entrys period (requires a helper to query overlay and usage balance).
- Otherwise, return null and require explicit selection.
- `getEligibleBillingPlansForUI`: include an overlay marker and, if feasible, a quick “has_balance” boolean for UX hints.
- Update unit tests to reflect new rules.
Acceptance
- Disambiguation chooses the overlay plan when sensible; tests cover tie cases.
Rollout
- N/A
---
## Phase 7 — Time Entry Plan Selector Defaults
Goals
- Keep the selector optional, default intelligently to overlay-backed plan with balance.
Tasks
- In `server/src/components/time-management/time-entry/time-sheet/TimeEntryEditForm.tsx`:
- When multiple plans are eligible, default to the one with an active overlay and remaining balance (fall back to previous heuristics if unknown).
- Show the selector only when >1 eligible plans.
- Tooltip copy to reflect overlay preference.
Acceptance
- UI defaults match disambiguation; selector appears only when needed.
Rollout
- N/A
---
## Phase 8 — Integration & Invoice Tests
Goals
- Ensure end-to-end generation with overlays passes and equals legacy behavior for equivalent configurations.
Tasks
- Update integration/infrastructure tests to use `createBucketOverlayForPlan`:
- `usageBucketAndFinalization.test.ts`
- `prepaymentInvoice.test.ts`
- Invoice subtotal/tax/consistency/edge-case tests that reference bucket tables.
- Keep assertions on `bucket_usage` updates and invoice line item correctness (included vs overage).
Acceptance
- Tests pass with overlays; no regressions in totals or tax.
Rollout
- N/A
---
## Phase 9 — Cleanup
Goals
- Remove `Bucket` from `plan_type` usages in code after the feature is stable and migration is complete.
Tasks
- Remove bucket plan special-cases across code and tests so overlays are the sole bucket source:
- `server/src/lib/actions/contractWizardActions.ts`: collapse the “bucket plan” branch so the wizard only ever mutates the fixed/usage line and appends overlay configuration rows; purge any fallbacks that still create a `contract_line_type = 'Bucket'`.
- `ee/server/src/__tests__/integration/contract-wizard-happy-path.playwright.test.ts`: update assertions/helper flows to verify overlay persistence (`contract_line_service_configuration` + `contract_line_service_bucket_config`) and stop expecting a bucket contract line or plan type string.
- Reporting and analytics callers (`server/src/lib/actions/client-portal-actions/client-billing-metrics.ts`, `server/src/lib/actions/report-actions/getRemainingBucketUnits.ts`, CSV/BI exports) need to replace `contract_line_type === 'Bucket'` filters with overlay joins and expose overlay metadata to the UI.
- Billing/usage helpers (`server/src/lib/utils/contractLineDisambiguation.ts`, `server/src/lib/services/contractLineServiceConfigurationService.ts`, `server/src/lib/billing/billingEngine.ts`, `server/src/lib/services/bucketUsageService.ts`) should look up overlays via `contract_line_service_configuration` and never branch on plan type.
- Test + seed utilities (`server/test-utils/billingTestHelpers.ts`, infrastructure invoice suites, nightly cleanup scripts) must drop `bucket_contract_lines` cleanup and rely solely on overlay tables.
- Update enum/documentation to drop `Bucket` as a plan type once callers above run overlay-only logic (e.g. `server/src/interfaces/billing.interfaces.ts`, `server/src/constants/billing.ts`, API surface types).
- Delete legacy UI step/components that are no longer referenced (wizard bucket step, bucket-plan filters, etc.).
- Add guardrails: introduce an automated check (`rg "contract_line_type.*Bucket"`) in CI or lint scripts so new plan-type branches cannot be reintroduced.
Acceptance
- Repository compiles and tests green without any `plan_type='Bucket'` coupling.
Rollout
- N/A
Verification
- `rg "contract_line_type.*'Bucket'"` and `rg "plan_type.*Bucket"` return no results in TypeScript/TSX sources (excluding documentation).
- Playwright contract wizard regression passes while persisting overlays.
- Billing smoke tests confirm bucket invoices derive from overlay rows only.
---
## Phase 10 — Documentation & Rollout
Goals
- Provide clear internal and customer-facing docs on overlays.
Tasks
- Internal docs:
- Architecture overview (overlay model, configs, usage ledger).
- Disambiguation rules, defaulting, and reporting by `billing_plan_id`.
- Customer-facing docs (if applicable):
- “Bucket of Hours/Units” under Hourly/Usage lines; effective rate; overage; rollover.
- Release notes and upgrade guidance for existing tenants.
Acceptance
- Docs published and linked in relevant UI tooltips.
Rollout
- N/A
---
## Appendix — Implementation Notes
- Table references (no schema changes required for MVP):
- `plan_service_configuration` (ensure per plan/service `configuration_type = 'Bucket'` exists)
- `plan_service_bucket_config` (uses `total_minutes` for hours or usage units; `overage_rate`, `allow_rollover`, `billing_period`)
- `bucket_usage` (minutes/units used + overage per period)
- Monthly fee for buckets:
- MVP: bill via Fixed Fee lines (per plan or per service allocation); engine already handles fixed charges.
- Future: add `monthly_fee` to `plan_service_bucket_config` if we want it stored alongside the overlay.
- Plan disambiguation/UI:
- `getEligibleBillingPlansForUI` may include overlay hints: `{ has_overlay: boolean, has_balance?: boolean }` to aid defaults and UX copy.
- Reporting:
- Keep `billing_plan_id` on time entries/usage for plan-level reporting; overlays dont change that contract.
---
## Success Criteria
- Users configure bucket hours/units directly on Hourly/Usage lines.
- Time entries/usage automatically consume included amounts; overage is billed correctly.
- Default plan selection prefers overlay-backed lines when appropriate.
- All tests pass; migrations maintain historical accuracy; no regressions in invoice totals or taxes.