Switch the connector to JSON-RPC; drive records with buttons

JSON-RPC is the recommended Odoo API from 19 on. odoo_connect.py now posts to
/jsonrpc over stdlib urllib instead of xmlrpc.client — same x()/get_or_create()/
fields_named() surface, common.version() becomes version(), server errors raise
OdooError with Odoo's message, 600s timeout so module installs survive.

Also: state changes must go through the model's own button method, never a write
on state. Writing the field skips the delivery order, the journal entries, the
sequence and the validations a hero feature exists to demonstrate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-08-09 02:36:39 +02:00
co-authored by Claude Opus 5
parent d913cdf09a
commit 3fd2c51a3d
4 changed files with 85 additions and 24 deletions
+32 -6
View File
@@ -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 XML-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 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".
---
# Callista Odoo Demo
@@ -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 XML-RPC is minutes of waiting that produces log spew nobody
after both. Installing over JSON-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,9 +129,33 @@ An extending skill may insert its own tier ahead of native — see [Extension po
### Connect
Use `scripts/odoo_connect.py`. Authenticate, print `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` (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.
### Move records with buttons, never with fields
**Every state change goes through the model's own button method** — the same method the UI
button calls — not through `write` on `state` or any other status field:
| Do | Never |
|---|---|
| `x("sale.order", "action_confirm", [ids])` | `write(ids, {"state": "sale"})` |
| `x("account.move", "action_post", [ids])` | `write(ids, {"state": "posted"})` |
| `x("stock.picking", "action_assign", [ids])` then `"button_validate"` | `write(ids, {"state": "done"})` |
| `x("purchase.order", "button_confirm", [ids])` | `write(ids, {"state": "purchase"})` |
| `x("account.move", "button_draft", [ids])` before cancel | `write(ids, {"state": "draft"})` |
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)
— call the wizard's own button rather than treating the dict as success.
### Install modules
@@ -299,6 +323,8 @@ Rules for the boundary:
- **Never invent that a feature works — test it.** *Test heroes* is mandatory.
- **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*).
- **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;
@@ -311,7 +337,7 @@ Rules for the boundary:
## Files
- `scripts/odoo_connect.py`XML-RPC connector + `x()` helper (correct context-kwarg passing).
- `scripts/odoo_connect.py`JSON-RPC connector + `x()` helper (correct context-kwarg passing).
- `scripts/build_demo.py` — config-driven data + hero builder (template to adapt per engagement).
- `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.