Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

References/Organization

From OHC Network Wiki
referenceaccess-governanceCARE 3.0+

Organizations are Care's FHIR-aligned grouping primitive: a nested tree that groups permissions and resources. A node might represent all doctors, a Cardiology sub-team, or a governance unit, and permissions attached to a parent flow implicitly to every descendant. You touch them whenever you model who can do what, and where.

Source: [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/organization.py care/emr/models/organization.py] · [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/spec.py resources/organization/spec.py] · [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/organization_user_spec.py resources/organization/organization_user_spec.py]

The Django model is only the storage layer. Its opaque columns — JSONFields (metadata, cached_parent_json) and arrays (parent_cache, managing_organizations) — get their structure from the resource specs under care/emr/resources/organization/, which also define the org_type enum, validation, the nested JSON returned for parent, and the read/write API schemas. See Resource specs (API schema).

Models

Model Purpose
OrganizationCommonBase Abstract base holding the tree structure, caching, and uniqueness logic shared by both organization types
Organization Instance-wide organization (permission/governance tree, not tied to a facility)
FacilityOrganization Facility-scoped organization (departments/teams within one Facility)
OrganizationUser Membership row linking a User to an Organization with a RoleModel
FacilityOrganizationUser Membership row linking a User to a FacilityOrganization with a RoleModel

Organization and FacilityOrganization extend OrganizationCommonBase, which extends EMRBaseModel — the shared Care EMR base providing external_id, audit fields, soft-delete, and history/meta JSON. OrganizationUser and FacilityOrganizationUser extend EMRBaseModel directly. OrganizationCommonBase is abstract = True and has no table of its own.

OrganizationCommonBase fields

These columns land on both the Organization and FacilityOrganization tables.

Identity & classification

Field Type Required Default Notes
name CharField(255) yes
org_type CharField(255) yes
description TextField no
active BooleanField no
system_generated BooleanField no
metadata JSONField no

Tree structure

Field Type Required Default Notes
parent FK → self no
root_org FK → self no
has_children BooleanField no

Denormalized caches

Care rebuilds these on insert so reading the tree doesn't need recursive joins. Do not set them from clients.

Field Type Default Rebuilt by Shape
level_cache IntegerField 0 set_organization_cache() Depth in the tree (parent.level_cache + 1)
parent_cache ArrayField[int] [] (list) set_organization_cache() Full ancestor internal id chain (parent.parent_cache + [parent.id])
cached_parent_json JSONField {} (dict) get_parent_json() Materialized parent record (shape below). Rebuilt after cache_expiry_days (15)

cached_parent_json is built in get_parent_json() and is {} for root orgs:

