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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
226 lines
11 KiB
Markdown
226 lines
11 KiB
Markdown
# PRD — Per-Phase Task Statuses
|
|
|
|
- Slug: `per-phase-task-statuses`
|
|
- Date: `2026-03-18`
|
|
- Status: Draft
|
|
|
|
## Summary
|
|
|
|
Allow each project phase to have its own independent set of task status columns. Currently all phases share the project-level status configuration. With this change, a "Design" phase can use "To Do / In Design / Review / Done" while a "Development" phase uses "Backlog / In Progress / Code Review / QA / Done".
|
|
|
|
## Problem
|
|
|
|
MSPs run projects with phases that represent fundamentally different workflows (design, development, deployment, onboarding). Forcing all phases to share the same status columns means either:
|
|
- Generic statuses that don't describe any phase's workflow well
|
|
- Cluttered boards with statuses irrelevant to the current phase
|
|
- Workarounds like separate projects per workflow type
|
|
|
|
## Goals
|
|
|
|
1. Each phase can optionally define its own set of task statuses
|
|
2. Phases without custom statuses fall back to the project-level defaults (backward compatible)
|
|
3. Tasks moving between phases with different statuses are automatically remapped
|
|
4. Project templates support per-phase status configuration
|
|
5. Client portal correctly displays phase-specific statuses
|
|
|
|
## Non-goals
|
|
|
|
- Per-task status overrides (statuses remain at the phase/project level)
|
|
- Status workflow rules (e.g., "must go through Review before Done")
|
|
- Cross-phase status analytics or reporting
|
|
- Drag-and-drop status column customization directly on the Kanban board
|
|
|
|
## Users and Primary Flows
|
|
|
|
### Personas
|
|
- **Project Manager (MSP admin)**: Configures per-phase statuses in project settings; creates templates with phase-specific statuses
|
|
- **Technician**: Views and interacts with phase-specific Kanban columns; moves tasks between phases
|
|
- **Client user**: Views project progress in client portal with correct phase-specific statuses (read-only)
|
|
|
|
### Primary Flows
|
|
|
|
**Flow 1 — Configure phase statuses (Project Manager)**
|
|
1. Open Project Settings > Task Statuses
|
|
2. Select a phase from the phase dropdown/tabs
|
|
3. Toggle "Use custom statuses for this phase" (vs. project defaults)
|
|
4. Add/remove/reorder statuses from the tenant library
|
|
5. Save — Kanban board now shows phase-specific columns
|
|
|
|
**Flow 2 — View phase-specific Kanban (Technician)**
|
|
1. Open a project, select a phase tab
|
|
2. Kanban board columns reflect that phase's effective statuses
|
|
3. Task status dropdown shows only that phase's statuses
|
|
4. Moving a task to a different phase auto-remaps its status
|
|
|
|
**Flow 3 — Move task between phases (Technician)**
|
|
1. Move or reassign a task from Phase A to Phase B
|
|
2. System resolves status: same name → keep; no match + open → first open; no match + closed → first closed
|
|
3. Task appears in the correct column in Phase B's board
|
|
|
|
**Flow 4 — Create template with phase statuses (Project Manager)**
|
|
1. Create/edit a project template
|
|
2. Define phases within the template
|
|
3. For each phase, optionally configure custom status columns
|
|
4. Creating a project from this template copies phase-specific statuses
|
|
|
|
**Flow 5 — Client portal view (Client user)**
|
|
1. Client views project in client portal
|
|
2. Selects a phase — sees that phase's status columns
|
|
3. Tasks grouped/displayed by phase-effective statuses
|
|
|
|
## UX / UI Notes
|
|
|
|
### MSP Project Settings
|
|
- Phase selector (dropdown or tabs) above the status configuration area
|
|
- Default shows "Project Defaults" — the existing project-level statuses
|
|
- Each phase shows toggle: "Use project defaults" / "Custom statuses for this phase"
|
|
- When toggling to custom: option to "Copy project defaults as starting point" or "Start empty"
|
|
- Add/remove/reorder UI remains the same, just scoped to the selected phase
|
|
|
|
### Kanban Board
|
|
- Already has phase tabs — no new navigation needed
|
|
- Status columns change when switching between phases that have different statuses
|
|
- No visual indicator needed that statuses are phase-specific vs. project defaults
|
|
|
|
### Task Status Select
|
|
- Shows effective statuses for the task's current phase
|
|
- No change to UX, just data source changes
|
|
|
|
### Client Portal
|
|
- Kanban and list views show phase-effective statuses
|
|
- No configuration UI (read-only)
|
|
|
|
## Requirements
|
|
|
|
### Functional Requirements
|
|
|
|
#### FR-1: Database Schema
|
|
- FR-1.1: Add nullable `phase_id` column to `project_status_mappings` table referencing `project_phases(phase_id)` with ON DELETE CASCADE
|
|
- FR-1.2: Add index on `(tenant, project_id, phase_id)` for efficient lookups
|
|
- FR-1.3: Existing rows with `phase_id = NULL` represent project-level defaults (no data migration needed)
|
|
- FR-1.4: Add nullable `template_phase_id` column to `project_template_status_mappings` referencing `project_template_phases(template_phase_id)` with ON DELETE CASCADE
|
|
- FR-1.5: EE/Citus companion migration — FK constraints use composite keys `(tenant, phase_id)` since both tables are distributed on `tenant`
|
|
|
|
#### FR-2: Effective Status Resolution
|
|
- FR-2.1: New model function `getEffectiveStatusMappings(knex, tenant, projectId, phaseId)` — returns phase-specific statuses if any exist for that phase_id, otherwise falls back to project-level statuses (phase_id IS NULL)
|
|
- FR-2.2: Resolution is transparent — callers get a flat list of statuses regardless of source
|
|
- FR-2.3: All status-fetching actions (`getProjectTaskStatuses`, `getProjectStatusMappings`, `getClientProjectStatuses`) accept optional `phaseId` and use effective resolution
|
|
|
|
#### FR-3: Phase Status Configuration
|
|
- FR-3.1: `addStatusToProject()` accepts optional `phaseId` — creates mapping with that phase_id
|
|
- FR-3.2: `reorderProjectStatuses()` scopes to phase if phaseId provided
|
|
- FR-3.3: New action `copyProjectStatusesToPhase(projectId, phaseId)` — copies project defaults as phase-specific mappings
|
|
- FR-3.4: New action `removePhaseStatuses(phaseId)` — deletes all mappings for a phase, reverting it to project defaults
|
|
- FR-3.5: `deleteProjectStatusMapping()` — no change needed (works by mapping ID)
|
|
|
|
#### FR-4: Cross-Phase Task Movement
|
|
- FR-4.1: When moving a task between phases within the same project, resolve status mapping:
|
|
- Same status name exists in target phase → use it
|
|
- No name match + source status was open → first open status by display_order
|
|
- No name match + source status was closed → first closed status by display_order
|
|
- FR-4.2: Cross-project moves continue to work as before (existing logic in `moveTaskToPhase`)
|
|
- FR-4.3: `updateTaskStatus()` validates the status mapping belongs to the task's phase's effective statuses
|
|
|
|
#### FR-5: Settings UI
|
|
- FR-5.1: `ProjectTaskStatusSettings` shows a phase selector (dropdown or tabs) above the status list
|
|
- FR-5.2: "Project Defaults" is the first/default option in the phase selector
|
|
- FR-5.3: Each phase shows toggle: "Use project defaults" vs "Custom statuses"
|
|
- FR-5.4: Toggling to custom offers "Copy from project defaults" action
|
|
- FR-5.5: Toggling back to defaults shows confirmation (will delete phase-specific mappings)
|
|
- FR-5.6: `AddStatusDialog` passes phaseId when adding to a phase
|
|
|
|
#### FR-6: Kanban Board & Task UI
|
|
- FR-6.1: `ProjectDetail` fetches phase-effective statuses when selectedPhase changes
|
|
- FR-6.2: KanbanBoard renders columns from phase-effective statuses (receives via props, minimal change)
|
|
- FR-6.3: TaskStatusSelect shows effective statuses for the task's phase (receives via props)
|
|
- FR-6.4: Status task counts computed from phase-effective statuses
|
|
- FR-6.5: Completed task count computed from phase-effective statuses
|
|
|
|
#### FR-7: Client Portal
|
|
- FR-7.1: `getClientProjectStatuses()` accepts optional phaseId and uses effective resolution
|
|
- FR-7.2: `ProjectDetailView` fetches phase-effective statuses when phase selection changes
|
|
- FR-7.3: `ClientKanbanBoard` renders phase-specific status columns
|
|
- FR-7.4: `ClientTaskListView` groups tasks by phase-effective status groups
|
|
|
|
#### FR-8: Template Support
|
|
- FR-8.1: `project_template_status_mappings` supports `template_phase_id` for per-phase template statuses
|
|
- FR-8.2: `TemplateStatusManager` allows per-phase status configuration with phase selector
|
|
- FR-8.3: `TemplateStatusColumnsStep` (wizard) supports phase-aware status setup
|
|
- FR-8.4: Project creation from template copies phase-specific template status mappings to `project_status_mappings` with correct `phase_id`
|
|
|
|
#### FR-9: Supporting Systems
|
|
- FR-9.1: Phase task import (`IImportReferenceData.statusMappings`) resolves against phase-effective statuses
|
|
- FR-9.2: Event payloads for `PROJECT_STATUS_ADDED/UPDATED/DELETED/REORDERED` include optional `phaseId`
|
|
- FR-9.3: `ProjectTaskStatusEditor` and `ProjectTaskStatusSelector` (used during project creation) handle phase statuses from templates
|
|
|
|
### Non-functional Requirements
|
|
|
|
- NFR-1: Backward compatible — existing projects with no phase-specific statuses work identically
|
|
- NFR-2: No data migration needed — existing rows have phase_id NULL which is the project-level default
|
|
- NFR-3: Citus compatible — all queries include tenant in WHERE/JOIN conditions; FK references use composite keys
|
|
|
|
## Data / API / Integrations
|
|
|
|
### Schema Changes
|
|
|
|
**`project_status_mappings`** — add column:
|
|
```sql
|
|
phase_id UUID NULL REFERENCES project_phases(phase_id) ON DELETE CASCADE
|
|
```
|
|
Index: `(tenant, project_id, phase_id)`
|
|
|
|
**`project_template_status_mappings`** — add column:
|
|
```sql
|
|
template_phase_id UUID NULL REFERENCES project_template_phases(template_phase_id) ON DELETE CASCADE
|
|
```
|
|
|
|
### Type Changes
|
|
|
|
**`IProjectStatusMapping`** — add: `phase_id?: string`
|
|
**`ProjectStatus`** — add: `phase_id?: string`
|
|
|
|
### Key Query Pattern — Effective Resolution
|
|
```sql
|
|
-- Check if phase has custom statuses
|
|
SELECT COUNT(*) FROM project_status_mappings
|
|
WHERE tenant = ? AND project_id = ? AND phase_id = ?;
|
|
|
|
-- If count > 0: use phase statuses
|
|
SELECT * FROM project_status_mappings
|
|
WHERE tenant = ? AND project_id = ? AND phase_id = ?
|
|
ORDER BY display_order;
|
|
|
|
-- If count = 0: fall back to project defaults
|
|
SELECT * FROM project_status_mappings
|
|
WHERE tenant = ? AND project_id = ? AND phase_id IS NULL
|
|
ORDER BY display_order;
|
|
```
|
|
|
|
## Security / Permissions
|
|
|
|
- No new permissions needed — status configuration is already gated by project management permissions
|
|
- Client portal: read-only access to effective statuses, enforced by existing `getClientProjectStatuses()` access checks
|
|
- Tenant isolation: all queries include `tenant` in WHERE clauses (existing pattern)
|
|
|
|
## Rollout / Migration
|
|
|
|
- **Zero-downtime deployment**: nullable column addition, no data changes
|
|
- **Backward compatible**: all phases fall back to project defaults until customized
|
|
- **No feature flag needed**: the feature is additive and opt-in per phase
|
|
- **Rollback**: drop the `phase_id` column; phase-specific mappings are lost but project defaults remain
|
|
|
|
## Open Questions
|
|
|
|
None — all design decisions have been agreed upon (see Key Design Decisions in SCRATCHPAD.md).
|
|
|
|
## Acceptance Criteria (Definition of Done)
|
|
|
|
1. Existing projects with no phase-specific statuses behave identically to before
|
|
2. A phase can be configured with custom statuses via the settings UI
|
|
3. Kanban board shows phase-specific status columns when viewing a customized phase
|
|
4. Moving a task between phases with different statuses correctly remaps the status
|
|
5. Project templates can define per-phase statuses
|
|
6. Creating a project from a template with phase statuses sets up correct mappings
|
|
7. Client portal displays phase-specific statuses correctly
|
|
8. All existing tests pass; new tests cover the new functionality
|