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

196 lines
16 KiB
Markdown

# Scratchpad — Resume Draft Contract
- Plan slug: `resume-draft-contract`
- Created: `2026-01-23`
## 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-23) Resume flow starts at Step 1 — user confirmed they want to review from the beginning when resuming a draft.
- (2026-01-23) Drafts in separate tab — dedicated "Drafts" tab alongside Templates and Client Contracts.
- (2026-01-23) No auto-save — manual save only via "Save Draft" button.
- (2026-01-23) Discard with confirmation — explicit "Discard Draft" action with confirmation dialog.
## Discoveries / Constraints
- (2026-01-23) Draft functionality already exists at DB level:
- `contracts.status` has `'draft'` value
- `createClientContractFromWizard` accepts `isDraft` option
- `handleSaveDraft()` exists in ContractWizard.tsx (lines 381-420)
- `is_active: false` + `status: 'draft'` for draft contracts
- (2026-01-23) Key files:
- Contract Wizard: `packages/billing/src/components/billing-dashboard/contracts/ContractWizard.tsx`
- Contracts List: `packages/billing/src/components/billing-dashboard/contracts/Contracts.tsx`
- Wizard Actions: `packages/billing/src/actions/contractWizardActions.ts`
- Contract Actions: `packages/billing/src/actions/contractActions.ts`
- Contract Model: `packages/billing/src/models/contract.ts`
- (2026-01-23) Current wizard state interface `ContractWizardData` already has:
- `contract_id?: string` — set when draft is saved
- `is_draft?: boolean` — draft indicator
- `template_id?: string` — template reference
- (2026-01-23) Contracts.tsx has two tabs: Templates and Client Contracts — we'll add a third "Drafts" tab
- (2026-01-23) Existing contract update blocks setting status to 'draft' if contract has invoices (contractActions.ts:293-299)
- (2026-01-24) Contracts page now has third subtab `drafts`:
- Added URL mapping for `subtab=drafts` and a placeholder Drafts tab view in `packages/billing/src/components/billing-dashboard/contracts/Contracts.tsx`
- Next: wire Drafts tab to real data/actions + resume/discard UX
- (2026-01-24) Draft count badge added to Drafts tab:
- Uses existing loaded `clientContracts` and counts `status === 'draft'`
- Badge is hidden when count is 0 (will align with later acceptance/test expectations)
- (2026-01-24) Added server action `getDraftContracts()`:
- Location: `packages/billing/src/actions/contractActions.ts`
- Query joins `contracts` + `client_contracts` + `clients` (and template name), filters by tenant + `status='draft'`, orders by `updated_at desc`
- (2026-01-24) Drafts tab now renders a Drafts DataTable with Contract Name column:
- Draft data fetched via `getDraftContracts()` in `packages/billing/src/components/billing-dashboard/contracts/Contracts.tsx`
- Added search input + localized date rendering (will be broken out into checklist items as we complete them)
- (2026-01-24) Drafts table includes Client column (`client_name`).
- (2026-01-24) Drafts table includes Created column (localized date).
- (2026-01-24) Drafts table includes Last Modified column (localized date).
- (2026-01-24) Drafts table includes Actions dropdown with Resume/Discard placeholders.
- (2026-01-24) Drafts table default-sorts by Last Modified (updated_at desc) and supports header sorting via DataTable.
- (2026-01-24) Drafts tab search filters by contract name.
- (2026-01-24) Drafts tab search filters by client name.
- (2026-01-24) Drafts tab uses standard DataTable pagination + items-per-page controls.
- (2026-01-24) Drafts tab shows empty state when there are no draft contracts.
- (2026-01-24) Added `getDraftContractForResume(contractId)` to load full wizard data from a draft:
- Location: `packages/billing/src/actions/contractWizardActions.ts`
- Validates `contracts.status === 'draft'`, then maps contract + client_contract + contract lines/services/configs into `DraftContractWizardData`
- (2026-01-24) Resume flow wired from Drafts table:
- Drafts Actions → Resume calls `getDraftContractForResume()`, then opens `ContractWizard` with `editingContract` data.
- Updated `ContractWizard` to re-initialize state when `open`/`editingContract` change (was previously only using initial props once).
- (2026-01-24) Resume always opens wizard on Step 1 (Contract Basics) by resetting `currentStep` to 0 on open.
- (2026-01-24) Resumed drafts pre-populate all wizard steps using `getDraftContractForResume()` mapping (lines/services/configs).
- (2026-01-24) Wizard now upserts drafts instead of creating duplicates:
- `ClientContractWizardSubmission` includes optional `contract_id`
- `createClientContractFromWizard()` updates existing draft when `contract_id` is provided (clears old lines/configs + rewrites in a transaction)
- (2026-01-24) Completing a resumed draft activates in-place:
- `createClientContractFromWizard()` transitions updated draft to `status='active'` / `is_active=true` when called without `isDraft`
- (2026-01-24) Discard draft flow now uses a confirmation dialog including contract + client names.
- (2026-01-24) Discard confirmation includes Cancel + Discard actions (ConfirmationDialog).
- (2026-01-24) Discard confirmation calls `deleteContract(contractId)` and shows toast success/error.
- (2026-01-24) Drafts list refreshes after discard by calling `fetchContracts()` (badge count updates too).
- (2026-01-24) Draft queries are tenant-scoped:
- `getDraftContracts()` and `getDraftContractForResume()` filter by `{ tenant }`
- Draft updates via `createClientContractFromWizard()` update path are tenant-scoped
- (2026-01-24) Permissions:
- Resuming drafts (`getDraftContractForResume`) requires `billing:create` + `billing:update` (unless `E2E_AUTH_BYPASS=true`)
- Discarding drafts (`deleteContract`) requires `billing:delete` (unless `E2E_AUTH_BYPASS=true`)
## Commands / Runbooks
- Run billing package tests: `npm test -w packages/billing`
- Run server dev: `npm run dev -w server`
- (2026-01-24) Implemented T052: discard confirmation dialog message includes permanent deletion + cannot-undo warning (unit test).
- (2026-01-24) Implemented T053: discard confirmation dialog renders a Cancel button (unit test).
- (2026-01-24) Implemented T054: discard confirmation dialog renders a Discard button (unit test).
- (2026-01-24) Implemented T055: Cancel closes discard dialog without calling deleteContract (unit test).
- (2026-01-24) Implemented T056: Discard confirmation calls deleteContract(contractId) (unit test).
- (2026-01-24) Implemented T057: confirming discard removes the draft from Drafts list (unit test via refreshed fetch).
- (2026-01-24) Implemented T058: successful discard shows toast.success('Draft discarded') (unit test with mocked react-hot-toast).
- (2026-01-24) Implemented T059: discard errors show toast.error(message) (unit test with deleteContract throwing).
- (2026-01-24) Implemented T060: discard triggers fetchContracts() refresh (assert getDraftContracts called again).
- (2026-01-24) Implemented T061: Drafts badge count clears after discard (unit test by rendering tabs icon).
- (2026-01-24) Implemented T062: getDraftContracts tenant filter prevents cross-tenant draft visibility (unit test with configurable tenant).
- (2026-01-24) Implemented T063: getDraftContractForResume rejects when user lacks billing:create permission.
- (2026-01-24) Implemented T064: deleteContract rejects when user lacks billing:delete permission.
- (2026-01-24) Stabilized jsdom test isolation: added `cleanup()` + body scroll-lock/style reset in wizard/drafts tests to prevent cross-file DOM leakage and pointer-events issues when running the full billing test suite.
- (2026-01-24) Tests:
- Added `packages/billing/tests/contractsTabs.test.ts` to assert the contracts tabs config includes Templates / Client Contracts / Drafts.
- Extracted tab label mapping into `packages/billing/src/components/billing-dashboard/contracts/contractsTabs.ts` for unit testing without DOM.
- Extended `packages/billing/tests/contractsTabs.test.ts` to assert Drafts label maps to `subtab=drafts`.
- Added `getDraftTabBadgeCount()` helper + test to assert Drafts badge appears when count > 0.
- Added a test asserting badge behavior as count changes (0 → 1) to represent badge updates after draft creation.
- Added a test asserting badge hides when count returns to 0 (represents update after discard).
- Badge hidden at 0 is asserted via `getDraftTabBadgeCount(0) === null`.
- Added `packages/billing/tests/draftContractsActions.test.ts` to unit test `getDraftContracts()` query constraints (status/tenant/select/order).
- (2026-01-24) Added `packages/billing/tests/draftContractsTable.test.tsx` and implemented T012 to assert draft contract names render in the Drafts DataTable (forces DataTable width in jsdom so non-Actions columns are visible).
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T013 to assert client names render in the Drafts DataTable.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T014 to assert created dates render via `toLocaleDateString()`.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T015 to assert updated dates render via `toLocaleDateString()`.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T016 to assert each draft row renders an actions menu trigger.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T017 to assert the actions menu includes a Resume option.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T018 to assert the actions menu includes a Discard option.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T019 to assert Drafts table headers trigger sorting (initial `updated_at desc` then `contract_name asc` on click).
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T020 to assert draft search filters by contract name.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T021 to assert draft search filters by client name.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T022 to assert draft search is case-insensitive.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T023 to assert Drafts table pagination renders (multiple pages when > page size).
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T024 to assert Drafts pagination can navigate to page 2.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T025 to assert Drafts empty state renders when there are no drafts.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T026 to assert Drafts empty state messaging prompts saving drafts.
- (2026-01-24) Added `packages/billing/tests/draftContractForResumeActions.test.ts` and implemented T027 to unit test `getDraftContractForResume()` returns core wizard data for a draft contract.
- (2026-01-24) Refactor to keep unit tests lightweight:
- Added `@alga-psa/auth/withAuth` export + Vitest alias so billing actions can import `withAuth` without importing the full `@alga-psa/auth` entrypoint (which pulls in NextAuth/UI modules in source form).
- Updated `packages/billing/src/actions/contractActions.ts` + `packages/billing/src/actions/contractWizardActions.ts` to import `withAuth` from `@alga-psa/auth/withAuth`.
- In resume tests, mocked `@alga-psa/auth/getCurrentUser` to avoid NextAuth session code paths when loading billing models.
- (2026-01-24) Extended `packages/billing/tests/draftContractForResumeActions.test.ts` and implemented T028 to assert contract lines map into wizard service arrays.
- (2026-01-24) Extended `packages/billing/tests/draftContractForResumeActions.test.ts` and implemented T029 to assert service configuration fields (rates, unit_of_measure, bucket overlays) map into resume payload.
- (2026-01-24) Extended `packages/billing/tests/draftContractForResumeActions.test.ts` and implemented T030 to assert non-draft contracts cannot be resumed.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T031 to assert clicking Resume opens the wizard dialog (via mocked `ContractWizard`).
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T032 to assert the Resume flow passes draft data into the wizard (contract_id/client_id/contract_name).
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T049 to assert clicking Discard opens the confirmation dialog.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T050 to assert discard confirmation includes the contract name.
- (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T051 to assert discard confirmation includes the client name.
- (2026-01-24) Added `packages/billing/tests/contractWizardResume.test.tsx` and implemented T033 to assert resuming a draft starts the wizard on Step 1 (Contract Basics) without rendering later steps.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T034 to assert Contract Basics step receives the draft client selection.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T035 to assert Contract Basics step receives the draft contract name.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T036 to assert Contract Basics step receives the draft dates.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T037 to assert Fixed Fee step receives pre-populated fixed services when navigating forward from basics.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T038 to assert Products step receives pre-populated product services when navigating forward.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T039 to assert Hourly step receives pre-populated hourly services when navigating forward.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T040 to assert Usage step receives pre-populated usage services when navigating forward.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T041 to assert Review step receives the full draft payload when navigating forward.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T042 to assert Save Draft submits `contract_id` + `{isDraft:true}` when resuming.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T043 to assert Save Draft keeps the existing `contract_id` (preventing duplicate creation).
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T044 to assert Save Draft reports the same `contract_id` via `onComplete`.
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T045 to assert finishing a resumed draft calls `createClientContractFromWizard` without `{isDraft:true}` (activation path).
- (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T046 to assert finishing a resumed draft does not pass `{isDraft:true}` (meaning `is_active` is set true in the action).
- (2026-01-24) Added `packages/billing/tests/contractsActivationFlow.test.tsx` and implemented T047 to assert a resumed/activated contract disappears from Drafts after wizard completion + refresh.
- (2026-01-24) Extended `packages/billing/tests/contractsActivationFlow.test.tsx` and implemented T048 to assert activated contracts appear in Client Contracts after switching tabs.
## Links / References
- PRD: [./PRD.md](./PRD.md)
- Contract Wizard: `packages/billing/src/components/billing-dashboard/contracts/ContractWizard.tsx`
- Contracts List: `packages/billing/src/components/billing-dashboard/contracts/Contracts.tsx`
- Wizard Actions: `packages/billing/src/actions/contractWizardActions.ts`
## Open Questions
- (None currently — key decisions made during planning)