{
  id:           str   (parent.external_id, UUID as string)
  name:         str
  description:  str | null
  org_type:     str   (OrganizationTypeChoices value)
  metadata:     dict
  parent:       dict  (recursively, the parent's own cached_parent_json)
  level_cache:  int
  cache_expiry: str   (ISO datetime; cache is reused while now < cache_expiry)
}

This is the same structure OrganizationReadSpec.parent returns on read (see parent read shape).

Organization

Instance-wide organization. Adds one field on top of OrganizationCommonBase:

Field Type Default Notes
managing_organizations ArrayField[int] [] (list)

FacilityOrganization

Facility-scoped organization — departments, teams, and groups within one facility. Adds one field on top of OrganizationCommonBase:

facility → FK facility.Facility  (CASCADE)

Its org_type binds to FacilityOrganizationTypeChoices rather than OrganizationTypeChoices (see Facility organization type values), and its specs live in resources/facility_organization/. FacilityOrganizationWriteSpec requires facility + org_type + optional parent (validated to be in the same facility); FacilityOrganizationReadSpec / FacilityOrganizationRetrieveSpec mirror the instance read specs, resolving the caller's permissions through get_permission_on_facility_organization. See References/Facility.

OrganizationUser

Grants a user access to an organization's resources through a role — only the resources assigned to the org, not everything it owns.

organization → FK Organization (CASCADE)
user         → FK users.User   (CASCADE)
role         → FK security.RoleModel (CASCADE)

FacilityOrganizationUser

The facility-scoped equivalent of OrganizationUser.

organization → FK FacilityOrganization (CASCADE)
user         → FK users.User           (CASCADE)
role         → FK security.RoleModel    (CASCADE)

Enum & coded values

Organization type values

Organization.org_type is a CharField in the DB, but every write binds to OrganizationTypeChoices (resources/organization/spec.py).

Value Meaning
team A working / department-style grouping of people (the default)
govt A governance unit mirroring a real administrative hierarchy (State → District → …). Any user can view these to support cross-governance coordination; create/edit is superadmin-only; facilities and patients reference these nodes geographically
role A user group — a flat set of users, e.g. Volunteer, ASHA Worker. Adding an OrganizationUser to a role-typed org invalidates that user's cached_role_orgs (see [[#organizationusersave
product_supplier Supplier organization (links to the supply chain)

Facility organization type values

FacilityOrganization.org_type binds to a separate enum — FacilityOrganizationTypeChoices (resources/facility_organization/spec.py), not OrganizationTypeChoices.

Value Meaning
root System-generated top node, one per facility (cannot be edited or deleted)
dept A department within the facility
team A team, often nested under a department
role A facility-scoped user group
other Any grouping outside the categories above

Root, govt, and role (instance) organizations, plus any system-generated organization, are restricted to superadmin management.

Resource specs (API schema)

Resource specs (EMRResource subclasses) are the API layer. serialize builds a read object from a DB row, running the perform_extra_serialization / perform_extra_user_serialization hooks; de_serialize builds a DB row from a write payload, running perform_extra_deserialization. __exclude__ lists the model fields the base serializer skips so a hook can handle them instead.

Organization specs (resources/organization/spec.py)

Spec Role Fields exposed Notes
OrganizationBaseSpec shared id, active, org_type, name, description, metadata __model__ = Organization, __exclude__ = ["parent"]. org_type bound to OrganizationTypeChoices; description defaults ""; metadata defaults {}
OrganizationUpdateSpec write · update (inherits OrganizationBaseSpec) No extra fields — parent can't change on update
OrganizationWriteSpec write · create null Validates parent exists (validate_parent_organization); on create, perform_extra_deserialization resolves parent from external_id (or sets None)
OrganizationReadSpec read · list base + level_cache, system_generated, has_children, parent: dict perform_extra_serialization sets id = external_id and parent = obj.get_parent_json() — see [[#parent-read-shape
OrganizationRetrieveSpec read · detail OrganizationReadSpec + permissions: list[str], managing_organizations: list[dict] perform_extra_user_serialization fills permissions (via AuthorizationController.get_permission_on_organization for the requesting user), expands managing_organizations to nested OrganizationReadSpec JSON, and adds audit users (serialize_audit_users)

parent read shape

On read, OrganizationReadSpec.parent (and OrganizationRetrieveSpec.parent) is not a UUID — it's the nested object from get_parent_json(), sharing the shape of cached_parent_json: { id, name, description, org_type, metadata, parent (recursive), level_cache, cache_expiry }, or {} for root orgs. On write, parent is instead a plain UUID4 (the parent's external_id).

Membership specs (resources/organization/organization_user_spec.py)

Spec Role Fields exposed Notes
OrganizationUserBaseSpec shared (none) __model__ = OrganizationUser, __exclude__ = ["user", "role"]
OrganizationUserUpdateSpec write · update role: UUID4 validate_role requires the RoleModel to exist; perform_extra_deserialization resolves role from external_id. Only the role can change on update
OrganizationUserWriteSpec write · create role: UUID4 + user: UUID4 Extends the update spec; validate_user requires the User to exist. On create, resolves both user and role from external_id
OrganizationUserReadSpec read id: UUID4, user: dict, role: dict user = cached UserSpec JSON (model_from_cache); role = RoleReadSpec JSON (full permissions)
OrganizationUserExtendedReadSpec read id: UUID4, role: dict, organization: dict Used by OrganizationUser.get_cached_role_orgs(). organization = OrganizationReadSpec JSON; role = RoleReadMinimalSpec JSON (no permission list)

Nested membership JSON shapes (from resources/role/spec.py, resources/user/spec.py):

role  (RoleReadSpec):        { id: UUID, name, description, is_system: bool,
                               is_archived: bool, contexts: [RoleContext],
                               permissions: [{ name, description, slug, context }] }
role  (RoleReadMinimalSpec): same as above without `permissions`
user  (UserSpec):            cached user summary JSON

Methods & save behaviour

OrganizationCommonBase.save()

On insert (no id yet), the base saves the row, then calls set_organization_cache() to populate the tree caches. Updates save normally and don't recompute caches.

set_organization_cache() side effects when a parent is set:

  1. Computes parent_cache = parent.parent_cache + [parent.id] and level_cache = parent.level_cache + 1.
  2. Derives root_org from the parent (parent.root_org, or the parent itself if the parent is a root).
  3. If the parent had no children before, flips parent.has_children = True and persists only that field.

get_parent_json()

Returns the cached parent JSON while cached_parent_json is present and cache_expiry is still in the future. Otherwise it recursively rebuilds the nested parent record, stamps a new expiry (cache_expiry_days = 15), and persists cached_parent_json. Returns {} for root orgs. This is what OrganizationReadSpec.parent exposes on every read.

validate_uniqueness(queryset, pydantic_instance, model_instance)

Classmethod enforcing name uniqueness among siblings — those sharing the same root_org and level_cache. For a new org it derives level_cache/root_org from the supplied parent (external_id); a null parent means level_cache = 0 and a null root_org. The resource layer calls it before create/update.

OrganizationUser.save()

After saving, if the linked organization's org_type is role (OrganizationTypeChoices.role), it clears user.cached_role_orgs (sets it to None and persists that field) so the user's role-org cache rebuilds on the next read.

OrganizationUser.get_cached_role_orgs(user_id) is a classmethod that serializes all of a user's role-typed org memberships via OrganizationUserExtendedReadSpec.

API integration notes

  • Organizations are exposed through Care's REST API and align with the FHIR Organization resource. Tree position (level_cache, parent_cache, root_org), has_children, and cached_parent_json are platform-maintained — don't set them from clients.
  • Write path: OrganizationWriteSpec (create) / OrganizationUpdateSpec (update). org_type is constrained to OrganizationTypeChoices; parent is supplied as the parent's external_id (UUID) and validated to exist; validate_uniqueness enforces sibling-name uniqueness. parent is immutable after create (the update spec omits it).
  • Read path: OrganizationReadSpec (list) returns parent as nested JSON (get_parent_json()) plus level_cache/system_generated/has_children; OrganizationRetrieveSpec (detail) adds the caller's permissions, expanded managing_organizations, and audit users.
  • Membership is managed through OrganizationUser / FacilityOrganizationUser (specs above); role is required and bounded by the assigning user's own role in that org. Adding a member to a role-typed org invalidates that user's cached_role_orgs.
  • metadata is the supported place for deployment-specific key-value data without schema migrations; the specs pass it through verbatim as dict.
  • Soft-delete applies (via EMRBaseModel); an organization can't be deleted while it still has children, and system-generated orgs can't be deleted.

Pages that link here:

Wiki: ohcnwiki.tellmey.fyi