feat(contacts): freeform company on contacts (nullable company_id + company_name) (PMS-402) #306

Merged
vas2000-work merged 1 commit from feat/PMS-402-freeform-company-on-contacts into main 2026-06-18 03:35:42 +02:00
Owner

Backend for freeform company on contacts (PMS-402), completing the SPA-side MAPPS-251. A contact's company is now optional: it can link an existing CRM company via company_id, carry a typed-in company_name, or have neither, with the two forms mutually exclusive (supplying both is a 422). Migration 060 makes contacts.company_id nullable (FK and indexes preserved) and adds a nullable company_name column. The create/update/read service paths and the Create/Update/Contact/ContactResponse/ContactRow DTOs are threaded through accordingly; read queries surface a single authoritative name via COALESCE(co.name, c.company_name) so the CRM name wins for linked contacts and the freeform value shows otherwise, and setting an FK on update clears any stored freeform name. Covered by new unit tests (freeform-only, FK-only, neither, both-rejected) and integration tests round-tripping a freeform contact and the freeform-to-FK transition.

#PMS-402

Backend for freeform company on contacts (PMS-402), completing the SPA-side MAPPS-251. A contact's company is now optional: it can link an existing CRM company via `company_id`, carry a typed-in `company_name`, or have neither, with the two forms mutually exclusive (supplying both is a 422). Migration 060 makes `contacts.company_id` nullable (FK and indexes preserved) and adds a nullable `company_name` column. The create/update/read service paths and the Create/Update/Contact/ContactResponse/ContactRow DTOs are threaded through accordingly; read queries surface a single authoritative name via `COALESCE(co.name, c.company_name)` so the CRM name wins for linked contacts and the freeform value shows otherwise, and setting an FK on update clears any stored freeform name. Covered by new unit tests (freeform-only, FK-only, neither, both-rejected) and integration tests round-tripping a freeform contact and the freeform-to-FK transition. #PMS-402
feat(contacts): accept freeform company on contacts
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 33s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m5s
Integration / integration tests (pull_request) Successful in 4m39s
Create release / Create release from merged PR (pull_request) Has been skipped
4c29f2ee05
Make a contact's company optional and freeform-capable (PMS-402, backend for the merged SPA MAPPS-251). A contact may now link an existing CRM company via company_id, carry a typed-in company_name, or have neither (a bare name plus phone); supplying both a company_id and a non-empty company_name is rejected with a 422.

Migration 060 drops the NOT NULL on contacts.company_id (keeping the companies(id) ON DELETE CASCADE FK and existing indexes) and adds a nullable company_name VARCHAR(255) stored label.

DTOs: CreateContactRequest.company_id and Contact/ContactResponse/ContactRow.company_id become Option<Uuid>; CreateContactRequest and UpdateContactRequest gain a company_name field with a length + not-blank/no-control-char validator and a struct-level schema guard enforcing the company_id/company_name mutual exclusion.

Service: create_contact skips the get_company existence check when no company_id is supplied and persists the freeform company_name (NULL when an FK is set); update_contact handles company_name in its dynamic UPDATE and clears the stored freeform name whenever a company_id is set. The get_contact, list_contacts, and get_company_contacts read queries project company_name via COALESCE(co.name, c.company_name) so the CRM name wins for FK-linked contacts and the freeform value surfaces otherwise.

Tests: unit cases cover freeform-only, FK-only, neither, and both-supplied request validation; integration tests round-trip a freeform contact (create then read, asserting company_name present and company_id null), the bare-person case, the both-supplied 422, and the freeform-to-FK update that clears the stored name. Seed data construction sites updated for the now-optional company_id.

#PMS-402

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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!306
No description provided.