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) <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-09-17 17:01:53 +02:00
co-authored by Claude Opus 5
parent adb6ebe25f
commit 9876f6ba10
4 changed files with 22 additions and 14 deletions
+2 -2
View File
@@ -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
+5 -4
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 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".
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 <client>", "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/<model>/<method>`, 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.
+9 -3
View File
@@ -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/<model>/<method>`, `Authorization: bearer <api key>`). `/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/<model>/<method>`, `Authorization: bearer <api key>`). `/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 `<ODOO_URL>/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.
+6 -5
View File
@@ -1,9 +1,10 @@
"""Generic Odoo JSON-2 connector for callista-odoo-demo.
Odoo 19's external API is **JSON-2**: `POST /json/2/<model>/<method>`, 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/<model>/<method>`,
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)