Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
8.2 KiB
PRD — Resume Draft Contract
- Slug:
resume-draft-contract - Date:
2026-01-23 - Status: Draft
Summary
Enable users to save client contracts as drafts during the wizard flow and later resume configuring them. Add a dedicated "Drafts" tab in the contracts management area that displays all draft contracts with clear actions to resume editing or discard them.
Problem
Currently, users creating complex client contracts through the 6-step wizard must complete the entire process in one session. If they:
- Get interrupted and close the browser
- Need to gather more information before completing
- Want to save work-in-progress for later
...they lose all their progress. While the backend already supports saving contracts with status: 'draft', there's no clear UI path to find and resume these draft contracts. Drafts are mixed in with active contracts and there's no obvious "Resume" action.
Goals
- Provide a dedicated "Drafts" tab in the Contracts management area to list all draft contracts
- Allow users to resume editing a draft contract, starting from Step 1 of the wizard
- Allow users to discard (delete) a draft contract with confirmation
- Show clear visual indication that contracts in the drafts tab are incomplete
- Preserve the existing "Save Draft" button functionality in the wizard
Non-goals
- Auto-save functionality (manual save only)
- Smart resume to a specific step (always start at Step 1)
- Draft versioning or history
- Draft sharing or collaboration features
- Draft expiration or auto-cleanup
- Draft contracts for templates (templates already have their own draft system via
template_status)
Users and Primary Flows
Persona: MSP Billing Administrator
Flow 1: Saving a Draft
- User starts creating a new client contract via the wizard
- User completes some steps but isn't ready to finalize
- User clicks "Save Draft" button
- System saves contract with
status: 'draft'and confirms success - User can close the wizard
Flow 2: Viewing Drafts
- User navigates to Contracts management area
- User clicks on "Drafts" tab (alongside Templates and Client Contracts)
- User sees list of all draft contracts with:
- Contract name
- Client name
- Created date
- Last modified date
- Actions (Resume, Discard)
Flow 3: Resuming a Draft
- User clicks "Resume" action on a draft contract
- Wizard opens at Step 1 (Contract Basics) with all saved data pre-populated
- User can navigate through steps, reviewing/modifying as needed
- User either:
- Saves as draft again (overwrites existing draft)
- Completes and activates the contract
Flow 4: Discarding a Draft
- User clicks "Discard" action on a draft contract
- Confirmation dialog appears: "Are you sure you want to discard this draft? This cannot be undone."
- User confirms
- Draft is deleted from the system
UX / UI Notes
Contracts Page Tabs
Add third tab to existing layout:
[Templates] [Client Contracts] [Drafts]
Drafts Tab Content
- DataTable with columns:
- Contract Name
- Client
- Created
- Last Modified
- Actions (dropdown menu)
- Actions menu items:
- "Resume" - opens wizard with draft data
- "Discard" - shows confirmation, then deletes
- Empty state: "No draft contracts. Start creating a new contract to save as draft."
Drafts Tab Visual Indicators
- Badge/count on tab showing number of drafts:
Drafts (3) - Optional: "Draft" badge next to contract name in list
Resume Experience
- When resuming, wizard opens as a dialog/drawer (same as creating new)
- Step 1 pre-populated with saved draft data
- All subsequent steps also pre-populated
- "Save Draft" button updates the existing draft (not creating new)
- "Cancel" button closes without saving (confirmation if changes made)
- Completing wizard converts draft to active contract
Discard Confirmation Dialog
Discard Draft Contract?
This will permanently delete the draft "{contract_name}" for {client_name}.
This action cannot be undone.
[Cancel] [Discard]
Requirements
Functional Requirements
FR1: Drafts Tab
- Add "Drafts" tab to the Contracts management area (third tab after Templates and Client Contracts)
- Tab shows count of draft contracts as badge
- Tab contains DataTable listing all draft contracts for the tenant
FR2: Drafts List
- Display contract name, client name, created date, last modified date
- Include actions dropdown with Resume and Discard options
- Support sorting by columns (default: last modified, descending)
- Support search/filter by contract name or client name
- Respect existing pagination patterns
FR3: Resume Draft
- "Resume" action opens the ContractWizard dialog
- Wizard loads with all saved draft data pre-populated
- User starts at Step 1 (Contract Basics)
- Existing "Save Draft" functionality updates the same draft
- Completing wizard activates the contract (changes
statusfrom'draft'to'active')
FR4: Discard Draft
- "Discard" action shows confirmation dialog
- Confirming deletes the contract and all associated data
- Deletion uses existing
deleteContractaction - List refreshes after successful deletion
FR5: Backend Support
- Create action to fetch draft contracts only:
getDraftContracts() - Ensure
updateContractproperly handles draft → active transition - Ensure existing draft save in wizard properly updates (not creates new)
Non-functional Requirements
- Drafts tab loads within acceptable time (<500ms for typical list sizes)
- Resume action loads wizard with data within acceptable time
- Delete operation completes within acceptable time with proper error handling
Data / API / Integrations
New Action Functions
// In contractActions.ts or new draftContractActions.ts
// Fetch all draft contracts for the current tenant
export const getDraftContracts = withAuth(async (
user,
{ tenant }
): Promise<ContractWithClient[]> => {
const { knex } = await createTenantKnex();
return knex('contracts')
.join('client_contracts', 'contracts.contract_id', 'client_contracts.contract_id')
.join('companies', 'client_contracts.client_id', 'companies.company_id')
.where('contracts.tenant', tenant)
.where('contracts.status', 'draft')
.select(
'contracts.*',
'companies.company_name as client_name',
'client_contracts.client_id'
)
.orderBy('contracts.updated_at', 'desc');
});
// Get a single draft contract with full data for resuming
export const getDraftContractForResume = withAuth(async (
user,
{ tenant },
contractId: string
): Promise<ContractWizardData> => {
// Fetch contract, lines, services, configurations
// Transform to ContractWizardData format for wizard consumption
});
Interface Updates
// Add to contract.interfaces.ts if not present
interface ContractWithClient extends IContract {
client_name: string;
client_id: string;
}
Wizard Updates
The ContractWizard already accepts editingContract prop — this will be used for resuming drafts. May need to enhance to handle full draft data reload.
Security / Permissions
- Drafts follow existing contract permissions
- Users who can create contracts can save/resume drafts
- Users who can delete contracts can discard drafts
- All queries filter by tenant to maintain multi-tenant isolation
Rollout / Migration
- No database migration required —
status: 'draft'already exists - Feature is additive — no breaking changes
- Existing drafts (if any) will appear in the new Drafts tab immediately
Open Questions
None — key decisions resolved during planning phase.
Acceptance Criteria (Definition of Done)
- "Drafts" tab appears in Contracts management alongside Templates and Client Contracts
- Drafts tab displays count badge showing number of drafts
- Drafts list shows contract name, client, created date, modified date, and actions
- "Resume" action opens wizard with draft data pre-populated at Step 1
- Saving draft again updates the existing draft (no duplicate creation)
- Completing the wizard from a resumed draft activates the contract
- "Discard" action shows confirmation dialog
- Confirming discard deletes the draft contract
- Empty state displays appropriately when no drafts exist
- List supports sorting and search/filter