diff --git a/components/reconciliation/AccountOverview.tsx b/components/reconciliation/AccountOverview.tsx index f2263cb8..4f5f9ec7 100644 --- a/components/reconciliation/AccountOverview.tsx +++ b/components/reconciliation/AccountOverview.tsx @@ -358,6 +358,16 @@ export function AccountOverview({ account, rail, window, onChanged }: AccountOve const fetchedAt = isSkv ? status.skattekonto?.fetched_at : account.source.synced_at const sourceLabel = isSkv ? t('source_skv') : t('source_bank') + const bankRaw = status.bank as { bank_transaction_inflow?: number; bank_transaction_outflow?: number; bank_transaction_count?: number } | null + const bankBreakdown = + !isSkv && bankRaw && typeof bankRaw.bank_transaction_inflow === 'number' && typeof bankRaw.bank_transaction_outflow === 'number' + ? t('tile_bank_breakdown', { + inflow: formatCurrency(bankRaw.bank_transaction_inflow, currency), + outflow: formatCurrency(Math.abs(bankRaw.bank_transaction_outflow), currency), + count: bankRaw.bank_transaction_count ?? 0, + }) + : null + const tiles: Array<{ key: string; label: string; value: string; sub: string; tone?: 'ok' | 'attn'; help?: string }> = [ { key: 'external', @@ -370,7 +380,8 @@ export function AccountOverview({ account, rail, window, onChanged }: AccountOve ? status.external_balance : (status.bridge.find((l) => l.key === 'bank_transactions')?.amount ?? status.external_balance), ), - sub: fetchedAt ? t('tile_synced', { date: formatDate(fetchedAt) }) : t('rail_never_synced'), + // Bank: the gross split makes the net self-explanatory; skattekonto: when it was fetched. + sub: bankBreakdown ?? (fetchedAt ? t('tile_synced', { date: formatDate(fetchedAt) }) : t('rail_never_synced')), }, { key: 'ledger', diff --git a/lib/reconciliation/bank-reconciliation.ts b/lib/reconciliation/bank-reconciliation.ts index 905c7c86..d38927a2 100644 --- a/lib/reconciliation/bank-reconciliation.ts +++ b/lib/reconciliation/bank-reconciliation.ts @@ -110,6 +110,12 @@ export interface ReconciliationStatus { * excluded-but-shown. */ bank_transaction_total: number + /** Gross inflow (sum of positive amounts) behind bank_transaction_total; informational, for the page's "in · ut · antal" line. */ + bank_transaction_inflow: number + /** Gross outflow (sum of negative amounts, itself negative) behind bank_transaction_total. */ + bank_transaction_outflow: number + /** Number of non-ignored bank transactions in the window. */ + bank_transaction_count: number /** Sum of ignored bank transactions in the window. NOT part of * bank_transaction_total or difference; informational, like the IB. */ ignored_transaction_total: number @@ -921,6 +927,10 @@ export async function getReconciliationStatus( // matched_count + unmatched_transaction_count always equals the number of // rows behind bank_transaction_total. const matchedCount = reconcilableTx.filter((tx) => tx.journal_entry_id !== null).length + // The gross split behind the net: what the user actually recognises as + // "what moved on the bank", so the page never has to explain "netto". + const bankInflow = reconcilableTx.reduce((sum, tx) => sum + Math.max(Number(tx.amount) || 0, 0), 0) + const bankOutflow = reconcilableTx.reduce((sum, tx) => sum + Math.min(Number(tx.amount) || 0, 0), 0) const unmatchedTx = reconcilableTx.filter((tx) => tx.journal_entry_id === null) const unmatchedTransactionCount = unmatchedTx.length @@ -976,6 +986,9 @@ export async function getReconciliationStatus( return { currency, bank_transaction_total: Math.round(bankTotal * 100) / 100, + bank_transaction_inflow: roundOre(bankInflow), + bank_transaction_outflow: roundOre(bankOutflow), + bank_transaction_count: reconcilableTx.length, ignored_transaction_total: roundOre(ignoredTotal), ignored_transaction_count: ignoredTx.length, gl_1930_balance: Math.round(glBalance * 100) / 100, diff --git a/lib/reconciliation/service.ts b/lib/reconciliation/service.ts index 9517df99..b893cb70 100644 --- a/lib/reconciliation/service.ts +++ b/lib/reconciliation/service.ts @@ -88,8 +88,8 @@ function bankBridge(status: Awaited