Project Stages
Contract stage records attached to a project. Stages represent contractual milestones used for cost-coding and per-stage ERP export CID. They can be added, edited, archived/unarchived, deleted (when unused), and reordered from the project’s Stages tab — in both the admin project detail page and the org project edit modal.
History: stages were originally append-only. The Create Project Cluster work (migration
111) added edit/archive/delete; issue #98 added drag-reorder and brought the org edit modal to parity with the admin view.
Data Model
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
project_id | UUID | Foreign key to projects |
org_id | UUID | Foreign key to organizations |
contract_no | TEXT | Contract number (required) |
contract_name | TEXT | Contract name (required) |
cid | TEXT | Contract identifier (required) — ERP export CID |
sort_order | INT | Display order within the project (0-based) |
created_at | TIMESTAMPTZ | Auto-set on insert |
updated_at | TIMESTAMPTZ | Auto-bumped on edit |
archived_at | TIMESTAMPTZ | Soft-archive timestamp (NULL = active) |
Lifecycle & semantics
- Add — appended at the end of the display order (
sort_order = MAX+1). Also creatable inline during project creation (thestagesarray on the create-project endpoint), which inherits the same ordering. - Edit — correct
contract_no/contract_name/cid. Correcting the CID only affects future ERP exports; historical export batches retain their original CID. - Archive / Unarchive — soft-hide a stage from the active working set (reversible). Archived stages still resolve on existing dockets/activities and ERP history.
- Delete — hard-delete, blocked (409) when the stage is referenced by activities or ERP export batches; the UI prompts the user to archive instead. (
erp_export_batches.stage_idisON DELETE SET NULL, so the delete is guarded byCountStageReferencesto protect the CID audit trail.) - Reorder — drag active stages to set their display order.
sort_orderis presentation-only: it participates in no foreign key and is not read by ERP/activity logic, so reordering never changes stage identity, ERP CID, or activity linkage. It does change the order stages appear in lists and docket/activity stage pickers.
The trg_notify_project_stage_change trigger fires on INSERT OR UPDATE OR DELETE and emits project_changes, which the WebSocket hub broadcasts so other sessions invalidate and refetch.
Reorder flow
- Open the Stages tab; active stages render as a drag-sortable list (grip handle).
- Drag a stage to a new position — the UI updates optimistically.
- The client sends the complete ordered active-stage id list to
PUT /v1/orgs/{orgId}/projects/{projectId}/stages/reorder. - The Go service sets each stage’s
sort_orderto its index (0-based) and returns the refreshed active list. - On error the order reverts and a toast is shown; on success the cache is invalidated.
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/orgs/{orgId}/projects/{projectId}/stages | List stages (?include_archived=true) |
POST | /v1/orgs/{orgId}/projects/{projectId}/stages | Add a stage |
PUT | /v1/orgs/{orgId}/projects/{projectId}/stages/reorder | Reorder active stages ({ stage_ids }) |
PUT | /v1/orgs/{orgId}/projects/{projectId}/stages/{stageId} | Edit a stage |
DELETE | /v1/orgs/{orgId}/projects/{projectId}/stages/{stageId} | Delete a stage (409 when referenced) |
POST | /v1/orgs/{orgId}/projects/{projectId}/stages/{stageId}/archive | Archive a stage |
POST | /v1/orgs/{orgId}/projects/{projectId}/stages/{stageId}/unarchive | Unarchive a stage |
Security
- Admin-only management — all add/edit/archive/delete/reorder operations require the
projects.manage_stagespermission (admin-tier roles). - Tenant isolation — every query is
org_id-scoped; reorder is additionallyproject_id-scoped (jointly scoped per the Cross-Tenant Rule).
Mobile
The mobile app lists and edits stages and inherits the new display order automatically (it renders the API list, now ordered by sort_order). A mobile drag-reorder gesture is a tracked follow-up.
Key Files
| File | Purpose |
|---|---|
backend/db/queries/project_stages.sql | sqlc queries: add / list / update / archive / delete / reorder |
backend/db/migrations/147_project_stage_sort_order.sql | sort_order column + backfill + index |
backend/api/internal/project/handler.go | ListStages / AddStage / UpdateStage / ReorderStages … |
backend/api/internal/project/service.go | Stage service methods incl. ReorderStages |
apps/web/src/actions/projects.ts | addProjectStage, updateProjectStage, reorderProjectStages, … |
apps/web/src/app/org/_components/edit-project-modal.tsx | Org stage workbench (edit / archive / delete / reorder) |