# `ExReconcile`
[🔗](https://github.com/ARTARNA/ex_reconcile/blob/v0.1.0/lib/ex_reconcile.ex#L1)

ExReconcile - ledger reconciliation for Elixir.

Given two lists of `ExReconcile.Transaction` structs (e.g. a bank export and an
accounting system export), `ExReconcile` finds matching pairs, surfaces discrepancies,
and identifies transactions that appear in only one source.

## Quick start

    left = [
      ExReconcile.Transaction.new(id: "REF-1", amount: 1050, date: ~D[2024-01-15], description: "Coffee"),
      ExReconcile.Transaction.new(id: "REF-2", amount: 5000, date: ~D[2024-01-16], description: "Taxi")
    ]

    right = [
      ExReconcile.Transaction.new(id: "REF-1", amount: 1050, date: ~D[2024-01-15], description: "COFFEE SHOP"),
      ExReconcile.Transaction.new(id: "REF-3", amount: 200,  date: ~D[2024-01-17], description: "Unknown")
    ]

    result = ExReconcile.reconcile(left, right, match_on: [:id])
    # %ExReconcile.Result{
    #   matched:          [],
    #   discrepancies:    [{txn_ref1_left, txn_ref1_right, [%{field: :description, ...}]}],
    #   unmatched_left:   [txn_ref2],
    #   unmatched_right:  [txn_ref3]
    # }

    IO.puts ExReconcile.format(result)

## Matching strategies

Control matching with the `:match_on` option and tolerance settings:

```elixir
# Match on ID (exact), ignore description differences
ExReconcile.reconcile(left, right,
  match_on: [:id],
  description_match: :ignore
)

# Match on amount + date, allow ±2 days and ±5 cents
ExReconcile.reconcile(left, right,
  match_on: [:amount, :date],
  amount_tolerance: 5,
  date_tolerance: 2
)
```

See `ExReconcile.Config` for all available options.

# `format`

```elixir
@spec format(
  ExReconcile.Result.t(),
  keyword()
) :: String.t()
```

Format a reconciliation result as a human-readable text report.

## Options

- `:title` - report header. Defaults to `"Reconciliation Report"`.
- `:show_matched` - include the full list of matched pairs. Defaults to `false`.

## Examples

    iex> result = ExReconcile.reconcile([], [])
    iex> ExReconcile.format(result) =~ "CLEAN"
    true

# `reconcile`

```elixir
@spec reconcile([Transaction.t()], [Transaction.t()], keyword()) ::
  ExReconcile.Result.t()
```

Reconcile two lists of transactions.

Returns an `ExReconcile.Result` with four fields:

- `:matched` - `[{left_txn, right_txn}]` perfectly reconciled pairs
- `:discrepancies` - `[{left_txn, right_txn, [diff]}]` pairs that match by key but
  differ in one or more field values
- `:unmatched_left` - transactions in `left` with no counterpart in `right`
- `:unmatched_right` - transactions in `right` with no counterpart in `left`

## Options

Accepts the same keyword options as `ExReconcile.Config.new/1`.

| Option | Default |
|---|---|
| `:match_on` | `[:amount, :date]` |
| `:amount_tolerance` | `0` |
| `:date_tolerance` | `0` |
| `:description_match` | `:case_insensitive` |

## Examples

    iex> alias ExReconcile.Transaction
    iex> left  = [Transaction.new(amount: 100, date: ~D[2024-01-01])]
    iex> right = [Transaction.new(amount: 100, date: ~D[2024-01-01])]
    iex> result = ExReconcile.reconcile(left, right)
    iex> length(result.matched)
    1
    iex> result.unmatched_left
    []

    iex> alias ExReconcile.Transaction
    iex> left  = [Transaction.new(id: "X1", amount: 100)]
    iex> right = [Transaction.new(id: "X1", amount: 105)]
    iex> result = ExReconcile.reconcile(left, right, match_on: [:id])
    iex> [{_l, _r, diffs}] = result.discrepancies
    iex> hd(diffs).field
    :amount

---

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