Bank Statement Import Pro
Four native statement parsers, two-layer duplicate defense, and a full per-file import history log, the piece Community accounting is missing.
Why this module
Bank Statement Import Pro
Five formats, one registry
CSV with per-bank column profiles, OFX, QIF, CAMT.053 against the ISO 20022 element tree, and a full MT940 SWIFT parser with :86: subfield extraction, all behind a pluggable parser registry. A new format is a registered class, not a fork.
Two-layer duplicate defense
An exact unique-reference constraint per journal makes re-import idempotent, and a separate fuzzy scan (amount to the cent, 3-day date window, reference and narration overlap) catches the same payment arriving through a second format with a different reference. It flags for review, it never deletes.
Nothing dropped silently
Every file import writes a history row (file hash, user, line count, skipped count, outcome) on success, duplicate, or failure, with the exact exception captured on a failed run. Bad rows raise a precise error naming the offending field, never a silent drop.
Day in the life
Monday morning. Five banks, three currencies, 1,200 statement lines.
The OFX from the US bank, the CAMT.053 from the EU bank, the MT940 from the SWIFT bank, and the CSV from the AU bank arrive through separate routes, and the registry picks the right parser per file. A comma-decimal European CSV ('1.234,56') is normalised correctly from the bank profile, so no amount is silently corrupted. One bank's CSV had a row inserted since last week, but because the per-line key is content plus occurrence counter and never file position, the unchanged lines do not re-import. A wire that came through both the CSV and the OFX feed under two different references is flagged as a probable cross-source duplicate for the operator to confirm, not deleted. One file has a malformed row: it raises with the offending field named, the failure is written to the import history log with the exception text, and the other four files still post clean.
Edge cases
The cases most modules quietly ignore.
In the shipped code today, each one a place where a cheaper module silently does the wrong thing.
Re-importing a statement that had a row added or removed elsewhere does not duplicate the unchanged lines, because each line's key is content plus an occurrence counter, never its position in the file. Two genuinely identical rows in one file are disambiguated only by order of appearance.
The same payment imported once via CSV and again via OFX under different per-line references is caught even though the exact-reference guard cannot see it, using a plus or minus 3-day value-date window, amount to the cent, and reference or narration overlap. The line is flagged and a chatter note posted, never auto-deleted.
Comma-decimal CSVs ('1.234,56') are normalised correctly per profile from a decimal-separator field on the bank profile, closing a common source of silent amount corruption between EU and US conventions.
RC and RD reversal marks flip the debit/credit sign an extra time, so a reversed credit becomes a debit to match bank accounting convention, and two-digit YYMMDD dates are century-pivoted at year 80.
On the live-feed cron, one connector that crashes rolls back only its own profile under a per-record savepoint and the run continues for the rest. The tested behaviour is that a single bad connector never freezes the others.
The since-date watermark auto-advances to the latest fetched posted date after each successful live run, so the next cron pass does not re-pull the same window.
Partner resolution batches res.partner lookups by unique name, so a 5,000-line CSV does not fire 5,000 separate queries on import.
What is inside
Built to do the job, end to end.
- Five native parsers, pluggable registry. CSV with per-bank profiles, OFX, QIF, CAMT.053 (ISO 20022), and MT940 (SWIFT, with :86: extension blocks). Format dispatch lives in a registry; partner packages add formats by registering a parser class.
- Idempotent re-import per journal. A unique constraint on (journal, line import reference) means re-importing the same file skips the lines already seen on that journal instead of duplicating them.
- Fuzzy cross-source duplicate flagging. A second scan keyed on journal, amount to the cent, a 3-day date window, and reference or narration overlap flags probable duplicates that the exact guard misses, recording the suspected original and posting a chatter note without deleting.
- Audited import history log. Each file import writes a row with file hash, importing user, line count, skipped count, and outcome on every result (done, duplicate no-op, or failure), capturing the exception text when a run fails.
- Precise bad-row errors. Parsers raise a specific error naming the offending row, tag, or field, and the wizard records it as an audited error rather than silently dropping the row.
- Per-statement and per-line currency capture, with a journal guard. The currency code reported by the file (CSV profile, OFX, QIF, CAMT.053, MT940) is parsed onto the imported lines, and a file whose currency does not match the target journal is rejected with an audited error instead of booking at face value.
- Pluggable live-connector framework. A connector profile model plus an abstract LiveBankConnector contract, a 2-hourly cron with per-profile savepoint isolation, an auto-advancing since-date watermark, and Plaid, Basiq, GoCardless, and manual adapters shipped as stubs for partner packages to extend.
- Multi-company aware. Profiles and statements are scoped through the bank journal, which carries the company, so the importer respects company boundaries by construction.
Honest about the edges
What this does not do, so nothing surprises you.
- OFX import runs on the third-party ofxparse Python package, which is not bundled. Install it once with pip install ofxparse, or let a repo-level requirements.txt install it for you (for example on Odoo.sh). CSV, QIF, CAMT.053, and MT940 need no extra packages.
- The live-connector framework ships Plaid, Basiq, GoCardless, and manual adapters as stubs only. It defines the contract and the cron, but does not include production transport to any specific bank; partner packages or your own code supply the live connection.
- The live-connector fetch path records its result on the connector profile and its chatter, but does not write a row to the file-import history log. The history log covers file imports.
- The import history log is a plain audited record. It captures hash, user, counts, and outcome, but rows are not write-protected or delete-protected at the framework level.
- PDF statement ingestion is on the roadmap and is not shipped. The five parsers are CSV, OFX, QIF, CAMT.053, and MT940.
- MT940 is fully implemented and selectable, but does not yet have a dedicated automated test.
odoo 19 bank statement import, odoo community bank statement import, import CAMT.053 odoo, import MT940 odoo, import OFX bank statement odoo, import QIF bank statement odoo, CSV bank statement import per-bank profile, odoo bank statement duplicate detection, idempotent bank statement re-import, ISO 20022 statement import odoo, bank feed connector framework odoo
Languages
Available in 19 languages
The interface ships translated out of the box. Switch language in Odoo and the fields, menus, and messages follow.
Please log in to comment on this module