Automation / Product build · Demo

BTP AI

From a site invoice to a checked draft in Odoo. Automation built for Moroccan construction workflows.

Original visual presentation for BTP AI

The work

The challenge

Connect field invoice intake, progressive billing, and site margins without depending on Odoo Enterprise. Accounting must never be wrong silently.

The approach

A Telegram intake feeds an n8n workflow and OCR service. Every generated vendor bill stays in draft; low-confidence reads require human review.

Implementation

Docker Compose hosts the services. Green CI gates releases, a database copy precedes deployment, and scripted database and filestore backups support recovery.

Context & results

A product running on demo data for a fictional company. The source case study explicitly records no client deployment. Detailed test results are source-reported, not customer outcomes.

Technology

Odoo 19n8nPythonTesseract OCRPostgreSQLDocker

Explore

Read the technical build ↗
Technical notes & complete original case study

Case study

BTP AI: supplier invoices from a Telegram photo to a checked draft in Odoo

Odoo 19 Community, n8n and self-hosted OCR for Moroccan construction companies: progressive billing, the 7% public-contract retention cap, a margin per site, and a posting guard so nothing is posted unchecked.

Client
Built for Moroccan BTP companies; no client deployment yet
Stack
Odoo 19 Community, PostgreSQL 16, n8n, Tesseract OCR, Docker Compose, GitHub Actions
Status
CI green on main (2026-10-18), demo data, private repository
  • Draft

    every automatically read bill, never posted

  • 7%

    public-contract retention cap enforced

  • 37/37

    n8n workflow tests passing

  • 0

    Odoo Enterprise licences needed

This is a product I built, not a client engagement. It runs on demo data for a fictional company; the figures below come from the repository and its CI, not from a customer.

The problem

On a Moroccan construction company's sites, the site manager collects supplier invoices and delivery notes on paper, and the accountant retypes them. Nobody can see the margin of a single site (chantier) until the books are closed. On top of that, Moroccan contracts bill progressively (situations de travaux), hold back a guarantee retention on each bill, and cap that retention at 7% of the contract amount on public contracts.

Constraints

  • Odoo Community only. No Enterprise licence, so no Enterprise features to lean on.
  • Four users with very different needs. The site manager has a phone, Telegram and no Odoo account. The accountant is careful and distrusts automation. The manager wants the margin at a glance. The admin needs to know quickly whether the setup works.
  • Accounting cannot be wrong silently. A misread total must never reach the ledger.
  • Free by default. OCR has to work without a paid AI service; a client can plug in their own key for better reading, with a daily cap.
  • Small server. The production target is a free ARM instance that already runs n8n and a reverse proxy, so the stack reuses them and the OCR container is held to 512 MB of RAM and one CPU.

Approach

  1. The site manager sends a photo or PDF to a Telegram bot. n8n checks the sender against a whitelist, hashes the file, and asks Odoo for the OCR settings.
  2. The file is read. By default a self-hosted Tesseract service (French, Arabic and English) plus a rules parser; if the client set a key, an OpenAI-compatible vision model, falling back to the free path on any error.
  3. Odoo decides. It re-checks everything it receives (file type and size, totals, VAT rates, the 15-digit ICE, duplicates, dates, which site the bill belongs to) and creates a draft vendor bill on the right site, with the original attached and the failed checks listed in plain French.
  4. The accountant reviews. A "Factures à vérifier" list, oldest first, with a confidence level (Fiable, À vérifier, Illisible) instead of a percentage, and one button to confirm the extraction.
  5. Billing and margin. Progressive situations compute the period amount from the cumulative percentage, apply the retention, cap it at 7% on public contracts, and print an A4 PDF with the three signature blocks. A SQL view gives revenue, costs and margin per site, readable on a phone.

Key decisions

  • Thin orchestrator, business rules in Odoo. n8n moves files and fills reply templates; every accounting rule lives in the Odoo module. Swapping n8n out would not change a single rule.
  • The posting guard sits in _post, not only on the button. Every posting path in Odoo ends in _post, so a bill still under review cannot be posted from anywhere, and the integration user cannot post at all, even a clean bill. Tests cover both.
  • Idempotent intake. The file's SHA-256 plus the company is unique, so sending the same photo twice returns "already sent" instead of a second bill.
  • Least privilege for the automation. n8n uses a dedicated Odoo user with no direct model access; its only entry point is the intake method, which checks its group.
  • Invoice text is data, not instructions. The vision model has no tools and must return JSON that matches a schema, and Odoo validates it again.
  • Deploys can roll back. A release copies the database, upgrades the module, waits for the health check and starts the previous release again if anything fails.

What went wrong, and the fix

  • A 22% margin showed as 2200%. Margin and progress are stored as 0 to 100, but four views used Odoo's percentage widget, which multiplies by 100. Fixed in all four views (pull request #18).
  • The first live import into n8n 2.43.6 refused to publish, and review found the run would have failed right after: a node defaulting to the wrong resource, the OCR step receiving a response with no file, parameters that version 4.2 of the HTTP Request node ignores, and binary data read the old way. Each point is now covered by a workflow structure test that fails on the old workflow (pull request #17).

Verified results

  • The legal and accounting rules are tested against a reference case. Contract of 1,000,000 HT, VAT 20%, retention 10% on TTC: the first situation at 30% must give a net payable of exactly 324,000, and the test asserts it to the cent.
  • CI is green on main (run of 2026-10-18): lint, a clean module install on a fresh Odoo 19 database with no warnings allowed, the Odoo module tests, the OCR service tests plus a memory check under three parallel requests, the n8n tests (one of which scans the exported workflows for secrets), and gitleaks.
  • The 37 n8n workflow and parser tests pass (run on 2026-10-20).
  • Backups and restore are scripted: a PostgreSQL dump plus the filestore, keeping 7 daily and 4 weekly copies, with optional encryption and off-site copy, and a restore script that loads a backup into a scratch database.

What I would do next

  • Put it in front of one real company. The design targets (a photo answered in under a minute, a draft reviewed in under a minute) are targets until a site manager and an accountant use it.
  • Record a demo video so the flow can be seen without access to the private repository.
  • Schedule and document a monthly restore test on the production server, not just the script.

Want the same approach on your AWS bill? The AWS Cost Audit is $250 fixed, delivered in 48 hours.

Let's make your
infrastructure work better.

Lower cloud costs, more reliable deployments,
and systems your team can operate with confidence.