# `Logistiki.Relationships`
[🔗](https://github.com/thanos/logistiki/blob/v0.1.0/lib/logistiki/relationships/context.ex#L1)

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.

# `link_entity_account`
*since 0.1.0* 

```elixir
@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{}` or `integer()` entity id.
  * `account` — `%VirtualAccount{}` or `integer()` account id.
  * `relationship_type` — `atom()` — one of `EntityAccount.relationship_types/0`
    (e.g. `:owner`, `:beneficiary`).
  * `attrs` — `map()` of optional attributes:
      * `:valid_from` — `Date.t` — defaults to `Date.utc_today/0`
      * `:valid_to` — `Date.t` — defaults to `nil` (active)
      * `:metadata` — `map()`

## Returns

  * `{:ok, %EntityAccount{}}` — the link was created.
  * `{:error, %Ecto.Changeset{}}` — validation failed (e.g. unknown
    `relationship_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"]

# `list_accounts_for_entity`
*since 0.1.0* 

```elixir
@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{}` or `integer()` 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])
    []

# `list_accounts_for_entity_tree`
*since 0.1.0* 

```elixir
@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 as `list_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", ...}]

# `list_entities_for_account`
*since 0.1.0* 

```elixir
@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{}` or `integer()` 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", ...}]

# `unlink_entity_account`
*since 0.1.0* 

```elixir
@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{}` or `integer()` entity id.
  * `account` — `%VirtualAccount{}` or `integer()` 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}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
