Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
25 KiB
Contract Template Overhaul & Client Assignment Alignment Plan
Summary
This plan restructures the contracts domain so that contracts and contract_lines represent reusable templates, while client_contracts and client_contract_lines continue to hold tenant-specific instances. The work spans backend schema changes, domain service updates, UI flow adjustments for both the contract template wizard and the client contract wizard, and a data migration to decouple template-only fields from client-specific values.
Goals
- Treat
contracts/contract_linesas tenant-scoped templates that omit client-specific commercial terms (rates, quantities, bucket settings, etc.). - Ensure
client_contractsandclient_contract_linesown all mutable billing terms and lifecycle metadata used for invoicing. - Provide a streamlined Contract Template Wizard that mirrors the client contract wizard but skips per-client pricing fields.
- Allow the Client Contract Wizard to bootstrap from a selected contract template, pre-filling compatible sections while prompting for client-specific details.
- Migrate existing data so that all commercial terms live on
client_contract_lines(with fallbacks for empty values). - Keep billing engine, reporting, and integrations functional during the transition.
Non-Goals
- Overhauling the service catalog or invoicing engine beyond required interface changes.
- Introducing new billing models or pricing logic.
- Building multi-tenant contract sharing across different Alga tenants.
Stakeholders
- Billing & Finance product owners
- Frontend billing dashboard team
- Backend billing platform team
- QA & Test Automation team
High-Level Timeline
- Phase 0 – Discovery & Alignment (1 week)
- Phase 1 – Schema Preparation & Migrations (1.5 weeks)
- Phase 2 – Backend Domain Updates (2 weeks)
- Phase 3 – Frontend Template Wizard & Client Wizard Enhancements (2 weeks)
- Phase 4 – Data Migration & Backfill (1 week)
- Phase 5 – QA, Rollout, & Monitoring (1 week)
Total estimated duration: ~8.5 weeks including buffer.
Phase 0 – Discovery & Alignment
Goals: Validate assumptions about current contracts usage, document edge cases, and align on template vs. client instance semantics.
- Inventory existing tables/fields using DB diagrams and confirm ownership (e.g.
contracts,contract_lines,contract_line_mappings,client_contracts,client_contract_lines,client_contract_line_service_configurations).- Review Knex migrations in
server/migrations(start with files containingcontract/billingnaming) to capture current columns, composite keys, and sequences on each related table. - Map multi-tenant helpers referenced by migrations (e.g.,
server/src/lib/db/admin.ts,server/src/lib/db/knexfile.ts,shared/db/index.ts) to understand how schema helpers are invoked during tenant provisioning. - Document any triggers, row level security policies, and dependent views/materialized views defined in SQL files (see
server/migrations/*cleanup_billing_to_contracts.cjsandcleanup_obsolete_email_tables.sqlfor precedent).
- Review Knex migrations in
- Review backend usage patterns:
- Run
rg "client_contract"andrg "contract_lines"acrossserver/src/lib/api/services,server/src/lib/billing, andee/server/src/libto list the service entrypoints, DTOs, and utilities that currently rely on template pricing data. - Trace how the REST/Next.js routes obtain contract data (e.g.,
server/src/app/api/v1/contracts, enterprise overrides underee/server/src/app/api) and note which serializers expect price fields oncontractsvs.client_contracts. - Summarize how integration and E2E suites seed contract data through helpers in
server/src/test/integration,server/src/test/e2e, and scripts such asserver/scripts/create-tenant.tsandserver/scripts/seed-templates.jsthat provision default billing records.
- Run
- Capture UI flows:
- Walk through
ContractWizard.tsx,ContractDetail.tsx, and billing dashboard routes to catalog inputs that currently bind to template pricing, quantities, or bucket settings. - Note feature flags, context providers, and state containers (
Contracts.tsxwizard state, billing feature flags) that determine whether a template can be selected during client contract creation.
- Walk through
- Produce a conclusive spec doc (append to this plan) enumerating which fields should remain on template tables, including an explicit mapping of “template column → client instance column” plus validation rules for missing data.
- Align with stakeholders on acceptance criteria and rollout checkpoints; schedule reviews with Billing PM, finance, and QA to confirm migration sequencing and sign-off expectations.
Phase 0 Progress Log (2025-02-14)
- Inventory current schema surface
contractstable stores template metadata today (contract_name,billing_frequency, activation flags) with tenant-scoped PKs and indexes (server/migrations/20251008000001_rename_billing_to_contracts.cjs:166-183).contract_linesand supporting config tables retain pricing knobs such as base rates, overtime multipliers, and bucket limits (server/migrations/20251008000001_rename_billing_to_contracts.cjs:400-936).- Client instance data is split across
client_contracts(start/end dates, active state) andclient_contract_lines(per-client assignments with timestamps) (server/migrations/20251008000001_rename_billing_to_contracts.cjs:240-569). - Multi-tenant enforcement relies on shared
addTenantForeignKeyhelper; no triggers or RLS rules are defined for these tables in current migrations (server/migrations/20251008000001_rename_billing_to_contracts.cjs:1214-1246).
- Review backend usage patterns
- REST entry points live under Next routes (
server/src/app/api/v1/contracts/route.ts,server/src/app/api/v1/contract-lines/*) backed byApiContractLineController, which exposes CRUD, template copy, and client assignment flows (server/src/lib/api/controllers/ApiContractLineController.ts:1-210). createContractFromWizardpersists pricing, rate tiers, and bucket settings directly onto template tables before seeding client assignments (server/src/lib/actions/contractWizardActions.ts:80-260).- Reporting and billing pipelines pull amounts from
contract_line_fixed_config,contract_line_services, and fall back tocontract_line_mappings.custom_rate, so template-vs-instance separation must preserve these call sites (server/src/lib/reports/definitions/contracts/expiration.ts:60-96). - Test helpers in
server/test-utils/billingTestHelpers.ts:520-640seed both template and client tables, mirroring production expectations for pricing fields.
- REST entry points live under Next routes (
- Capture current UI flows
ContractWizardcollects client-facing pricing inputs (base rates, quantities, usage rates, bucket overlays) and posts them throughcreateContractFromWizard, reinforcing that templates currently own monetary fields (server/src/components/billing-dashboard/contracts/ContractWizard.tsx:23-210).- Contract list surfaces (
server/src/components/billing-dashboard/contracts/Contracts.tsx:300-360) and rate dialogs (server/src/components/billing-dashboard/contracts/ContractPlanRateDialog.tsx:12-72) expect per-line pricing to be editable on template entities.
- Template field ownership spec
- Captured a column-by-column mapping for core tables plus proposed client-instance counterparts under “Template vs Instance Field Ownership”.
- Stakeholder alignment
- Schedule finance/QA/billing sync once ownership matrix is stable; confirm rollout checkpoints and validation requirements.
Template vs Instance Field Ownership
| Table | Column / Concern | Current Role | Proposed Owner | Notes / Follow-ups |
|---|---|---|---|---|
contracts |
contract_name, contract_description, billing_frequency, is_active, timestamps (server/migrations/20251008000001_rename_billing_to_contracts.cjs:166-227) |
Wizard defaults + feature gating | Template | Keep columns; add template_metadata JSON for guidance strings and UI hints. |
client_contracts |
client_id, start_date, end_date, is_active, contract_id, timestamps (server/migrations/20251008000001_rename_billing_to_contracts.cjs:240-284) |
Client lifecycle | Client instance | Add template_contract_id FK + created_from_template_version to track template revisions. |
contract_line_mappings |
display_order, custom_rate, timestamps (server/migrations/20251008000001_rename_billing_to_contracts.cjs:300-345) |
Associates template contracts↔lines & overrides | Mixed | Keep ordering on template. Move custom_rate into client_contract_line_pricing table keyed by client_contract_line_id. |
contract_lines |
contract_line_name, description, contract_line_type, service_category, activation flags (server/migrations/20251008000001_rename_billing_to_contracts.cjs:400-468) |
Template scaffolding | Template | Preserve; add is_template flag (Phase 1). |
contract_lines |
billing_frequency, overtime/after-hours columns (enable_overtime, overtime_rate, etc.) |
Currently shared defaults but billable | Client instance | Copy into client_contract_line_terms (new) linked to client_contract_line_id; template retains optional suggested defaults stored in contract_line_template_terms. |
contract_line_fixed_config |
base_rate, enable_proration, billing_cycle_alignment (server/migrations/20251008000001_rename_billing_to_contracts.cjs:470-522) |
Controls fixed-fee pricing | Client instance | Duplicate structure into client_contract_line_fixed_config; template table keeps nullable default columns only. |
contract_line_services |
service_id, quantity, custom_rate (server/migrations/20251008000001_rename_billing_to_contracts.cjs:790-838) |
Bundled services + rate overrides | Client instance | Create client_contract_services keyed by client_contract_line_id; template table keeps service_id + default_role style metadata (quantity -> default_quantity, custom_rate -> remove). |
contract_line_service_configuration |
configuration_type, custom_rate, quantity (server/migrations/20251008000001_rename_billing_to_contracts.cjs:840-894) |
Service-specific config (Fixed/Bucket/Usage) | Client instance | Introduce client_contract_service_configuration keyed by client_contract_service_id; template table retains configuration_type + optional metadata only. |
contract_line_service_bucket_config |
total_minutes, overage_rate, allow_rollover (server/migrations/20251008000001_rename_billing_to_contracts.cjs:960-1010) |
Bucket sizing & overage pricing | Client instance | New client_contract_service_bucket_config; templates store defaults in contract_line_service_defaults JSON. |
contract_line_service_fixed_config |
base_rate (server/migrations/20251008000001_rename_billing_to_contracts.cjs:1012-1060) |
Fixed rate overrides | Client instance | Move to client_contract_service_fixed_config; template retains default guidance. |
contract_line_service_hourly_config |
minimum_billable_time, overtime multipliers, etc. (server/migrations/20251008000001_rename_billing_to_contracts.cjs:1062-1114) |
Hourly behavior | Client instance | New client_contract_service_hourly_config; template table trimmed to recommended values. |
contract_line_service_hourly_configs |
hourly_rate, minimum_billable_time (server/migrations/20251008000001_rename_billing_to_contracts.cjs:1116-1152) |
Hourly tier matrix | Client instance | Mirror table client_contract_service_hourly_tiers (per config_id + rate). Templates keep structural tiers without dollar amounts. |
contract_line_service_rate_tiers |
Tier pricing ladder (server/migrations/20251008000001_rename_billing_to_contracts.cjs:1154-1194) |
Usage tier pricing | Client instance | Move rate fields to client_contract_service_rate_tiers; templates retain min/max_quantity. |
contract_line_service_usage_config |
unit_of_measure, enable_tiered_pricing, base_rate, minimum_usage (server/migrations/20251008000001_rename_billing_to_contracts.cjs:1196-1244) |
Usage pricing baseline | Mixed | Keep descriptive fields (unit_of_measure, enable_tiered_pricing) on template; relocate base_rate, minimum_usage to client usage config table. |
contract_line_discounts |
Discount linkages (server/migrations/20251008000001_rename_billing_to_contracts.cjs:896-940) |
Template-level discount association | Mixed | Maintain template associations for suggestion; create client_contract_line_discounts for actual applied discounts per client. |
client_contract_lines |
client_id, contract_line_id, start_date, is_active, timestamps (server/migrations/20251008000001_rename_billing_to_contracts.cjs:520-569) |
Client assignment roster | Client instance | Add template_contract_line_id, billing_frequency, is_customized, monetary columns or FK out to new pricing tables. |
Client Instance Table Additions
client_contract_line_terms: (client_contract_line_idPK/FK,billing_frequency,enable_overtime,overtime_rate,overtime_threshold,enable_after_hours_rate,after_hours_multiplier). Values copied fromcontract_linesdefaults during migration; future writes require these fields for billing engine parity.client_contract_services: (client_contract_service_iduuid,client_contract_line_id,service_id,quantity,rate_cents,effective_date, timestamps). Supersedescontract_line_servicespricing data for clients; templates keep a slimcontract_template_servicestable withdefault_quantity/default_role.client_contract_service_configurationtree:client_contract_service_configuration(mirrors schema ofcontract_line_service_configurationminus template defaults; keyed byclient_contract_service_id).client_contract_service_bucket_config,client_contract_service_fixed_config,client_contract_service_hourly_config,client_contract_service_hourly_tiers,client_contract_service_rate_tiers,client_contract_service_usage_configmatching existing template tables but referencingclient_contract_service_configuration_id.
client_contract_line_pricing: (client_contract_line_id,source_template_contract_line_id,custom_rate_cents,source_contract_line_mapping_id,notes). Receives data fromcontract_line_mappings.custom_rate.client_contract_line_discounts: (client_contract_line_id,discount_id,applied_rate,start_date,end_date) to represent actual discount applications, distinct from template recommendations.contract_line_template_terms: New template companion storing optional defaults for term-related fields stripped fromcontract_lines(mirrors columns added toclient_contract_line_termsbut nullable).
Cross-System Considerations
- Billing engine (
server/src/lib/billing/billingEngine.ts:409-470): Update all joins to referenceclient_contract_*tables for pricing, ensuring totals never pull from template columns. - Reporting (
server/src/lib/reports/definitions/contracts/expiration.ts:60-96,.../revenue.ts:45-85): Redirect rate lookups toclient_contract_line_pricingand corresponding client config tables. - Test fixtures (
server/test-utils/billingTestHelpers.ts:520-640): Extend helper APIs to seed both template defaults and client-specific pricing, aligned with new schema. - Historical data: For closed contracts, persist original
contract_id/contract_line_idassociations via newtemplate_*foreign keys to support audits without keeping legacy rate columns on templates.
Phase 1 – Schema Preparation & Migrations
Goals: Add new columns/constraints to support template semantics and prepare for data migration.
- Draft ERD updates showing final relationships.
- Database migrations (Knex + SQL):
- Add
is_templateboolean flags tocontractsandcontract_lines(defaulttrue, enforced via check to keep backward compatibility during migration). - Introduce
template_contract_idFK onclient_contracts(nullable) referencingcontracts.idto track origin template. - Introduce
template_contract_line_idFK onclient_contract_linesreferencingcontract_lines.id. - Create template companion tables (
contract_line_template_terms,contract_template_services,contract_line_service_defaults) to store recommended values separated from instance pricing. - Add nullable JSONB column
template_metadatatocontractsfor wizard hints (e.g. recommended payment cadence, notes). - Introduce client-instance tables (
client_contract_line_terms,client_contract_services,client_contract_service_configurationand child tables,client_contract_line_pricing,client_contract_line_discounts) mirroring the template schema but keyed toclient_contract_line_id. - Update existing template tables to drop or rename monetary fields:
- Convert
contract_line_services.quantity→default_quantity, removecustom_rate. - Remove rate columns from
contract_line_service_*tables or mark nullable defaults pending migration. - Drop
contract_line_mappings.custom_rateonce client pricing table is populated.
- Convert
- Add
- Create forward migration scripts under
server/migrations(e.g.,YYYYMMDDHHMM_contract_template_overhaul.cjs), run locally withnpm run migrate, and document verification queries for teardown. - Write migration rollback strategy and add to
ee/docs/plans/...appendix.
Phase 1 Status – 2025-02-14
- Drafted initial Knex migration (
server/migrations/20250214090000_contract_templates_phase1.cjs) that introduces template flags, template metadata JSON, and scaffolds client-specific tables for pricing/config cloning. - Update existing migrations/services to remove legacy pricing fields once data migration completes (tracked in Phase 4).
- Align migration with rip-and-replace approach (no dual-write): adjust contract assignment services once data backfill happens.
Phase 2 – Backend Domain Updates
Goals: Ensure services treat templates as blueprints and client contracts as concrete instances.
- Update repositories/services:
server/src/lib/api/services/ContractService.ts→ expose APIs to manage template contracts (CRUD) without pricing fields.server/src/lib/api/services/ClientContractService.ts→ accept optionaltemplateContractIdand clone relevant lines intoclient_contract_lineswith new pricing inputs.server/src/lib/api/services/ContractLineService.ts→ ensure template lines handle service attachment and metadata only.server/src/lib/billing/billingEngine.tsand related modules → confirm all billing amounts come fromclient_contract_lines.
- Adjust DTOs/interfaces under
server/src/interfaces/billing.interfaces.tsto distinguishContractTemplatevsClientContractpayloads. - Implement cloning utilities in
server/src/lib/billing/utils/templateClone.ts(new file) to copy template scaffolding into client contract context. - Update validation logic to forbid setting pricing on template endpoints; add API errors.
- Ensure service catalog linkage remains consistent (e.g.,
plan_service_configurationusage if applicable). - Extend unit/integration tests:
- New tests in
server/src/test/unit/contractsverifying template CRUD. - Adjust existing tests in
server/src/test/integration/api/storageHandlers.test.tsandserver/src/test/e2e/api/services.e2e.test.tsto use client contract rates only.
- New tests in
Phase 2 Status – 2025-02-14
- Implemented
cloneTemplateContractLineutility and integrated it with API and server-action entry points for client contract assignments (ContractLineService.assignPlanToClient, client contract apply/add flows). Assignments now populate the newclient_contract_*tables directly (rip-and-replace path). - Updated shared interfaces/schemas to expose template linkage fields consumed by the revised services.
- Update billing engine/report consumers to rely on
client_contract_line_pricingand related tables (pending once data migration completes).
Phase 3 – Frontend Template Wizard & Client Wizard Enhancements
Goals: Provide UX for managing templates and applying them when creating client contracts.
- Create Contract Template Wizard:
- New component
server/src/components/billing-dashboard/contracts/ContractTemplateWizard.tsxbased onContractWizard.tsxbut omitting rate/quantity inputs. - Update routing in
BillingDashboard.tsxto expose template management section. - Add API hooks in
server/src/components/billing-dashboard/contracts/hooks.tsfor template CRUD (useCreateContractTemplate,useUpdateContractTemplate). - Ensure UI surfaces template metadata (line types, services) with ability to add/remove lines and attach services.
- New component
- Update existing
ContractWizard.tsxto support selecting a template:- Add initial step that lists templates via new API.
- On template selection, pre-fill line items, services, default descriptions.
- Prompt user to enter client-specific pricing/quantities before finalizing.
- Handle optional template metadata hints (e.g., recommended billing cadence) in the UI.
- Split the contract detail surface into dedicated components:
ContractTemplateDetail.tsxfocused on template metadata, line composition, and recommended usage notes.ClientContractDetail.tsxfocused on client-specific terms (billing cycles, PO info, assignments) with clear linkage back to the originating template.- Update routing so the Billing Dashboard chooses the appropriate detail component based on whether the record is a template or a client assignment, and provide navigation between the two where relevant.
- Refresh jest/react-testing-library coverage for wizards (
server/src/components/billing-dashboard/contracts/__tests__/).
Phase 4 – Data Migration & Backfill
Goals: Move existing pricing data from templates to client contracts and link instances to their originating templates.
- Author migration script in
server/scripts/contract-template-decoupling.ts:- For each
client_contractreferencing acontract_id, settemplate_contract_id. - For each
client_contract_line, settemplate_contract_line_id. - Copy pricing fields from template tables into new client-instance tables:
contract_line_fixed_config.base_rate,contract_line_services.quantity/custom_rate,contract_line_service_bucket_config.total_minutes/overage_rate, hourly configs, usage configs, and discount links. - Populate
client_contract_line_pricingusing data fromcontract_line_mappings.custom_rateand fixed-config fallbacks; ensure historical overrides preserved. - Null out or remove template pricing columns once client data is populated, leaving only optional defaults.
- For each
- Provide dry-run mode and logging (tenant ID, contract ID counts).
- Write verification queries (appendix) to ensure no non-null pricing remains on templates.
- Coordinate deployment window; run migration in staging then production.
Phase 5 – QA, Rollout, & Monitoring
Goals: Validate functionality end-to-end and ensure stability after launch.
- Manual QA checklist:
- Create new template, apply to new client contract, confirm pricing only captured in client contract.
- Update template and ensure existing client contracts unaffected.
- Regression test billing runs/invoice generation.
- Automation updates:
- Extend Playwright flow
ee/server/src/__tests__/integration/contract-wizard-happy-path.playwright.test.tsto cover template selection. - Update Cypress/Playwright dashboards (if any) to reflect new UI navigation.
- Extend Playwright flow
- Monitoring:
- Add logs/metrics in contract creation endpoints to monitor template usage.
- Add alert if billing engine encounters template pricing (should be null).
- Launch plan:
- Enable feature flag
billing.contractTemplatesV2for internal tenants first. - Roll out to all tenants after one billing cycle.
- Enable feature flag
Appendix
- Schema Change Checklist:
- Add Knex migration files covering forward changes and matching rollback scripts.
- Create SQL migrations for production deployment (
shared/db/migrations/20251020120000_contract_template_overhaul.sql). - Add rollback scripts in
shared/db/migrations/rollback/.
- Documentation:
- Update
docs/billing.mdanddocs/overview.mdsections on contracts. - Add how-to guide under
ee/docs/guides/contract-templates.mdonce implemented.
- Update
- Decisions (2025-02-14):
- No third-party integrations currently ingest template pricing fields. Code search across
sdk/*,shared/**/*, and enterprise services showscontract_line_mappings.custom_rateis only read by internal billing engines, reports, and UI controllers (server/src/lib/reports/definitions/contracts/*.ts,server/src/lib/api/services/ContractLineService.ts). Safe to migrate pricing without breaking external consumers; document in release notes for partners. - Contract template metadata will include a
recommended_servicesarray (service IDs plus optional notes) alongside UI hint fields intemplate_metadata. Wizard will surface these recommendations when initializing client contracts, while actual service bundling remains client-specific viaclient_contract_services.
- No third-party integrations currently ingest template pricing fields. Code search across