# Organization domain

Phases 1B.1 and 1B.2 provide organizational master data and lifecycle integrity
in `apps.organization`.
Django Admin is its CRUD interface. Phase 1B.3 adds
[user organizational assignments](ASSIGNMENTS.md); scope-based permissions remain
out of scope. Existing Django staff and model permissions apply.

```text
Company
+-- Region
|   +-- ServiceCenter
+-- Department
```

Company is the legal/operational organization running c-care. Region is a
geographic or management grouping inside one company. ServiceCenter is a physical
service location in one region, with an explicit company foreign key for future
company-scoped lookups. Department is a company-level operational department;
it can be referenced by user assignments, but has no direct service-center relationship.

## Shared record conventions

All four models use UUID v4 primary keys, following ADR 0001. The app-local abstract
`OrganizationRecord` shares UUID, code, name, active flag, timestamps, normalization,
and validated saves. It creates no table and does not alter accounts.User.
This is a concrete reuse within organization, not a project-wide base-model mandate.

`created_at` records creation and `updated_at` records ordinary saves, including
partial saves. Codes are required, at most 32 characters, trimmed and uppercased
before model field validation. They use ASCII letters/digits, hyphens and underscores
and must begin with a letter or digit. Display names, legal names and addresses
are not normalized by model code. Names are required; legal name, phone, email and
address are optional. Email uses Django's EmailField validation.

Service-center type is required, using TextChoices: OWN (Own Service Center),
AUTHORIZED (Authorized Service Center), PARTNER (Partner Service Center).
No default business classification is inferred.

## Database constraints and indexes

- Company code is globally unique: `org_company_code_uniq`.
- Region code is unique per company: `org_region_company_code_uniq`.
- ServiceCenter code is unique per company, across all its regions:
  `org_center_company_code_uniq`.
- Department code is unique per company: `org_department_company_code_uniq`.

Each table also has a named `organization_<model>_code_format` CHECK requiring the
canonical uppercase code format. Combined with UniqueConstraint, this prevents
case-equivalent or whitespace-equivalent duplicates even when model validation is
bypassed. Bulk/raw writes of lowercase or padded codes are rejected, not normalized
by PostgreSQL. Such import paths must normalize codes before writing.
`org_center_type_valid` protects the supported center types at database level.

UUID primary keys, unique constraints and Django's foreign-key indexes support
primary-key, company/code, company, and region lookups. No low-selectivity Boolean
indexes or duplicate indexes are added without a demonstrated query need.

## Hierarchy invariants and supported writes

ServiceCenter.company must equal ServiceCenter.region.company. Region, Department
and ServiceCenter company ownership is stable after creation, even for empty or
inactive records. Cross-company subtree movement is not supported.

An active Region or Department requires an active Company. An active ServiceCenter
requires an active Company and an active Region in that same Company. Inactive
children may exist under inactive parents; no records are deleted by lifecycle
operations. Model validation queries current database state rather than cached
parent objects.

Normal model saves (including objects.create() and admin saves) normalize codes,
run full_clean(), and protect these invariants. Partial saves additionally validate
the persisted fields combined with the proposed update, so an unsaved change to
is_active or region cannot hide an invalid partial write. The previous requirement
to include both company and region in update_fields has been removed: company
ownership is now stable, and partial region changes are validated against the
actual stored state. Full validation of the in-memory object still applies, so
refresh stale objects before editing them.

Direct Company/Region deactivation is rejected if active children remain, with an
instruction to use the lifecycle service/action. A direct save never silently
updates descendants. Direct activation and leaf-state changes are allowed when
invariants hold. Code corrections remain allowed through validated saves, preserving
normalization and uniqueness. Codes are editable business identifiers, not relational
identity; future references should use UUID foreign keys.

## Lifecycle services

Use keyword arguments with persisted instances. Services reload current state and
return the updated instance; callers should use that return value or refresh their
original object. Pending unsaved edits on the argument are not persisted.

```python
from apps.organization.services import (
    deactivate_company, deactivate_region,
    deactivate_department, deactivate_service_center,
    reactivate_company, reactivate_region,
    reactivate_department, reactivate_service_center,
    move_service_center,
)

company = deactivate_company(company=company)
company = reactivate_company(company=company)
region = reactivate_region(region=region)
center = move_service_center(service_center=center, destination_region=destination)
```

- Company deactivation atomically deactivates all its active centers, regions and
  departments, then the company. Other companies are unaffected.
- Region deactivation atomically deactivates its active centers and then the region.
  Its company, sibling regions and departments are unaffected.
- Department and ServiceCenter deactivation affect the selected record and its
  active user assignments.
- Reactivation changes only the selected record, subject to the active-parent rules.
  Reactivate deliberately from company to region to center; departments require only
  their company. Parent reactivation never reopens descendants automatically.
