The context for hierarchical business entities.
Business entities represent legal, operational, customer, organizational, or
ownership structures. They form a tree maintained through a closure table
(Logistiki.BusinessEntities.BusinessEntityClosure).
Closure table rules
- a self-row (depth 0) is always present
- cycles are prevented on move
- the closure is rewritten on insert and on
move_entity/2 - subtree and ancestor queries are O(closure rows), not recursive CTEs
Summary
Functions
Returns the ids of all ancestors of entity (including itself).
Creates a new business entity. When :parent_id is given the entity is
linked as a child and the closure table is updated.
Returns the ids of all descendants of entity (including itself).
Fetches a single entity by id.
Fetches a single entity by id, raising if not found.
Lists all ancestors of entity ordered from nearest to farthest, using the
closure table.
Lists direct children of entity.
Lists all descendants of entity (excluding the entity itself), using the
closure table for O(rows) performance.
Lists entities, optionally filtered.
Moves entity under new_parent (which may be nil for a root), rewriting
the closure table for the entire subtree.
Updates a business entity's mutable attributes (not its parent — use
move_entity/2 to change the parent).
Functions
@spec ancestor_ids(Logistiki.BusinessEntities.BusinessEntity.t()) :: [integer()]
Returns the ids of all ancestors of entity (including itself).
Arguments
entity—%BusinessEntity{}.
Returns
[integer()]— ancestor ids includingentity.id.
Examples
iex> Logistiki.BusinessEntities.ancestor_ids(acme_trading)
[2, 1]
@spec create_entity(map()) :: {:ok, Logistiki.BusinessEntities.BusinessEntity.t()} | {:error, term()}
Creates a new business entity. When :parent_id is given the entity is
linked as a child and the closure table is updated.
Arguments
attrs—map()orkeyword()of entity attributes::name—String.t— required (e.g."Acme Holdings"):entity_type—String.t— required (e.g."company","trust"):status—String.t— defaults to"pending":parent_id—integer() | nil— the parent entity id;nilfor roots:legal_name—String.t:jurisdiction—String.t— e.g."US":external_id—String.t— must be unique:metadata—map()
Returns
{:ok, %BusinessEntity{}}— the created entity.{:error, %Ecto.Changeset{}}— validation failed (e.g. invalidentity_type).{:error, :parent_closure_missing}— the parent has no closure rows (should not happen).
Examples
iex> {:ok, root} = Logistiki.BusinessEntities.create_entity(%{name: "Acme Holdings", entity_type: "company", status: "active"})
iex> root.parent_id
nil
iex> {:ok, child} = Logistiki.BusinessEntities.create_entity(%{
...> name: "Acme Trading Ltd", entity_type: "company", parent_id: root.id
...> })
iex> child.parent_id
1
iex> {:error, changeset} = Logistiki.BusinessEntities.create_entity(%{name: "X", entity_type: "wizard"})
iex> errors_on(changeset)[:entity_type]
["is invalid"]
@spec descendant_ids(Logistiki.BusinessEntities.BusinessEntity.t()) :: [integer()]
Returns the ids of all descendants of entity (including itself).
Uses the closure table directly — faster than list_descendants/1 when only
ids are needed (e.g. for balance aggregation queries).
Arguments
entity—%BusinessEntity{}.
Returns
[integer()]— descendant ids includingentity.id.
Examples
iex> Logistiki.BusinessEntities.descendant_ids(acme_holdings)
[1, 2, 3]
@spec get_entity(integer()) :: {:ok, Logistiki.BusinessEntities.BusinessEntity.t()} | {:error, :not_found}
Fetches a single entity by id.
Arguments
id—integer()— the entity primary key.
Returns
{:ok, %BusinessEntity{}}— the entity was found.{:error, :not_found}— no entity with that id.
Examples
iex> {:ok, entity} = Logistiki.BusinessEntities.get_entity(1)
iex> entity.name
"Acme Holdings"
iex> {:error, :not_found} = Logistiki.BusinessEntities.get_entity(999)
@spec get_entity!(integer()) :: Logistiki.BusinessEntities.BusinessEntity.t()
Fetches a single entity by id, raising if not found.
Arguments
id—integer()— the entity primary key.
Returns
%BusinessEntity{}— the entity. RaisesEcto.NoResultsErrorif not found.
Examples
iex> entity = Logistiki.BusinessEntities.get_entity!(1)
iex> entity.name
"Acme Holdings"
@spec list_ancestors(Logistiki.BusinessEntities.BusinessEntity.t()) :: [ Logistiki.BusinessEntities.BusinessEntity.t() ]
Lists all ancestors of entity ordered from nearest to farthest, using the
closure table.
Arguments
entity—%BusinessEntity{}— the descendant entity.
Returns
[BusinessEntity.t()]— ordered bydepthascending. Empty list if the entity is a root.
Examples
iex> Logistiki.BusinessEntities.list_ancestors(acme_trading)
[%BusinessEntity{name: "Acme Holdings", ...}]
@spec list_children(Logistiki.BusinessEntities.BusinessEntity.t()) :: [ Logistiki.BusinessEntities.BusinessEntity.t() ]
Lists direct children of entity.
Arguments
entity—%BusinessEntity{}— the parent entity.
Returns
[BusinessEntity.t()]— ordered by name. Empty list if no children.
Examples
iex> Logistiki.BusinessEntities.list_children(acme_holdings)
[%BusinessEntity{name: "Acme Trading Ltd", ...}, %BusinessEntity{name: "Acme Treasury Ltd", ...}]
@spec list_descendants(Logistiki.BusinessEntities.BusinessEntity.t()) :: [ Logistiki.BusinessEntities.BusinessEntity.t() ]
Lists all descendants of entity (excluding the entity itself), using the
closure table for O(rows) performance.
Arguments
entity—%BusinessEntity{}— the root of the subtree.
Returns
[BusinessEntity.t()]— ordered by name. Empty list if no descendants.
Examples
iex> Logistiki.BusinessEntities.list_descendants(acme_holdings)
[%BusinessEntity{name: "Acme Trading Ltd", ...}, %BusinessEntity{name: "Acme Treasury Ltd", ...}]
@spec list_entities(keyword()) :: [Logistiki.BusinessEntities.BusinessEntity.t()]
Lists entities, optionally filtered.
Arguments
opts—keyword()of options::status—String.t— e.g."active","frozen":entity_type—String.t— e.g."company","trust":parent_id—integer() | nil— filter by parent;nillists roots
Returns
[BusinessEntity.t()]— ordered by name ascending. Empty list if none match.
Examples
iex> Logistiki.BusinessEntities.list_entities(status: "active")
[%BusinessEntity{name: "Acme Holdings", ...}, %BusinessEntity{name: "Bluewater Trust", ...}]
iex> Logistiki.BusinessEntities.list_entities(parent_id: nil)
[%BusinessEntity{name: "Acme Holdings", ...}]
@spec move_entity( Logistiki.BusinessEntities.BusinessEntity.t(), Logistiki.BusinessEntities.BusinessEntity.t() | integer() | nil ) :: {:ok, Logistiki.BusinessEntities.BusinessEntity.t()} | {:error, term()}
Moves entity under new_parent (which may be nil for a root), rewriting
the closure table for the entire subtree.
Arguments
entity—%BusinessEntity{}— the entity to move.new_parent— one of:%BusinessEntity{}— the new parentnil— move to root (no parent)integer()— the new parent's id
Returns
{:ok, %BusinessEntity{}}— the moved entity with updatedparent_id.{:error, term()}— the move failed (rolled back).
Examples
iex> {:ok, moved} = Logistiki.BusinessEntities.move_entity(acme_trading, bluewater_trust)
iex> moved.parent_id
5
iex> {:ok, root} = Logistiki.BusinessEntities.move_entity(acme_trading, nil)
iex> root.parent_id
nil
@spec update_entity(Logistiki.BusinessEntities.BusinessEntity.t(), map()) :: {:ok, Logistiki.BusinessEntities.BusinessEntity.t()} | {:error, Ecto.Changeset.t()}
Updates a business entity's mutable attributes (not its parent — use
move_entity/2 to change the parent).
Arguments
entity—%BusinessEntity{}— the entity to update.attrs—map()orkeyword()of attributes to change (e.g.%{status: "active", legal_name: "Acme Holdings LLC"}).
Returns
{:ok, %BusinessEntity{}}— the updated entity.{:error, %Ecto.Changeset{}}— validation failed.
Examples
iex> {:ok, updated} = Logistiki.BusinessEntities.update_entity(entity, %{status: "active"})
iex> updated.status
"active"