errors
Failed to load YAML frontmatter: Tried to load unspecified class: Date

Référence API Odoo — Documentation Complète

Base URL : https://sarl-le-relais-de-louest.odoo.com/jsonrpc Protocole : JSON-RPC 2.0 Auth : common.login + API Key


1. Authentification

1.1 Obtenir un UID

POST /jsonrpc
Content-Type: application/json

{
    "jsonrpc": "2.0",
    "method": "call",
    "params": {
        "service": "common",
        "method": "login",
        "args": ["<db_name>", "<email>", "<api_key>"]
    },
    "id": 1
}

Réponse :

{"jsonrpc": "2.0", "id": 1, "result": 2}

result = UID (integer, généralement 2 pour l'admin).

1.2 API Key

Générée dans Odoo : Profil → Preferences → Account Security → API Keys → New API Key.

⚠️ Ne pas confondre avec un token de session navigateur (SHA visible dans l'URL Odoo).

1.3 Helper Python

import requests

DB = "sarl-le-relais-de-louest"
UID = None
PWD = None  # API key
URL = f"https://{DB}.odoo.com/jsonrpc"

def odoo_call(model, method, *args):
    payload = {
        "jsonrpc": "2.0", "method": "call",
        "params": {
            "service": "object",
            "method": "execute_kw",
            "args": [DB, UID, PWD, model, method] + list(args)
        }, "id": 1
    }
    resp = requests.post(URL, json=payload, timeout=30).json()
    if "error" in resp:
        raise Exception(resp["error"]["data"]["message"])
    return resp.get("result")

2. Méthodes génériques

Tous les modèles supportent ces 7 méthodes :

search_read — Rechercher et lire

records = odoo_call("model.name", "search_read",
    [domain],           # liste de conditions
    {kwargs}            # fields, offset, limit, order
)

Paramètres : | Param | Type | Description | |——-|——|————-| | domain | list | Conditions de filtrage (voir §2.1) | | fields | list | Champs à retourner | | offset | int | Pagination : sauter N enregistrements | | limit | int | Max résultats (défaut: 80) | | order | str | Tri : "date_order desc" |

search — Rechercher (IDs seulement)

ids = odoo_call("model.name", "search", [domain])
# Retourne [1, 5, 42, ...]

search_count — Compter

count = odoo_call("model.name", "search_count", [domain])
# Retourne int

read — Lire par ID

data = odoo_call("model.name", "read", [[id1, id2]], {"fields": ["name", "email"]})

create — Créer

new_id = odoo_call("model.name", "create", [{champs}])
# ⚠️ UN seul dict, pas une liste de dicts

write — Modifier

odoo_call("model.name", "write", [[id1, id2], {champs}])

unlink — Supprimer

odoo_call("model.name", "unlink", [[id1, id2]])

2.1 Syntaxe des domaines

# Égalité
["field", "=", value]

# Inégalité
["field", "!=", value]
["field", ">", value]
["field", "<", value]
["field", ">=", value]
["field", "<=", value]

# LIKE / NOT LIKE
["field", "=like", "pattern%"]    # % = wildcard
["field", "not like", "xyz"]

# IN / NOT IN
["field", "in", [val1, val2]]
["field", "not in", [val1, val2]]

# AND = liste de conditions
[["field1", "=", v1], ["field2", "=", v2]]
# field1 = v1 ET field2 = v2

# OR = conditions dans une sous-liste
[["field1", "=", v1], "|", ["field1", "=", v2]]
# field1 = v1 OU field1 = v2

2.2 Many2one / One2many / Many2many

# Many2one → integer
"partner_id": 42

# Many2many → commandes spéciales
"taxes_id": [(6, 0, [id1, id2])]      # Remplacer par cette liste
"taxes_id": [(4, id3)]                 # Ajouter un élément
"taxes_id": [(3, id3)]                 # Supprimer un élément
"taxes_id": [(5, 0, 0)]               # Vider tout

# One2many → commandes identiques
"order_line": [(0, 0, {vals})]         # Créer et lier
"order_line": [(1, id, {vals})]        # Modifier existant
"order_line": [(2, id)]                # Supprimer existant

3. Modèles — Référence exhaustive

3.1 res.partner — Contacts / Clients

Champ Type Obligatoire Description
name char Nom complet
email char   Email
phone char   Téléphone fixe
mobile char   Téléphone mobile
street char   Rue
city char   Ville
zip char   Code postal
country_id m2o   Pays
customer_rank int   1 = client, 0 = prospect
supplier_rank int   1 = fournisseur
is_company bool   True = société, False = particulier
company_type selection   person ou company
vat char   N° TVA intracommunautaire
barcode char   Code-barres client
active bool   False = archivé
comment text   Notes internes
category_id m2m   Étiquettes (tags)

Recherche par email :

partner = odoo_call("res.partner", "search_read",
    [[["email", "=", "jean@email.com"]]],
    {"fields": ["id", "name", "email"]})

3.2 sale.order — Commandes / Devis

Champ Type Obligatoire Description
name char Référence commande
partner_id m2o → res.partner Client
partner_invoice_id m2o → res.partner Adresse facturation
partner_shipping_id m2o → res.partner Adresse livraison
company_id m2o → res.company Société (généralement 1)
date_order datetime Date commande
picking_policy selection direct (livraison immédiate) ou one
state selection   draft, sent, sale, done, cancel
client_order_ref char   Référence externe (ex: BilletWeb)
team_id m2o → crm.team   Équipe de vente
user_id m2o → res.users   Vendeur
partner_shipping_id m2o   Adresse livraison
warehouse_id m2o   Entrepôt
payment_term_id m2o   Conditions de paiement
pricelist_id m2o   Liste de prix
currency_id m2o   Devise (automatique)
note text   Notes (visibles sur le PDF)
amount_untaxed float   Total HT (lecture seule)
amount_tax float   Total TVA (lecture seule)
amount_total float   Total TTC (lecture seule)
invoice_status selection   to invoice, invoiced, no
require_signature bool   Signature requise
require_payment bool   Paiement requis avant confirmation
order_line o2m   Lignes de commande

Création minimale :

so_id = odoo_call("sale.order", "create", [{
    "name": "SO-REF-123",
    "partner_id": partner_id,
    "partner_invoice_id": partner_id,
    "partner_shipping_id": partner_id,
    "company_id": 1,
    "date_order": "2026-05-29 10:00:00",
    "picking_policy": "direct",
    "state": "draft",
}])

3.3 sale.order.line — Lignes de commande

Champ Type Description
order_id m2o → sale.order Commande parente
product_id m2o → product.product Produit
product_uom_qty float Quantité
product_uom m2o Unité de mesure
price_unit float Prix unitaire
discount float Remise (%)
name char Description
event_ticket_id m2o → event.event.ticket Billet lié
registration_ids o2m → event.registration Inscriptions liées
tax_id m2m → account.tax Taxes
customer_lead float Délai de livraison (jours)
state selection draft, sale, done, cancel

3.4 product.product — Produits

Champ Type Obligatoire Description
name char Nom du produit
type selection   consu, service, product
list_price float   Prix de vente TTC
standard_price float   Prix de revient
barcode char   Code-barres
categ_id m2o → product.category   Catégorie
sale_ok bool   Disponible à la vente
purchase_ok bool   Disponible à l'achat
available_in_pos bool   Visible dans le PDV
active bool   Actif
taxes_id m2m → account.tax   Taxes à la vente
supplier_taxes_id m2m   Taxes à l'achat
description_sale text   Description sur les devis
default_code char   Référence interne
weight float   Poids (kg)

⚠️ detailed_type n'existe pas sur Odoo Online SaaS → utiliser type.

3.5 product.category — Catégories

Champ Type Description
name char ✅ Nom
parent_id m2o Catégorie parente
complete_name char Chemin complet (lecture seule)

3.6 product.template — Modèles de produits

Mêmes champs que product.product pour l'essentiel. La différence :

  • product.template = produit générique
  • product.product = variante spécifique

3.7 event.event — Événements

Champ Type Obligatoire Description
name char Nom de l'événement
date_begin datetime   Date début
date_end datetime   Date fin
seats_max int   Places maximum
seats_limited bool   Limiter les places
seats_available int   Places restantes (lecture seule)
auto_confirm bool   ⚠️ Confirmation auto par email
event_ticket_ids o2m   Types de billets
registration_ids o2m   Inscriptions
state selection   draft, confirm, done, cancel
user_id m2o   Responsable
address_id m2o   Lieu
country_id m2o   Pays

3.8 event.event.ticket — Types de billets

Champ Type Obligatoire Description
name char Nom du billet
event_id m2o → event.event Événement parent
product_id m2o → product.product Produit associé
price float   Prix
seats_limited bool   Nombre limité
seats_max int   Places max
description text   Description

3.9 event.registration — Participants

Champ Type Description
event_id m2o → event.event Événement
name char Nom du participant
email char Email
phone char Téléphone
barcode char QR/Barcode (utilisé pour BilletWeb)
state selection draft, open, done, cancel
event_ticket_id m2o → event.event.ticket Type de billet
sale_order_id m2o → sale.order Commande liée
sale_order_line_id m2o → sale.order.line Ligne de commande liée
attendee_partner_id m2o → res.partner Contact participant
date_closed datetime Date de clôture

⚠️ description n'existe pas → utiliser barcode pour les références externes.

3.10 pos.order — Commandes PDV

Champ Type Description
name char Référence (ex: POS/2026/05/29/001)
date_order datetime Date
session_id m2o → pos.session Session PDV
partner_id m2o → res.partner Client (si renseigné)
amount_total float Total TTC
amount_tax float Total TVA
amount_paid float Montant payé
amount_return float Rendu monnaie
state selection draft, paid, done, invoiced, cancel
lines o2m → pos.order.line Lignes
payment_ids o2m → pos.payment Paiements

3.11 pos.order.line — Lignes PDV

Champ Type Description
order_id m2o → pos.order Commande parente
product_id m2o → product.product Produit
qty float Quantité
price_unit float Prix unitaire
discount float Remise
price_subtotal float Sous-total
tax_ids m2m → account.tax Taxes

3.12 pos.session — Sessions PDV

Champ Type Description
name char Nom session
config_id m2o → pos.config Configuration PDV
start_at datetime Ouverture
stop_at datetime Fermeture
state selection opening_control, opened, closing_control, closed
cash_register_balance_start float Solde ouverture
cash_register_balance_end float Solde fermeture
order_ids o2m → pos.order Commandes

3.13 pos.config — Configuration PDV

Champ Type Description
name char ✅ Nom ("Parc de la Luge - Caisse")
iface_cashdrawer bool Tiroir caisse
iface_print_via_proxy bool Impression ticket
iface_scan_via_proxy bool Scanner
module_pos_restaurant bool Mode restaurant
company_id m2o Société

3.14 account.move — Factures / Écritures comptables

Champ Type Description
name char Numéro de facture
move_type selection out_invoice, out_refund, in_invoice, in_refund
partner_id m2o → res.partner Client/Fournisseur
invoice_date date Date facture
invoice_date_due date Date d'échéance
state selection draft, posted, cancel
amount_untaxed float HT
amount_tax float TVA
amount_total float TTC
amount_residual float Restant dû
payment_state selection not_paid, in_payment, paid, partial, reversed
invoice_line_ids o2m Lignes de facture
ref char Référence externe

3.15 account.payment — Paiements

Champ Type Description
payment_type selection inbound (reçu) / outbound (envoyé)
partner_type selection customer / supplier
partner_id m2o Client
amount float Montant
date date Date
journal_id m2o → account.journal Journal (banque, caisse)
payment_method_id m2o Mode de paiement
state selection draft, posted, sent, reconciled, cancelled

3.16 account.tax — Taxes

Champ Type Description
name char ✅ Nom ("TVA 8.5% Réunion")
amount float Taux (8.5, 2.1, 0)
amount_type selection percent, fixed, division, group
type_tax_use selection sale, purchase, none
tax_group_id m2o Groupe
active bool Actif
price_include bool Prix TTC
description char Texte sur la facture

3.17 account.tax.group — Groupes de taxes

Champ Type Description
name char ✅ Nom
country_id m2o Pays (⚠️ ne pas mettre si DOM !)

3.18 stock.quant — Inventaire

Champ Type Description
product_id m2o Produit
location_id m2o Emplacement
quantity float Quantité
inventory_quantity float Qté comptée
inventory_diff_quantity float Écart

3.19 ir.module.module — Modules

Champ Type Description
name char Nom technique
display_name char Nom affiché
state selection uninstalled, installed, to install, to upgrade

Installer un module :

mid = odoo_call("ir.module.module", "search_read",
    [[["name", "=", "point_of_sale"]]], {"fields": ["id"]})[0]["id"]
odoo_call("ir.module.module", "button_immediate_install", [[mid]])

3.20 mail.mail — Emails en file d'attente

Champ Type Description
state selection outgoing, sent, exception, cancel
subject char Sujet
email_to char Destinataires

3.21 ir.mail_server — Serveur d'envoi

Champ Type Description
name char Nom
smtp_host char Serveur SMTP
active bool False pour désactiver

3.22 ir.cron — Tâches planifiées

Champ Type Description
name char Nom
model_id m2o Modèle
active bool Actif
interval_number int Fréquence
interval_type selection minutes, hours, days, weeks, months

3.23 res.company — Société

Champ Type Description
name char Nom
country_id m2o Pays (187 = Réunion)
currency_id m2o Devise (EUR)
phone char Téléphone
email char Email
street char Adresse

4. Flux BilletWeb → Odoo

4.1 Ordre des opérations

① res.partner       — find or create (par email)
② product.product   — find (par barcode BW-*)
③ event.event.ticket — find or create (produit + event)
④ sale.order        — create
⑤ sale.order.line   — create (lié au ticket)
⑥ event.registration — create (avec barcode BilletWeb)
⑦ sale.order.line   — write registration_ids ← lier

4.2 Anti-doublon

# Charger TOUS les barcodes existants au démarrage
existing = odoo_call("event.registration", "search_read",
    [[]], {"fields": ["barcode"]})
done = {r["barcode"] for r in existing if r.get("barcode")}

# Vérifier avant création
if barcode in done:
    continue  # déjà importé

# Réutiliser les commandes existantes
existing_so = odoo_call("sale.order", "search_read",
    [[["client_order_ref", "=", ref]]], {"fields": ["id"]})
so_id = existing_so[0]["id"] if existing_so else None

4.3 Désactiver les emails (avant import massif)

# 1. Vider la file d'attente
stuck = odoo_call("mail.mail", "search",
    [[["state", "in", ["outgoing", "exception"]]]])
for mid in stuck:
    odoo_call("mail.mail", "unlink", [[mid]])

# 2. Désactiver serveur SMTP
servers = odoo_call("ir.mail_server", "search_read", [],
    {"fields": ["id"]})
for s in servers:
    odoo_call("ir.mail_server", "write", [[s["id"]], {"active": False}])

# 3. Désactiver fetchmail
for s in odoo_call("fetchmail.server", "search_read", [],
    {"fields": ["id"]}):
    odoo_call("fetchmail.server", "write", [[s["id"]], {"active": False}])

# 4. Désactiver templates d'email
for t in odoo_call("mail.template", "search_read", [],
    {"fields": ["id"]}):
    odoo_call("mail.template", "write", [[t["id"]], {"auto_delete": True}])

# 5. Désactiver cron jobs email
for c in odoo_call("ir.cron", "search_read", [],
    {"fields": ["id", "name"]}):
    if any(kw in c["name"].lower() for kw in
           ["mail", "email", "send", "notification"]):
        odoo_call("ir.cron", "write", [[c["id"]], {"active": False}])

# 6. Désactiver confirmation auto événements
for ev_id in event_ids:
    odoo_call("event.event", "write", [[ev_id],
        {"auto_confirm": False}])

5. TVA Réunion (DOM)

5.1 Taux applicables

Taux Vente (ID) Achat (ID) Usage
8,5% 79 32 Taux normal DOM
2,1% 80 35 Taux réduit
0% 61-70 50 Exonéré

5.2 Créer une taxe DOM

# Groupe (sans country_id !)
grp = odoo_call("account.tax.group", "create",
    [{"name": "TVA 8.5%"}])

# Taxe vente
odoo_call("account.tax", "create", [{
    "name": "8.5% S (Réunion)",
    "amount": 8.5,
    "amount_type": "percent",
    "type_tax_use": "sale",
    "tax_group_id": grp,
    "price_include": False,
}])

5.3 Désactiver les taux métropole

# Désactiver 20%
for t in odoo_call("account.tax", "search_read",
    [[["amount", "=", 20.0]]], {"fields": ["id"]}):
    odoo_call("account.tax", "write", [[t["id"]], {"active": False}])
# Idem pour 10%, 5.5%

5.4 Appliquer une taxe à un produit

odoo_call("product.product", "write",
    [[product_id], {"taxes_id": [(6, 0, [tax_id])]}])

6. Modules de localisation française

Module Rôle
l10n_fr Base localisation France
l10n_fr_account Plan comptable français + templates taxes
l10n_fr_pos_cert Certification NF 525 (obligatoire)
l10n_fr_reports Rapports comptables français
# Vérifier les modules installés
modules = odoo_call("ir.module.module", "search_read",
    [[["name", "in",
       ["l10n_fr", "l10n_fr_account",
        "l10n_fr_pos_cert", "l10n_fr_reports"]]]],
    {"fields": ["name", "state"]})

# Installer
for m in modules:
    if m["state"] != "installed":
        odoo_call("ir.module.module",
            "button_immediate_install", [[m["id"]]])

⚠️ Le plan comptable (l10n_fr_account) doit être installé manuellement via l'interface web (Paramètres → Comptabilité → Localisation fiscale). L'API ne peut pas déclencher l'installation du plan comptable sur SaaS.


7. Pièges et erreurs courantes

Erreur Solution
1 AccessDenied Utiliser common.login (pas /web/session/authenticate)
2 create retourne None Il manque un champ obligatoire → fields_get
3 create prend une liste Non → UN dict. Passer [{...}] pas [[{...}]]
4 detailed_type introuvable Ne pas utiliser → type uniquement
5 Taxe "country_id mismatch" Créer les groupes de taxe sans country_id
6 Taxes écrasées après l10n_fr_account Ré-appliquer après installation
7 auto_confirm envoie des emails Désactiver avant import massif
8 unlink échoue Utiliser write{"active": False}
9 search_read limit=80 par défaut Utiliser limit=0 pour tout récupérer
10 barcode champ inexistant Vérifier le modèle — event.registration l'a, sale.order non
11 BilletWeb 403 Forbidden Ajouter User-Agent: Mozilla/5.0
12 API key scope événement La clé doit être créée avec "Tous les événements"

8. Limites Odoo Online (SaaS)

✅ Possible ❌ Impossible
API JSON-RPC complète SSH / accès serveur
CRUD tous modèles standards Modules Python customs
Modules Studio (UI) Accès direct PostgreSQL
Imports CSV/XML Modification du core Odoo
Webhooks sortants Installation packages Python
Extensions Chrome (DOM) Scripts serveur personnalisés

9. Exemples pratiques

9.1 Créer un client

partner_id = odoo_call("res.partner", "create", [{
    "name": "Jean Dupont",
    "email": "jean@email.com",
    "phone": "+262692123456",
    "customer_rank": 1,
}])

9.2 Créer une commande complète

# Étape 1 : créer la commande
so_id = odoo_call("sale.order", "create", [{
    "name": "CMD-2026-001",
    "partner_id": partner_id,
    "partner_invoice_id": partner_id,
    "partner_shipping_id": partner_id,
    "company_id": 1,
    "date_order": "2026-05-29 10:00:00",
    "picking_policy": "direct",
    "client_order_ref": "BW-ORDER-456",
    "team_id": 4,  # BilletWeb
}])

# Étape 2 : ajouter une ligne
line_id = odoo_call("sale.order.line", "create", [{
    "order_id": so_id,
    "product_id": 13,
    "product_uom_qty": 2,
    "price_unit": 11.00,
    "name": "Luge - Forfait 4 tours",
}])

# Étape 3 : confirmer en commande
odoo_call("sale.order", "write", [[so_id], {"state": "sale"}])

9.3 Créer une inscription événement

reg_id = odoo_call("event.registration", "create", [{
    "event_id": 1,
    "name": "Jean Dupont",
    "email": "jean@email.com",
    "barcode": "1234567890",
    "state": "open",
    "event_ticket_id": 8,
    "sale_order_id": so_id,
}])

9.4 Lier inscription → ligne de commande

odoo_call("sale.order.line", "write",
    [[line_id], {"registration_ids": [(6, 0, [reg_id])]}])

9.5 Rechercher avec pagination

all_records = []
offset = 0
while True:
    batch = odoo_call("event.registration", "search_read",
        [[["event_id", "=", 1]]],
        {"fields": ["id", "name", "barcode", "state"],
         "offset": offset, "limit": 100})
    if not batch:
        break
    all_records.extend(batch)
    offset += 100