Logistiki.BusinessEntities (logistiki v0.1.0)

Copy Markdown View Source

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

ancestor_ids(business_entity)

(since 0.1.0)

Returns the ids of all ancestors of entity (including itself).

Arguments

  • entity%BusinessEntity{}.

Returns

  • [integer()] — ancestor ids including entity.id.

Examples

iex> Logistiki.BusinessEntities.ancestor_ids(acme_trading)
[2, 1]

create_entity(attrs \\ %{})

(since 0.1.0)
@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

  • attrsmap() or keyword() of entity attributes:
    • :nameString.trequired (e.g. "Acme Holdings")
    • :entity_typeString.trequired (e.g. "company", "trust")
    • :statusString.t — defaults to "pending"
    • :parent_idinteger() | nil — the parent entity id; nil for roots

    • :legal_nameString.t
    • :jurisdictionString.t — e.g. "US"
    • :external_idString.t — must be unique
    • :metadatamap()

Returns

  • {:ok, %BusinessEntity{}} — the created entity.
  • {:error, %Ecto.Changeset{}} — validation failed (e.g. invalid entity_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"]

descendant_ids(business_entity)

(since 0.1.0)
@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 including entity.id.

Examples

iex> Logistiki.BusinessEntities.descendant_ids(acme_holdings)
[1, 2, 3]

get_entity(id)

(since 0.1.0)
@spec get_entity(integer()) ::
  {:ok, Logistiki.BusinessEntities.BusinessEntity.t()} | {:error, :not_found}

Fetches a single entity by id.

Arguments

  • idinteger() — 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)

get_entity!(id)

(since 0.1.0)

Fetches a single entity by id, raising if not found.

Arguments

  • idinteger() — the entity primary key.

Returns

Examples

iex> entity = Logistiki.BusinessEntities.get_entity!(1)
iex> entity.name
"Acme Holdings"

list_ancestors(business_entity)

(since 0.1.0)

Lists all ancestors of entity ordered from nearest to farthest, using the closure table.

Arguments

  • entity%BusinessEntity{} — the descendant entity.

Returns

  • [BusinessEntity.t()] — ordered by depth ascending. Empty list if the entity is a root.

Examples

iex> Logistiki.BusinessEntities.list_ancestors(acme_trading)
[%BusinessEntity{name: "Acme Holdings", ...}]

list_children(business_entity)

(since 0.1.0)

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", ...}]

list_descendants(business_entity)

(since 0.1.0)

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", ...}]

list_entities(opts \\ [])

(since 0.1.0)

Lists entities, optionally filtered.

Arguments

  • optskeyword() of options:
    • :statusString.t — e.g. "active", "frozen"
    • :entity_typeString.t — e.g. "company", "trust"
    • :parent_idinteger() | nil — filter by parent; nil lists 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", ...}]

move_entity(entity, new_parent)

(since 0.1.0)

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 parent
    • nil — move to root (no parent)
    • integer() — the new parent's id

Returns

  • {:ok, %BusinessEntity{}} — the moved entity with updated parent_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

update_entity(entity, attrs)

(since 0.1.0)

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.
  • attrsmap() or keyword() 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"