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

ColumnTypeDescription
idUUIDPrimary key
project_idUUIDForeign key to projects
org_idUUIDForeign key to organizations
contract_noTEXTContract number (required)
contract_nameTEXTContract name (required)
cidTEXTContract identifier (required) — ERP export CID
sort_orderINTDisplay order within the project (0-based)
created_atTIMESTAMPTZAuto-set on insert
updated_atTIMESTAMPTZAuto-bumped on edit
archived_atTIMESTAMPTZSoft-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 (the stages array 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_id is ON DELETE SET NULL, so the delete is guarded by CountStageReferences to protect the CID audit trail.)
  • Reorder — drag active stages to set their display order. sort_order is 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

  1. Open the Stages tab; active stages render as a drag-sortable list (grip handle).
  2. Drag a stage to a new position — the UI updates optimistically.
  3. The client sends the complete ordered active-stage id list to PUT /v1/orgs/{orgId}/projects/{projectId}/stages/reorder.
  4. The Go service sets each stage’s sort_order to its index (0-based) and returns the refreshed active list.
  5. On error the order reverts and a toast is shown; on success the cache is invalidated.

API Endpoints

MethodPathDescription
GET/v1/orgs/{orgId}/projects/{projectId}/stagesList stages (?include_archived=true)
POST/v1/orgs/{orgId}/projects/{projectId}/stagesAdd a stage
PUT/v1/orgs/{orgId}/projects/{projectId}/stages/reorderReorder 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}/archiveArchive a stage
POST/v1/orgs/{orgId}/projects/{projectId}/stages/{stageId}/unarchiveUnarchive a stage

Security

  • Admin-only management — all add/edit/archive/delete/reorder operations require the projects.manage_stages permission (admin-tier roles).
  • Tenant isolation — every query is org_id-scoped; reorder is additionally project_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

FilePurpose
backend/db/queries/project_stages.sqlsqlc queries: add / list / update / archive / delete / reorder
backend/db/migrations/147_project_stage_sort_order.sqlsort_order column + backfill + index
backend/api/internal/project/handler.goListStages / AddStage / UpdateStage / ReorderStages …
backend/api/internal/project/service.goStage service methods incl. ReorderStages
apps/web/src/actions/projects.tsaddProjectStage, updateProjectStage, reorderProjectStages, …
apps/web/src/app/org/_components/edit-project-modal.tsxOrg stage workbench (edit / archive / delete / reorder)