PhoenixKitManufacturing.EntitiesRegistry (PhoenixKitManufacturing v0.3.2)

Copy Markdown View Source

ETS-backed cache of the machine_type / operation / defect_reason phoenix_kit_entities records, keyed by kind + uuid, with per-locale title resolution. Subscribes to PhoenixKitEntities.Events and reloads on any entity/entity-data change. Concurrent readers always see a consistent snapshot.

Modeled 1:1 on Andi.Orders.StatusRegistry. The three "blueprint" entities (machine_type/operation/defect_reason) are provisioned idempotently by provision_blueprints/0, retried until all three are confirmed present. Two retry paths run in parallel:

  • Event-driven: every reload (triggered by a PubSub event or an explicit reload/0 call) retries provisioning at the top of do_reload/1.
  • Timer-driven: while blueprints_provisioned is false, init/1 and each failed attempt schedule a Process.send_after(self(), :retry_provision, @retry_provision_interval) (default 30 s). handle_info(:retry_provision, ...) retries and reschedules if still not provisioned, and is a no-op once provisioned. This guarantees the subtabs become available even on a host that boots with no users and receives no entities PubSub events for a long time.

See dev_docs/ENTITIES_MIGRATION_SPEC.md for the original design rationale.

Not wired into children/0 by this module alone — see PhoenixKitManufacturing.children/0 for supervision-tree wiring.

Record shape

Every cached record is a plain map:

%{
  uuid: "01…",
  entity_name: "machine_type",
  status: "published",
  position: 0,
  metadata: %{"field_template" => [...], "legacy_uuid" => "…"},
  primary_title: "CNC Mill",
  titles: %{"en-US" => "CNC Mill", "et-EE" => "CNC-frees"},
  name: "CNC Mill",
  unit: nil,
  base_time_norm_seconds: nil
}

titles only carries the locale keys actually present on that record's data — it is not restricted to the host's currently "enabled" languages, so a translation someone filled in survives even while that language is temporarily disabled site-wide.

name is a convenience field: the record's primary-language title by default (as returned by get/2), or the title resolved for the locale requested via list/3 / label/3. unit / base_time_norm_seconds only carry a value for :operation records — they are read once from the primary-language data block and never locale-overridden, even though the generic entities form technically allows editing them on secondary language tabs (see dev_docs/ENTITIES_MIGRATION_SPEC.md §5 — a known, accepted limitation of the generic-UI approach). metadata is passed through raw so callers (e.g. Machines.merged_field_template/2, which reads metadata["field_template"]) can access machine_type-specific keys this registry itself doesn't interpret.

Locale handling

Callers pass this module's own bare Gettext locale codes ("en", "et", "ru"); phoenix_kit_entities stores translations under BCP-47 dialect codes ("en-US", …). normalize_locale/1 bridges the two by mapping a bare or dialect code to an enabled PhoenixKit Language sharing its prefix, falling back to the primary language for nil or an unmatched code. On a fresh host with the Languages module disabled, only the primary language's own prefix resolves distinctly — every other requested locale falls back to the primary title, which is the intended graceful-degradation behavior.

Summary

Functions

Returns a specification to start this module under a supervisor.

Fetches a single cached record by uuid, or nil if unknown. nil is accepted as uuid (returns nil) so callers can pass an optional linked-record uuid straight through without a separate nil-check.

Resolves the title for uuid in locale, or "Unknown" if the uuid isn't cached (including nil).

Lists cached records for kind, with :name resolved for locale (a bare Gettext code or BCP-47 dialect; nil resolves to the primary language — see normalize_locale/1).

Normalizes a bare Gettext locale ("en") or BCP-47 dialect ("en-US") to the dialect code of an enabled PhoenixKit Language sharing its prefix. nil and codes with no enabled match fall back to the primary language.

True once the registry has completed its initial ETS load.

Forces an immediate synchronous reload from the database.

Types

kind()

@type kind() :: :machine_type | :operation | :defect_reason

record()

@type record() :: %{
  uuid: String.t(),
  entity_name: String.t(),
  status: String.t(),
  position: integer(),
  metadata: map(),
  primary_title: String.t() | nil,
  titles: %{optional(String.t()) => String.t() | nil},
  name: String.t(),
  unit: String.t() | nil,
  base_time_norm_seconds: number() | nil
}

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

get(uuid, kind)

@spec get(String.t() | nil, kind()) :: record() | nil

Fetches a single cached record by uuid, or nil if unknown. nil is accepted as uuid (returns nil) so callers can pass an optional linked-record uuid straight through without a separate nil-check.

Unlike list/3, this does not take a locale — :name on the returned record is the primary-language title (see label/3 for locale-specific resolution of a single record).

label(uuid, kind, locale)

@spec label(String.t() | nil, kind(), String.t() | nil) :: String.t()

Resolves the title for uuid in locale, or "Unknown" if the uuid isn't cached (including nil).

list(kind, locale, opts \\ [])

@spec list(kind(), String.t() | nil, keyword()) :: [record()]

Lists cached records for kind, with :name resolved for locale (a bare Gettext code or BCP-47 dialect; nil resolves to the primary language — see normalize_locale/1).

Options

  • :status — when given, only records with this exact status are returned (e.g. "published"). Defaults to all cached (i.e. all non-trashed — trashed rows are never cached in the first place) statuses; callers that previously filtered status: "active" should now pass status: "published" explicitly.

normalize_locale(locale)

@spec normalize_locale(String.t() | nil) :: String.t()

Normalizes a bare Gettext locale ("en") or BCP-47 dialect ("en-US") to the dialect code of an enabled PhoenixKit Language sharing its prefix. nil and codes with no enabled match fall back to the primary language.

ready?()

@spec ready?() :: boolean()

True once the registry has completed its initial ETS load.

reload()

@spec reload() :: :ok

Forces an immediate synchronous reload from the database.

start_link(_)