feat(tickets): management CRUD for ticket status/priority/type/queue/category lookups #239

Merged
nrupard merged 3 commits from feat/PMS-321-ticket-lookup-crud into main 2026-06-15 16:27:13 +02:00
Owner

Summary

Adds management (create/update/delete) endpoints for the four ticket lookup tables (statuses, priorities, types, queues) and full list/create/update/delete for the previously route-less ticket_categories table, mounted under /api/v1/tickets/*. Read GET handlers are unchanged. This is the server half that unblocks the MAPPS Settings-hub ticket-lookup editors (MAPPS-172).

What changed

  • New routes: POST /tickets/{statuses,priorities,types,queues} + PUT/DELETE on /{id}; GET/POST /tickets/categories + PUT/DELETE /tickets/categories/{id}.
  • New shared DTOs in mokosh-types: Upsert{Status,Priority,Type,Queue,Category}Request, plus TicketCategory and TicketCategoryResponse.
  • TicketService gains create/update/delete for each lookup and list/CRUD for categories, mirroring the asset-type CRUD shape.

Behavior / guarantees

  • Mutations are admin-gated (RequireAdmin), consistent with the asset-type write routes; reads stay on RequireAuth.
  • Field lengths validate against the column widths in migrations/005_tickets.sql, so an over-long value is a 400 here rather than a 500 from Postgres. sla_multiplier is range-checked to fit DECIMAL(3, 2).
  • For the is_default lookups (statuses, priorities, queues), setting a new default clears the prior default in the same transaction, so a tenant never carries two defaults.
  • Deleting a lookup still referenced by a ticket or child record maps the FK violation (SQLSTATE 23503) to a 409, not a 500.
  • A category parent_id must reference a category in the same tenant, and a category may not be its own parent.
  • Lists remain paginated and unchanged in shape.

Tests

Integration coverage added in tests/tickets.rs (all green against a throwaway Postgres, mirroring CI integration.yml): status CRUD lifecycle + re-delete 404, new-default-clears-prior, delete-referenced-returns-409, category create/child/parent-validation (cross-tenant + self-parent rejection), and non-admin-forbidden. cargo fmt --check and clippy --all-targets -D warnings clean.

Notes

Cargo.lock carries a one-line correction bumping the stale mokosh-server entry 0.2.0 -> 0.3.0 to match Cargo.toml after the v0.3.0 release; the build regenerated it.

#PMS-321

## Summary Adds management (create/update/delete) endpoints for the four ticket lookup tables (statuses, priorities, types, queues) and full list/create/update/delete for the previously route-less `ticket_categories` table, mounted under `/api/v1/tickets/*`. Read GET handlers are unchanged. This is the server half that unblocks the MAPPS Settings-hub ticket-lookup editors (MAPPS-172). ## What changed - New routes: `POST /tickets/{statuses,priorities,types,queues}` + `PUT`/`DELETE` on `/{id}`; `GET`/`POST /tickets/categories` + `PUT`/`DELETE /tickets/categories/{id}`. - New shared DTOs in `mokosh-types`: `Upsert{Status,Priority,Type,Queue,Category}Request`, plus `TicketCategory` and `TicketCategoryResponse`. - `TicketService` gains create/update/delete for each lookup and list/CRUD for categories, mirroring the asset-type CRUD shape. ## Behavior / guarantees - Mutations are admin-gated (`RequireAdmin`), consistent with the asset-type write routes; reads stay on `RequireAuth`. - Field lengths validate against the column widths in `migrations/005_tickets.sql`, so an over-long value is a 400 here rather than a 500 from Postgres. `sla_multiplier` is range-checked to fit `DECIMAL(3, 2)`. - For the `is_default` lookups (statuses, priorities, queues), setting a new default clears the prior default in the same transaction, so a tenant never carries two defaults. - Deleting a lookup still referenced by a ticket or child record maps the FK violation (SQLSTATE 23503) to a 409, not a 500. - A category `parent_id` must reference a category in the same tenant, and a category may not be its own parent. - Lists remain paginated and unchanged in shape. ## Tests Integration coverage added in `tests/tickets.rs` (all green against a throwaway Postgres, mirroring CI `integration.yml`): status CRUD lifecycle + re-delete 404, new-default-clears-prior, delete-referenced-returns-409, category create/child/parent-validation (cross-tenant + self-parent rejection), and non-admin-forbidden. `cargo fmt --check` and `clippy --all-targets -D warnings` clean. ## Notes `Cargo.lock` carries a one-line correction bumping the stale `mokosh-server` entry `0.2.0 -> 0.3.0` to match `Cargo.toml` after the v0.3.0 release; the build regenerated it. #PMS-321
feat(tickets): management CRUD for status/priority/type/queue/category lookups
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 39s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m4s
Integration / integration tests (pull_request) Successful in 2m43s
06bbe5c16a
Add create/update/delete endpoints for the four ticket lookup tables (statuses, priorities, types, queues) plus full list/create/update/delete for the previously route-less ticket_categories table, mounted under /api/v1/tickets/*. The read GET handlers are unchanged.

Each mutation validates field lengths against the column widths in migrations/005_tickets.sql, is tenant-scoped, and is admin-gated via RequireAdmin (matching the asset-type write routes). For the is_default lookups (statuses, priorities, queues) setting a new default clears the prior default in the same transaction so a tenant never carries two defaults. Deleting a lookup still referenced by a ticket or child record is mapped from the FK violation (SQLSTATE 23503) to a 409 instead of a 500, and a category parent_id must reference a category in the same tenant (a category may not be its own parent).

New shared DTOs live in mokosh-types: Upsert{Status,Priority,Type,Queue,Category}Request plus TicketCategory and TicketCategoryResponse. Integration coverage in tests/tickets.rs: status CRUD lifecycle + re-delete 404, new-default-clears-prior, delete-referenced-returns-409, category create/child/parent-validation, and non-admin-forbidden.

#PMS-321

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(tickets): reject transitive category cycles; cover priority sla_multiplier
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 37s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m3s
Integration / integration tests (pull_request) Successful in 2m38s
e7ec4a1716
Code review follow-up. update_category only rejected the depth-1 self-parent case; a transitive re-parent (set A's parent to its own descendant B) closed a cycle A -> B -> A that any parent_id tree walk would loop on. Add a recursive-CTE ancestor check that rejects re-parenting a category under any of its descendants with a 400.

Close a test gap: the priority/type/queue create paths had no integration coverage, leaving the sla_multiplier f64 -> DECIMAL(3,2) bind unverified against Postgres (the codebase has hit numeric encode/decode mismatches before). Add lookup_priority_crud_and_default (round-trips 1.5 and 2.25, asserts single-default invariant, and pins the over-range multiplier as a 422 validation error rather than a 500) and category_transitive_cycle_rejected.

#PMS-321

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(tickets): enforce same-tenant category parent at the DB layer (migration 047)
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 43s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m6s
Integration / integration tests (pull_request) Successful in 3m48s
Create release / Create release from merged PR (pull_request) Has been skipped
d6c7f17c48
Code-review follow-up for the last open finding: ticket_categories.parent_id was a single-column self-FK (migration 005) that only checked the parent row exists, not its tenant, so a write bypassing the service layer could parent a category under another tenant's row. The service already guards this (validate_fk + the recursive-CTE cycle check), but the schema did not.

Migration 047 adds UNIQUE (tenant_id, id) and replaces the FK with a composite (tenant_id, parent_id) REFERENCES ticket_categories(tenant_id, id), following the 043_pms196 precedent of a new forward migration (005's checksum is validated on migrate run and cannot be edited). MATCH SIMPLE leaves root categories (NULL parent_id) unchecked; ON DELETE stays RESTRICT so a parent with children still raises 23503, which delete_lookup maps to 409.

Test category_parent_cross_tenant_blocked_at_db inserts directly (bypassing the API's earlier 400) and asserts a same-tenant child is allowed while a cross-tenant parent raises the FK violation.

Deploy note: migration 047 will fail-boot (PMS-286) any environment that already holds a cross-tenant parent row. The service has always validated same-tenant parents, so this should be vacuous in practice; verify before deploy if any category tree predates the validation.

#PMS-321

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/PMS-321-ticket-lookup-crud 2026-06-15 16:27:13 +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!239
No description provided.