Install
openclaw skills install @zmtucker/drivethru-payable-matchingPayable matching for BaconCo — reconcile vendor documents in Odoo's Documents app against their purchase orders and correct incorrect PO line pricing. Use for requests like "check the Purchasing folder against the POs and fix the pricing", "match the vendor invoice / order confirmation / acknowledgement to its PO", "AP price matching / invoice-to-PO matching / three-way match", "reconcile the vendor documents and mark the POs checked", or "go through the Purchasing folder". The flow: read every document in a Documents-app folder (extracting text out-of-context so large batches don't bloat the context window), pull the PO number / line items / unit prices from each, compare to the purchase order line by line, correct any wrong price_unit, post a "checked" note on the PO, and FILE every document into the Matched or Questions subfolder — escalating genuine questions to a reviewer (default Zach Tucker). Also runs the buying-group payables flow: pull Sports Inc invoices from the SportsLink API (via the sportsinc-sportslink adapter), reconcile each to its PO, correct price variances, and create the DRAFT vendor bill for a human to post ("get the Sports Inc invoices and bill them", "match the SI invoices to POs and create the payables"). Runs at volume on a low-cost model. Driven by the Odoo drivethru_mcp MCP server; complements the broader drivethru-odoo skill.
openclaw skills install @zmtucker/drivethru-payable-matchingReconcile vendor documents against their purchase orders and fix PO line pricing. A vendor document is an order confirmation, shipment acknowledgement, or invoice filed in the Documents app's Purchasing folder; its authority for what BaconCo will be billed is the vendor's own numbers.
The task, per document: read it → extract the PO number, line items, and unit
prices → find the PO in Odoo → compare line by line → correct any wrong
price_unit → post a "checked" note on the PO → file the document into
Matched (reconciled) or Questions (needs a human). Nothing stays in the
inbox.
This changes live financial data (PO prices, chatter, activities). The standing request "review the Purchasing folder and fix the pricing" authorizes the corrections and the "checked" notes; still, only correct a line when the vendor document unambiguously supports it — when in doubt, route it to Questions rather than guessing.
This skill drives the same Odoo drivethru_mcp MCP as drivethru-odoo, and
ODOO_MCP_URL / ODOO_MCP_TOKEN are already configured. Never say you "can't
reach Odoo" or "don't have the tools in this thread" and guess instead — call a
tool, or state the exact call you tried and the error you got.
documents_list_folders {"name": "Purchasing"}, whose document_count is how
many documents are waiting to be matched. They may be deferred — if your
runtime lazy-loads tools, search your tools for documents_search,
ap_search_purchase_orders, ap_update_po_lines, po_post_message, etc. and
load them before calling. "I don't see them yet" is not "I don't have them."scripts/paymatch.py) — only if you have a shell. The helper below
is a context-economy optimization (bulk PDF text extraction, one call per
folder); prefer it for large batches when a shell is available.drivethru_mcp MCP natively — or
giving the agent a shell — is an operator step; env vars alone don't
attach the tools. See drivethru-odoo's Operator setup and
Troubleshooting sections before concluding you can't reach Odoo.scripts/paymatch.py (keep the work out of context)Reading PDFs is the expensive part. documents_get returns each file as
base64, and a multimodal PDF reader adds a page image — both dwarf the
few hundred characters of text that matter. Do that per file across a folder,
many times a day, and the context window fills with bytes and renders (and the
token bill balloons). So never loop documents_get/a PDF reader over the
folder. Use the helper, which does the heavy, deterministic work locally and
returns only what the model needs.
# 1. Read the whole folder as TEXT (no base64, no render) — ONE call
python3 scripts/paymatch.py extract '{"folder": "Purchasing"}'
# 2. Per document: pull the PO trimmed to matchable fields
python3 scripts/paymatch.py po-lines '{"po": "P13189"}'
# 3. Correct any wrong line(s) in one call (PO must be confirmed)
python3 scripts/paymatch.py apply '{"po_id": 13145, "lines": [{"line_id": 40941, "price_unit": 11.94}]}'
# 4a. Clean/fixed → post the checked note AND file to Matched (one call)
python3 scripts/paymatch.py matched '{"po_id": 13145, "document_id": 481, "body": "Pricing checked against SanMar Order Confirmation ... corrected size S $13.94→$11.94."}'
# 4b. Genuine question → raise a reviewer activity AND file to Questions (one call)
python3 scripts/paymatch.py questions '{"document_id": 485, "question": "Totals don't reconcile — please review.", "reviewer": "Zach Tucker"}'
Every command prints one JSON object, or {"error": {...}} with a non-zero
exit. Requires ODOO_MCP_URL / ODOO_MCP_TOKEN (if missing, the script exits
with config_error — stop and tell the user to configure them; never ask for
the key in chat).
If you have no shell (e.g. a chat agent), use the native documents_* /
ap_* / po_* MCP tools directly — they do everything the script does; the
script is only a context-economy wrapper for bulk PDF extraction. Working
natively, still fetch one document at a time and drop the base64 — never
pull a whole folder's bytes into context. (If creds are unset or the MCP isn't
attached, see drivethru-odoo's Troubleshooting — don't guess.)
Read the folder once. paymatch.py extract '{"folder": "Purchasing"}'
→ documents[] with document_id, name, and extracted text. Work from
that text. A document flagged needs_vision: true is scanned/image-only —
for those only, fetch with documents_get {"document_id"} and read the
bytes with a vision reader.
Extract from each document's text (not its filename). The PO number is
inside the document; a filename may show the vendor's order number
instead (e.g. Order Acknowledgement 48482500.pdf whose real PO is
P13183). Capture item/style, color, size, quantity, and unit price per
line.
Pull the PO. paymatch.py po-lines '{"po": "<PO#>"}' → the PO trimmed to
{po_id, name, vendor, partner_ref, state, amount_untaxed, freight_cost, fees_cost, lines:[{line_id, sku, style, description, qty, price_unit}]}.
Confirm the vendor / partner_ref line up with the document. The PO must be
state: "purchase" to edit lines. (found: false with candidates means
the PO# didn't resolve — that's a Questions case.)
Compare line by line. Pair each document line to a PO line by (style/ item, color, size) — never by row order. Size upcharges are normal (base sizes one price; 2XL/3XL/4XL higher) — that's correct pricing, not an error.
Correct mismatches in one call:
paymatch.py apply '{"po_id", "lines":[{"line_id","price_unit"}]}'. Set
freight_cost/fees_cost only when the document itself gives an
authoritative figure — a pre-existing freight estimate or partner fee the
document doesn't itemize is not a line error; note it for the invoice match.
File the document — always.
paymatch.py matched '{"po_id", "document_id", "body": "<what you checked/fixed>"}'
(posts the checked note + moves the doc to Matched).paymatch.py questions '{"document_id", "question": "<what to resolve>", "reviewer": "Zach Tucker"}'
(raises the activity + moves the doc to Questions). Pass po_id +
po_note too if the PO also warrants a note.A shipment acknowledgement is often one box of a multi-shipment order: it covers a subset of the PO's lines, so its total is legitimately less than the PO total — the rest ships later. Match on lines; a total gap fully explained by un-shipped lines is not a discrepancy. Say so in the checked note so a human isn't confused by the header total.
Escalate only a real ambiguity: can't read the PO#, the PO doesn't resolve, prices don't reconcile, unexpected/missing lines, or the wrong vendor. An unambiguous correction the vendor document plainly supports (a size priced $2 off the vendor's own confirmation) is a Matched fix — applying it is exactly what the task asks. Don't manufacture a question where the document is clear; don't guess where it isn't.
Per document: PO number, lines changed (old → new) or "no change", whether the checked note was posted, and Matched vs Questions (and why). End with a folder tally so the inbox state is obvious.
The same reconcile-then-file loop extends to creating the vendor bill once a PO's pricing is reconciled:
python3 scripts/paymatch.py bill '{"po_id": 13145, "vendor_bill_number": "<inv#>", "invoice_date": "2026-07-22", "expected_total": 1041.90, "tolerance": 0.02, "reviewer_user_id": 6, "review_note": "..."}'
bill creates the bill in draft and schedules a review activity — it never
posts (a human posts it). expected_total (the invoice total) is verified within
tolerance; a mismatch returns success:false and no bill is created, so you
escalate rather than bill blind.
Sports Inc doesn't email individual invoices; they live in the SportsLink
API. The sportsinc-sportslink adapter pulls them (normalised to the same
invoice shape a PDF would give) and marks them consumed. The end-to-end loop —
pull active SI invoices → reconcile to the PO → auto-fix price variances,
escalate quantity/line variances → create the draft bill → mark the SI
doc consumed only after the bill exists (exactly-once) — plus credit/scanned
handling and the SI-fee/expected_total nuance, is the dedicated procedure in
references/sportsinc_payables.md. Read it
before running the SI payables flow.
Scope note: this skill reconciles and drafts; a human posts. Only create bills when the task is payables (folder pricing review alone stops at the "checked" note + filing).
Full matching rules, the exact MCP tool payload shapes, a worked five-document
example, and the low-cost model recommendation + per-match economics are in
references/matching_procedure.md. Read it
when you need the details behind a step; the SKILL above is the operating loop.