Story-1117: Client Email Unique Per Firm

This walkthrough documents the backend rule that a real client email can belong to only one client inside a firm, while missing email remains allowed for any number of clients.

Why This Exists

Without this rule, two clients in the same firm can share the same email address. Downstream systems then cannot reliably tell which client is meant by that email.

Allowed:
Client A    no email
Client B    no email
Client C    client@example.com

Blocked:
Client C    client@example.com
Client D    CLIENT@example.com

Request Path

POST/PATCH /app/clients
        |
        v
router_clients.py
        |
        v
ClientService.create_client/update_client
        |
        v
repository.find_by_email
        |
        v
PramaanValidationError: pramaan.client.email_in_use

The router stays thin. The service owns the business rule because the rule needs current firm data. The shared error handler turns the service exception into the API response.

Database Guard

Migration 0092_client_email_unique_per_firm adds a partial unique index on (firm_id, lower(email)) only when email IS NOT NULL AND email <> ''.

API Contract

Create and update document the typed validation error: pramaan.client.email_in_use. The response includes details: {"field": "email"} so the app can attach the message to the email field.

Test Coverage