From 9876f6ba1025eaf0a94b61186e321d13769e76db Mon Sep 17 00:00:00 2001 From: glenn schrooyen Date: Thu, 17 Sep 2026 17:01:53 +0200 Subject: [PATCH] Verify the skill against Odoo 20.0 and note what changed Odoo 20.0 (FINAL) is on GitHub. Every v19 field the skill relies on (group_ids, privilege_id, is_storable, credit-limit fields, stock_fleet dispatch fields) is unchanged; the JSON-2 connector already speaks the v20 API. Real deltas: the RPC db service is gone, custom-module security is a single ir.access.csv, binary fields have a new RPC shape. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 4 ++-- SKILL.md | 9 +++++---- reference/odoo19-field-gotchas.md | 12 +++++++++--- scripts/odoo_connect.py | 11 ++++++----- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index eb041d6..d7773eb 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ same-named fork silently shadows upstream depending on install order. `scripts/creds.json` and `scripts/demo_config.json` are gitignored — they hold client API keys. Keep it that way. -## Read this before touching Odoo 19 +## Read this before touching Odoo 19/20 -Several core fields were renamed in v19 (`res.users.group_ids`, `res.groups` lost `category_id`, etc.), credit limit is **warning-only** natively, and fleet load-by-weight needs `stock_fleet`. All the footguns are in [`reference/odoo19-field-gotchas.md`](reference/odoo19-field-gotchas.md) so you don't relearn them the hard way. +Several core fields were renamed in v19 (`res.users.group_ids`, `res.groups` lost `category_id`, etc. — still true on v20, which additionally replaces `ir.model.access.csv` with `ir.access.csv` in custom modules), credit limit is **warning-only** natively, and fleet load-by-weight needs `stock_fleet`. All the footguns are in [`reference/odoo19-field-gotchas.md`](reference/odoo19-field-gotchas.md) so you don't relearn them the hard way. ## Files diff --git a/SKILL.md b/SKILL.md index 22e77e9..bd21c95 100644 --- a/SKILL.md +++ b/SKILL.md @@ -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 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 ", "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/20 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 ", "Odoo proof of concept", "stand up a demo before the meeting", "write the follow-up after the demo". --- # Callista Odoo Demo @@ -130,7 +130,7 @@ An extending skill may insert its own tier ahead of native — see [Extension po ### Connect Use `scripts/odoo_connect.py`. It speaks **JSON-2** (`POST /json/2//`, API key -as a bearer token) — Odoo 19's external API. `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc` are all +as a bearer token) — the Odoo 19/20 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 @@ -244,7 +244,8 @@ Rules: Portable to any Odoo, no deploy. - **Custom module only if needed:** generate a minimal module, push to the instance's git (Odoo.sh) or hand it to the user to deploy, then install + test. Keep modules tiny and - version-correct. + version-correct: on v20 security is one `security/ir.access.csv` (no `ir.model.access.csv`, + no `ir.rule`); on v19 it is still `ir.model.access.csv`. Check `version()` first. - Example hero (credit hard-block + manager override): override `sale.order.action_confirm` to raise `UserError` when `partner.credit + amount_total > credit_limit` unless the user is in an approver group; add an "Override" button gated to that group. @@ -406,7 +407,7 @@ Rules for the boundary: - Never hand over an environment holding test records. Purge them and read back the counts; do not settle for "cancelled and archived" and do not write leftovers off as a note. - Never send anything client-facing without the user reading it first. -- Read `reference/odoo19-field-gotchas.md` before touching Odoo 19 — several core fields +- Read `reference/odoo19-field-gotchas.md` before touching Odoo 19/20 — several core fields were renamed. - `discovery_brief.md`, `demo_config.json` and the follow-through docs hold client data and API keys — keep them in the project directory, never in a scratchpad you'd forget. diff --git a/reference/odoo19-field-gotchas.md b/reference/odoo19-field-gotchas.md index 44fec87..91a44d0 100644 --- a/reference/odoo19-field-gotchas.md +++ b/reference/odoo19-field-gotchas.md @@ -1,6 +1,6 @@ -# Odoo 19 (Enterprise) — field names & footguns +# Odoo 19 / 20 (Enterprise) — field names & footguns -Hard-won notes from a real v19 demo build. Check these before you waste a build cycle. Always confirm the major version first: `version()["version"]`. +Hard-won notes from a real v19 demo build, re-verified against the Odoo 20.0 source (released 2026-09-17). Everything below holds on v20 unless a **v20** note says otherwise. Check these before you waste a build cycle. Always confirm the major version first: `version()["version"]`. ## Renamed core fields (these break v17/v18 code) | Concept | v17/18 | **v19** | @@ -11,6 +11,12 @@ Hard-won notes from a real v19 demo build. Check these before you waste a build A custom module that sets `category_id` on `res.groups` or `groups_id` on `res.users` will fail to install on v19 with `Invalid field ... / Cannot ...`. Drop the category or use `privilege_id`. +## v20-only changes (verified on the 20.0 branch) +- **Custom-module security:** `security/ir.model.access.csv` + `ir.rule` records are replaced by ONE `security/ir.access.csv` (model `ir.access`, merges ACLs and record rules). Columns: `id,name,model_id,group_id/id,operation,domain` — `operation` is a subset of `crud`, `domain` is optional. A v19 module shipping `ir.model.access.csv` will not install on v20. +- **Binary fields over RPC:** `write` accepts `{"file_name": ..., "content": ...}` on binary fields and `web_read` no longer returns base64 blobs; `bin_size` context is gone. Only matters if you upload logos/images — this skill doesn't. +- **RPC `db` service removed** (`/xmlrpc/db`, `/jsonrpc` service `db`). Use `/web/database/*` or JSON-2 (`res.lang`, `res.country`). The connector never used it. +- Field renames from v19 (`group_ids`, `privilege_id`, `is_storable`, credit-limit fields, `stock_fleet` dispatch fields) are unchanged in v20. + ## fields_get returns null attributes over RPC (some v19 builds) `fields_get([], {"attributes": ["string","type"]})` may return each field with `string=None`. The **keys (technical names) are still correct** — match on keys, not labels. (`fields_named()` in `odoo_connect.py` does this.) @@ -43,7 +49,7 @@ A custom module that sets `category_id` on `res.groups` or `groups_id` on `res.u - `stock.picking.button_validate` can return a wizard dict (`stock.backorder.confirmation`, immediate transfer) instead of `True` — create the wizard from `res_model`/`context` and call its `process` button. ## RPC helper footgun -- v19's API is **JSON-2** (`POST /json/2//`, `Authorization: bearer `). `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc` still work but are deprecated and go away in Odoo 22. +- v19's API is **JSON-2** (`POST /json/2//`, `Authorization: bearer `). `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc` still work but are deprecated: `db` service gone in Odoo 20, the rest in Odoo 22. - **No positional arguments exist.** ids go in the `ids` body key, everything else under the ORM parameter's own name: `domain`, `fields`, `vals` (write), `vals_list` (create), `context`, `limit`. Wrong name = `TypeError` from the server, not a silent misread — but guessing still costs a round trip. Every model+method signature for YOUR database is published at `/doc`. - The old execute_kw footgun (a context dict passed positionally landing in `offset` → `psycopg2 can't adapt type 'dict'`) is gone with the endpoint. If you see it, you are on legacy code. - **One call, one transaction.** Committed on success, discarded on error, and nothing chains across calls. Prefer a single method that does the whole job (`search_read`, `action_confirm`) over a sequence you orchestrate client-side. diff --git a/scripts/odoo_connect.py b/scripts/odoo_connect.py index 9b17aca..6d8af68 100644 --- a/scripts/odoo_connect.py +++ b/scripts/odoo_connect.py @@ -1,9 +1,10 @@ """Generic Odoo JSON-2 connector for callista-odoo-demo. -Odoo 19's external API is **JSON-2**: `POST /json/2//`, authenticated -with an API key as a bearer token. The old `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc` -endpoints (services common / db / object) are all deprecated and scheduled for removal -in Odoo 22. https://www.odoo.com/documentation/19.0/developer/reference/external_api.html +Odoo 19+ (incl. 20) external API is **JSON-2**: `POST /json/2//`, +authenticated with an API key as a bearer token. The old `/xmlrpc`, `/xmlrpc/2` and +`/jsonrpc` endpoints are deprecated: the `db` service is already gone in Odoo 20, +`common`/`object` go in Odoo 22. +https://www.odoo.com/documentation/master/developer/reference/external_api.html Three things differ from execute_kw, and they bite: 1. No uid, no password. The API key alone identifies the user. @@ -93,7 +94,7 @@ def x(model, method, ids=None, timeout=600, **kwargs): def version(): - """Server version — {"version_info": [19, 0, ...], "version": "19.0"}.""" + """Server version — {"version_info": [20, 0, ...], "version": "20.0"} (or 19.0).""" req = urllib.request.Request(f"{URL}/web/version") with urllib.request.urlopen(req, timeout=30) as r: return json.load(r)