[Story-1083] Add proceeding types catalog

Story 1083Epic None21 files · +2591/−2

PR #274 · pramaan-io/pramaan-functions · merged 2026-06-08

Why this matters

Free-text 'proceeding types' is a ticking audit failure

What breaks if firms share a global, unprotected list

Free text is never a catalog.

The fix — Number the tabs, lock the drawer, never let a case point to air.

How it flows

sequenceDiagram
  autonumber
  participant app as Firm Admin
  participant svc as Service
  participant db as DB + RLS
  app->>svc: POST create
  svc->>svc: name conflict check
  svc->>svc: usage + refs check
  svc->>db: insert + next PT-NNN
  db->>db: RLS firm lock
  db->>svc: view + formatted ref
  svc->>app: ProceedingTypeView

service.py + proceeding_type_ref.py

Methodin → outwhat it does
create_proceeding_type(session, payload) → ProceedingTypeViewcreates a new label and allocates the next PT-NNN for the firm
archive_proceeding_type(session, proceeding_type_id) → ProceedingTypeViewmoves the label from active drawer to storage pocket without losing references
delete_proceeding_type(session, proceeding_type_id) → Nonerefuses to remove a label that any case still points to

What changed, file by file

Service layer for firm-scoped CRUD, archive, unarchive, and delete flows on proceeding types, with usage and name-conflict guards.

new classsignature
ProceedingTypeService(class)
_current_user_id(session) → UUID
_view(proceeding_type, usage_count) → ProceedingTypeView
_find_duplicate_name(session, firm_id, name, excluding_proceeding_type_id) → ProceedingType | None
_get_visible_proceeding_type(session, proceeding_type_id) → ProceedingType
_get_editable_firm_proceeding_type(session, proceeding_type_id, firm_id) → ProceedingType
_raise_if_name_conflict(session, firm_id, name, excluding_proceeding_type_id) → None
_in_use_references(proceeding_type_id, firm_id) → tuple[InUseReference, ...]
_usage_count(session, proceeding_type_id, firm_id) → int
list_proceeding_types(session, include_archived) → ProceedingTypeListResponse
create_proceeding_type(session, payload) → ProceedingTypeView
update_proceeding_type(session, proceeding_type_id, payload) → ProceedingTypeView
archive_proceeding_type(session, proceeding_type_id) → ProceedingTypeView
unarchive_proceeding_type(session, proceeding_type_id) → ProceedingTypeView
delete_proceeding_type(session, proceeding_type_id) → None

Migration that creates the proceeding_types table, enables per-firm RLS, adds the soft-delete columns, and installs the updated_at trigger.

new methodsignature
_replace_reference_kind_constraint(include_proceeding_type) → None
_enable_proceeding_type_rls() → None
_disable_proceeding_type_rls() → None
upgrade() → None
downgrade() → None
New fields: revision : strdown_revision : str | Sequence[str] | Nonebranch_labels : str | Sequence[str] | Nonedepends_on : str | Sequence[str] | None

Request, update, view, and list DTOs defining the public proceeding type shape, including the formatted PT-001 reference and delete-safety flags.

new classsignature
ProceedingTypeAvailability(class)
ProceedingTypeCreateRequest(class)
_trim_name(value) → str | None
_trim_description(value) → str | None
ProceedingTypeUpdateRequest(class)
ProceedingTypeView(class)
ProceedingTypeListResponse(class)
New fields: name : strdescription : str | Noneavailability : ProceedingTypeAvailabilityid : UUIDfirm_id : UUIDproceeding_type_number : intproceeding_type_number_formatted : strarchived_at : datetime | Nonearchived_by_user_id : UUID | Noneusage_count : intis_in_use : boolcan_delete : boolcreated_by_user_id : UUID | None

SQLAlchemy entity mapping the proceeding_types table with firm ownership, per-firm number, archive columns, and soft-delete columns.

new classsignature
ProceedingType(type)
New fields: id : UUIDfirm_id : UUIDproceeding_type_number : intname : strdescription : str | Noneavailability : strcreated_by_user_id : UUID | Nonearchived_at : datetime | Nonearchived_by_user_id : UUID | Nonedeleted_at : datetime | Nonedeleted_by_user_id : UUID | None

FastAPI routes exposing the create, list, update, archive, unarchive, and delete endpoints for proceeding types under the app API.

new methodsignature
list_proceeding_typesasync def list_proceeding_types( db: AppDbDep, include_archived: Annotated[ bool, Query( alias="includeArchived", description=( "Include archived proceeding type rows for settings management. " "Deleted proceeding type rows are never return
create_proceeding_type(req, db, _) → ProceedingTypeView
update_proceeding_type(proceeding_type_id, req, db, _) → ProceedingTypeView
delete_proceeding_type(proceeding_type_id, db, _) → Response
archive_proceeding_type(proceeding_type_id, db, _) → ProceedingTypeView
unarchive_proceeding_type(proceeding_type_id, db, _) → ProceedingTypeView

Shared formatter that renders a per-firm proceeding type number as the standard PT-001 string used in views and references.

new methodsignature
format_proceeding_type_ref(proceeding_type_number) → str

Files changed (21)

File±
alembic/versions/6a7b8c9d0e12_0082_proceeding_types_catalog.py ★+227
docs/walkthroughs/story-1083-walkthrough.html+100
openapi/app-api.json ★+879
src/app_api/app.py+2
src/domains/proceeding_type/AGENTS.md+40
src/domains/proceeding_type/__init__.py+5
src/domains/proceeding_type/dtos/__init__.py+17
src/domains/proceeding_type/dtos/proceeding_type.py+154
src/domains/proceeding_type/entity.py+139
src/domains/proceeding_type/router.py+135
src/domains/proceeding_type/service.py ★+390
src/domains/reference_counter/entity.py+5 −1
src/shared/constants/firm_status.py+1
src/shared/db/entities.py+2
src/shared/formatters/__init__.py+7 −1
src/shared/formatters/proceeding_type_ref.py+16
tests/contract/test_app_proceeding_types_openapi.py+93
tests/integration/test_app_proceeding_types.py+311
tests/integration/test_rls_isolation.py+1
tests/shared/formatters/test_proceeding_type_ref.py+27
tests/unit/test_proceeding_type_migration.py+40