feat(contacts): company-industries settings lookup (PMS-601) #412

Merged
longjacksonle merged 5 commits from feat/PMS-601-company-industries-lookup into main 2026-06-30 19:09:28 +02:00

PMS-601 (backend half)

Promotes the company Industry suggestion list from a hardcoded frontend constant to a tenant-scoped, admin-editable lookup, following the 048_project_types_lookup pattern. Industry stays free text on companies (PMS-582); this lookup only feeds the combobox's suggestions and does NOT convert the column to a foreign key.

Commits (small, logical)

  1. Migration 086: company_industries table + tenant index + case-insensitive unique (tenant_id, lower(name)) + RLS policy + updated_at trigger + per-tenant seed of the canonical defaults for every existing tenant.
  2. copy_default_config: replicate the lookup to newly provisioned tenants (copied from the seed tenant).
  3. Types: CompanyIndustryResponse + UpsertCompanyIndustryRequest in mokosh-types.
  4. Service: list / create / update / delete on ContactService (tenant-scoped; duplicate name -> 409 via the existing sqlx unique-violation mapping; create audited).
  5. Routes: GET/POST /contacts/company-industries, PUT/DELETE /contacts/company-industries/{id} (list open to any authed user for the combobox; writes RequireAdmin).

Verified

Dev server restarted on this branch: migration 086 ran clean, server listening, and company_industries seeded 58 rows across 2 tenants (29 each).

Companion

Frontend PR in mokosh-apps: the Settings admin page + sourcing the combobox suggestions from this endpoint (and removing the constant).

🤖 Generated with Claude Code

## PMS-601 (backend half) Promotes the company Industry suggestion list from a hardcoded frontend constant to a tenant-scoped, admin-editable lookup, following the 048_project_types_lookup pattern. Industry stays free text on `companies` (PMS-582); this lookup only feeds the combobox's suggestions and does NOT convert the column to a foreign key. ## Commits (small, logical) 1. Migration `086`: `company_industries` table + tenant index + case-insensitive unique `(tenant_id, lower(name))` + RLS policy + updated_at trigger + per-tenant seed of the canonical defaults for every existing tenant. 2. `copy_default_config`: replicate the lookup to newly provisioned tenants (copied from the seed tenant). 3. Types: `CompanyIndustryResponse` + `UpsertCompanyIndustryRequest` in mokosh-types. 4. Service: list / create / update / delete on `ContactService` (tenant-scoped; duplicate name -> 409 via the existing sqlx unique-violation mapping; create audited). 5. Routes: `GET/POST /contacts/company-industries`, `PUT/DELETE /contacts/company-industries/{id}` (list open to any authed user for the combobox; writes `RequireAdmin`). ## Verified Dev server restarted on this branch: migration `086` ran clean, server listening, and `company_industries` seeded 58 rows across 2 tenants (29 each). ## Companion Frontend PR in mokosh-apps: the Settings admin page + sourcing the combobox suggestions from this endpoint (and removing the constant). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Tenant-scoped, admin-editable lookup that will back the company Industry combobox's suggestions, replacing the hardcoded frontend COMPANY_INDUSTRIES constant. Mirrors the 048_project_types_lookup pattern: table + tenant index + case-insensitive unique (tenant_id, lower(name)) so "IT" / "I.T." / "Information Technology" cannot split into separate rows, explicit RLS policy (the 038 fail-closed sweep already ran), updated_at trigger, and a per-tenant seed of the canonical defaults for every existing tenant.

Industry stays a free-text column on companies (PMS-582); this lookup only feeds suggestions and does not convert the column to a foreign key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
copy_default_config replicates the seed tenant's lookup rows to each freshly provisioned tenant. Add company_industries to that copy so new tenants start with the canonical industry vocabulary instead of an empty suggestion list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add CompanyIndustryResponse and UpsertCompanyIndustryRequest to mokosh-types (where the contacts DTOs live), following the asset-types single-Upsert-DTO convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
list / create / update / delete for company_industries on ContactService, tenant-scoped via begin_with_tenant. Create audits the write; the case-insensitive unique index turns a duplicate name into a 409 through the existing AppError From<sqlx::Error> mapping. Update/delete 404 when the row is not in the tenant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(contacts): company-industry lookup endpoints (PMS-601)
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 1m5s
Check / fmt + clippy + build + tests (pull_request) Successful in 2m11s
Create release / Gate (release-branch merges only) (pull_request) Successful in 0s
Create release / Create release from merged PR (pull_request) Has been skipped
Integration / integration tests (pull_request) Successful in 9m8s
75c30753bd
GET/POST /contacts/company-industries and PUT/DELETE /contacts/company-industries/{id}. List is open to any authed user (the company form's combobox fetches it); create/update/delete require RequireAdmin, matching the asset-types lookup admin pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/PMS-601-company-industries-lookup 2026-06-30 19:09:28 +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!412
No description provided.