Plank help · updated 2026-08-12

Management accounting from bank statements

Upload bank statements and get a reconciled cash-flow statement and a cash-basis P&L. How the importer deduplicates, how transfers and categories are decided, and what cash basis does not include.

Agents: fetch the raw markdown of this page at /ru/help/management-accounting.md

Management accounting from bank statements

The Finance template turns bank statements into two reports you can act on:

  • a cash-flow statement — opening cash, money in, money out, closing cash, reconciled against what the bank says;
  • a cash-basis profit and loss — revenue and costs as they were actually paid.

Everything is there before you type anything: the tables, the category list, the starter rules, the importer, and the dashboard. What is missing is your statements.

Cash basis, and what that excludes

Every figure comes from money that moved through a bank account. That means these are not in it:

  • invoices you have issued but not been paid for;
  • bills you have received but not paid;
  • stock, depreciation, provisions and accruals;
  • a balance sheet — there isn't one.

This is the right basis for deciding what you can spend this week and where the money actually went last month. It is the wrong basis for statutory accounts, and the dashboard says so on every screen. If someone asks for "profit", tell them which basis you are quoting.

Getting started

  1. Put your statements in statements/. Any export your bank gives you: CSV, semicolon- or tab-separated, or the 1CClientBankExchange (КЛ) file. UTF-8 and Windows-1251 both work.
  2. Ask the agent to import them, or run it yourself.
  3. Open finance/dashboard.html.

Upload every account, not just the main one. A transfer between two of your own accounts can only be recognised when both sides are present.

The workspace also arrives with anonymised demo rows so the dashboard is not blank on the first open. They are all marked as preview, the dashboard hides them as soon as real data exists, and the first import can clear them outright.

The commands

# Read a file and report what is in it. Writes nothing.
python3 scripts/finance/parse-statement.py statements/june.csv

# Import. Safe to re-run — duplicates are skipped and counted.
python3 scripts/finance/import-statement.py statements/june.csv --account MAIN

# On the first real import, clear the demo rows in the same step.
python3 scripts/finance/import-statement.py statements/june.csv --account MAIN --replace-preview

# Check every account against the balances the banks reported.
python3 scripts/finance/reconcile.py

# After saving a rule, apply it to transactions already imported.
python3 scripts/finance/apply-rules.py

Importing twice is safe

Every transaction carries a fingerprint built from what the payment is — account, date, direction, amount, currency, document number, counterparty account and purpose — plus an occurrence index, so two genuinely identical payments on the same day both survive. It is a unique constraint in the database, not a convention.

So:

  • re-importing the same file adds nothing and tells you how many rows it skipped;
  • two statements whose periods overlap contribute each payment exactly once;
  • you never have to work out which period you already loaded.

What it will not do

It will not guess. A row it cannot read completely is rejected with a reason and kept, word for word, in the rejected list. A date it cannot parse does not become today; an amount it cannot parse does not become zero. Rejected rows are visible on the dashboard and are excluded from every total, so a hole in the data looks like a hole rather than like a smaller number.

It will not count a transfer as income. Money moved between your own accounts is excluded from both income and expense. Counting it is the most expensive mistake available here: a weekly sweep between two accounts, counted as revenue, invents a year of sales that never happened. When only one leg has been imported, the payment is still excluded and appears as an exception — the fix is uploading the other account's statement.

It will not invent a category. A payment nothing recognises is marked Needs review and shown on its own line, never folded into "other expenses". The cash result is stated twice: once without the unreviewed money and once with it.

How a category is chosen

In order, highest first:

  1. What you decided. Never overwritten — not by a rule, not by a re-import, not by the agent.
  2. A matched internal transfer.
  3. The rule table. Your rules sit above the built-in ones whatever their priority.
  4. A suggestion from the agent, capped below the review threshold so it always lands in the review queue rather than settling itself.
  5. Needs review, said out loud.

In the dashboard's review queue, pick a category and press Save, or Save + rule if it will happen again. Then run apply-rules.py and the same rule clears the payments already waiting, not just the next one.

Reading the dashboard

Cash-flow statement. The identity always holds and is always on screen:

opening cash + inflows − outflows + internal transfers + adjustments = closing cash

Across all your accounts the transfers line reads zero — that is the proof the elimination worked. Filter to a single account and it is not zero, correctly, because money really did leave that account; the statement still balances instead of looking wrong by exactly that amount.

Lines are grouped into operating, investing and financing. Click any line to see the transactions behind it — every total on the page is exactly the rows it lists.

Cash-basis P&L. Revenue, cost of sales, gross profit, operating expenses, cash result. Money not yet categorised is shown separately, below the result, with a second result including it.

Reconciliation exceptions. The panel to read before quoting any figure. It reports an account that does not match the bank's own closing balance, transfer legs with no partner, payments waiting for a category, and statement rows that could not be read at all. An empty panel is the only state in which these numbers can be forwarded without a caveat.

Filters — month or custom range, company, account, branch, project, currency, category, review status, and a search across counterparty and payment purpose — apply to every table on the page at once. Amounts in different currencies are never added together; if the filters span more than one, the dashboard says so instead of producing a total.

Branches, projects and cost centres

These are rows, not code. Add one to fin_branches, fin_projects or fin_cost_centers and set the code on the transactions that belong to it — the branch comparison and every filter pick it up immediately. One branch works from the start; twenty work the same way, with no change to anything.

Accounts, companies and categories work the same way. Ask the agent to add a category and it becomes available everywhere, including in the review queue's dropdown.

Adding a bank the importer has not seen

The importer recognises a statement from its own header rather than being told which bank it came from, so most new exports work with no change at all. When one does not, it is usually a column heading spelled a way the list has not met — that is a small addition to scripts/finance/_normalize.py, not new code. Run parse-statement.py on the file first: it will tell you exactly which rows it could not read and why.