Use the real Odoo 19 external API (JSON-2), and batch calls
The previous commit moved to /jsonrpc, which the v19 docs list as deprecated
alongside /xmlrpc — both go away in Odoo 22. The actual v19 external API is
JSON-2: POST /json/2/<model>/<method>, API key as a bearer token.
Three consequences, all of which change calling code:
- no uid and no password; UID now comes from res.users/context_get
- every argument is named, positional args do not exist, so x() becomes
x(model, method, ids=None, **kwargs) and callers pass domain=/vals=/fields=
- one call is one transaction and nothing chains, which is the mechanical
reason state changes must go through a button method
Batching: no multi-call envelope exists, so batching means widening a call, not
bundling calls. Added get_or_create_many() (one search_read + one create for a
whole set) and put build_demo.py's product, customer, vehicle, invoice and
headroom loops through set-based calls.
test_odoo_connect.py checks the call shape and the batching offline, with a
faked urlopen — none of this can be exercised without a live instance otherwise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3fd2c51a3d
commit
93edf9296e
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: callista-odoo-demo
|
||||
description: Build a complete, client-tailored Odoo demo end to end — assembles the discovery brief from any source (transcript, notes, email, Knowcap, or a live interview), researches native-vs-custom per hero feature, stands up a LIVE Odoo database over JSON-RPC (modules + realistic data + working heroes), tests it, and generates the sales docs and the post-demo follow-through set (objection sheet, scoped quote, timeline, SOW). Use when prepping an Odoo sales demo, POC, or proof-of-value, or when writing the follow-up after one. Triggers: "build an Odoo demo", "demo DB for <client>", "Odoo proof of concept", "stand up a demo before the meeting", "write the follow-up after the demo".
|
||||
description: Build a complete, client-tailored Odoo demo end to end — assembles the discovery brief from any source (transcript, notes, email, Knowcap, or a live interview), researches native-vs-custom per hero feature, stands up a LIVE Odoo database over the Odoo 19 JSON-2 API (modules + realistic data + working heroes), tests it, and generates the sales docs and the post-demo follow-through set (objection sheet, scoped quote, timeline, SOW). Use when prepping an Odoo sales demo, POC, or proof-of-value, or when writing the follow-up after one. Triggers: "build an Odoo demo", "demo DB for <client>", "Odoo proof of concept", "stand up a demo before the meeting", "write the follow-up after the demo".
|
||||
---
|
||||
|
||||
# Callista Odoo Demo
|
||||
@@ -73,7 +73,7 @@ Everything else is supporting flow.
|
||||
Odoo apps in scope, plus any vertical modules routed by an extending skill.
|
||||
|
||||
## Odoo instance
|
||||
URL, database, login, API key. Ask if absent — do not guess.
|
||||
URL, database, API key (JSON-2 needs no login). Ask if absent — do not guess.
|
||||
UI language + `l10n_*` localisation to install.
|
||||
|
||||
## Demo audience
|
||||
@@ -96,7 +96,7 @@ casually, because extending skills point at these names.
|
||||
|
||||
*Instance prep* and *client research* are independent: the install list comes from the
|
||||
brief (plus any routing overlay), while research feeds hero *building*, which happens
|
||||
after both. Installing over JSON-RPC is minutes of waiting that produces log spew nobody
|
||||
after both. Installing over RPC is minutes of waiting that produces log spew nobody
|
||||
needs in context. Run them as two parallel subagents launched in **one** message:
|
||||
|
||||
**Agent 1 — instance prep.** Give it the Odoo credentials and the module list. It sets
|
||||
@@ -129,10 +129,51 @@ An extending skill may insert its own tier ahead of native — see [Extension po
|
||||
|
||||
### Connect
|
||||
|
||||
Use `scripts/odoo_connect.py` (JSON-RPC — the recommended API from Odoo 19 on). Authenticate,
|
||||
print `version()["server_version"]` to confirm the major version (field names differ across
|
||||
versions — see `reference/odoo19-field-gotchas.md`). All build steps go through the
|
||||
`x(model, method, *args, **kwargs)` helper.
|
||||
Use `scripts/odoo_connect.py`. It speaks **JSON-2** (`POST /json/2/<model>/<method>`, API key
|
||||
as a bearer token) — Odoo 19's external API. `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc` are all
|
||||
deprecated and disappear in Odoo 22; do not write execute_kw calls.
|
||||
|
||||
Print `version()["version"]` to confirm the major version (field names differ across versions
|
||||
— see `reference/odoo19-field-gotchas.md`). All build steps go through the
|
||||
`x(model, method, ids=None, **kwargs)` helper. Three JSON-2 facts to build around:
|
||||
|
||||
- **Every argument is named. There are no positional arguments.** Record ids go in `ids`;
|
||||
everything else goes under the ORM method's own parameter name —
|
||||
`x("res.partner", "search", domain=[...], limit=5)`,
|
||||
`x("res.partner", "write", ids, vals={...})`, `x(model, "create", vals_list={...})`.
|
||||
Guessing the parameter name is the new version-assumption trap: the database publishes its
|
||||
own signatures at `<ODOO_URL>/doc` — read it rather than guess.
|
||||
- **No uid, no password.** The API key identifies the user; `UID` comes from
|
||||
`res.users/context_get`. Keys expire — three months maximum, and a demo built on a
|
||||
one-day key stops working the morning of the demo.
|
||||
- **Each call is its own SQL transaction**, committed on success, discarded on error. Nothing
|
||||
chains. This is exactly why the next step exists.
|
||||
|
||||
Odoo Online serves the external API on **Custom** plans only — not One App Free, not Standard.
|
||||
Check this before promising a live build on a client's own trial instance.
|
||||
|
||||
### Batch every call you can
|
||||
|
||||
**One call over N records, never N calls over one record.** Odoo's ORM methods are all
|
||||
set-based; the round trip is the expensive part, and a build that loops is the difference
|
||||
between a demo standing up in one minute and in twenty.
|
||||
|
||||
| Loop of N calls | The one call that replaces it |
|
||||
|---|---|
|
||||
| `create` per record | `create(vals_list=[{...}, {...}, ...])` → list of ids, in order |
|
||||
| `write` per record, same values | `write(ids, vals={...})` — ids is a list |
|
||||
| `search` then `read` | `search_read(domain=..., fields=[...])` — also one transaction, so no TOCTOU |
|
||||
| `read` per id | `read(ids, fields=[...])` |
|
||||
| `action_confirm` / `action_post` per record | the same button over the whole id list |
|
||||
| get-or-create per record | `get_or_create_many(model, key_field, rows)` — one search_read + one create |
|
||||
|
||||
There is **no multi-call batch envelope** in JSON-2 — you cannot pack unrelated calls into
|
||||
one HTTP request, and each call commits separately. Batching means widening a call, not
|
||||
bundling calls. Where records genuinely differ (different `vals` per record), a loop is
|
||||
correct; keep it and move on.
|
||||
|
||||
Two places not to batch: anything whose per-record failure you need to isolate (a batched
|
||||
`create` fails as a unit, so one bad row loses the set), and the live demo click itself.
|
||||
|
||||
### Move records with buttons, never with fields
|
||||
|
||||
@@ -147,14 +188,19 @@ button calls — not through `write` on `state` or any other status field:
|
||||
| `x("purchase.order", "button_confirm", [ids])` | `write(ids, {"state": "purchase"})` |
|
||||
| `x("account.move", "button_draft", [ids])` before cancel | `write(ids, {"state": "draft"})` |
|
||||
|
||||
This is also the only way to stay consistent: every JSON-2 call is a separate transaction, so
|
||||
a hand-rolled sequence of writes can be interrupted halfway by a concurrent change and leave a
|
||||
half-confirmed record. The button method does the whole thing in one transaction — all of it
|
||||
commits, or none of it does.
|
||||
|
||||
Writing the field skips everything the button does — no delivery order, no journal entries,
|
||||
no sequence number, no stock moves — so the record *looks* confirmed and every downstream
|
||||
screen in the demo is empty. It also silently skips the very validations a hero feature is
|
||||
meant to demonstrate.
|
||||
|
||||
Don't guess a method name: `fields_named()` finds fields, but for methods read the button off
|
||||
the form view (`x(model, "get_views", [[[False, "form"]]], ...)`) or check the model in the
|
||||
docs. `button_validate` on a picking may return a wizard dict (immediate transfer, backorder)
|
||||
the form view (`x(model, "get_views", views=[[False, "form"]])`) or read the database's own
|
||||
signatures at `<ODOO_URL>/doc`. `button_validate` on a picking may return a wizard dict (immediate transfer, backorder)
|
||||
— call the wizard's own button rather than treating the dict as success.
|
||||
|
||||
### Install modules
|
||||
@@ -325,6 +371,7 @@ Rules for the boundary:
|
||||
- **Research before building.** Version assumptions are the #1 source of wasted effort.
|
||||
- **Buttons, not fields.** Never advance a record's state with `write` — call the button
|
||||
method (see *Move records with buttons, never with fields*).
|
||||
- **Batch.** One call over N records, never N calls over one (see *Batch every call you can*).
|
||||
- **Don't hard-code the client.** Everything client-specific lives in `demo_config.json`.
|
||||
- **Currency before invoices. Weight before fleet. Pre-stage before the live click.** Order matters.
|
||||
- Never hand over an environment holding test records. Purge them and read back the counts;
|
||||
@@ -337,8 +384,9 @@ Rules for the boundary:
|
||||
|
||||
## Files
|
||||
|
||||
- `scripts/odoo_connect.py` — JSON-RPC connector + `x()` helper (correct context-kwarg passing).
|
||||
- `scripts/odoo_connect.py` — JSON-2 connector + `x()` helper (named-argument call shape).
|
||||
- `scripts/build_demo.py` — config-driven data + hero builder (template to adapt per engagement).
|
||||
- `scripts/test_odoo_connect.py` — offline self-check: JSON-2 call shape + batched get-or-create.
|
||||
- `demo_config.example.json` — the shape of a per-client config.
|
||||
- `reference/odoo19-field-gotchas.md` — version-specific field names + footguns learned the hard way.
|
||||
- `templates/` — doc templates (brand-themed, `{{placeholder}}` driven).
|
||||
|
||||
Reference in New Issue
Block a user