Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
16 KiB
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.statushas'draft'valuecreateClientContractFromWizardacceptsisDraftoptionhandleSaveDraft()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
- Contract Wizard:
-
(2026-01-23) Current wizard state interface
ContractWizardDataalready has:contract_id?: string— set when draft is savedis_draft?: boolean— draft indicatortemplate_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=draftsand a placeholder Drafts tab view inpackages/billing/src/components/billing-dashboard/contracts/Contracts.tsx - Next: wire Drafts tab to real data/actions + resume/discard UX
- Added URL mapping for
-
(2026-01-24) Draft count badge added to Drafts tab:
- Uses existing loaded
clientContractsand countsstatus === 'draft' - Badge is hidden when count is 0 (will align with later acceptance/test expectations)
- Uses existing loaded
-
(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 byupdated_at desc
- Location:
-
(2026-01-24) Drafts tab now renders a Drafts DataTable with Contract Name column:
- Draft data fetched via
getDraftContracts()inpackages/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)
- Draft data fetched via
-
(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 intoDraftContractWizardData
- Location:
-
(2026-01-24) Resume flow wired from Drafts table:
- Drafts Actions → Resume calls
getDraftContractForResume(), then opensContractWizardwitheditingContractdata. - Updated
ContractWizardto re-initialize state whenopen/editingContractchange (was previously only using initial props once).
- Drafts Actions → Resume calls
-
(2026-01-24) Resume always opens wizard on Step 1 (Contract Basics) by resetting
currentStepto 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:
ClientContractWizardSubmissionincludes optionalcontract_idcreateClientContractFromWizard()updates existing draft whencontract_idis provided (clears old lines/configs + rewrites in a transaction)
-
(2026-01-24) Completing a resumed draft activates in-place:
createClientContractFromWizard()transitions updated draft tostatus='active'/is_active=truewhen called withoutisDraft
-
(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()andgetDraftContractForResume()filter by{ tenant }- Draft updates via
createClientContractFromWizard()update path are tenant-scoped
-
(2026-01-24) Permissions:
- Resuming drafts (
getDraftContractForResume) requiresbilling:create+billing:update(unlessE2E_AUTH_BYPASS=true) - Discarding drafts (
deleteContract) requiresbilling:delete(unlessE2E_AUTH_BYPASS=true)
- Resuming drafts (
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.tsto assert the contracts tabs config includes Templates / Client Contracts / Drafts. - Extracted tab label mapping into
packages/billing/src/components/billing-dashboard/contracts/contractsTabs.tsfor unit testing without DOM. - Extended
packages/billing/tests/contractsTabs.test.tsto assert Drafts label maps tosubtab=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.tsto unit testgetDraftContracts()query constraints (status/tenant/select/order). - (2026-01-24) Added
packages/billing/tests/draftContractsTable.test.tsxand 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.tsxand implemented T013 to assert client names render in the Drafts DataTable. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T014 to assert created dates render viatoLocaleDateString(). - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T015 to assert updated dates render viatoLocaleDateString(). - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T016 to assert each draft row renders an actions menu trigger. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T017 to assert the actions menu includes a Resume option. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T018 to assert the actions menu includes a Discard option. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T019 to assert Drafts table headers trigger sorting (initialupdated_at descthencontract_name ascon click). - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T020 to assert draft search filters by contract name. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T021 to assert draft search filters by client name. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T022 to assert draft search is case-insensitive. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T023 to assert Drafts table pagination renders (multiple pages when > page size). - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T024 to assert Drafts pagination can navigate to page 2. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T025 to assert Drafts empty state renders when there are no drafts. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T026 to assert Drafts empty state messaging prompts saving drafts. - (2026-01-24) Added
packages/billing/tests/draftContractForResumeActions.test.tsand implemented T027 to unit testgetDraftContractForResume()returns core wizard data for a draft contract. - (2026-01-24) Refactor to keep unit tests lightweight:
- Added
@alga-psa/auth/withAuthexport + Vitest alias so billing actions can importwithAuthwithout importing the full@alga-psa/authentrypoint (which pulls in NextAuth/UI modules in source form). - Updated
packages/billing/src/actions/contractActions.ts+packages/billing/src/actions/contractWizardActions.tsto importwithAuthfrom@alga-psa/auth/withAuth. - In resume tests, mocked
@alga-psa/auth/getCurrentUserto avoid NextAuth session code paths when loading billing models.
- Added
- (2026-01-24) Extended
packages/billing/tests/draftContractForResumeActions.test.tsand implemented T028 to assert contract lines map into wizard service arrays. - (2026-01-24) Extended
packages/billing/tests/draftContractForResumeActions.test.tsand 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.tsand implemented T030 to assert non-draft contracts cannot be resumed. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T031 to assert clicking Resume opens the wizard dialog (via mockedContractWizard). - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand 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.tsxand implemented T049 to assert clicking Discard opens the confirmation dialog. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T050 to assert discard confirmation includes the contract name. - (2026-01-24) Extended
packages/billing/tests/draftContractsTable.test.tsxand implemented T051 to assert discard confirmation includes the client name. - (2026-01-24) Added
packages/billing/tests/contractWizardResume.test.tsxand 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.tsxand implemented T034 to assert Contract Basics step receives the draft client selection. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T035 to assert Contract Basics step receives the draft contract name. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T036 to assert Contract Basics step receives the draft dates. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand 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.tsxand implemented T038 to assert Products step receives pre-populated product services when navigating forward. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T039 to assert Hourly step receives pre-populated hourly services when navigating forward. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T040 to assert Usage step receives pre-populated usage services when navigating forward. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T041 to assert Review step receives the full draft payload when navigating forward. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T042 to assert Save Draft submitscontract_id+{isDraft:true}when resuming. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T043 to assert Save Draft keeps the existingcontract_id(preventing duplicate creation). - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T044 to assert Save Draft reports the samecontract_idviaonComplete. - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T045 to assert finishing a resumed draft callscreateClientContractFromWizardwithout{isDraft:true}(activation path). - (2026-01-24) Extended
packages/billing/tests/contractWizardResume.test.tsxand implemented T046 to assert finishing a resumed draft does not pass{isDraft:true}(meaningis_activeis set true in the action). - (2026-01-24) Added
packages/billing/tests/contractsActivationFlow.test.tsxand implemented T047 to assert a resumed/activated contract disappears from Drafts after wizard completion + refresh. - (2026-01-24) Extended
packages/billing/tests/contractsActivationFlow.test.tsxand implemented T048 to assert activated contracts appear in Client Contracts after switching tabs.
- Added
Links / References
- PRD: ./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)