feat(projects): tenant-scoped project_types lookup table + CRUD #240

Merged
nrupard merged 1 commit from feat/PMS-322-project-type-lookup into main 2026-06-15 16:44:17 +02:00
Owner

Summary

Replaces the hardcoded projects.project_type enum (VARCHAR(20) CHECK (project_type IN ('client','internal'))) with a tenant-scoped project_types lookup table + full CRUD, so the MAPPS Settings hub (MAPPS-169) can manage project types per tenant. Mirrors the task_statuses pattern in the same module. Blocks the MAPPS project-type editor (MAPPS-173).

Migration 048

  • Creates project_types (id, tenant_id, name, is_default, is_active, sort_order, is_system), with its own RLS tenant_isolation policy and updated_at trigger attached explicitly (the 038 RLS sweep and 024/043 trigger sweeps already ran, so a new table is not covered by them).
  • Seeds client / internal as system defaults (is_system = true, client default) for every existing tenant.
  • Adds projects.project_type_id UUID REFERENCES project_types(id), backfills it from the legacy string (every value is seeded, so no orphans / no NULLs), indexes it, then drops the CHECK.
  • Keeps the legacy project_type string column for one release (create/read still write/return it). New forward migration per the 043 precedent (007's checksum is locked).

Behavior

  • CRUD at /api/v1/project-types, admin-gated (RequireAdmin), reads on RequireProjects.
  • One default per tenant: setting a new default clears the prior in the same transaction.
  • System rows (is_system = true) cannot be deleted via the API (409).
  • A type still referenced by a project is delete-blocked with 409, not a 500 (FK 23503 mapped).
  • create_project resolves project_type_id from the legacy string, so new rows are linked too; ProjectResponse now carries project_type_id.
  • copy_default_config gains a project_types copy block (carrying is_system) so future tenants inherit the seeds.

Audit (AC)

Grepped the server for code branching on the literal client / internal values. None exists - the string is only defaulted (CreateProjectRequest), stored, and echoed back, so the values moved to data with zero behavioral change. The other internal matches are unrelated (knowledge_base visibility, ticket note type).

Tests

tests/projects.rs: seeded system defaults present, custom-type CRUD + re-delete 404, project create resolves project_type_id, system-row delete 409, referenced-type delete 409, new-default-clears-prior, technician mutation 403. The four pre-existing project tests still pass (added column did not disturb create/read). tenants + tenant_isolation suites pass (cover the copy_default_config change). clippy --tests -D warnings + fmt clean.

Follow-up (not this PR)

Dropping the legacy projects.project_type string column once clients read project_type_id is a separate release.

#PMS-322

## Summary Replaces the hardcoded `projects.project_type` enum (`VARCHAR(20) CHECK (project_type IN ('client','internal'))`) with a tenant-scoped `project_types` lookup table + full CRUD, so the MAPPS Settings hub (MAPPS-169) can manage project types per tenant. Mirrors the `task_statuses` pattern in the same module. Blocks the MAPPS project-type editor (MAPPS-173). ## Migration 048 - Creates `project_types` (`id, tenant_id, name, is_default, is_active, sort_order, is_system`), with its own RLS `tenant_isolation` policy and `updated_at` trigger attached explicitly (the 038 RLS sweep and 024/043 trigger sweeps already ran, so a new table is not covered by them). - Seeds `client` / `internal` as system defaults (`is_system = true`, `client` default) for **every** existing tenant. - Adds `projects.project_type_id UUID REFERENCES project_types(id)`, backfills it from the legacy string (every value is seeded, so no orphans / no NULLs), indexes it, then drops the CHECK. - Keeps the legacy `project_type` string column for one release (create/read still write/return it). New forward migration per the 043 precedent (007's checksum is locked). ## Behavior - CRUD at `/api/v1/project-types`, admin-gated (`RequireAdmin`), reads on `RequireProjects`. - One default per tenant: setting a new default clears the prior in the same transaction. - System rows (`is_system = true`) cannot be deleted via the API (409). - A type still referenced by a project is delete-blocked with 409, not a 500 (FK 23503 mapped). - `create_project` resolves `project_type_id` from the legacy string, so new rows are linked too; `ProjectResponse` now carries `project_type_id`. - `copy_default_config` gains a `project_types` copy block (carrying `is_system`) so future tenants inherit the seeds. ## Audit (AC) Grepped the server for code branching on the literal `client` / `internal` values. **None exists** - the string is only defaulted (`CreateProjectRequest`), stored, and echoed back, so the values moved to data with zero behavioral change. The other `internal` matches are unrelated (`knowledge_base` visibility, ticket note type). ## Tests `tests/projects.rs`: seeded system defaults present, custom-type CRUD + re-delete 404, project create resolves `project_type_id`, system-row delete 409, referenced-type delete 409, new-default-clears-prior, technician mutation 403. The four pre-existing project tests still pass (added column did not disturb create/read). `tenants` + `tenant_isolation` suites pass (cover the `copy_default_config` change). `clippy --tests -D warnings` + `fmt` clean. ## Follow-up (not this PR) Dropping the legacy `projects.project_type` string column once clients read `project_type_id` is a separate release. #PMS-322
feat(projects): tenant-scoped project_types lookup + CRUD
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 38s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m5s
Integration / integration tests (pull_request) Successful in 2m43s
Create release / Create release from merged PR (pull_request) Has been skipped
77625ca440
Replace the hardcoded projects.project_type enum (VARCHAR CHECK client/internal) with a tenant-scoped project_types lookup table so the MAPPS Settings hub can manage the option set per tenant. Mirrors the task_statuses CRUD shape.

Migration 048 creates project_types (id, tenant_id, name, is_default, is_active, sort_order, is_system) with its own RLS policy and updated_at trigger (the 038/043 sweeps already ran, so a table created now needs both attached explicitly), seeds client/internal as system defaults for every existing tenant, adds projects.project_type_id with a backfill from the legacy string, and drops the CHECK. The legacy project_type string column is kept for one release; create/read still write and return it, and create_project now also resolves project_type_id from that string so new rows are linked too. copy_default_config gains a project_types copy block so future tenants inherit the seeds (including is_system).

CRUD at /api/v1/project-types, admin-gated, mirroring task-statuses. Adds: one default per tenant (a new default clears the prior in the same transaction), system rows cannot be deleted (409), and a delete blocked by a referencing project FK surfaces as 409 not 500.

Audit (AC): grepped the server for code branching on the literal 'client'/'internal' project_type values. None exists. The string is only defaulted (CreateProjectRequest), stored, and echoed back, so the values moved to data with no behavioral change. (The 'internal' hits in knowledge_base/tickets are unrelated: KB visibility and ticket note type.)

Tests in tests/projects.rs: seeded system defaults present, custom-type CRUD + re-delete 404, project create resolves project_type_id, system-row delete 409, referenced-type delete 409, new-default-clears-prior, technician mutation 403. The four pre-existing project tests still pass, proving the added column did not disturb the create/read path. tenants + tenant_isolation suites pass, covering the copy_default_config change.

#PMS-322

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/PMS-322-project-type-lookup 2026-06-15 16:44:18 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!240
No description provided.