Open source · Apache 2.0

A bill posts only if it matches.The order on price, the receipts on quantity.

OpenERP checks every vendor bill against its purchase order and the goods received before anything is written. A bill that disagrees is refused, with every discrepancy listed.

Why a prompt isn't enough.

  1. 01

    Supplier bills arrive wrong

    A bill for more than arrived, or at a higher price than agreed, is ordinary. An agent reading a PDF passes the number along.

  2. 02

    A warning needs a reader

    A check that only warns works when someone reads the warning. An agent told to post the bills will post them.

  3. 03

    Overbilling is found late

    A bill paid for goods that never came turns up weeks later, at a stock count or in a supplier dispute.

The match

Checked on every bill.Before anything is written.

Price

The billed price must match the order.

Each bill line is compared with its purchase order line. The allowed difference is an org setting that defaults to zero cents.

  • Compared line by line against the PO
  • Tolerance stored in org_settings, zero by default
  • match_vendor_bill runs the check without posting

Quantity

A bill can't exceed what arrived.

The bill is compared with received quantities that haven't been billed yet. A bill for goods still on the truck is refused, and so is a second bill for the same delivery.

  • Partial receipts can be billed as they arrive
  • Received but unbilled quantity is tracked per line
  • Billing what's left posts and clears GRNI

Every discrepancy

All the problems, in one refusal.

The match checks every line before it answers, so a bill with a wrong price and a short delivery comes back with both. The agent fixes the bill in one pass.

  • Checks: line exists, price, quantity
  • Returned as JSON beside the error
  • The whole bill is one transaction, so a refusal leaves no rows

Unbilled receipts

What's received and unbilled is one balance.

Receiving goods credits Goods received not invoiced. Posting the bill debits it. What's left is the value of deliveries no supplier has billed, and get_unbilled_receipts reads it back.

  • Receipt: debit Inventory, credit GRNI
  • Bill: debit GRNI, credit Accounts payable
  • A balance that never clears means a bill is missing

What else purchasing covers.

  • Purchase orders

    Draft, then confirm. Only a confirmed order can receive goods.

  • Partial receipts

    Receive some lines, or part of a line. The rest stays open on the order.

  • Vendor bills

    Due dates come from the supplier's payment terms.

  • Payments

    Pay a bill in full or in part from the bank. Paying more than is owed is refused.

  • Payables aging

    Who you owe, in current, 1–30, 31–60, 61–90 and 90+ day buckets.

  • Services

    A service line posts to expense when received, instead of to stock.

See the match work.

All three run against the open-source server and its seeded business.

  1. Connect Claude

    Add the MCP server to Claude Code from the open-erp folder.

    claude mcp add openerp -s user -- $PWD/.venv/bin/python $PWD/run_mcp.py
  2. Ask for a bill that doesn't match

    The server refuses it and lists the discrepancy.

    > Order 100 brackets from Northwind at $18, receive 90, then bill all 100.
  3. Run the smoke test

    It checks the same guarantees through the MCP tools.

    .venv/bin/python scripts/smoke_test.py

What each table holds.

Read from scripts/schema.sql in the open-erp repository.

TableWhat it holdsHow it changes
purchase_ordersSupplier, warehouse, date, status and totalCreated as a draft, then confirmed
purchase_order_linesItem, quantity, unit price, and the quantities received and billedReceived and billed quantities only go up
goods_receiptsThe receipt and the journal entry it postedWritten once per receipt
vendor_billsSupplier reference, dates, total and amount paidWritten only when the match passes
org_settingsBusiness name, base currency and price toleranceOne row per deployment

The match runs in the purchasing service, not as a database constraint. Writing to the SQLite file directly bypasses it.

Questions.

Can a person override a failed match?

Not in the open-source server. There's no override flag: fix the bill, or wait for the goods. Attri adds approval rules in managed deployments.

Is there a price tolerance?

Yes. It's stored in org_settings and defaults to zero cents. No tool changes it yet.

What about tax on a bill?

Not in v0.1. Line totals are quantity times price. Tax codes are planned for v0.2.

Does it handle credit notes?

Not yet. Credit notes and vendor debit notes are planned for v0.3. Today a posted entry is corrected with reverse_entry.

Does it have sign-in?

Not yet. The open-source server has no sign-in or access control, and Docker binds it to localhost. Keep it off shared networks. API keys with read, write and admin roles are planned for v0.2, and Attri adds access control in managed deployments.

Let your agents run purchasing and stock.Keep the rules on the server.

Run OpenERP on a sample business in minutes, or have Attri deploy it inside your environment with access control and approval rules.