- Service-center moves change only region within the same company, retaining the
  business code. Active centers require an active destination region. Inactive
  centers may move to inactive regions. Cross-company moves are rejected. Centers
  referenced by assignments (including inactive history) cannot move, to preserve
  recorded region consistency.

Services update timestamps for records whose state they change; already inactive
children are left untouched. Repeated deactivation is safe. is_active remains an
operational flag, not soft deletion, authorization or an audit trail.

Organization deactivation also atomically ends affected active user assignments
and clears their primary flags. Reactivation never reopens assignments. Direct unit
deactivation is rejected while active assignments remain. See
[assignment lifecycle and locking](ASSIGNMENTS.md) for details.

## Transactions and concurrency boundary

Every supported organization master-record save takes transaction.atomic() and locks the owning
Company row with select_for_update() before validation and persistence. Company
saves lock their own row; creation of a new company has no existing row to lock.
Lifecycle services acquire that same company lock, reload the target, and perform
the entire operation in one atomic transaction. Cascades use deliberately scoped
bulk updates under this lock and explicitly set updated_at. No cascade is hidden in
save(). Region moves use the same transaction and lock protocol.

The company row is a coordination point for writers within one organizational tree.
This deliberately serializes even separate child edits within the same company,
trading write throughput for a simple, consistent locking order appropriate to this
master-data phase. Different companies have independent coordination rows. This
implementation targets the project's single default PostgreSQL database.

PostgreSQL TransactionTestCase tests use separate connections and observe real
pg_blocking_pids() waits for company deactivation versus child creation, region
deactivation versus center reactivation, and destination deactivation versus a
center move. Failure-injection tests verify company/region cascades roll back fully.
These tests cover those scenarios; they are not a claim of universal deadlock freedom
for arbitrary callers that acquire other locks or combine multiple company writes.
If adding a multi-company transaction, establish a consistent company lock order.

**Known boundary:** QuerySet.update(), bulk_create(), bulk_update(), raw SQL and
fixture loading bypass model save()/clean() and the locking protocol. They can
violate activation, stable ownership and company/region equality. Such writes are
unsupported for hierarchy/lifecycle changes except inside the reviewed lifecycle
services. Database code, uniqueness, type and foreign-key constraints still apply,
but cross-table equality and activation rules are application-level invariants.
No triggers or new database constraints are introduced in Phase 1B.2. Stronger
cross-table enforcement can be revisited for future imports/integrations.

## Deletion

All organization foreign keys use PROTECT. Companies with regions, centers or
departments cannot be deleted through Django, nor can regions with service centers.
Unreferenced records can still be deleted. There is no soft-delete mechanism.
PostgreSQL foreign keys also prevent dangling references on direct database deletes.

## Admin and verification

All four models have code/name search, ordering, useful list columns and activation
filters. Child lists include company filters; centers include region/type filters.
Related objects are selected with the list query, and foreign keys use autocomplete.
UUIDs and timestamps are read-only in admin. Company ownership is also read-only
after child creation. Existing change permissions gate explicit deactivate/reactivate
actions, which call the lifecycle services. Deactivation includes descendants for
companies/regions; reactivation applies only to selected records. Each selected
record/tree is its own atomic service operation, not an all-or-nothing batch across
companies. Validation errors are displayed and other selected records may succeed.
Forms reject active children under inactive parents and direct parent deactivation
with active children. No custom frontend or custom permissions are added.

`organization.0001_initial` contains only four organization tables and their
constraints/relationships. It does not seed records or modify Phase 1A migrations.
Enter real master data deliberately through admin when ready.

```powershell
python manage.py check
python manage.py makemigrations --check
python manage.py migrate
python manage.py test --noinput
python manage.py showmigrations organization
```

Organization tests cover normalization, database uniqueness/checks, relationships,
UUIDs/timestamps, activation, deletion, types/email, mismatched/stale relationships,
partial saves, region moves, and admin forms/pages. Fixtures exist only within
Django's isolated PostgreSQL test database. Phase 1A tests remain part of the suite.

Phase 1B.2 requires no schema migration. The suite contains 73 tests: the 40 baseline
test cases (with expectations updated where this phase deliberately changes prior
move/deactivation behavior), plus 33 lifecycle/admin/transaction tests. Phase 1A
tests and all existing migrations remain unchanged. Phase 1B.3 extends this suite
to 123 tests and adds only the assignment migration described in ASSIGNMENTS.md.

Phase 1B.4 extends the assignment cascade to access-owned role assignments through
explicit synchronous hooks after locking affected assignment rows. Receiver failures
roll back the whole lifecycle operation. Role assignments are never automatically
reactivated; see [RBAC integration](RBAC.md). Existing hierarchy behavior is preserved.
