The context for entity-to-account relationships.
Business entities and virtual accounts are separate hierarchies. They connect through relationships that support many-to-many links, multiple relationship types between the same pair, and effective dating.
Effective dating
Each relationship has valid_from and valid_to (nil while active). List
functions accept an :at option (a Date) to filter relationships active at
a point in time.
Summary
Functions
Links entity to account with relationship_type and optional attributes.
Lists accounts linked to entity, optionally filtered.
Lists accounts linked to entity or any of its descendants.
Lists entities linked to account, optionally filtered.
Unlinks entity and account for relationship_type by setting valid_to
to today. The relationship row is preserved for audit — only the active link
is ended.
Functions
@spec link_entity_account( Logistiki.BusinessEntities.BusinessEntity.t() | integer(), Logistiki.VirtualAccounts.VirtualAccount.t() | integer(), atom(), map() ) :: {:ok, Logistiki.Relationships.EntityAccount.t()} | {:error, Ecto.Changeset.t()}
Links entity to account with relationship_type and optional attributes.
Arguments
entity—%BusinessEntity{}orinteger()entity id.account—%VirtualAccount{}orinteger()account id.relationship_type—atom()— one ofEntityAccount.relationship_types/0(e.g.:owner,:beneficiary).attrs—map()of optional attributes::valid_from—Date.t— defaults toDate.utc_today/0:valid_to—Date.t— defaults tonil(active):metadata—map()
Returns
{:ok, %EntityAccount{}}— the link was created.{:error, %Ecto.Changeset{}}— validation failed (e.g. unknownrelationship_type, duplicate(entity, account, type)triple).
Examples
iex> {:ok, link} = Logistiki.Relationships.link_entity_account(acme_entity, nostro_account, :owner)
iex> link.relationship_type
"owner"
iex> {:ok, link} = Logistiki.Relationships.link_entity_account(acme_entity, nostro_account, :beneficiary,
...> valid_from: ~D[2026-01-01], metadata: %{note: "joint"}
...> )
iex> link.metadata
%{"note" => "joint"}
iex> {:error, changeset} = Logistiki.Relationships.link_entity_account(entity, account, :wizard)
iex> errors_on(changeset)[:relationship_type]
["is invalid"]
@spec list_accounts_for_entity( Logistiki.BusinessEntities.BusinessEntity.t() | integer(), keyword() ) :: [Logistiki.VirtualAccounts.VirtualAccount.t()]
Lists accounts linked to entity, optionally filtered.
Arguments
entity_or_id—%BusinessEntity{}orinteger()entity id.opts—keyword()of options::relationship_type—atom()— filter by type (e.g.:owner):at—Date.t— effective date; only relationships active on this date are returned. When omitted, only currently active relationships (valid_to IS NULL) are returned.:currency—String.t— filter to a currency
Returns
[VirtualAccount.t()]— ordered by code. Empty list if none.
Examples
iex> Logistiki.Relationships.list_accounts_for_entity(acme_entity)
[%VirtualAccount{code: "LIABILITIES:CLIENT_DEPOSITS:USD:ACME:OPERATING", ...}]
iex> Logistiki.Relationships.list_accounts_for_entity(acme_entity, relationship_type: :owner)
[%VirtualAccount{code: "LIABILITIES:CLIENT_DEPOSITS:USD:ACME:OPERATING", ...}]
iex> Logistiki.Relationships.list_accounts_for_entity(acme_entity, at: ~D[2026-01-01])
[]
@spec list_accounts_for_entity_tree( Logistiki.BusinessEntities.BusinessEntity.t(), keyword() ) :: [Logistiki.VirtualAccounts.VirtualAccount.t()]
Lists accounts linked to entity or any of its descendants.
Uses the business-entity closure table to find all descendant entity ids, then finds all accounts linked to any of them.
Arguments
entity—%BusinessEntity{}— the root of the entity subtree.opts—keyword()— same options aslist_accounts_for_entity/2.
Returns
[VirtualAccount.t()]— ordered by code. Empty list if none.
Examples
iex> Logistiki.Relationships.list_accounts_for_entity_tree(acme_holdings)
[%VirtualAccount{code: "LIABILITIES:CLIENT_DEPOSITS:USD:ACME:OPERATING", ...},
%VirtualAccount{code: "LIABILITIES:CLIENT_DEPOSITS:USD:ACME:PAYROLL", ...}]
@spec list_entities_for_account( Logistiki.VirtualAccounts.VirtualAccount.t() | integer(), keyword() ) :: [Logistiki.BusinessEntities.BusinessEntity.t()]
Lists entities linked to account, optionally filtered.
Arguments
account_or_id—%VirtualAccount{}orinteger()account id.opts—keyword()of options::relationship_type—atom()— filter by type (e.g.:owner):at—Date.t— effective date
Returns
[BusinessEntity.t()]— ordered by name. Empty list if none.
Examples
iex> Logistiki.Relationships.list_entities_for_account(operating_account)
[%BusinessEntity{name: "Acme Holdings", ...}]
iex> Logistiki.Relationships.list_entities_for_account(operating_account, relationship_type: :owner)
[%BusinessEntity{name: "Acme Holdings", ...}]
@spec unlink_entity_account( Logistiki.BusinessEntities.BusinessEntity.t() | integer(), Logistiki.VirtualAccounts.VirtualAccount.t() | integer(), atom() ) :: {integer(), nil}
Unlinks entity and account for relationship_type by setting valid_to
to today. The relationship row is preserved for audit — only the active link
is ended.
Arguments
entity—%BusinessEntity{}orinteger()entity id.account—%VirtualAccount{}orinteger()account id.relationship_type—atom()— e.g.:owner.
Returns
{integer(), nil}— the number of rows updated (0 or 1).
Examples
iex> Logistiki.Relationships.unlink_entity_account(acme_entity, operating_account, :owner)
{1, nil}
iex> Logistiki.Relationships.unlink_entity_account(acme_entity, unknown_account, :owner)
{0, nil}