{% extends "base.html" %} {% import "_bank.html" as bank with context %} {% import "_ledger.html" as ledger_row with context %} {% block title %}{{ t('statements.title') }} — painfree{% endblock %} {% block body %}

{{ t('statements.heading') }}

{% if family == 'account' %}{{ t('statements.lede') }}{% else %}{{ t('statements.lede_documents') }}{% endif %}

{# Three tabs. The account is what a reader has in mind; the documents are what arrived, and are still one click away for the day somebody needs a particular one. #} {% set filters = ('&connection_id=' ~ selected_connection if selected_connection else '') ~ ('&message_type=' ~ selected_type if selected_type else '') %}

{{ t('statements.tab_account') }} {{ t('statements.tab_accounts') }} {{ counts.get('account') | count }} {{ t('statements.tab_responses') }} {{ counts.get('response') | count }}

{% if family == 'account' %} {# ---- the account overview ------------------------------------------------ #} {# Two selects and a button, no script: choosing a bank submits and lands on that bank's account, then the second select offers the bank's accounts. The bank select is there before any statement is, so the page always says which connections the reader holds. #}
{% if bank_accounts %}
{% endif %} {%- if source == 'remembered' %}{{ t('statements.remembered') }} {%- elif source == 'first' and chosen %}{{ t('statements.not_remembered') }}{% endif %}
{% if not accounts %}

{{ t('statements.no_accounts') }}

{% elif not chosen %}

{{ t('statements.no_accounts_here') }}

{% else %} {% set newest = overview.newest %} {% set account = (newest.payload.get('account') if newest else none) or {} %} {% set currency = chosen.currency %}

{{ account.get('owner') or chosen.iban }}

{{ chosen.iban }} · {{ currency }} {%- if account.get('servicer_bic') %} · {{ account.get('servicer_bic') }}{% endif %} · {{ chosen.connection_id }}
{# The four figures, the way the statement page draws its own: what the balance is now, and what moved over the period. #}
{{ t('account.balance') }}
{% if chosen.closing_balance is not none %}{{ chosen.closing_balance | amount(currency) }}{% else %}—{% endif %}
{% if chosen.closing_at %}{{ t('statements.closing_on', date=chosen.closing_at | day) }}{% else %}{{ t('statements.no_closing') }}{% endif %}
{{ t('account.money_in') }}
{{ overview.credited | amount(currency) }}
{{ t('statements.entries_since', count=overview.credits, date=since | day) }}
{{ t('account.money_out') }}
{{ overview.debited | amount(currency) }}
{{ t('statements.entries_since', count=overview.debits, date=since | day) }}
{{ t('statements.since_last') }}
{{ overview.intraday | length | count }}
{{ t('statements.since_last_note') }}
{# Whether the statements meet. A gap is a statement this service has not got, and is said in front of the figures rather than found behind them. #}

{%- if not overview.days %}{{ t('statements.no_statements_in_period') }} {%- elif overview.continuous %}✓ {{ t('statements.coverage', count=overview.days | length, from_day=overview.oldest.to_datetime | day, to_day=overview.newest.to_datetime | day) }} {%- else %}× {% for older, newer in overview.gaps %} {{ t('statements.gap', before=older.to_datetime | day, after=newer.to_datetime | day) }} {% endfor %}{% endif %}

{% set chart = overview.chart() %} {% if chart %}

{{ t('statements.chart') }}

{% for tick in chart.ticks %} {{ tick.value | count }} {% endfor %} {{ chart.first | day }} {{ chart.last | day }}
{% endif %}
{# Links rather than a form, so each state is a URL an operator can keep. #} {% set keep = 'connection_id=' ~ chosen.connection_id ~ '&iban=' ~ chosen.iban %}

{{ t('statements.period') }} {% for days in periods %} {{ t('statements.days', count=days) }} {% endfor %}

{{ t('account.show') }} {{ t('account.show_all') }} {{ t('account.money_out') }} {{ t('account.money_in') }} {% if overview.ours %} {{ t('account.show_ours') }} {% endif %}

{# Reported intraday, above the statements and outside them: not yet in an end-of-day statement, so nothing here moves the balance. #} {% if overview.intraday %} {% for line in overview.intraday %} {{ ledger_row.row(line, false) }} {% endfor %} {% endif %} {# One group per statement, newest first, and the statement is the link: the day's own page keeps document order and the normalised JSON. #} {% for day in overview.days %} {% for line in day.lines %} {{ ledger_row.row(line, day.ledger.running) }} {% else %} {% endfor %} {% else %} {% endfor %}
{{ t('account.booked') }} {{ t('account.value_date') }} {{ t('account.counterparty') }} {{ t('account.money_out') }} {{ t('account.money_in') }} {{ t('account.balance') }}
{{ t('statements.since_last') }} {{ t('statements.since_last_note') }}
{{ day.date | day }} {{ day.row.message_type }} {%- if day.row.identification %} · {{ day.row.identification }}{% endif %} · +{{ day.ledger.credited | amount }} · −{{ day.ledger.debited | amount }}
{{ t('account.no_entries') }}
{{ t('statements.no_statements_in_period') }}

{{ t('account.balance_derived') }}

{% endif %} {% elif family == 'response' %} {# ---- bank responses: what the bank said, and about which order. No amount and no counterparty: the index is held to the rule the webhook envelope is, and the money is one click away on the report itself. ---------------- #}
{% if not statements %}

{{ t('statements.empty') }}

{% else %} {% for row in statements %} {% set code = status_codes.get(row.outcome.status or '') %} {% endfor %}
{{ t('statements.reports_on') }}{{ t('order.status') }} {{ t('order.accepted_count') }} {{ t('order.rejected_count') }} {{ t('order.reason') }}{{ t('response.received') }}
{% if row.order_id %}{{ row.order_id }} {% else %}{{ t('response.unmatched_heading') }}{% endif %}
{{ row.statement_id }}
{% if row.outcome.status %} {{ row.outcome.status }}
{{ code.name if code else t('response.unknown_status') }} {% else %}{{ t('response.not_reported') }}{% endif %}
{{ row.outcome.accepted | count }} {{ row.outcome.rejected | count }} {% if row.outcome.reason_code %}{{ row.outcome.reason_code }} {% endif %} {{ bank.quiet(row.outcome.reason_text) }} {{ row.ingested_at | moment }}
{% endif %}
{% else %} {# ---- the documents: an account and its balances, one row per statement. No entries here on purpose; they are on the statement and on the account the reader chose. --------------------------------------------------------- #}
{% if not statements %}

{{ t('statements.empty') }}

{% else %} {% for row in statements %} {% endfor %}
{{ t('statements.account') }}{{ t('statement.period') }} {{ t('statements.closing_balance') }} {{ t('statements.entries') }} {{ t('statements.type') }}{{ t('order.ingested') }}
{{ row.iban or '—' }} {% if row.currency %}{{ row.currency }}{% endif %}
{{ row.connection_id }} {{ row.statement_id }}
{% if row.from_datetime %}{{ row.from_datetime | day }} → {{ row.to_datetime | day }} {% else %}{{ row.ingested_at | day }}{% endif %} {% if row.sequence_number %}
{{ t('statement.sequence') }} {{ row.sequence_number }}{% endif %}
{% if row.closing_balance is not none %}{{ row.closing_balance | amount(row.currency) }}{% else %}—{% endif %} {{ row.entry_count | count }} {{ row.message_type }} {{ row.ingested_at | moment }}
{% endif %}
{% endif %} {% endblock %}