| Availability |
Odoo Online
Odoo.sh
On Premise
|
| Odoo Apps Dependencies |
•
Invoicing (account)
• Discuss (mail) |
| Community Apps Dependencies | Show |
| Lines of code | 6157 |
| Technical Name |
eh_account_sepa_dd |
| License | LGPL-3 |
| Website | https://www.erpheritage.com.au/ |
| Availability |
Odoo Online
Odoo.sh
On Premise
|
| Odoo Apps Dependencies |
•
Invoicing (account)
• Discuss (mail) |
| Community Apps Dependencies | Show |
| Lines of code | 6157 |
| Technical Name |
eh_account_sepa_dd |
| License | LGPL-3 |
| Website | https://www.erpheritage.com.au/ |
SEPA Direct Debit
Generate ISO 20022 PAIN.008 direct debit files and run the full mandate lifecycle, with scheme guards that stop a bad file before it reaches the bank.
Why this module
SEPA Direct Debit
Written from the public spec
The PAIN.008.001.02 generator is original work, built strictly from the public ISO 20022 specification and the SEPA Direct Debit Rulebook. Tests parse the output back with lxml and assert the scheme structure, namespace, sequence type, local instrument, mandate block, creditor identifier, charge bearer, and control sum.
Bad files never reach the bank
A pre-notification lead-time guard and single-currency enforcement reject non-compliant batches at export time with a clear message, rather than producing a file the bank rejects days later. SEPA character sanitisation and field-length clipping keep every value inside scheme maxima.
The sequence machine that does not race
FRST flips to RCUR via an atomic SQL counter increment, so concurrent collection attempts cannot double-issue a FRST. The 36-month dormancy rule is enforced on both the collection path and a daily savepoint-isolated cron.
Day in the life
A finance operator runs the monthly subscription collection.
A batch of posted EUR payments is exported. For each payment the mandate is resolved, disambiguated by debtor IBAN when a partner holds more than one active mandate for the creditor. Each mandate is consumed for collection: a first-time mandate yields FRST, the rest yield RCUR, and the counter increments atomically so no two runs can issue the same FRST twice. Before anything is written, the export checks the collection date against the creditor's pre-notification window and refuses if the debtor would be unwarned, and it rejects the batch outright if it mixes currencies. The local-instrument code from the creditor configuration is written into the file's LclInstrm element. Debtor names, end-to-end ids, and remittance info are sanitised and clipped to scheme maxima before rendering. The PAIN.008.001.02 file is generated, fingerprinted with SHA-256, and any earlier file for the same batch and sequence type is marked superseded so the audit trail shows which file is current.
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.
The export refuses to run when the collection date is closer than the creditor's pre-notification window (default 14 days for CORE FRST), so a file never lands at the bank with the debtor unwarned. The window is configurable per creditor.
A batch mixing payment currencies is rejected at export time with a message naming the offending currencies, rather than producing an ISO 20022 invalid file the bank would reject later. SEPA requires one currency per file.
When a collection hits an expired mandate, the state flips to expired via a raw SQL update plus flush and invalidate, so the expiry survives the outer error rollback that an ordinary ORM write would be undone by.
When a partner holds multiple active mandates for the same creditor, resolution disambiguates by debtor bank IBAN, falling back to the most recent active mandate.
Debtor and creditor names, end-to-end ids, and remittance info are run through SEPA character sanitisation and clipped to scheme maxima (35, 70, 140) before rendering, so non-SEPA characters or over-long values never reach the bank file.
Re-generating a file for the same batch and sequence type automatically marks the prior generated or downloaded export rows as superseded, so the audit trail always shows which file is current.
Mandate id is unique per company and length-capped at 35 characters, enforced by explicit constraints so a duplicate or over-long id is caught at write time.
What is inside
Built to do the job, end to end.
- PAIN.008.001.02 generator. ISO 20022 direct debit XML written from the public spec, with the message-level control sum, transaction count, sequence type, local instrument, mandate-related block, and creditor scheme identifier all rendered and structurally asserted by tests.
- Mandate lifecycle. Full FRST, RCUR, FNAL, OOFF sequencing driven by an atomic SQL counter increment. States are Draft, Active, Completed, Revoked, Expired. One-off mandates render as OOFF and complete on first use.
- Creditor configuration. Per-company creditor identifier, default local instrument (CORE, B2B, COR1), and pre-notification window. Local IBAN and BIC validation against ISO 13616 and ISO 9362 with no external library dependency.
- Export audit trail. Each generated file carries a SHA-256 fingerprint. Regenerating a file for the same batch and sequence type supersedes the prior file, and the original export row is retained so the audit trail shows which file is current.
- Dormancy enforcement. The 36-month scheme dormancy rule is enforced on the collection path and on a daily cron that processes each mandate inside its own savepoint, so one bad record never aborts the batch.
Honest about the edges
What this does not do, so nothing surprises you.
- The local instrument (CORE, B2B, or COR1) is sourced per creditor, not per mandate. The exporter writes the creditor's default instrument into every file in a run, so a mandate flagged differently from its creditor still exports under the creditor default. Files are grouped only by sequence type, so a single generated file never mixes instruments.
- The SHA-256 fingerprint and supersede flag give an audit trail, not a hard duplicate-submission block. There is no uniqueness guard that prevents re-exporting and re-consuming a batch, so the same batch can be regenerated; the prior file is marked superseded rather than the new export being refused.
- The mandate amendment table is operator-entry only. It is available to record IBAN, name, or scheme changes, but entries are not captured automatically when the debtor IBAN is edited on the mandate.
- Tests parse the generated XML back with lxml and assert the scheme structure. No XSD file is shipped and there is no xmllint or schema validation step, so external XSD compatibility is not asserted in the test suite.
- Per-record savepoint isolation applies to the dormancy-expiry cron. The export itself iterates posted payments in a plain loop, so a single bad payment aborts that export run.
SEPA direct debit Odoo 19, PAIN.008.001.02 XML generator, SEPA mandate management, FRST RCUR FNAL OOFF sequence type, SEPA creditor identifier, CORE B2B direct debit scheme, 36-month mandate dormancy, recurring direct debit collection, SEPA direct debit Community edition, IBAN BIC validation Odoo
Please log in to comment on this module