# Générer des flux Maillon : instructions pour un assistant IA

Ce document permet à un assistant IA (Claude, ChatGPT, Le Chat ou un autre) d'écrire un **fichier de flux Maillon** : un fichier JSON qui décrit un ou plusieurs flux d'automatisation entre Sage 100, Odoo et d'autres logiciels. La personne qui vous a donné ce document l'importe ensuite dans Maillon (page **Flux**, bouton **Importer**) : Maillon vérifie chaque flux, puis le crée désactivé, pour qu'elle le teste avant de l'activer.

Format décrit : `maillon-flux`, version 1. Ce document est généré à partir du code de Maillon : les noms d'étapes, d'outils, d'actions, de réglages, de filtres et d'opérateurs qu'il donne sont exacts. N'en utilisez pas d'autres.

## 1. Comment travailler

1. **Comprenez le besoin avant d'écrire.** Demandez ce qui manque, en une seule fois :
   - ce qui lance le flux : à la main, à heure fixe (laquelle), à l'appel d'un autre logiciel (webhook ou point d'entrée API), à chaque modification dans Odoo ;
   - les logiciels concernés et le sens des données (d'Odoo vers Sage, de Sage vers Odoo, de Sage vers un e-mail…) ;
   - le code du dossier Sage (par exemple `BIJOU`) et, si Odoo a plusieurs sociétés, si son compte a le réglage **Plusieurs sociétés** de Maillon (sinon, la société Odoo de ce dossier) ;
   - le nom exact des **comptes connectés** à utiliser, tels qu'ils apparaissent dans Maillon (page **Flux**, **Comptes connectés**), par exemple « Odoo production » ;
   - ce que vous ne pouvez pas deviner : numérotation des nouveaux tiers, familles d'articles, dépôt, souche, journal, adresses e-mail…
2. **N'inventez rien.** Utilisez uniquement ce que décrit ce document. Les colonnes Sage (informations libres comprises) et les champs Odoo dépendent du dossier et de la base de la personne : quand vous n'êtes pas sûr d'un nom, dites-le et indiquez comment le vérifier (le bouton `{ }` de l'éditeur de flux, l'outil `sage_schema` depuis un assistant connecté à Maillon, ou le mode développeur d'Odoo).
3. **Répondez avec le fichier complet**, dans un seul bloc de code `json`, sans commentaire à l'intérieur (le JSON n'en accepte pas). Après le bloc, listez en quelques lignes ce que la personne devra faire après l'import : choisir un compte, créer la clé d'API, tester le flux, l'activer.
4. **Corrigez à partir du rapport d'import.** Maillon affiche un rapport par flux : « Refusé » (le flux n'est pas importé), « À vérifier », « Ensuite ». Si la personne vous le colle, corrigez le fichier et redonnez-le en entier.
5. **Faites simple et lisible** : une étape par opération, un titre clair pour chaque étape (`title`), des identifiants d'étapes parlants.
6. **Aucun secret dans le fichier** : ni mot de passe, ni clé d'API, ni jeton. Les comptes connectés sont désignés par leur nom ; leurs identifiants restent dans Maillon.
7. **Pour modifier un flux existant**, demandez à la personne de l'exporter (bouton de téléchargement sur la ligne du flux, dans la page **Flux**) et de vous coller le fichier. L'import crée toujours de nouveaux flux : elle supprimera l'ancien une fois le nouveau vérifié.

## 2. Le fichier

```json
{
  "format": "maillon-flux",
  "version": 1,
  "flows": [
    {
      "name": "Nom du flux",
      "graph": {
        "nodes": [],
        "edges": []
      }
    }
  ]
}
```

| Clé | Règle |
|---|---|
| `format` | Toujours `"maillon-flux"`. |
| `version` | Toujours `1`. |
| `flows` | De 1 à 50 flux. Le fichier fait au plus 900 000 caractères. |
| `flows[].name` | Nom du flux, de 1 à 120 caractères. |
| `flows[].graph.nodes` | Les étapes du flux, de 1 à 50 (section 3). |
| `flows[].graph.edges` | Les liens entre les étapes, 150 au plus (section 4). |

N'ajoutez pas d'autre clé : elle serait ignorée. Un flux fait au plus 256 Ko en JSON.

## 3. Les étapes (`nodes`)

```json
{ "id": "lire_clients", "type": "action", "data": { "title": "Clients modifiés dans Odoo" } }
```

| Clé | Règle |
|---|---|
| `id` | Identifiant unique dans le flux : lettres, chiffres, `_` et `-`, 40 caractères au plus, sans accent. `trigger` est réservé. Les étapes suivantes s'en servent dans leurs références (`{{lire_clients}}`) : choisissez des noms courts et parlants. |
| `type` | `trigger`, `sage`, `action`, `condition`, `loop` ou `map`. |
| `data` | Les réglages de l'étape, selon son type (ci-dessous). `data.title` (facultatif, 80 caractères au plus) est le titre affiché dans l'éditeur et dans l'historique des exécutions. |
| `position` | Facultatif : `{ "x": 0, "y": 0 }`, en pixels. Sans position, Maillon range les étapes en colonnes depuis le déclencheur. Donnez-la pour toutes les étapes ou pour aucune. |

### 3.1 Déclencheur (`trigger`)

Exactement un par flux. Aucun lien ne mène au déclencheur. `data.mode` vaut :

| `mode` | Le flux démarre | Réglages |
|---|---|---|
| `manual` | quand la personne clique sur **Lancer maintenant** dans l'éditeur (le flux enregistré, pour de vrai), ou sur **Tester** (un essai) | aucun |
| `schedule` | à heure fixe, heure de Paris | `schedule`, et `odooRealtime` en option |
| `webhook` | à chaque appel `POST` de son adresse de webhook (créée par Maillon après l'import) | aucun |
| `endpoint` | à chaque appel `POST` de son point d'entrée API, avec sa clé | `endpoint` |

**Planification** (`schedule`) :

- `{ "frequency": "minutes", "every": 5 }` : toutes les 5 minutes (`every` : 1, 2, 5, 10, 15, 30, 5 par défaut), pour suivre Sage presque en temps réel ;
- `{ "frequency": "hourly", "minute": 15 }` : toutes les heures, à la minute 15 (0 à 59, 0 par défaut) ;
- `{ "frequency": "daily", "time": "07:30" }` : chaque jour à 7 h 30 (`HH:MM`, 08:00 par défaut) ;
- `{ "frequency": "weekly", "time": "09:00", "weekday": 1 }` : chaque semaine, `weekday` de 0 (dimanche) à 6 (samedi), 1 (lundi) par défaut.

Un flux planifié ne s'exécute qu'une fois à la fois : une exécution demandée pendant qu'il tourne démarre à sa fin (avec `minutes`, une exécution plus longue que l'intervalle n'est jamais doublée).

**Détection en temps réel dans Odoo** (`odooRealtime`, déclencheur `schedule` seulement) : Maillon installe dans Odoo une règle d'automatisation qui lance le flux dès qu'un enregistrement choisi est créé ou modifié. La planification reste comme rattrapage. Pour un document à lignes, `lines` (`model`, `parent` : le champ qui mène au document, `fields` : les champs qu'une personne change) ajoute deux règles qui datent le document quand une ligne change seule, par exemple `{ "model": "sale.order.line", "parent": "order_id", "fields": ["name", "product_id", "product_uom_qty", "price_unit", "discount"] }` pour `sale.order`. La règle d'un flux qui travaille dans une société (`company`, identifiant de la société Odoo) ne se déclenche que pour les documents de cette société. Les modèles de la galerie ajoutent parfois des règles complémentaires (`extra`) au rôle fixé par Maillon : `partnerDatesOrders` (un client ou un fournisseur qui reçoit son numéro Sage date ses commandes en cours) et `orderDatesPartner` (une commande date son client sans numéro Sage). Une IA n'en écrit pas.

**Société Odoo** (Odoo multi-société) : un flux travaille dans une société en donnant `"context": { "allowed_company_ids": [3] }` à chacune de ses étapes Odoo (`3` : l'identifiant de la société dans Odoo) ; Odoo ne montre alors que les documents, listes de prix et taxes de cette société, et y crée les nouveaux. Quand le compte Odoo a le réglage **Plusieurs sociétés** de Maillon (chaque société reliée à son dossier Sage), les contacts (`res.partner`) ont deux champs de plus, avec une valeur par société, lue et écrite dans la société du contexte :

- `x_maillon_ref_sage` (« Référence Sage », 17 caractères) : le numéro du tiers dans le dossier de cette société. Utilisez-le à la place de `ref`, commune à toutes les sociétés, pour retrouver et marquer les tiers ;
- `x_maillon_vendeur` (« Vendeur de la société », un utilisateur) : le vendeur du client dans cette société ; vide, c'est `user_id` (filtre `else`, section 5).

Un contact créé dans une société lui appartient (`company_id`) et ne concerne que son dossier ; un contact sans société est partagé entre toutes. Sur un tel compte, Maillon donne à l'import la société reliée au dossier Sage du flux (`allowed_company_ids` des étapes Odoo et `company` du déclencheur) : écrivez seulement le dossier. Un dossier relié à aucune société fait refuser le flux.

**Réglages installés dans Odoo** (`odooSetup`, sur le déclencheur) : écrits par les modèles de la galerie seulement (champ « Price Type » des clients, juste au-dessus de leur liste de prix, et articles créés dans Sage seulement), installés depuis la console avec la clé d'un administrateur d'Odoo. Une IA n'en écrit pas.

```json
{
  "id": "depart",
  "type": "trigger",
  "data": {
    "title": "Toutes les heures, et à chaque modification dans Odoo",
    "mode": "schedule",
    "schedule": { "frequency": "hourly", "minute": 7 },
    "odooRealtime": { "connection": "Odoo production", "model": "res.partner", "domain": [["customer_rank", ">", 0]] }
  }
}
```

`connection` : nom du compte Odoo connecté. `model` : modèle Odoo surveillé. `domain` : domaine Odoo des enregistrements surveillés (30 éléments au plus, `[]` pour tous). La règle se déclenche à la création d'un enregistrement et à chacune de ses modifications, tant qu'il correspond au domaine ; plusieurs changements rapprochés ne lancent le flux qu'une fois, 10 secondes après. La règle s'installe après l'import, depuis l'onglet Odoo du flux. L'appel d'Odoo ne transmet pas l'enregistrement : le flux relit les données modifiées depuis son point de reprise (section 3.5).

**Point d'entrée API** (`endpoint`) : un autre logiciel appelle `POST https://api.maillon.dev/hooks/flows/in/mfe_…` (adresse créée par Maillon) avec la clé `mfk_…` (créée par la personne après l'import) dans l'en-tête `Authorization: Bearer mfk_…` ou `X-Maillon-Key`. Le corps est lu dans le format choisi, quel que soit son Content-Type, puis les champs déclarés en sont extraits.

```json
{
  "id": "depart",
  "type": "trigger",
  "data": {
    "title": "Commande reçue du site",
    "mode": "endpoint",
    "endpoint": {
      "format": "json",
      "respond": "result",
      "fields": [
        { "key": "client", "source": "body", "path": "commande.client", "type": "text", "required": true },
        { "key": "lignes", "source": "body", "path": "commande.lignes", "type": "list", "required": true },
        { "key": "date", "source": "body", "path": "commande.date", "type": "date" },
        { "key": "canal", "source": "query", "path": "canal", "type": "text", "default": "site" }
      ]
    }
  }
}
```

| Clé | Règle |
|---|---|
| `format` | `json`, `xml`, `csv`, `form`, `text` (`json` par défaut). CSV : une ligne d'en-tête, le corps devient une liste d'enregistrements par nom de colonne ; `delimiter` : `auto`, `;`, `,`, `tab` (`auto` par défaut). XML : les attributs se lisent `@nom`, un document de type (DOCTYPE) est refusé. `form` : formulaire encodé. `text` : le corps brut. |
| `respond` | `accepted` (par défaut : réponse 202 immédiate) ou `result` (attend la fin du flux, 25 secondes au plus, et renvoie la sortie de la dernière étape). |
| `fields` | 50 au plus. `key` : nom du champ (une lettre, puis lettres, chiffres ou `_`, 40 au plus), lu ensuite avec `{{trigger.data.<key>}}`. `source` : `body`, `query`, `header` (`body` par défaut). `path` : chemin dans le corps (`commande.client.code`, `lignes.0.ref`, `0.Code client` en CSV, `@numero` en XML, vide pour tout le corps), ou nom du paramètre de l'adresse ou de l'en-tête. `type` : `text`, `number`, `integer`, `boolean`, `date`, `list`, `object` (`text` par défaut). `required` : `false` par défaut. `default` : texte utilisé quand la valeur manque. |

Conversions : un nombre accepte la virgule française et les espaces (`1 250,50`), une date `AAAA-MM-JJ` ou `JJ/MM/AAAA` (devient `AAAA-MM-JJ`), un booléen `true`/`false`, `1`/`0`, `oui`/`non`. Un appel dont les champs ne correspondent pas reçoit une réponse 422 qui liste les écarts, sans lancer le flux. Les en-têtes `Authorization`, `X-Maillon-Key`, `Cookie` et `Proxy-Authorization` ne sont jamais lus.

### 3.2 Outil Sage 100 (`sage`)

```json
{
  "id": "chercher",
  "type": "sage",
  "data": {
    "title": "Chercher le client dans Sage",
    "tool": "sage_search",
    "arguments": { "dossier": "BIJOU", "entity": "client", "filters": [["CT_Num", "=", "{{chaque.item.ref}}"]], "fields": ["CT_Num", "CT_Intitule"], "limit": 1 }
  }
}
```

`tool` : un outil de la section 7.6. `arguments` : ses arguments, avec **toujours** `dossier`, le code du dossier Sage. Toute valeur peut contenir des références (section 5). La sortie de l'étape est la réponse de l'outil (section 7.5).

Le flux appelle Sage avec les droits de la personne qui l'importe dans Maillon : un outil d'écriture demande le droit indiqué dans la section 7.6 (`write`, `documents`, `accounting`, `stock`, `settings`, et `delete` en plus pour une suppression), et l'offre Essentiel ne permet que la lecture.

### 3.3 Action d'un connecteur (`action`)

```json
{
  "id": "ecrire_ref",
  "type": "action",
  "data": {
    "title": "Écrire le numéro Sage dans Odoo",
    "connector": "odoo",
    "action": "odoo_update",
    "connection": "Odoo production",
    "values": { "model": "res.partner", "ids": "{{chaque.item.id}}", "values": { "ref": "{{creer.record.CT_Num}}" } }
  }
}
```

| Clé | Règle |
|---|---|
| `connector` | Identifiant du connecteur (sections 8 et 9). |
| `action` | Nom technique de l'action. |
| `connection` | **Nom** du compte connecté dans Maillon, jamais un identifiant ni un secret. S'il est inconnu ou absent, la personne choisit le compte à l'import ou dans l'éditeur. Inutile pour une action sans compte. |
| `values` | Les réglages de l'action, par leur clé. Un réglage absent prend sa valeur par défaut. |

### 3.4 Condition (`condition`)

```json
{ "id": "existe", "type": "condition", "data": { "title": "Déjà dans Sage ?", "left": "{{chercher.records}}", "operator": "exists" } }
```

`left` et `right` : valeurs ou références (500 caractères au plus) ; `operator` : section 6 (`right` est inutile pour `exists` et `is_empty`). Une condition a deux sorties, des liens avec `"sourceHandle": "yes"` (vrai) et `"sourceHandle": "no"` (faux) ; seule la branche choisie s'exécute. Une branche peut rester sans lien : si c'est elle qui est choisie, rien ne suit. Sa sortie est `{ "result": true }` ou `{ "result": false }`.

### 3.5 Pour chaque élément (`loop`)

```json
{
  "id": "chaque",
  "type": "loop",
  "data": {
    "title": "Pour chaque client",
    "items": "{{lire_clients}}",
    "max": 50,
    "onError": "continue",
    "cursor": { "at": "{{chaque.item.write_date}}", "id": "{{chaque.item.id}}" }
  }
}
```

| Clé | Règle |
|---|---|
| `items` | Référence à une liste : `{{lire_clients}}`, `{{chercher.records}}`, `{{trigger.data.lignes}}`. |
| `max` | Éléments traités au plus par exécution : 100 par défaut, 1000 au plus. Une liste plus longue fait échouer l'étape : lisez les données par paquets (limite de la recherche, égale à `max`) et laissez le point de reprise avancer. |
| `onError` | `stop` (par défaut : le flux s'arrête au premier élément en échec, la sortie `done` ne s'exécute pas) ou `continue` (toute la liste est traitée, les étapes de la sortie `done` s'exécutent avec le bilan, échecs compris, puis l'exécution est marquée en échec s'il y a eu des erreurs). |
| `cursor` | Point de reprise, facultatif : 10 valeurs au plus, clés formées d'une lettre puis de lettres, chiffres ou `_`. Après chaque élément traité (avec `continue` : après chaque élément vu), Maillon garde ces valeurs ; l'exécution suivante les lit avec `{{trigger.cursor.<clé>}}`. Une seule boucle par flux peut en avoir un. Il n'avance pas pendant un test depuis l'éditeur. |

Une boucle a deux sorties : les liens `"sourceHandle": "each"` mènent aux étapes exécutées pour chaque élément (le corps de la boucle), les liens `"sourceHandle": "done"` aux étapes exécutées une fois, après toute la liste. La sortie `done` est facultative.

**Relances automatiques.** Dans un flux planifié, avec `onError: "continue"` et un point de reprise, un élément en échec ne bloque pas les suivants et n'est pas perdu : Maillon le garde et le rejoue seul (les étapes du corps, sans relire la source). Une panne passagère (Odoo ou Sage injoignable, délai dépassé, fiche verrouillée) est relancée après 5 min, 15 min, 1 h, puis toutes les 4 h, pendant 24 h ; une donnée refusée, ou une panne qui dure plus longtemps, est mise de côté dans l'onglet **À traiter** du flux, où la personne la relance après correction ou l'ignore, et un e-mail prévient les administrateurs. Maillon reconnaît l'élément par la valeur `id` du point de reprise (donnez-y l'identifiant de la fiche, sinon c'est le point entier) : une fiche modifiée entre-temps et relue par le flux remplace celle qu'il gardait. Un rejeu peut suivre un délai dépassé : cherchez toujours avant de créer (section 12). Avec `stop`, le flux s'arrête au premier échec et le point de reprise reste sur le dernier élément réussi : l'exécution suivante repart de l'élément en échec.

- Dans le corps, l'élément en cours est `{{chaque.item}}` (`{{chaque.item.name}}`…), sa position `{{chaque.index}}` (à partir de 0) et la taille de la liste `{{chaque.count}}` (`chaque` étant l'identifiant de la boucle).
- Une étape du corps ne mène qu'à d'autres étapes du corps, et n'est atteinte que depuis la boucle (`each`) ou depuis le corps. Le déclencheur et une autre boucle ne peuvent pas être dans un corps.
- Après la boucle (sortie `done`), `{{chaque}}` est le bilan `{ "count", "succeeded", "failed", "failures": [{ "index", "step", "error" }] }` ; les sorties des étapes du corps ne sont plus disponibles.
- Un flux qui contient une boucle (ou une étape longue, section 4) peut s'exécuter 15 minutes, au lieu de 5. L'historique détaille les trois premiers éléments et chaque élément en échec.
- Un flux planifié dont la boucle à point de reprise traite un lot complet (autant d'éléments que `max`) repart aussitôt pour le lot suivant, jusqu'à un lot incomplet : donnez la même valeur à la limite de la recherche et à `max`.

### 3.6 Mettre en forme une liste (`map`)

Construit une liste à partir d'une ou de plusieurs autres, par exemple des lignes de commande Odoo en lignes de document Sage.

```json
{
  "id": "lignes",
  "type": "map",
  "data": {
    "title": "Lignes pour Sage",
    "items": "{{commande.order_line}}",
    "where": { "left": "{{lignes.item.display_type | not}}", "operator": "equals", "right": "true" },
    "fields": { "article": "{{lignes.item.product_id.default_code}}", "quantite": "{{lignes.item.product_uom_qty}}", "prix_unitaire": "{{lignes.item.price_unit}}" }
  }
}
```

| Clé | Règle |
|---|---|
| `items` | Une référence à une liste, ou une liste de 10 références au plus, mises bout à bout (par exemple les lignes, puis une ligne de frais de port). |
| `where` | Facultatif : `{ "left", "operator", "right" }` (section 6) ; seuls les éléments pour lesquels la condition est vraie sont gardés. |
| `fields` | Facultatif. Un objet : chaque élément devient cet objet, ses valeurs étant des références ou des textes ; une clé dont la valeur n'existe pas (référence absente, filtre `date` sur une date vide) est retirée. Un texte seul (`"{{lignes.item.id}}"`) : une liste de valeurs. Absent : les éléments tels quels. |
| `max` | Éléments lus au plus : 1000 par défaut, 50000 au plus. |

Dans `where` et `fields`, l'élément en cours est `{{<id de l'étape>.item}}` et sa position `{{<id>.index}}`. La sortie est la liste construite : `{{lignes}}`.

## 4. Les liens (`edges`)

```json
{ "source": "existe", "target": "mettre_a_jour", "sourceHandle": "yes" }
```

| Clé | Règle |
|---|---|
| `source`, `target` | Identifiants des deux étapes. |
| `sourceHandle` | Depuis une condition : `yes` ou `no` (obligatoire). Depuis une boucle : `each` ou `done` (obligatoire). Depuis toute autre étape : absent. |
| `id` | Facultatif (Maillon en donne un). |

Exécution : depuis le déclencheur, en suivant les liens. Une étape s'exécute quand une étape qui y mène l'a activée (la branche choisie d'une condition, par exemple), après toutes les étapes qui la précèdent. Le flux s'arrête à la première étape en échec (sauf dans une boucle en `continue`). Chaque étape a 60 secondes, sauf les étapes longues (`odoo_sync`, `sage_tarification`, `sage_fiscalite`), qui copient ou calculent des listes entières : 5 minutes.

Interdit : un lien vers le déclencheur, un lien d'une étape vers elle-même, un circuit (un flux ne revient jamais en arrière ; pour répéter, utilisez une boucle). Une étape que rien n'atteint ne s'exécute jamais.

## 5. Les références `{{ }}`

Une valeur peut reprendre une donnée du déclencheur ou d'une étape précédente : `{{trigger.data.client}}`, `{{chercher.records.0.CT_Intitule}}`.

- Le chemin commence par `trigger` ou par l'identifiant d'une étape **exécutée avant** (en amont par les liens), puis descend par des points : clés d'objet et positions dans une liste, à partir de 0.
- Une valeur faite d'une seule référence garde son type (liste, objet, nombre) : `"items": "{{lire_clients}}"`, `"ids": "{{chaque.item.id}}"`.
- Une référence dans un texte y est écrite en texte : `"Relance {{trigger.data.client}}"`. Une liste d'enregistrements donne une ligne par élément (valeurs séparées par ` · `), une liste de valeurs est séparée par des virgules, un enregistrement donne `clé : valeur, …`.
- Une référence absente donne une valeur vide.
- Les références s'écrivent dans les textes de `arguments`, `values`, `items`, `left`, `right`, `fields` et `cursor`, à toute profondeur (dans un domaine Odoo ou un filtre Sage, par exemple).

**Ce que donne le déclencheur** (`{{trigger…}}`) :

| Mode | Données |
|---|---|
| `manual` | un objet vide (un test depuis l'éditeur peut fournir un exemple) |
| `schedule` | `scheduledAt` (date et heure ISO) ; lancé par la détection Odoo : `odoo.model` |
| `webhook` | `body` (JSON ou formulaire lu en objet, sinon le texte), `query` (paramètres de l'adresse), `contentType`, `receivedAt` (date et heure ISO de réception) |
| `endpoint` | `data` (les champs déclarés, convertis), `body` (le corps lu), `query`, `format`, `receivedAt` |
| tous | `cursor` : le point de reprise de la boucle qui en garde un (`{}` à la première exécution) |

**Ce que donne une étape** (`{{<id>…}}`) : sa sortie. Outil Sage : la réponse de l'outil (section 7.5). Action : son résultat (section 8 pour Odoo, catalogue des connecteurs pour les autres). Condition : `result`. Boucle : voir la section 3.5. `map` : la liste construite.

**Filtres** : écrits après le chemin, séparés par `|`, appliqués dans l'ordre : `{{chaque.item.name | text | truncate:35 | upper}}`. Un argument est un mot simple, ou un texte entre guillemets doubles s'il contient `:`, `|`, `}` ou des espaces : `default:"Sans ville"`. Dans le fichier JSON, ces guillemets s'écrivent `\"` : `"{{chaque.item.city | default:\"Sans ville\"}}"`. Liste complète : `default`, `else`, `upper`, `lower`, `trim`, `truncate`, `pad`, `padstart`, `digits`, `suffix`, `prefix`, `abs`, `round`, `sign`, `bool`, `not`, `text`, `date`, `choose`, `strip_start`, `description`, `first`, `join`.

| Filtre | Effet |
|---|---|
| `default:x` | `x` si la valeur est absente ou vide ; `x` : `true`, `false`, `null`, un nombre ou un texte |
| `text` | texte simple ; `false`, `null` ou une valeur absente (champ vide d'Odoo) donnent un texte vide |
| `upper`, `lower`, `trim` | majuscules, minuscules, espaces retirés aux deux bouts |
| `truncate:n` | garde les `n` premiers caractères |
| `pad:n`, `pad:n:c` | complète à droite jusqu'à `n` caractères avec `c` (`0` par défaut) : avec `pad:8`, `411` devient `41100000` |
| `padstart:n`, `padstart:n:c` | complète à gauche : avec `padstart:6`, `42` devient `000042` |
| `digits` | ne garde que les chiffres (SIRET, téléphone) |
| `suffix:t` | ajoute `t` après une valeur non vide, rien si elle est vide |
| `prefix:t` | ajoute `t` devant une valeur non vide : `{{x.prenom \| prefix:" "}}` donne ` Jean`, rien quand le prénom est vide |
| `abs` | valeur absolue d'un nombre |
| `round`, `round:n` | arrondi à `n` décimales (0 par défaut) |
| `sign:a:b` | `a` si le nombre est positif ou nul, sinon `b` |
| `bool`, `not` | vrai ou faux, ou l'inverse ; sont faux : une valeur vide, `0`, `"0"`, `false`, `"false"` |
| `date` | les 10 premiers caractères (`AAAA-MM-JJ`) ; rien si la date est vide |
| `strip_start:t` | retire `t` du début de la valeur s'il y est : `"Sage Catégorie Grossistes" \| strip_start:"Sage Catégorie "` donne `Grossistes` |
| `description` | ligne d'un document Odoo (`[BAAR01] Bague argent` puis, à la ligne, `Gravure`) : ce qui suit la première ligne, sur une seule ligne ; sinon le nom de l'article sans sa `[référence]` |
| `else:chemin` | quand la valeur est vide, celle d'un autre chemin du flux : `{{step_2.x_maillon_vendeur.email \| else:step_2.user_id.email}}` donne le vendeur de la société, sinon celui d'Odoo |
| `first` | le premier élément d'une liste |
| `join`, `join:sep` | une liste écrite en texte, séparée par `sep` (`, ` par défaut) |

Les nombres lus en texte acceptent la virgule décimale et les espaces. Un filtre inconnu ou mal écrit fait refuser le flux.

## 6. Opérateurs des conditions

Liste complète : `equals`, `not_equals`, `contains`, `greater_than`, `less_than`, `exists`, `is_empty`.

| Opérateur | Vrai quand |
|---|---|
| `equals` | `left` et `right`, écrits en texte, sont identiques |
| `not_equals` | ils diffèrent |
| `contains` | le texte `left` contient `right`, ou la liste `left` contient un élément égal à `right` |
| `greater_than` | le nombre `left` est plus grand que `right` |
| `less_than` | le nombre `left` est plus petit que `right` |
| `exists` | `left` n'est pas vide : ni absent, ni `null`, ni `""`, ni liste ou objet vide |
| `is_empty` | `left` est vide |

Odoo écrit un champ vide `false`, qui n'est pas « vide » pour `exists` et `is_empty` : testez `{{x.ref | text}}`.

## 7. Sage 100

### 7.1 Conventions

- `dossier` : le code du dossier Sage dans Maillon (par exemple `BIJOU`), dans chaque appel. Si le fichier en nomme un autre, la personne choisit à l'import le dossier à utiliser.
- Colonnes : les noms SQL de Sage (`CT_Num`, `CT_Intitule`, `AR_Ref`, `DO_Piece`, `EC_Montant`…), et les informations libres du dossier par leur nom exact.
- Énumérations : valeurs numériques (section 7.4) ; chaque réponse ajoute `<colonne>__label`, le libellé (`DO_Type__label`).
- Dates `AAAA-MM-JJ`, montants dans la devise du dossier. `EC_Montant` est toujours positif, `EC_Sens` vaut 0 (débit) ou 1 (crédit).
- Chaque table a `cbMarq` (numéro interne croissant) et `cbModification` (date et heure de dernière modification, à la milliseconde) : les colonnes d'un point de reprise.
- Lecture en SQL en lecture seule ; écriture uniquement par les Objets Métiers de Sage, qui appliquent les contrôles de Sage ; un refus de Sage fait échouer l'étape avec le message de Sage. Les outils d'écriture acceptent `validate_only: true` pour tout vérifier sans enregistrer.
- Longueurs Sage à respecter (filtre `truncate`) : numéro de tiers `CT_Num` 17 caractères, intitulé 35, adresse et ville 35, code postal 9, téléphone 21, e-mail 69, référence article `AR_Ref` 18, désignation 69.

### 7.2 Rechercher : `sage_search`

`entity` : une entité de la section 7.3. `filters` : une liste de conditions `[colonne, opérateur, valeur]`, combinées par ET, et des groupes `{ "or": [ … ] }` ou `{ "and": [ … ] }` :

```json
[["DO_Type", "in", [6, 7]], ["DO_Date", ">=", "2026-01-01"], { "or": [["DO_Tiers", "=", "CARAT"], ["DO_Tiers", "=", "BILLOT"]] }]
```

Opérateurs : `=`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `between` (valeur `[min, max]`), `like`, `not like`, `contains`, `not contains`, `starts_with`, `ends_with`, `is null`, `is not null`. Les colonnes sont vérifiées contre le dossier : une colonne inconnue fait échouer l'étape. `search` : texte cherché dans les colonnes principales. `fields` : colonnes renvoyées (`["*"]` pour toutes). `order` : `"DO_Date desc, DO_Piece asc"`. `limit` : 50 par défaut, 1000 au plus ; `offset` : à partir de.

Point de reprise sur Sage (fiches modifiées depuis la dernière exécution) :

```json
"filters": [{ "or": [["cbModification", ">", "{{trigger.cursor.at | default:\"1900-01-01\"}}"], { "and": [["cbModification", "=", "{{trigger.cursor.at | default:\"1900-01-01\"}}"], ["cbMarq", ">", "{{trigger.cursor.id | default:0}}"]] }] }],
"order": "cbModification asc, cbMarq asc"
```

avec, dans la boucle, `"cursor": { "at": "{{chaque.item.cbModification}}", "id": "{{chaque.item.cbMarq}}" }` (et `cbModification`, `cbMarq` dans `fields`).

### 7.3 Entités

| Entité (`entity`) | Table Sage | Clé (`key` de `sage_get`) | Enregistrements liés (`related` de `sage_get`) | Contenu | Colonnes principales (renvoyées sans `fields`) |
|---|---|---|---|---|---|
| `tiers` | F_COMPTET | `CT_Num` | `contacts`, `ribs`, `livraisons`, `medias`, `conditions_reglement` | Every third party (clients, suppliers, employees, others). | CT_Num, CT_Intitule, CT_Type, CT_Qualite, CT_Classement, CT_Contact, CT_Adresse, CT_Complement, CT_CodePostal, CT_Ville, CT_Pays, CT_Telephone, CT_EMail, CT_Site, CT_Siret, CT_Identifiant, CT_Ape, CG_NumPrinc, N_CatTarif, N_CatCompta, CO_No, CT_Encours, CT_Sommeil, CT_Prospect, cbModification |
| `client` | F_COMPTET | `CT_Num` | `contacts`, `ribs`, `livraisons`, `medias`, `conditions_reglement` | Customers (CT_Type = 0), prospects included. | CT_Num, CT_Intitule, CT_Type, CT_Qualite, CT_Classement, CT_Contact, CT_Adresse, CT_Complement, CT_CodePostal, CT_Ville, CT_Pays, CT_Telephone, CT_EMail, CT_Site, CT_Siret, CT_Identifiant, CT_Ape, CG_NumPrinc, N_CatTarif, N_CatCompta, CO_No, CT_Encours, CT_Sommeil, CT_Prospect, cbModification |
| `fournisseur` | F_COMPTET | `CT_Num` | `contacts`, `ribs`, `livraisons`, `medias`, `conditions_reglement` | Suppliers (CT_Type = 1). | CT_Num, CT_Intitule, CT_Type, CT_Qualite, CT_Classement, CT_Contact, CT_Adresse, CT_Complement, CT_CodePostal, CT_Ville, CT_Pays, CT_Telephone, CT_EMail, CT_Site, CT_Siret, CT_Identifiant, CT_Ape, CG_NumPrinc, N_CatTarif, N_CatCompta, CO_No, CT_Encours, CT_Sommeil, CT_Prospect, cbModification |
| `prospect` | F_COMPTET | `CT_Num` | `contacts`, `ribs`, `livraisons`, `medias`, `conditions_reglement` | Prospects (customers flagged CT_Prospect = 1). | CT_Num, CT_Intitule, CT_Type, CT_Qualite, CT_Classement, CT_Contact, CT_Adresse, CT_Complement, CT_CodePostal, CT_Ville, CT_Pays, CT_Telephone, CT_EMail, CT_Site, CT_Siret, CT_Identifiant, CT_Ape, CG_NumPrinc, N_CatTarif, N_CatCompta, CO_No, CT_Encours, CT_Sommeil, CT_Prospect, cbModification |
| `contact` | F_CONTACTT | `cbMarq` |  | Contacts attached to a third party. | CT_Num, CT_Civilite, CT_Nom, CT_Prenom, CT_Fonction, N_Service, CT_Telephone, CT_TelPortable, CT_EMail, cbMarq |
| `rib_tiers` | F_BANQUET | `cbMarq` |  | Bank accounts (RIB / IBAN) of third parties. | CT_Num, BT_Num, BT_Intitule, BT_Struct, BT_Banque, BT_Guichet, BT_Compte, BT_Cle, BT_IBAN, BT_BIC, BT_NomAgence, cbMarq |
| `condition_reglement_tiers` | F_REGLEMENTT | `cbMarq` |  | Payment conditions of third parties (RT_Condition, RT_NbJour, RT_JourTb01-06, RT_TRepart, RT_VRepart, N_Reglement = payment method number). Written with sage_tiers_reglement. | CT_Num, RT_Condition, RT_NbJour, RT_JourTb01, RT_JourTb02, RT_TRepart, RT_VRepart, N_Reglement, cbMarq |
| `modele_reglement` | F_MODELER | `MR_No` | `lignes` | Payment terms templates (modèles de règlement). Written with sage_modele kind reglement. | MR_No, MR_Intitule |
| `modele_reglement_ligne` | F_EMODELER | `cbMarq` |  | Lines of payment terms templates (ER_Condition, ER_NbJour, ER_JourTb01-06, ER_TRepart, ER_VRepart, N_Reglement). | MR_No, ER_Condition, ER_NbJour, ER_JourTb01, ER_JourTb02, ER_TRepart, ER_VRepart, N_Reglement, cbMarq |
| `modele_saisie` | F_PIECE | `PI_No` | `lignes` | Entry templates (modèles de saisie) with their journal. Written with sage_modele kind saisie. | PI_No, PI_Intitule, PI_Raccourci, JO_Type, JO_Num |
| `modele_saisie_ligne` | F_PIECEG | `cbMarq` |  | Lines of entry templates; text columns may hold Sage functions (=MacroSaisir()...). | PI_No, PG_Ligne, CG_Num, CG_NumCont, CT_Num, PG_Intitule, PG_Sens, PG_Montant, PG_Piece, PG_Echeance, N_Reglement, TA_Code |
| `modele_grille` | F_MODELEG | `MG_No` | `lignes` | Distribution grids (modèles de grille). Written with sage_modele kind grille. | MG_No, MG_Type, MG_Intitule, MG_Raccourci |
| `modele_grille_ligne` | F_EMODELEG | `cbMarq` |  | Lines of distribution grids (CX_Num = general account or analytical section, EG_TRepart, EG_VRepart). | MG_No, N_Analytique, CX_Num, EG_TRepart, EG_VRepart, cbMarq |
| `ressource` | F_RESSOURCEPROD | `RP_Code` |  | Production resources and load centres (RP_TypeRess 0 = resource, 1 = load centre; RP_Type 2 = Outil et Matériel; RP_Temps HHMMSS) with capacity, standard cost and warehouse. Written with sage_ressource. | RP_Code, RP_TypeRess, RP_Type, RP_Intitule, RP_Complement, RP_Capacite, RP_Temps, RP_CoutStd, RP_Sommeil, DE_No, AR_RefDefaut, RP_Visite |
| `ressource_centre` | F_RESSCENTRE | `cbMarq` |  | Resources of each load centre. | RP_CodeCentre, RP_CodeRessource, cbMarq |
| `ressource_article` | F_ARTICLERESSOURCE | `cbMarq` |  | Items attached to production resources (sage_ressource lier / delier). | AR_Ref, RP_Code, cbMarq |
| `agenda_evenement` | F_AGENDA | `cbMarq` |  | Agenda events (AG_Domaine 1 = client, 2 = supplier, 3 = collaborateur; AG_Interes = CT_Num or CO_No; AD_Evenem = event kind; AD_Champ = agenda number). Written with sage_agenda. | AG_Domaine, AD_Champ, AG_Interes, AD_Evenem, AG_Debut, AG_HeureDebut, AG_Fin, AG_HeureFin, AG_Comment, AG_Confirme, AG_Veille, AG_Ignorer, cbMarq |
| `glossaire` | F_GLOSSAIRE | `GL_No` |  | Glossaries: reusable texts (GL_Domaine 0 = article, 1 = document) inserted in documents. Written with sage_glossaire. | GL_No, GL_Domaine, GL_Intitule, GL_Raccourci, GL_Text, GL_TextLangue1, GL_TextLangue2, GL_PeriodeDeb, GL_PeriodeFin |
| `glossaire_article` | F_ARTGLOSS | `cbMarq` |  | Article glossaries attached to items (GL_No of glossaire). Written with sage_glossaire lier / delier. | AR_Ref, GL_No, AGL_Num, cbMarq |
| `media_tiers` | F_COMPTETMEDIA | `cbMarq` |  | Attachments of third parties: a link (file path or URL) with a comment, not the file itself. Written with sage_media. | CT_Num, ME_Fichier, ME_Commentaire, ME_TypeMIME, cbMarq |
| `media_article` | F_ARTICLEMEDIA | `cbMarq` |  | Attachments of items (links: file path or URL). Written with sage_media. | AR_Ref, ME_Fichier, ME_Commentaire, ME_TypeMIME, cbMarq |
| `media_document` | F_DOCENTETEMEDIA | `cbMarq` |  | Attachments of commercial documents (links; DM_Transmettre = 1 when sent with the document). Written with sage_media. | DO_Type, DO_Piece, DM_Fichier, DM_Intitule, DM_TypeMIME, DM_Transmettre, cbMarq |
| `media_ecriture` | F_ECRITURECMEDIA | `cbMarq` |  | Attachments of accounting entries (links). Written with sage_media. | EC_No, EM_Fichier, EM_Commentaire, EM_TypeMIME, cbMarq |
| `livraison` | F_LIVRAISON | `LI_No` |  | Delivery addresses of customers (LI_Principal = 1 for the main one). | LI_No, CT_Num, LI_Intitule, LI_Adresse, LI_Complement, LI_CodePostal, LI_Ville, LI_CodeRegion, LI_Pays, LI_Contact, LI_Telephone, LI_EMail, N_Expedition, N_Condition, LI_Principal |
| `collaborateur` | F_COLLABORATEUR | `CO_No` |  | Sales representatives, buyers and other staff members. | CO_No, CO_Nom, CO_Prenom, CO_Fonction, CO_EMail, CO_Telephone, CO_Vendeur, CO_Acheteur, CO_Sommeil |
| `article` | F_ARTICLE | `AR_Ref` |  | Items (products and services). | AR_Ref, AR_Design, FA_CodeFamille, AR_Type, AR_PrixAch, AR_PrixVen, AR_PrixTTC, AR_Coef, AR_UniteVen, AR_SuiviStock, AR_Nomencl, AR_CodeBarre, AR_Sommeil, AR_Publie, cbModification |
| `stock` | F_ARTSTOCK | `AR_Ref`, `DE_No` | `emplacements` | Stock per item and warehouse (quantity, value, reserved, ordered). | AR_Ref, DE_No, AS_QteSto, AS_MontSto, AS_QteRes, AS_QteCom, AS_QteMini, AS_QteMaxi, AS_Principal |
| `tarif_fournisseur` | F_ARTFOURNISS | `cbMarq` |  | Item purchase conditions per supplier. | AR_Ref, CT_Num, AF_RefFourniss, AF_PrixAch, AF_Remise, AF_DelaiAppro, AF_Principal, cbMarq |
| `tarif_client` | F_ARTCLIENT | `cbMarq` |  | Item prices per customer price category or per customer. | AR_Ref, AC_Categorie, CT_Num, AC_PrixVen, AC_Coef, AC_Remise, AC_PrixTTC, cbMarq |
| `gamme_article` | F_ARTGAMME | `AG_No` |  | Values of the product ranges of items (AG_Type 0 = range 1, 1 = range 2). | AG_No, AR_Ref, AG_Type, EG_Enumere, EG_Ligne |
| `reference_gamme` | F_ARTENUMREF | `cbMarq` |  | Combinations of range values of items (AG_No1 / AG_No2 from gamme_article) with their reference, barcode and purchase price. | AR_Ref, AG_No1, AG_No2, AE_Ref, AE_CodeBarre, AE_PrixAch, AE_Sommeil, cbMarq |
| `nomenclature` | F_NOMENCLAT | `cbMarq` |  | Components of the bills of materials (AR_Ref = composed item, NO_RefDet = component). | AR_Ref, NO_RefDet, NO_Qte, NO_Type, DE_No, NO_Operation, NO_Commentaire, NO_Ordre, cbMarq |
| `conditionnement_article` | F_CONDITION | `cbMarq` |  | Packs of items sold by packaging (EC_Enumere, EC_Quantite) with their reference and barcode. | AR_Ref, CO_No, EC_Enumere, EC_Quantite, CO_Ref, CO_CodeBarre, CO_Principal, cbMarq |
| `famille` | F_FAMILLE | `FA_CodeFamille` |  | Item families. | FA_CodeFamille, FA_Type, FA_Intitule, FA_UniteVen, FA_Coef, FA_SuiviStock |
| `tarif_famille_client` | F_FAMCLIENT | `cbMarq` |  | Family conditions per customer (CT_Num, FC_Remise), and per price category (FC_Categorie) in versions without tarif_famille_categorie. | FA_CodeFamille, FC_Categorie, CT_Num, FC_Coef, FC_PrixTTC, FC_Remise, FC_Arrondi, FC_QteMont, cbMarq |
| `tarif_famille_categorie` | F_FAMTARIF | `cbMarq` |  | Family price conditions per price category (FT_Categorie: coefficient, prices incl. tax, discount, FT_TypeRem 1 = hors remise, EG_Champ = gamme de remise), in recent Sage versions. | FA_CodeFamille, FT_Categorie, FT_Coef, FT_PrixTTC, FT_Remise, FT_TypeRem, FT_QteMont, EG_Champ, cbMarq |
| `article_compta` | F_ARTCOMPTA | `cbMarq` |  | Accounting of items per catégorie comptable (Paramètres > Comptabilité): ACP_Type 0 = vente, 1 = achat, 2 = stock; ACP_Champ = number of the catégorie; general account and up to three tax codes. An item without a row takes its family's (famille_compta). sage_fiscalite resolves it. | AR_Ref, ACP_Type, ACP_Champ, ACP_ComptaCPT_CompteG, ACP_ComptaCPT_Taxe1, ACP_ComptaCPT_Taxe2, ACP_ComptaCPT_Taxe3, cbMarq |
| `famille_compta` | F_FAMCOMPTA | `cbMarq` |  | Accounting of item families per catégorie comptable: FCP_Type 0 = vente, 1 = achat, 2 = stock; FCP_Champ = number of the catégorie; general account and up to three tax codes. | FA_CodeFamille, FCP_Type, FCP_Champ, FCP_ComptaCPT_CompteG, FCP_ComptaCPT_Taxe1, FCP_ComptaCPT_Taxe2, FCP_ComptaCPT_Taxe3, cbMarq |
| `tarif_famille_fournisseur` | F_FAMFOURNISS | `cbMarq` |  | Family purchase conditions per supplier. | FA_CodeFamille, CT_Num, FF_Unite, FF_Conversion, FF_DelaiAppro, FF_Garantie, FF_Colisage, FF_QteMini, FF_QteMont, FF_Remise, cbMarq |
| `tarif_palier` | F_TARIFQTE | `cbMarq` |  | Quantity or amount tiers of item prices (TQ_RefCF = customer / supplier account, or 'a' + category number). | AR_Ref, TQ_RefCF, TQ_BorneSup, TQ_Remise01REM_Valeur, TQ_Remise01REM_Type, TQ_PrixNet, cbMarq |
| `tarif_famille_palier` | F_FAMTARIFQTE | `cbMarq` |  | Quantity or amount tiers of family discounts. | FA_CodeFamille, FQ_RefCF, FQ_BorneSup, FQ_Remise01REM_Valeur, FQ_Remise01REM_Type, cbMarq |
| `categorie_tarifaire` | P_CATTARIF | `cbMarq` |  | Price categories (catégories tarifaires): the n-th row (cbMarq order) is category n (AC_Categorie / FC_Categorie). Unused slots have an empty CT_Intitule. | cbMarq, CT_Intitule, CT_PrixTTC |
| `categorie_comptable` | P_CATCOMPTA | `cbMarq` |  | Catégories comptables: a single row, CA_ComptaVen01..50 = sales catégories, CA_ComptaAch01..50 = purchase catégories (number n = N_CatCompta of a third party, ACP_Champ / FCP_Champ of item accounting). Unused slots are empty. sage_fiscalite reads them. | CA_ComptaVen01, CA_ComptaVen02, CA_ComptaVen03, CA_ComptaAch01, CA_ComptaAch02, CA_ComptaAch03 |
| `bareme` | F_TARIF | `TF_No` |  | Price schemes (barèmes): customer / supplier discounts (rabais), sales and promotions (soldes) and commissions. | TF_No, TF_Intitule, TF_Interes, TF_Debut, TF_Fin, TF_Objectif, TF_Calcul, TF_Base, TF_Domaine, CT_Num, AR_Ref, TF_Remise01REM_Valeur, TF_Remise01REM_Type |
| `bareme_tranche` | F_TARIFREMISE | `cbMarq` |  | Tiers of a price scheme (upper bound and discount). | TF_No, TR_BorneSup, TR_Remise01REM_Valeur, TR_Remise01REM_Type, TR_Remise02REM_Valeur, TR_Remise02REM_Type, cbMarq |
| `bareme_selection` | F_TARIFSELECT | `cbMarq` |  | Items, families and third parties a price scheme is restricted to (set in Sage: not available through the Objets Metiers). | TF_No, TS_Interes, TS_Ref, cbMarq |
| `gamme` | P_GAMME | `cbMarq` |  | Ranges (gammes): product ranges and discount ranges (gamme_remise of sage_tarif), by label G_Intitule. Unused slots have an empty G_Intitule. | cbMarq, G_Intitule, G_Type |
| `depot` | F_DEPOT | `DE_No` |  | Warehouses. | DE_No, DE_Code, DE_Intitule, DE_Adresse, DE_CodePostal, DE_Ville, DE_Principal |
| `emplacement` | F_DEPOTEMPL | `DP_No` |  | Storage locations of warehouses (DE_No). | DP_No, DE_No, DP_Code, DP_Intitule, DP_Type, DP_Zone, DP_Principal |
| `stock_emplacement` | F_ARTSTOCKEMPL | `AR_Ref`, `DE_No`, `DP_No` |  | Stock per item and storage location (dossiers with several locations per warehouse): quantity, prepared, to check. DP_Code is the location's code. | AR_Ref, DE_No, DP_No, DP_Code, AE_QteSto, AE_QtePrepa, AE_QteAControler, cbModification, cbMarq |
| `lot` | F_LOTSERIE | `cbMarq` |  | Lots and serial numbers: one entry row per lot (DL_NoOut = 0, LS_QteRestant = quantity left) and one row per exit (DL_NoOut = the exit line). | AR_Ref, DE_No, LS_NoSerie, LS_Qte, LS_QteRestant, LS_QteRes, LS_Peremption, LS_Fabrication, LS_Complement, LS_LotEpuise, DL_NoIn, DL_NoOut, cbMarq |
| `document` | F_DOCENTETE | `DO_Domaine`, `DO_Type`, `DO_Piece` | `lignes`, `echeances`, `medias` | Every commercial document header (sales, purchase, stock, internal). | DO_Domaine, DO_Type, DO_Piece, DO_Date, DO_Ref, DO_Tiers, CO_No, DO_Statut, DE_No, DO_TotalHT, DO_TotalHTNet, DO_TotalTTC, DO_NetAPayer, DO_MontantRegle, DO_DateLivr, DO_Souche, DO_Expedit, cbModification |
| `document_vente` | F_DOCENTETE | `DO_Domaine`, `DO_Type`, `DO_Piece` | `lignes`, `echeances`, `medias` | Sales documents (quotes, orders, delivery notes, invoices, credit notes). | DO_Domaine, DO_Type, DO_Piece, DO_Date, DO_Ref, DO_Tiers, CO_No, DO_Statut, DE_No, DO_TotalHT, DO_TotalHTNet, DO_TotalTTC, DO_NetAPayer, DO_MontantRegle, DO_DateLivr, DO_Souche, DO_Expedit, cbModification |
| `document_achat` | F_DOCENTETE | `DO_Domaine`, `DO_Type`, `DO_Piece` | `lignes`, `echeances`, `medias` | Purchase documents. | DO_Domaine, DO_Type, DO_Piece, DO_Date, DO_Ref, DO_Tiers, CO_No, DO_Statut, DE_No, DO_TotalHT, DO_TotalHTNet, DO_TotalTTC, DO_NetAPayer, DO_MontantRegle, DO_DateLivr, DO_Souche, DO_Expedit, cbModification |
| `document_stock` | F_DOCENTETE | `DO_Domaine`, `DO_Type`, `DO_Piece` | `lignes`, `echeances`, `medias` | Stock documents (movements, transfers, manufacturing). | DO_Domaine, DO_Type, DO_Piece, DO_Date, DO_Ref, DO_Tiers, CO_No, DO_Statut, DE_No, DO_TotalHT, DO_TotalHTNet, DO_TotalTTC, DO_NetAPayer, DO_MontantRegle, DO_DateLivr, DO_Souche, DO_Expedit, cbModification |
| `document_interne` | F_DOCENTETE | `DO_Domaine`, `DO_Type`, `DO_Piece` | `lignes`, `echeances`, `medias` | Internal documents. | DO_Domaine, DO_Type, DO_Piece, DO_Date, DO_Ref, DO_Tiers, CO_No, DO_Statut, DE_No, DO_TotalHT, DO_TotalHTNet, DO_TotalTTC, DO_NetAPayer, DO_MontantRegle, DO_DateLivr, DO_Souche, DO_Expedit, cbModification |
| `document_ligne` | F_DOCLIGNE | `cbMarq` |  | Lines of commercial documents. | DO_Domaine, DO_Type, DO_Piece, DO_Date, DL_Ligne, CT_Num, AR_Ref, DL_Design, DL_Qte, DL_PrixUnitaire, DL_Remise01REM_Valeur, DL_MontantHT, DL_MontantTTC, DE_No, DL_PieceBC, DL_PieceBL, DL_No, cbMarq |
| `document_ligne_emplacement` | F_DOCLIGNEEMPL | `DL_No`, `DP_No` |  | Storage locations of document lines (multi-emplacement dossiers): the quantity of the line put in or taken from each location, joined to document_ligne by DL_No. | DL_No, DP_No, DP_Code, DL_Qte, DL_QteAControler, cbModification, cbMarq |
| `echeance_document` | F_DOCREGL | `DR_No` |  | Payment due dates of commercial documents. | DR_No, DO_Domaine, DO_Type, DO_Piece, DR_Date, DR_Montant, DR_Pourcent, DR_Regle, N_Reglement, DR_Libelle |
| `reglement_tiers` | F_CREGLEMENT | `RG_No` |  | Customer and supplier payments recorded in commercial management. | RG_No, RG_Type, CT_NumPayeur, RG_Date, RG_Reference, RG_Libelle, RG_Montant, N_Reglement, JO_Num, RG_Piece, RG_Impute |
| `compte_general` | F_COMPTEG | `CG_Num` |  | General ledger accounts (plan comptable). | CG_Num, CG_Type, CG_Intitule, CG_Classement, N_Nature, CG_Report, CG_Lettrage, CG_Sommeil |
| `compte_analytique` | F_COMPTEA | `N_Analytique`, `CA_Num` |  | Analytical accounts, per analytical plan (N_Analytique). | N_Analytique, CA_Num, CA_Intitule, CA_Type, CA_Sommeil |
| `plan_analytique` | P_ANALYTIQUE | `cbMarq` |  | Analytical plans: the n-th row (cbMarq order) is plan n (N_Analytique). Unused slots have an empty A_Intitule. | cbMarq, A_Intitule, A_Obligatoire |
| `journal` | F_JOURNAUX | `JO_Num` |  | Accounting journals. | JO_Num, JO_Intitule, JO_Type, CG_Num, JO_NumPiece, JO_Sommeil |
| `ecriture` | F_ECRITUREC | `EC_No` | `analytique`, `medias` | General accounting entries (one row per entry line). | EC_No, JO_Num, JM_Date, EC_Jour, EC_Piece, EC_RefPiece, CG_Num, CT_Num, EC_Intitule, EC_Echeance, EC_Sens, EC_Montant, EC_Lettre, EC_Lettrage, N_Reglement, EC_Norme, cbModification |
| `ecriture_analytique` | F_ECRITUREA | `cbMarq` |  | Analytical breakdown of general entries. | EC_No, N_Analytique, CA_Num, EA_Montant, EA_Quantite, cbMarq |
| `taxe` | F_TAXE | `TA_Code` |  | Tax codes (VAT rates). | TA_Code, TA_Intitule, TA_Taux, TA_TTaux, TA_Type, TA_Sens, CG_Num |
| `mode_reglement` | P_REGLEMENT | `cbMarq` |  | Payment methods (modes de règlement) by label R_Intitule. Unused slots have an empty R_Intitule. | cbMarq, R_Intitule, R_Code, R_ModePaieDebit, R_ModePaieCredit |
| `banque` | F_BANQUE | `BQ_No` |  | Banks of the company. | BQ_No, BQ_Abrege, BQ_Intitule, BQ_Ville, BQ_Contact |

### 7.4 Énumérations

| Colonne | Valeurs |
|---|---|
| `CT_Type` | 0 = Client ; 1 = Fournisseur ; 2 = Salarié ; 3 = Autre |
| `DO_Domaine` | 0 = Vente ; 1 = Achat ; 2 = Stock ; 3 = Ticket ; 4 = Document interne |
| `DO_Type` | 0 = Vente : devis ; 1 = Vente : bon de commande ; 2 = Vente : préparation de livraison ; 3 = Vente : bon de livraison ; 4 = Vente : bon de retour ; 5 = Vente : bon d'avoir financier ; 6 = Vente : facture ; 7 = Vente : facture comptabilisée ; 8 = Vente : archive ; 10 = Achat : demande d'achat ; 11 = Achat : préparation de commande ; 12 = Achat : bon de commande ; 13 = Achat : bon de livraison ; 14 = Achat : bon de retour ; 15 = Achat : bon d'avoir financier ; 16 = Achat : facture ; 17 = Achat : facture comptabilisée ; 18 = Achat : archive ; 20 = Stock : mouvement d'entrée ; 21 = Stock : mouvement de sortie ; 22 = Stock : dépréciation ; 23 = Stock : virement de dépôt à dépôt ; 24 = Stock : préparation de fabrication ; 25 = Stock : ordre de fabrication ; 26 = Stock : bon de fabrication ; 27 = Stock : archive ; 30 = Ticket de caisse ; 40 = Document interne 1 ; 41 = Document interne 2 ; 42 = Document interne 3 ; 43 = Document interne 4 ; 44 = Document interne 5 ; 45 = Document interne 6 ; 46 = Saisie du réalisé ; 47 = Document interne archivé |
| `EC_Sens` | 0 = Débit ; 1 = Crédit |
| `CG_Type` | 0 = Détail ; 1 = Total |
| `JO_Type` | 0 = Achats ; 1 = Ventes ; 2 = Trésorerie ; 3 = Général ; 4 = Situation |
| `AR_SuiviStock` | 0 = Aucun ; 1 = Sérialisé ; 2 = CMUP ; 3 = FIFO ; 4 = LIFO ; 5 = Par lot |
| `FA_Type` | 0 = Détail ; 1 = Total ; 2 = Centralisateur |
| `CT_Sommeil` | 0 = Actif ; 1 = En sommeil |
| `AR_Sommeil` | 0 = Actif ; 1 = En sommeil |
| `CG_Sommeil` | 0 = Actif ; 1 = En sommeil |
| `CT_Prospect` | 0 = Non ; 1 = Prospect |
| `BT_Struct` | 0 = Locale ; 1 = BBAN ; 2 = IBAN ; 3 = Autre |
| `LI_Principal` | 0 = Non ; 1 = Adresse principale |
| `RT_Condition` | 0 = Jour(s) net(s) ; 1 = Fin de mois civil ; 2 = Fin de mois |
| `ER_Condition` | 0 = Jour(s) net(s) ; 1 = Fin de mois civil ; 2 = Fin de mois |
| `RT_TRepart` | 0 = Pourcentage ; 1 = Équilibre ; 2 = Montant |
| `ER_TRepart` | 0 = Pourcentage ; 1 = Équilibre ; 2 = Montant |
| `EG_TRepart` | 0 = Pourcentage ; 1 = Équilibre ; 2 = Montant |
| `MG_Type` | 0 = Analytique ; 1 = Général |
| `AG_Domaine` | 1 = Client ; 2 = Fournisseur ; 3 = Collaborateur |
| `GL_Domaine` | 0 = Article ; 1 = Document |
| `PG_Sens` | 0 = Débit ; 1 = Crédit |

### 7.5 Réponses des outils

| Outil | Réponse (sortie de l'étape) |
|---|---|
| `sage_search` | `{ "dossier", "entity", "total_count", "count", "offset", "has_more", "next_offset", "records": [ … ] }` : `{{chercher.records}}`, `{{chercher.records.0.CT_Num}}` |
| `sage_get` | `{ "dossier", "entity", "record": { … }, "related": { "<nom>": { "entity", "count", "truncated", "records": [ … ] } } }` : `{{lire.record.DO_TotalTTC}}`, `{{lire.related.lignes.records}}` |
| `sage_finance` | `{ "dossier", "action", "count", "rows": [ … ] }` |
| `sage_document`, `sage_document_transform` et les autres outils de documents | `{ "dossier", "action", "validate_only", "saved", "message", "documents", "applied_fields", "preview", "records": [{ "header": { … }, "lines": [ … ] }] }` : `{{commande.records.0.header.DO_Piece}}` |
| outils de fiches (`sage_tiers`, `sage_article`, `sage_contact`…) | `{ "dossier", "entity", "action", "key", "validate_only", "saved", "applied_fields", "message", "record": { … } }` : `{{creer.record.CT_Num}}` |

Les autres outils renvoient un objet JSON propre à chacun, avec au moins `dossier`. En cas de doute, testez l'étape dans l'éditeur : sa sortie s'affiche dans l'historique et le bouton `{ }` la propose aux étapes suivantes.

### 7.6 Les 46 outils et leurs arguments

Descriptions données par la passerelle Sage aux assistants (en anglais). Les noms d'arguments sont exacts. `dossier` est facultatif pour la passerelle mais à donner dans un flux. `fields` (fiches et documents) : les valeurs à écrire, par les noms indiqués dans la description de l'outil ou par les noms de propriétés Sage.

#### `sage_context`

Droit : aucun. Lecture.

Call first. Returns the Sage 100 dossiers (companies) this key can access, its scopes, which tools are available, the data conventions (keys, dates, amounts, enumerations) and features: what this version of the connector can do beyond the earlier ones (transform_lines, line_locations, stock_locations), for flows that must not run on an older one.

Aucun argument.

#### `sage_schema`

Droit : `read`. Lecture.

Without 'entity': lists every entity (tiers, client, fournisseur, article, document_vente, ecriture...) with availability. With 'entity': its real columns in this dossier (custom 'infos libres' included), key, default fields, enumeration values, relations and the filter syntax.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `dossier` | texte | non | Dossier id from sage_context. Optional when the key has a single dossier. |
| `entity` | texte | non | Entity name, e.g. client, article, document_vente, ecriture. Omit to list entities. |

#### `sage_search`

Droit : `read`. Lecture.

Searches records of one entity with filters, free-text search, sorting and pagination. Returns total_count, has_more, next_offset and the records. Filters: JSON array of [column, operator, value] combined with AND, e.g. [["DO_Type","=",6],["DO_Date",">=","2026-01-01"]]. Operators: = != < <= > >= in, not in, between, like, contains, starts_with, ends_with, is null, is not null. OR group: {"or": [...]}.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `entity` | texte | oui | Entity name (see sage_schema), e.g. client, fournisseur, article, document_vente, document_ligne, ecriture. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |
| `filters` | JSON | non | Filter conditions, see the tool description. |
| `search` | texte | non | Free text searched in the entity's main text columns (name, number, city, e-mail...). |
| `fields` | liste de textes | non | Columns to return. Default: the entity's main columns. ["*"] returns every column. |
| `order` | texte | non | Sort, e.g. 'DO_Date desc, DO_Piece asc'. Default: the entity's natural order. |
| `limit` | entier | non | Page size, default 50, max 1000. |
| `offset` | entier | non | Rows to skip (use next_offset from the previous page). |

#### `sage_get`

Droit : `read`. Lecture.

Reads one record by key with all its columns and its related records: a document with its lines and due dates, a third party with its contacts and bank accounts, an item with its stock per warehouse and prices, an accounting entry with its analytical breakdown.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `entity` | texte | oui | Entity name, e.g. client, article, document_vente, ecriture. |
| `key` | JSON | oui | Key: a string or number for simple keys (CT_Num, AR_Ref, DO_Piece, EC_No...), or an object for composite keys, e.g. {"DO_Domaine":0,"DO_Type":6,"DO_Piece":"FA00012"}. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |
| `fields` | liste de textes | non | Columns to return. Default: every column. |
| `include_related` | vrai/faux | non (`true`) | Include related records (lines, contacts, stock...). Default true. |

#### `sage_finance`

Droit : `read`. Lecture.

Computed business figures. Actions: tiers_balance (balance of third parties), aged_balance (balance âgée by due date buckets), open_items (unlettered entries and days overdue), account_balance (general account balances over a period), stock_status (available and forecast stock), sales_summary (invoiced amounts grouped by tiers, article, famille, month or collaborateur).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | tiers_balance, aged_balance, open_items, account_balance, stock_status or sales_summary. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |
| `tiers` | texte | non | Third-party account (CT_Num). |
| `tiers_type` | texte | non | client, fournisseur, salarie or autre. |
| `account` | texte | non | General account number or prefix (account_balance), e.g. 512 or 6. |
| `journal` | texte | non | Journal code (account_balance). |
| `article` | texte | non | Item reference (stock_status, sales_summary). |
| `depot` | entier | non | Warehouse number DE_No (stock_status). |
| `date_from` | texte | non | Start date YYYY-MM-DD. |
| `date_to` | texte | non | End date YYYY-MM-DD. |
| `as_of` | texte | non | Reference date YYYY-MM-DD for balances and ageing. Default today. |
| `group_by` | texte | non | sales_summary grouping: tiers, article, famille, month, collaborateur. |
| `domain` | texte | non | sales_summary domain: vente (default) or achat. |
| `overdue_only` | vrai/faux | non (`false`) | open_items: only overdue entries. |
| `below_minimum_only` | vrai/faux | non (`false`) | stock_status: only items whose forecast stock is below the minimum. |
| `limit` | entier | non | Max rows, default 100, max 1000. |

#### `sage_tarification`

Droit : `read`. Lecture.

Sales prices of the dossier as price lists, computed like Sage computes a document line: one list per price category (net price excl. tax of each item that differs from its sale price, per quantity tier; prices incl. tax converted with the item's taxes), one list per customer with conditions of its own, based on its category's list (exceptions, family discounts, customer discount). Rules: portee article (prix from quantite_min, or base list with remise 0), famille or tous (remise % on the base list). Also each customer's list, families and the family of each item. Order of Sage: exception, category, customer's family, family's category, customer discount; hors remise takes none.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |
| `exceptions` | vrai/faux | non (`true`) | Customer exceptions (tarifs d'exception): default true. |
| `familles` | vrai/faux | non (`true`) | Discounts per family for customers, families of the items: default true. |
| `paliers` | vrai/faux | non (`true`) | Quantity and amount tiers (paliers): default true. false keeps the price without tiers. |
| `remise_client` | vrai/faux | non (`true`) | Discount of the customer record (CT_Taux01), applied last as Sage does: default true. |
| `categorie_comptable` | entier | non (`1`) | Number of the catégorie comptable whose taxes convert prices incl. tax (1 = first, default). |

#### `sage_fiscalite`

Droit : `read`. Lecture.

Tax codes (F_TAXE, with those an ERP cannot compute marked reprise false), catégories comptables, the general account and taxes of each item for the reference catégorie de vente and d'achat (its own Paramètres > Comptabilité, else its family's), the replacements each other catégorie makes (fiscal positions: taxes and accounts, the most frequent when items differ), the position of each customer and supplier, and the accounts all this refers to (with the number on longueur_comptes digits).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |
| `categorie_vente` | entier | non (`1`) | Reference catégorie comptable de vente (1 = first, default). |
| `categorie_achat` | entier | non (`1`) | Reference catégorie comptable d'achat (1 = first, default). |
| `longueur_comptes` | entier | non | Number of digits of the target chart of accounts: each account also comes cut or completed to it. |

#### `sage_tiers`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates, deletes a third party (client, fournisseur, prospect, salarie, autre) or converts a prospect into a client (action convert; fields intitule, classement, compte_collectif optional), through the Sage Objets Metiers with Sage's own consistency checks. fields (JSON object): intitule, classement, contact, qualite, adresse, complement, code_postal, ville, region, pays, telephone, portable, telecopie, email, site, siret, tva_intracom, ape, commentaire, encours_max, assurance_credit, sommeil, lettrage_auto, nb_factures, taux_remise, taux_escompte, compte_collectif (CG_Num, e.g. 4110000), representant ({nom, prenom}, or {email}: unknown or empty leaves it as it is, said in the message), categorie_tarifaire (a client's price category: label or number, 1 = first; empty leaves it unchanged), any info libre by its exact name, or any Sage property name (e.g. CT_Facture). Scope write (delete also needs scope delete). Returns the saved record.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update, delete or convert (prospect to client). |
| `numero` | texte | oui | Third-party account number CT_Num (17 chars max, uppercase recommended). |
| `type` | texte | non | On create: client, fournisseur, prospect, salarie or autre. |
| `fields` | JSON | non | Values to set, see the tool description. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_contact`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a contact of a third party. The contact is identified by tiers + nom (+ prenom). fields: nom, prenom, fonction, civilite (0 Monsieur, 1 Madame, 2 Mademoiselle), telephone, portable, telecopie, email, site. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `tiers` | texte | oui | Third-party account number (CT_Num) the contact belongs to. |
| `nom` | texte | oui | Contact last name (identifies the contact for update and delete). |
| `prenom` | texte | non | Contact first name. |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_depot`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a warehouse (dépôt), identified by its name. fields: intitule (rename), code, contact, exclure (from replenishment), adresse, complement, code_postal, ville, region, pays, or any DE_ property. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `intitule` | texte | oui | Warehouse name DE_Intitule. |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_emplacement`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a storage location (emplacement) of a warehouse, identified by warehouse name + location code. fields: code (rename), intitule, or any DP_ property. Needs multi-location management enabled in the Sage dossier. Read them with sage_search entity emplacement. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `depot` | texte | oui | Warehouse name DE_Intitule. |
| `code` | texte | oui | Location code DP_Code. |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_collaborateur`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a collaborateur (sales representative, buyer, financial manager...), identified by nom + prenom. fields: nom (rename), prenom, fonction, service, matricule, utilisateur, vendeur, acheteur, financier (bon à payer), caissier, charge_recouvrement, receptionnaire, chef_ventes, adresse, complement, code_postal, ville, region, pays, telephone, portable, telecopie, email, site, facebook, linkedin, skype, or any CO_ property. Read them with sage_search entity collaborateur. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `nom` | texte | oui | Last name (identifies the collaborateur with prenom). |
| `prenom` | texte | non | First name (may be empty). |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_livraison`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a delivery address (lieu de livraison) of a customer, identified by client + intitule. fields: intitule (rename), contact, adresse, complement, code_postal, ville, region, pays, telephone, portable, telecopie, email, site, expedition (shipping method label), condition_livraison (label), principal (true = main delivery address of the customer), or any Sage property name (LI_...). Read them with sage_search entity livraison. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `client` | texte | oui | Customer account number (CT_Num). |
| `intitule` | texte | oui | Name of the delivery address (LI_Intitule), identifies it for update and delete. |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_rib`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a bank account (RIB / IBAN) of a third party (client, supplier...), identified by tiers + intitule. fields: intitule (rename), structure (0 local, 1 BBAN, 2 IBAN, 3 autre), banque, guichet, compte, cle, iban, bic, calcul_iban, nom_agence, pays_agence, commentaire, adresse, complement, code_postal, ville, region, pays (bank address), principal (true = main bank account of the third party), or any Sage property name (BT_...). For an IBAN account use structure 2 + iban (Sage stores it as the account number). Sage checks the RIB key and the IBAN. Read them with sage_search entity rib_tiers. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `tiers` | texte | oui | Third-party account number (CT_Num). |
| `intitule` | texte | oui | Name of the bank account (BT_Intitule), identifies it for update and delete. |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_article`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes an item (article). Text lengths follow sage_schema (max_length). On creation, famille (FA_CodeFamille) and designation are required; the family's defaults (accounts, units, stock tracking) are applied. fields: designation, famille, unite_vente, prix_achat, prix_vente, prix_ttc, coefficient, code_barre, code_fiscal, poids_net, poids_brut, suivi_stock, type, sommeil, publie, hors_stat, langue1, langue2, delai, garantie, any info libre by its exact name, or any Sage property name (e.g. AR_Escompte). Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `reference` | texte | oui | Item reference AR_Ref (18 chars max). |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_article_gamme`

Droit : `write`. Écriture (Objets Métiers de Sage).

Product ranges of an item (article à gamme, e.g. size and colour): gamme1 / gamme2 are product ranges of the dossier (sage_parametre kind gamme, type divers), enumeres1 / enumeres2 add their values to the item, references [{enum1, enum2?, reference?, code_barre?, prix_achat?, sommeil?}] sets each combination. Without arguments besides article, returns the item's ranges. Documents take a range item with the line keys gamme1 / gamme2 (or the combination reference as article). Scope write.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `article` | texte | oui | Item reference AR_Ref. |
| `gamme1` | texte | non | Product range 1 (label). |
| `gamme2` | texte | non | Product range 2 (label). |
| `enumeres1` | liste de textes | non | Values of range 1 to add. |
| `enumeres2` | liste de textes | non | Values of range 2 to add. |
| `references` | JSON | non | Combinations to update: [{enum1, enum2?, reference?, code_barre?, prix_achat?, sommeil?}]. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_article_conditionnement`

Droit : `write`. Écriture (Objets Métiers de Sage).

Packs of an item sold by packaging (article conditionné, created with sage_article fields.conditionnement): without packs lists them, packs [{enumere, reference?, code_barre?, code_edi?}] sets each pack's reference and barcodes. Documents take a pack with the line key conditionnement (quantite = number of packs) or the pack reference as article. Scope write.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `article` | texte | oui | Item reference AR_Ref. |
| `packs` | JSON | non | Packs to update: [{enumere, reference?, code_barre?, code_edi?}]. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_nomenclature`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Bill of materials of an item: type (aucun, fabrication, compose, composant, lie) and composants [{article, quantite, variable?, depot?, operation?, commentaire?}] which replace the whole list. Without type nor composants, returns the item's bill of materials. A 'compose' item adds its components to sales documents; a 'fabrication' item is made through manufacturing documents. Read with sage_search entity nomenclature. Scope write.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `article` | texte | oui | Item reference AR_Ref (the composed or manufactured item). |
| `type` | texte | non | aucun, fabrication, compose, composant or lie. |
| `composants` | JSON | non | Components (replace the list): [{article, quantite, variable?, depot?, operation?, commentaire?}]. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_famille`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes an item family. fields: intitule, type (0 detail, 1 total, 2 centralisateur), coefficient, suivi_stock, code_fiscal, publie, hors_stat, delai, garantie, unite_vente, or any Sage property name. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `code` | texte | oui | Family code FA_CodeFamille (10 chars max). |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_bareme`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Price schemes (barèmes) identified by their intitule. WARNING: a scheme created here has no item or third-party restriction, so Sage applies it to EVERY matching document of the dossier within its dates (restrict it in Sage if needed); use validate_only and dates. kind: rabais / rabais_fournisseur (discounts by tier: objectif quantite|valeur, calcul tranche|global, article_remise (required: discount item AR_Ref), tranches), solde / solde_fournisseur (sales and promotions: remise), commission (interesse representant|global, objectif quantite|valeur|taux_remise, domaine commande|preparation|livraison|facturation|encaissement, base ca_ht|marge_ht, calcul tranche|global, tranches). Common fields: intitule (rename), date_debut, date_fin; supplier kinds need fournisseur. tranches: [{borne_sup, remise}] replaces all tiers. The restriction to some items or third parties is set in Sage (read it with sage_search entity bareme_selection): a scheme created here applies without restriction. action appliquer: Sage's 'Barèmes' command on a document (type + piece): adds the discount lines of the schemes that apply (scope documents). Read schemes with sage_search entity bareme (relations tranches, selection). Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update, delete or appliquer. |
| `kind` | texte | non | create/update/delete: rabais, rabais_fournisseur, solde, solde_fournisseur or commission. |
| `intitule` | texte | non | create/update/delete: name of the scheme. |
| `fields` | JSON | non | create/update: values, see the tool description. |
| `tranches` | JSON | non | create/update: tiers [{"borne_sup": n, "remise": "10%"}] (replace the existing ones). |
| `type` | texte | non | appliquer: document type (commande, facture, commande_achat...). |
| `piece` | texte | non | appliquer: document number DO_Piece. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_tarif`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Sets (creates or updates) or deletes the price conditions of an article or a famille for a client, a categorie (price category, by label or number) or a fournisseur, through the Objets Metiers. fields for article + client/categorie: prix, prix_ttc, coefficient, calcul, remise (Sage accepts a single percentage here, e.g. '5%'), hors_remise, gamme_remise, reference (client only); article + fournisseur: prix (purchase), remise, hors_remise, gamme_remise, reference, code_barre, colisage, qte_mini, delai_appro, garantie, conversion, conv_div, unite, principal (true = main supplier); famille + client: remise, hors_remise, gamme_remise; famille + categorie: coefficient, prix_ttc, calcul, remise, hors_remise, gamme_remise; famille + fournisseur: remise, hors_remise, gamme_remise, colisage, qte_mini, delai_appro, garantie, conversion, conv_div, unite. paliers [{borne_sup, prix_net?, remise?}] updates the quantity tiers of a price that has a gamme_remise, in order. Read prices with sage_search: tarif_client, tarif_fournisseur, tarif_famille_client, tarif_famille_fournisseur, tarif_palier, categorie_tarifaire. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | set (create or update) or delete. |
| `cible` | texte | oui | client, categorie or fournisseur. |
| `article` | texte | non | Item reference AR_Ref (or pass famille). |
| `famille` | texte | non | Detail family code FA_CodeFamille (or pass article). |
| `tiers` | texte | non | cible client or fournisseur: CT_Num. |
| `categorie` | texte | non | cible categorie: price category label or number (1 = first). |
| `fields` | JSON | non | Values to set, see the tool description. |
| `paliers` | JSON | non | Quantity tiers: [{"borne_sup": n, "prix_net"?: n, "remise"?: "10%"}]. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_ressource`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Production resources (kind ressource) and load centres (kind centre), identified by their code RP_Code. create / update fields: intitule, complement, commentaire, capacite, temps (seconds), cout_standard, continue, sommeil, visite (YYYY-MM-DD), type (creation only, Sage number: 2 = Outil et Matériel, needed to attach an item of type ressource multiple), depot (label), article_defaut (AR_Ref), unite (label); resources also code_externe, adresse, complement, code_postal, ville, region, pays, telephone, portable, telecopie, email, site. lier / delier (kind ressource): attach the resource to an item of type ressource unitaire or multiple (article) or to a load centre of the same type (centre). Read them with sage_search entity ressource. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update, delete, lier or delier. |
| `kind` | texte | oui | ressource or centre. |
| `code` | texte | oui | Code RP_Code. |
| `fields` | JSON | non | Values to set. |
| `article` | texte | non | lier / delier: item reference. |
| `centre` | texte | non | lier / delier: load centre code. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_controle_qualite`

Droit : `documents`. Écriture (Objets Métiers de Sage).

Quality control of quantities placed on control locations (Traitement / Contrôle qualité), domaine achat (receptions) or vente. list: location lines waiting for a control (piece, article, emplacement, quantite, a_controler), filters depot, du, au. traiter: decisions [{piece, article, emplacement?, valider?, retourner?, motif_retour?, rebut?, motif_rebut?}]: valider transfers to the main location, retourner creates a return document (type_retour: retour_achat or facture_achat for achat, livraison or facture for vente), rebut a stock exit; date = date of the generated documents, reference = their reference. Needs quality control locations enabled (sage_dossier emplacements_controle) and a control location per item (sage_stock_article emplacement_controle). Scope documents (list: read).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | list or traiter. |
| `domaine` | texte | oui | achat or vente. |
| `depot` | texte | non | Warehouse label. |
| `du` | texte | non | Documents from (YYYY-MM-DD). |
| `au` | texte | non | Documents to (YYYY-MM-DD). |
| `decisions` | JSON | non | traiter: decisions per location line. |
| `type_retour` | texte | non | traiter: document type for returns. |
| `date` | texte | non | traiter: date of the generated documents (YYYY-MM-DD). |
| `reference` | texte | non | traiter: reference of the generated documents. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_agenda`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Agenda events of commercial management (visits, calls...) in an agenda defined with sage_parametre kind agenda, about a client or supplier (interesse = CT_Num) or a collaborateur (interesse = last name, prenom), depending on the agenda type. list: events (filters agenda, interesse; a_venir = not past only). create: agenda, interesse, debut, fin, evenement (an event kind of the agenda), commentaire, confirme, veille, ignorer. update / delete: the event is found by agenda + interesse + debut; update can move it with nouveau_debut. Dates YYYY-MM-DD or YYYY-MM-DDTHH:mm. Read them with sage_search entity agenda_evenement. Scope write (list: read; delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | list, create, update or delete. |
| `agenda` | texte | non | Label of the agenda. |
| `interesse` | texte | non | CT_Num or collaborateur last name. |
| `prenom` | texte | non | Collaborateur first name. |
| `debut` | texte | non | Start (YYYY-MM-DD or YYYY-MM-DDTHH:mm). |
| `fin` | texte | non | End (default: debut). |
| `nouveau_debut` | texte | non | update: new start. |
| `evenement` | texte | non | Event kind (one of the agenda's enumeres). |
| `commentaire` | texte | non | Comment. |
| `confirme` | vrai/faux | non | Confirmed event. |
| `veille` | vrai/faux | non | Pending event (veille). |
| `ignorer` | vrai/faux | non | Ignored event. |
| `a_venir` | vrai/faux | non (`false`) | list: only events not past yet. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_dossier`

Droit : `settings`. Écriture (Objets Métiers de Sage).

get: identity of the company (raison sociale, SIRET, APE, VAT number, address, contact), fiscal years, currency and storage options. update fields: raison_sociale, siret, ape, identifiant, activite, commentaire, expert, email_expert, numero_dossier_expert, adresse, complement, code_postal, ville, region, pays, telephone, telecopie, email, site, emplacements_controle (quality control locations, commercial management; multi_emplacements is read only). Scope settings (get: read).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | get or update. |
| `fields` | JSON | non | update: values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_numerotation`

Droit : `settings`. Écriture (Objets Métiers de Sage).

Next document numbers of a numbering series (souche), as in Sage's company settings (Documents). list: next number of every document type of domaine (vente, achat, stock, interne); null = no number set yet for that type. set: next number of one document type; refused if that number is already used. souche: series label (default: the first one, see sage_parametre kind souche_vente...). Scope settings (list: read).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | list or set. |
| `domaine` | texte | non | list: vente, achat, stock or interne. |
| `type` | texte | non | set: document type (facture, commande...). |
| `numero` | texte | non | set: new next number (13 characters max). |
| `souche` | texte | non | Numbering series label (default: the first one). |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_modele`

Droit : `settings`. Écriture (Objets Métiers de Sage), peut supprimer.

Accounting templates identified by kind + intitule; lignes are replaced as a whole. kind reglement (payment terms): lignes [{condition: jours | fin_mois_civil | fin_mois, nb_jours, jours_tombee: [days 1-31, 6 max], repartition: pourcentage | montant | equilibre, valeur, mode: payment method label}], jours: date + nb_jours; fin_mois: date + nb_jours then end of month; fin_mois_civil: nb_jours counted as calendar months (30 = 1 month) then end of month; then the next jour de tombée if any. Each line needs its own nb_jours (Sage sorts the lines by it); exactly one line is the balance (equilibre, the default for a line without valeur); list shows each line's due date (echeance) for a document dated date (default today). kind saisie (entry template): fields raccourci, journal (JO_Num) or type_journal (achats, ventes, tresorerie, general, situation); lignes [{compte, compte_contrepartie, tiers, tiers_contrepartie, intitule, sens debit|credit, montant, piece, reference, ref_piece, echeance, jour, quantite, montant_devise, parite, code_taxe, mode, analytique: [{plan, section, montant, quantite}]}], text values may be Sage functions (fields left out are entered by hand, = MacroSaisir(), and are not listed) (=MacroSaisir(), =MacroRepeter(), =MacroCalculer(), =MacroEquilibrer(), =MacroIncrementer(), =Montant()...). kind grille (distribution grid): fields raccourci, type general | analytique; lignes [{compte} or {plan, section}, repartition, valeur]. Apply a modèle de règlement to a third party with sage_tiers_reglement. Scope settings (list: read; delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | list, create, update or delete. |
| `kind` | texte | oui | saisie, reglement or grille. |
| `intitule` | texte | non | Name of the template (create, update, delete). |
| `fields` | JSON | non | Header values (intitule to rename, raccourci, journal...). |
| `lignes` | JSON | non | Lines of the template, replaced as a whole. |
| `date` | texte | non | list, kind reglement: document date used to compute the due dates (YYYY-MM-DD, default today). |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_tiers_reglement`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Payment conditions (échéances) of a client or supplier. list: current lines with their due date (echeance) for a document dated date (default today). set: replaces them all with lignes (same syntax as sage_modele kind reglement: condition, nb_jours, jours_tombee, repartition, valeur, mode; one equilibre line; [] removes them all) or with the lines of a modèle de règlement (modele). Scope write (list: read).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | list or set. |
| `tiers` | texte | oui | Third-party account number (CT_Num). |
| `lignes` | JSON | non | set: payment lines. |
| `modele` | texte | non | set: name of a modèle de règlement to copy. |
| `date` | texte | non | list: document date used to compute the due dates (YYYY-MM-DD, default today). |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_glossaire`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a glossary (reusable text: conditions, warranty, maintenance...), identified by domaine (article or document) + intitule; lier / delier attach an article glossary to an item (article). fields: intitule (rename, 35 chars), raccourci (7 chars), texte, langue1, langue2, debut, fin (YYYY-MM-DD), or any GL_ property. Insert a glossary in a document with a line {"glossaire": intitule} in sage_document. Read them with sage_search entity glossaire (glossaire_article for item links). Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update, delete, lier or delier. |
| `domaine` | texte | oui | article or document. |
| `intitule` | texte | oui | Name of the glossary (GL_Intitule). |
| `article` | texte | non | lier / delier: item reference (AR_Ref). |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_media`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Attachments (médias) of a tiers (key CT_Num), article (AR_Ref), document (type + DO_Piece) or ecriture (EC_No). Sage stores a link, not the file. list; add with contenu_base64 + nom_fichier (max 10 MB), or lien (path of a file in a folder the gateway administrator allowed for attachments; refused otherwise): Sage copies the file into the Multimedia folder of the dossier and sets the MIME type itself; delete by numero (from list), supprimer_fichier also deletes Sage's copy. Scopes: write for tiers and article, documents for document, accounting for ecriture (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | list, add or delete. |
| `entity` | texte | oui | tiers, article, document or ecriture. |
| `key` | texte | oui | CT_Num, AR_Ref, DO_Piece or EC_No. |
| `type` | texte | non | document only: document type (commande, facture...). |
| `lien` | texte | non | add: full path of a file in a folder allowed by the gateway administrator (Pièces jointes on the administration page). Prefer contenu_base64. |
| `nom_fichier` | texte | non | add: file name with extension when sending contenu_base64. |
| `contenu_base64` | texte | non | add: file content in base64. |
| `commentaire` | texte | non | add: comment. |
| `transmettre` | vrai/faux | non | add, document only: true to send the file with the document (e-mail / EDI). |
| `numero` | entier | non (`0`) | delete: number of the attachment (from list). |
| `supprimer_fichier` | vrai/faux | non (`false`) | delete: true to also delete the file Sage copied into its Multimedia folder. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_parametre`

Droit : `settings`. Écriture (Objets Métiers de Sage), peut supprimer.

Company settings kept by Sage in fixed slots, identified by their intitule. kind: categorie_tarifaire (fields intitule, prix_ttc), gamme (intitule, type divers (product range) | quantites | montant | prix_net, enumeres [values]; a discount range's enumeres are the tier bounds used by sage_tarif paliers: quantites and montant tiers carry a remise, prix_net tiers a prix_net), unite (intitule, nb_unite), mode_reglement (intitule, code), expedition (intitule, mode, frais, frais_ttc, franco, franco_ttc), condition_livraison (intitule, mode), conditionnement (intitule, enumeres [{intitule, quantite}] = packs, replaced as a whole), souche_vente | souche_achat | souche_stock | souche_interne (numbering series: intitule, valide), agenda (intitule, type client | fournisseur | collaborateur, enumeres = event kinds, indisponible), periode_rappel (reminder periods: intitule, debut, fin, nb_jours), categorie_compta_vente | categorie_compta_achat | categorie_compta_stock (accounting categories: intitule), periodicite (intitule), devise (intitule, code_iso, sigle, monnaie, sous_monnaie, cours, euro, format), pays (intitule, code, code_iso2, code_edi, assurance, transport, sepa), taxe (identified by its code TA_Code: code to rename, libelle, taux, sens deductible | collectee, compte, assujettissement, non_percue, regroupement, type / type_taux / provenance as Sage numbers, 0 = TVA sur les débits / percentage / national). list: the defined ones with their number (numero = position used by Sage, e.g. price category number). create takes the first free slot; delete frees the slot. These settings apply to the whole company. Scope settings (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | list, create, update or delete. |
| `kind` | texte | oui | categorie_tarifaire, gamme, unite, mode_reglement, expedition, condition_livraison, conditionnement, agenda, periode_rappel, souche_vente, souche_achat, souche_stock, souche_interne, categorie_compta_vente, categorie_compta_achat, categorie_compta_stock, periodicite, devise, pays or taxe. |
| `intitule` | texte | non | Label of the setting (create, update, delete); the code for a taxe. |
| `fields` | JSON | non | Values to set, see the tool description. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_compte`

Droit : `write`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a general ledger account (plan comptable). fields: intitule, classement, type (0 detail, 1 total), saisie_analytique, saisie_echeance, saisie_quantite, lettrage, sommeil, report, regroupement, any info libre or Sage property name. Scope write (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `numero` | texte | oui | Account number CG_Num. |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = run every Sage check on the values without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_document`

Droit : `documents`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes a commercial document (quote, order, delivery note, invoice, purchase documents, stock movements) through the Sage Objets Metiers: prices, discounts, taxes, totals and stock are computed by Sage exactly as in the software. create: type, tiers, lines [{article, quantite, prix_unitaire?, remise? ('10%', '5U', '100F'), designation?, lot?, peremption?, fabrication?, conditionnement?, emplacement?} or {texte}], optional fields (reference, date, date_livraison, statut, contact, depot (name DE_Intitule or number DE_No), souche, representant {nom, prenom} or {email}, infos libres) and piece (otherwise Sage numbers it). emplacement (storage location DP_Code of the document's warehouse, multi-emplacement dossiers): on the lines of entree_stock and sortie_stock created here, and on lines added to any document that moves stock. update: type + piece, then fields and/or add_lines (same keys, plus depot of the line), update_lines [{dl_no, quantite?, prix_unitaire?, remise?, designation?}] (DL_No from sage_get), remove_lines [DL_No]. delete: type + piece (needs scope delete). Items tracked by lot or serial number: on entries (reception, facture_achat, retour, entree_stock) lot is the new lot/serial number with optional peremption and fabrication dates; on exits (livraison, facture, preparation_livraison, retour_achat, sortie_stock) lot is an existing lot of the line's warehouse, or 'auto' to take lots first expiring first (one line per lot). Serial numbers: one line per serial with quantite 1, or 'auto'. Scope documents. Returns the saved document with its lines.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `type` | texte | oui | Document type: devis, commande, preparation_livraison, livraison, retour, avoir, facture (sales); demande_achat, preparation_commande_achat, commande_achat, reception, retour_achat, avoir_achat, facture_achat (purchases); entree_stock, sortie_stock, preparation_fabrication, ordre_fabrication, bon_fabrication (manufacturing: a line on a 'fabrication' item brings its components); interne1 to interne6. |
| `piece` | texte | non | Document number DO_Piece (update, delete; optional on create). |
| `tiers` | texte | non | create: client (sales) or supplier (purchases) CT_Num. |
| `lines` | JSON | non | create: document lines, see the tool description. |
| `fields` | JSON | non | Header values: reference, date (YYYY-MM-DD), date_livraison, statut, contact, depot, souche, representant ({nom, prenom}, or {email}: unknown or empty keeps the third party's), infos libres, or DO_ property names. |
| `add_lines` | JSON | non | update: lines to add. |
| `update_lines` | JSON | non | update: lines to change, each with its dl_no. |
| `remove_lines` | JSON | non | update: DL_No of the lines to remove. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check everything without saving (recommended before a first write). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_document_transform`

Droit : `documents`. Écriture (Objets Métiers de Sage), peut supprimer.

Transforms one or several documents into the next stage, as Sage does: sales devis/commande/preparation_livraison/livraison into commande, preparation_livraison, livraison or facture; purchases demande_achat/preparation_commande_achat/commande_achat/reception into commande_achat, reception or facture_achat. Several documents of the same third party can be grouped into one. The source documents are consumed as in Sage (unless keep_sources). lines transforms only some lines for part of their quantity, the rest staying on the source line as when delivering or receiving part of an order in Sage: [{piece?, ligne? (DL_Ligne) or dl_no?, quantite, conditionnement? (quantite = number of packs), emplacements? [{emplacement, quantite}]}]. add_lines adds lines to the new document in the same call ({article, quantite, prix_unitaire?, conditionnement?, designation?, emplacement?, depot?}, e.g. items received but not ordered at price 0). With lines, strict (default true) undoes everything when Sage transforms less than asked (missing stock). The answer gives lines_result (the lines made) and remaining (what is left on each source line). Items tracked by lot/serial: receptions need lots [{article, lot, quantite, peremption?, fabrication?}] (new lots); deliveries and invoices take the given existing lots, then choose automatically (first expiring first) for the rest. Scope documents.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `source_type` | texte | oui | Type of the source documents (e.g. devis, commande, livraison, commande_achat). |
| `pieces` | liste de textes | oui | DO_Piece of the source documents. |
| `target` | texte | oui | Target: commande, preparation_livraison, livraison, facture, commande_achat, reception or facture_achat. |
| `date` | texte | non | Date of the new documents (YYYY-MM-DD). Default: today. |
| `keep_sources` | vrai/faux | non | Keep the source documents instead of consuming them (Sage option ConserveDocuments). |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check the transformation without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |
| `lots` | JSON | non | Lots for items tracked by lot/serial: [{"article": AR_Ref, "lot": number, "quantite"?: n, "peremption"?: "YYYY-MM-DD", "fabrication"?}]. |
| `lines` | JSON | non | Lines to transform for part of their quantity: [{"piece"?: DO_Piece, "ligne"?: DL_Ligne, "dl_no"?: DL_No, "quantite": n, "conditionnement"?: pack (quantite = number of packs), "emplacements"?: [{"emplacement": DP_Code, "quantite": n}]}]. A line is named by dl_no, or by ligne (DL_Ligne, unique in a document); piece is needed with ligne when pieces has several documents. Omit to transform whole documents. |
| `add_lines` | JSON | non | Lines added to the new document: [{"article", "quantite", "prix_unitaire"?, "conditionnement"?, "designation"?, "emplacement"?, "depot"?}] (keys of sage_document). |
| `strict` | vrai/faux | non | With lines: true (default) = everything is undone when Sage transforms less than asked; false = keep what Sage transformed. |

#### `sage_document_traitement`

Droit : `documents`. Écriture (Objets Métiers de Sage).

Sage processing on an existing document. sous_total: inserts a sub-total line totalling the lines dl_nos (DL_No from sage_get) of the document (type + piece), optional commentaire. colisage: splits the lines of a preparation_livraison (piece) into parcels, colis [{dl_no, colis, quantite}]; a quantity left without parcel stays on its own line. recalcul_prix_compose: recomputes the cost price of the manufactured items of a manufacturing document (type preparation_fabrication, ordre_fabrication or bon_fabrication + piece), all lines or dl_nos. Scope documents.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | sous_total, colisage or recalcul_prix_compose. |
| `piece` | texte | oui | Document number DO_Piece. |
| `type` | texte | non | sous_total: document type. |
| `dl_nos` | liste d'entiers | non | sous_total: DL_No of the lines to total. |
| `commentaire` | texte | non | sous_total: label of the sub-total line. |
| `colis` | JSON | non | colisage: [{"dl_no": line of the preparation, "colis": parcel number, "quantite": quantity in this parcel}]. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_acompte`

Droit : `documents`. Écriture (Objets Métiers de Sage), peut supprimer.

Down payments (acomptes) of a sales or purchase document (type + piece). list: the down payments with their position (numero), date, montant, libelle, mode. add: fields {montant, date?, libelle?, mode? (payment method label)}. update: numero + fields. delete: numero (needs scope delete). Scope documents.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | list, add, update or delete. |
| `type` | texte | oui | Document type (commande, facture, commande_achat...). |
| `piece` | texte | oui | Document number DO_Piece. |
| `numero` | entier | non | update, delete: position of the down payment (from list). |
| `fields` | JSON | non | add, update: {montant, date (YYYY-MM-DD), libelle, mode}. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_reglement`

Droit : `documents`. Écriture (Objets Métiers de Sage).

Records a customer or supplier payment and allocates it to the due dates of one or several invoices of the same payer (Sage process ReglerEcheances). type: facture, facture_achat, avoir or avoir_achat; pieces: DO_Piece; journal: treasury journal JO_Num; montant: default = everything left to pay, a smaller amount makes a partial payment; mode: payment method name (optional, else the payer's default). Scope documents.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `type` | texte | oui | facture, facture_achat, avoir or avoir_achat. |
| `pieces` | liste de textes | oui | DO_Piece of the documents to settle. |
| `date` | texte | oui | Payment date YYYY-MM-DD. |
| `journal` | texte | oui | Treasury (or general) journal code JO_Num. |
| `montant` | nombre | non | Amount. Default: everything left to pay. |
| `mode` | texte | non | Payment method name (R_Intitule). |
| `reference` | texte | non | Payment reference (e.g. cheque number). |
| `libelle` | texte | non | Label. Default: 'Règlement <pieces>'. |
| `validate_only` | vrai/faux | non (`false`) | true = check documents, amounts, journal and method without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_ecriture`

Droit : `accounting`. Écriture (Objets Métiers de Sage), peut supprimer.

Posts a balanced accounting entry (pièce comptable) through the Sage Encoder process, or deletes entries. create: journal (JO_Num), date (YYYY-MM-DD, in an open fiscal year), lines [{compte?, tiers?, sens: debit|credit, montant, intitule?, echeance?, reglement?}], optional intitule, reference, numero_facture, piece (otherwise the journal's next number). Debit must equal credit. In a treasury journal, only enter the counterpart lines: Sage adds the bank line itself. delete: journal + piece + date (any day of the month) deletes the whole piece, or ec_nos (EC_No of lines that balance); needs scope delete. Analytical accounting: a line can carry analytique [{plan, section, montant?, quantite?}] (plan = label or number; a single section of a plan takes the whole amount), or analytique_auto = true lets Sage apply the accounts' default sections. analytique: replaces the breakdown of a posted entry (ec_no + analytique), plan by plan; plans not given are kept. Scope accounting. Returns the EC_No of every line of the piece.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, delete or analytique. |
| `journal` | texte | non | Journal code JO_Num (create, delete by piece). |
| `date` | texte | non | Entry date YYYY-MM-DD (create); any day of the piece's month (delete by piece). |
| `lines` | JSON | non | create: entry lines, see the tool description. |
| `intitule` | texte | non | create: label used for every line without its own intitule. |
| `reference` | texte | non | create: reference (EC_Reference). |
| `numero_facture` | texte | non | create: invoice number (EC_RefPiece). |
| `piece` | texte | non | create: piece number (EC_Piece), default next number of the journal. delete: piece to delete. |
| `ec_nos` | JSON | non | delete: EC_No of the lines to delete. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check everything without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |
| `analytique_auto` | vrai/faux | non (`false`) | create: let Sage generate the analytical lines from the accounts' default sections. |
| `ec_no` | entier | non | analytique: EC_No of the posted entry line. |
| `analytique` | JSON | non | analytique: new breakdown [{"plan": analytical plan label or number, "section": CA_Num, "montant"?: amount (default: the whole line when it is the only section of its plan), "quantite"?: number}]. |

#### `sage_lettrage`

Droit : `accounting`. Écriture (Objets Métiers de Sage).

Letters (matches) accounting entries of the same account and third party, e.g. an invoice with its payment. ec_nos: EC_No of the entries (find them with sage_finance open_items or sage_search on ecriture). type: lettrage (default, balance must be zero), prelettrage or pointage. lettre: optional code, otherwise Sage takes the next one. Scope accounting.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `ec_nos` | JSON | oui | EC_No of the entries to letter together. |
| `type` | texte | non | lettrage (default), prelettrage or pointage. |
| `lettre` | texte | non | Lettering code. Default: next code chosen by Sage. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check the lettering without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_journal`

Droit : `accounting`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes an accounting journal. fields: intitule, type (0 achats, 1 ventes, 2 trésorerie, 3 général, 4 situation), compte (treasury account CG_Num, required for trésorerie), numerotation (0 manuelle, 1 continue journal, 2 continue fichier, 3 mensuelle), contrepartie, rapprochement, saisie_analytique, lettrage_saisie, reglement, sommeil. Scope accounting (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `code` | texte | oui | Journal code JO_Num (6 chars max). |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check the values without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_bon_a_payer`

Droit : `accounting`. Écriture (Objets Métiers de Sage).

Bon à payer workflow of supplier invoices (Sage process IPMBonAPayer). valider, attendre, payer, conformer or rejeter: ec_no (EC_No of the supplier invoice entry) + collaborateur_nom (+ collaborateur_prenom) of the person deciding + optional commentaire. historique: current bon à payer of an entry (statut aucun, a_valider, non_conforme, conforme, en_attente, bon_a_payer). parametres: company settings; without fields reads them, with fields {autorisation: aucune|financier|acheteur_et_financier, facture: toutes|selon_montant, seuil, responsable: 'Nom Prenom' or '' for none} changes them (also needs scope settings). Scope accounting.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | valider, attendre, payer, conformer, rejeter, historique or parametres. |
| `ec_no` | entier | non | EC_No of the entry (not for parametres). |
| `collaborateur_nom` | texte | non | Last name of the collaborateur taking the decision. |
| `collaborateur_prenom` | texte | non | First name of the collaborateur. |
| `commentaire` | texte | non | Comment of the decision. |
| `fields` | JSON | non | parametres: settings to change. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_compte_analytique`

Droit : `accounting`. Écriture (Objets Métiers de Sage), peut supprimer.

Creates, updates or deletes an analytical account (section) of an analytical plan. plan: label or number (see sage_search entity plan_analytique); numero: CA_Num. fields: intitule, type (0 detail, 1 total), classement, raccourci, report, sommeil, infos libres or any CA_ property. Scope accounting (delete: also delete).

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | create, update or delete. |
| `plan` | texte | oui | Analytical plan label or number (1 = first plan). |
| `numero` | texte | oui | Section number CA_Num. |
| `fields` | JSON | non | Values to set. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check the values without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_stock_transfert`

Droit : `stock`. Écriture (Objets Métiers de Sage).

Moves items from one warehouse to another (Sage 'virement de dépôt à dépôt' with the DocTransferer process, one per line, stock and valuation handled by Sage), or from one storage location to another inside a warehouse (origine = destination). origine and destination: warehouse name (DE_Intitule) or number (DE_No); lines [{article, quantite, emplacement_origine?, emplacement_destination?, lot?, gamme1?, gamme2?}] (locations DP_Code of multi-emplacement dossiers; an item tracked by lot takes its origin location from its lot). Returns the document and lines_result (one exit and one entry line per item, with their locations). Scope stock.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `origine` | JSON | oui | Origin warehouse: name (DE_Intitule) or number (DE_No). |
| `destination` | JSON | oui | Destination warehouse: name or number. The same as origine to move between locations of one warehouse. |
| `lines` | JSON | oui | Items to move: [{"article": AR_Ref, "quantite": number, "emplacement_origine"?: DP_Code, "emplacement_destination"?: DP_Code, "lot"?: lot or serial number, "gamme1"?/"gamme2"?: range values}]. |
| `date` | texte | non | Document date YYYY-MM-DD. Default: today. |
| `reference` | texte | non | Reference (DO_Ref). |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check the transfer without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_stock_article`

Droit : `stock`. Écriture (Objets Métiers de Sage).

Stock settings of an item in a warehouse where it is stocked: fields {qte_mini, qte_maxi, emplacement (code of an existing storage location of that warehouse)}. depot: warehouse name (default main warehouse). Scope stock.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `article` | texte | oui | Item reference AR_Ref. |
| `fields` | JSON | oui | {qte_mini?, qte_maxi?, emplacement?}. |
| `depot` | texte | non | Warehouse name. Default: main warehouse. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check the values without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_inventaire`

Droit : `stock`. Écriture (Objets Métiers de Sage).

Records a stock count (Sage process Inventaire): for each item, the counted quantity and unit value in a warehouse at a date; Sage adjusts the stock with an exit and an entry movement. depot: warehouse name (default main warehouse); date YYYY-MM-DD (default today); lines [{article, quantite (counted), prix? (default: current average cost), lot? (lot/serial items: one line per lot), gamme1?/gamme2?, emplacement? (storage location DP_Code: the quantity counted in it)}]. Returns the difference per item (stock before read in the location when one is given) and the generated documents. Scope stock.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `lines` | JSON | oui | Counted items: [{"article": AR_Ref, "quantite": counted quantity, "prix"?: unit value (default: current average cost), "lot"?: lot or serial number, "gamme1"?/"gamme2"?: range values, "emplacement"?: storage location DP_Code (the quantity counted there)}]. |
| `depot` | texte | non | Warehouse name or number. Default: main warehouse. |
| `date` | texte | non | Count date YYYY-MM-DD. Default: today. |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check the count without saving (returns the differences). |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

#### `sage_lot`

Droit : `stock`. Écriture (Objets Métiers de Sage).

Lots and serial numbers of items tracked by lot/serial. Actions: prelever: assigns lots to the lines of a preparation_livraison still waiting for them (Sage process PreleverLot; Sage's automatic proposal, or lots [{article, lot, quantite}]), each line is split into one line per lot (scopes stock and documents); update: changes the peremption or fabrication date of an existing lot (article, lot, depot?, fields); suivant: next free lot/serial numbers after 'lot', computed by Sage (article, lot, count?). Stock per lot is read with sage_search entity lot. Scope stock.

| Argument | Type | Obligatoire | Description |
|---|---|---|---|
| `action` | texte | oui | prelever, update or suivant. |
| `piece` | texte | non | prelever: DO_Piece of the preparation_livraison. |
| `article` | texte | non | update, suivant: item reference AR_Ref. |
| `lot` | texte | non | update: the lot / serial number; suivant: the number to start from (e.g. LOT0001). |
| `depot` | texte | non | Warehouse name (update, suivant). Default: main warehouse. |
| `lots` | JSON | non | prelever: lots to use instead of Sage's proposal [{article, lot, quantite}]. |
| `fields` | JSON | non | update: {peremption?: YYYY-MM-DD, fabrication?: YYYY-MM-DD}. |
| `count` | entier | non (`1`) | suivant: how many numbers (1 to 50, default 1). |
| `validate_only` | vrai/faux | non (`false`) | true = let Sage check without saving. |
| `dossier` | texte | non | Dossier id. Optional when the key has a single dossier. |

## 8. Odoo

- **Compte** : un compte Odoo connecté dans Maillon (adresse, base, identifiant, clé d'API), désigné par son nom dans `connection`. Odoo 18 (JSON-RPC, ou XML-RPC quand son serveur web ferme JSON-RPC), 19 et plus, Odoo Online (JSON-2) : Maillon choisit seul le protocole.
- **N'utilisez que les actions `odoo_*` ci-dessous.** Les autres actions Odoo du catalogue restent pour d'anciens flux.
- **Domaine** : une liste de conditions `[champ, opérateur, valeur]`, combinées par ET ; `"|"` (OU), `"&"` (ET) et `"!"` (NON) se placent avant leurs deux (ou une) conditions, en notation préfixée : `[["customer_rank", ">", 0], "|", ["email", "!=", false], ["phone", "!=", false]]`. Opérateurs courants : `=`, `!=`, `>`, `>=`, `<`, `<=`, `in`, `not in`, `like`, `ilike`, `=like`, `=ilike`, `child_of`. Un champ lié s'atteint par un point : `["partner_id.ref", "=", "C001"]`.
- **Valeurs lues par `odoo_search`** : un champ vide vaut `false` (utilisez le filtre `text`) ; un champ many2one vaut `[id, "nom"]` (`{{x.partner_id.0}}` pour l'identifiant, `{{x.partner_id.1}}` pour le nom) ; un champ one2many ou many2many, une liste d'identifiants ; une date `AAAA-MM-JJ`, une date et heure `AAAA-MM-JJ HH:MM:SS` en UTC.
- **Valeurs lues par `odoo_read`** : la sortie est l'enregistrement lui-même (pas une liste). Un many2one lu avec `fields` dans `specification` devient un objet `{ "id", … }` (`{{commande.partner_id.ref}}`), un one2many lu ainsi une liste d'objets (`{{commande.order_line}}`, chaque ligne avec ses champs demandés).
- **Lignes de commande et de facture** : `display_type` vaut `false` pour une ligne d'article, `line_section` pour un titre de section, `line_note` pour une note. Pour ne garder que les articles : `{{x.item.display_type | not}}` égal à `true`.
- **Valeurs écrites** : un many2one prend un identifiant ; une liste de lignes ou d'étiquettes prend des commandes Odoo : `[[0, 0, { … }]]` crée une ligne, `[[4, id]]` ajoute un lien, `[[6, 0, [id, …]]]` remplace la liste.
- **Versions** : la plupart des champs sont les mêmes en 18, 19 et 20 ; quelques-uns changent (SIRET français : `siret` en 18, `company_registry` en 19, `l10n_fr_siret` en 20). Demandez la version si vous utilisez un champ de localisation.
- **Modèles courants** : `res.partner` (contacts et sociétés : `name`, `ref`, `is_company`, `parent_id`, `customer_rank`, `supplier_rank`, `email`, `phone`, `street`, `street2`, `zip`, `city`, `country_id`, `vat`), `product.product` et `product.template` (`default_code`, `name`, `list_price`, `standard_price`, `barcode`), `sale.order` et `sale.order.line`, `purchase.order`, `account.move` (factures : `move_type` = `out_invoice`, `in_invoice`…) et `account.move.line`, `stock.picking`, `crm.lead`.
- **Point de reprise** sur Odoo (enregistrements modifiés depuis la dernière exécution), dans `odoo_search` :

```json
"domain": [["customer_rank", ">", 0], "|", ["write_date", ">", "{{trigger.cursor.at | default:\"1970-01-01 00:00:00\"}}"], "&", ["write_date", "=", "{{trigger.cursor.at | default:\"1970-01-01 00:00:00\"}}"], ["id", ">", "{{trigger.cursor.id | default:0}}"]],
"fields": ["id", "name", "ref", "write_date"],
"order": "write_date asc, id asc",
"limit": 50
```

avec, dans la boucle, `"cursor": { "at": "{{chaque.item.write_date}}", "id": "{{chaque.item.id}}" }`. Sur Odoo Online, restez à 50 enregistrements par exécution.

- `odoo_search` lit 80 enregistrements par défaut, 1000 au plus. `odoo_call` refuse les méthodes `unlink`, `sudo`, `with_user`, `with_context`, `with_env`, `browse`, `copy`, `load`, `web_save`, `write`, `create` : utilisez `odoo_create` et `odoo_update`.

### Actions Odoo de Maillon (`connector` : `odoo`)

#### `odoo_search` : Rechercher des enregistrements

Lit les enregistrements d'un modèle qui correspondent à un domaine (search_read). Renvoie une liste. Compte connecté : oui.

| Réglage (`values`) | Nom dans l'éditeur | Type | Obligatoire | Par défaut | Description |
|---|---|---|---|---|---|
| `model` | Modèle | texte | oui |  | Nom technique du modèle Odoo, par exemple res.partner (contacts), product.product (articles), sale.order (commandes). |
| `domain` | Domaine | JSON | non | `[]` | Critères au format Odoo, par exemple [["customer_rank", ">", 0], ["write_date", ">=", "2026-10-01 00:00:00"]]. Vide : tous les enregistrements. |
| `fields` | Champs | liste | non |  | Champs à lire, par exemple name, ref, email. Vide : tous les champs (déconseillé, plus lent). |
| `order` | Tri | texte | non |  | Par exemple write_date asc ou name. |
| `limit` | Nombre maximum | nombre | non | `80` | Au plus 1000. |
| `offset` | À partir de | nombre | non |  | Nombre d'enregistrements à sauter. |
| `context` | Contexte | JSON | non |  | Contexte Odoo facultatif, par exemple {"lang": "fr_FR"} ou {"active_test": false} pour inclure les archivés. |

#### `odoo_read` : Lire un enregistrement avec ses lignes

Lit un enregistrement et, en une fois, ses lignes et les champs des enregistrements liés (web_read), par exemple une commande avec la référence de chaque article. Compte connecté : oui.

| Réglage (`values`) | Nom dans l'éditeur | Type | Obligatoire | Par défaut | Description |
|---|---|---|---|---|---|
| `model` | Modèle | texte | oui |  | Nom technique du modèle Odoo, par exemple res.partner (contacts), product.product (articles), sale.order (commandes). |
| `id` | Identifiant | nombre | oui |  | Identifiant Odoo de l'enregistrement. |
| `specification` | Champs et lignes | JSON | oui |  | Champs à lire au format web_read, par exemple {"name": {}, "partner_id": {"fields": {"ref": {}}}, "order_line": {"fields": {"product_id": {"fields": {"default_code": {}}}, "product_uom_qty": {}}}}. |
| `context` | Contexte | JSON | non |  | Contexte Odoo facultatif, par exemple {"lang": "fr_FR"} ou {"active_test": false} pour inclure les archivés. |

#### `odoo_create` : Créer un enregistrement

Crée un enregistrement et renvoie son identifiant. Compte connecté : oui. Sortie : `id`.

| Réglage (`values`) | Nom dans l'éditeur | Type | Obligatoire | Par défaut | Description |
|---|---|---|---|---|---|
| `model` | Modèle | texte | oui |  | Nom technique du modèle Odoo, par exemple res.partner (contacts), product.product (articles), sale.order (commandes). |
| `values` | Valeurs | JSON | oui |  | Champs du nouvel enregistrement, par exemple {"name": "Garage des Alpes", "ref": "C001", "customer_rank": 1}. |
| `context` | Contexte | JSON | non |  | Contexte Odoo facultatif, par exemple {"lang": "fr_FR"} ou {"active_test": false} pour inclure les archivés. |

#### `odoo_update` : Modifier des enregistrements

Écrit les mêmes valeurs sur un ou plusieurs enregistrements. Compte connecté : oui. Sortie : `ids`, `updated`.

| Réglage (`values`) | Nom dans l'éditeur | Type | Obligatoire | Par défaut | Description |
|---|---|---|---|---|---|
| `model` | Modèle | texte | oui |  | Nom technique du modèle Odoo, par exemple res.partner (contacts), product.product (articles), sale.order (commandes). |
| `ids` | Identifiants | texte | oui |  | Un identifiant, une liste (12, 15) ou une valeur d'une étape précédente. |
| `values` | Valeurs | JSON | oui |  | Champs à modifier, par exemple {"ref": "C001"}. |
| `context` | Contexte | JSON | non |  | Contexte Odoo facultatif, par exemple {"lang": "fr_FR"} ou {"active_test": false} pour inclure les archivés. |

#### `odoo_upsert` : Créer ou mettre à jour

Cherche l'enregistrement décrit par le domaine de correspondance : le met à jour s'il existe, le crée sinon. Compte connecté : oui. Sortie : `id`, `created`.

| Réglage (`values`) | Nom dans l'éditeur | Type | Obligatoire | Par défaut | Description |
|---|---|---|---|---|---|
| `model` | Modèle | texte | oui |  | Nom technique du modèle Odoo, par exemple res.partner (contacts), product.product (articles), sale.order (commandes). |
| `match` | Correspondance | JSON | oui |  | Domaine qui identifie un seul enregistrement, par exemple [["ref", "=", "C001"]]. |
| `values` | Valeurs | JSON | oui |  | Champs écrits dans les deux cas. |
| `create_values` | Valeurs à la création | JSON | non |  | Champs ajoutés seulement quand l'enregistrement est créé, par exemple {"customer_rank": 1}. |
| `context` | Contexte | JSON | non |  | Contexte Odoo facultatif, par exemple {"lang": "fr_FR"} ou {"active_test": false} pour inclure les archivés. |

#### `odoo_sync` : Synchroniser une liste

Recopie une liste dans un modèle : chaque élément est retrouvé par sa clé, créé s'il manque, mis à jour s'il diffère ; ce qui n'est plus dans la liste peut être archivé ou supprimé, dans le périmètre donné seulement. Les liens se donnent par une valeur ({"$ref": …}), sans identifiant. Compte connecté : oui. Sortie : `total`, `created`, `updated`, `unchanged`, `removed`, `skipped`, `problems`.

| Réglage (`values`) | Nom dans l'éditeur | Type | Obligatoire | Par défaut | Description |
|---|---|---|---|---|---|
| `model` | Modèle | texte | oui |  | Nom technique du modèle Odoo, par exemple res.partner (contacts), product.product (articles), sale.order (commandes). |
| `records` | Éléments | JSON | oui |  | Liste des enregistrements voulus, en général une liste mise en forme d'une étape précédente. Un lien : {"$ref": {"model": "product.template", "field": "default_code", "value": "BAAR01"}} (domain, optional facultatifs ; "keep": true : sans valeur, introuvable ou ambigu, le champ n'est pas touché ; "many": true pour une liste de valeurs d'un champ many2many, liens ajoutés et retirés, ceux des autres sociétés gardés ; sans valeur, le champ n'est pas touché) ; plusieurs valeurs possibles : {"$any": ["4457120", "445712"]}. Les champs de "$create" ne sont écrits qu'à la création. |
| `key` | Clé | liste | oui |  | Champs qui identifient un enregistrement, par exemple default_code, ou pricelist_id et product_tmpl_id et min_quantity. |
| `adopt` | Reprendre par | liste | non |  | Facultatif. Champs qui retrouvent un enregistrement créé avant que la clé existe (les champs de la clé absents d'ici y sont vides), par exemple parent_id et name : il est mis à jour et reçoit sa clé au lieu d'être créé une seconde fois. |
| `scope` | Périmètre | JSON | non | `[]` | Domaine des enregistrements que cette étape gère, par exemple [["pricelist_id.name", "=like", "Sage %"]]. Obligatoire pour archiver ou supprimer. |
| `create` | Créer ce qui manque | vrai/faux | non | `true` | Décoché : les éléments absents d'Odoo sont ignorés (mise à jour seulement). |
| `update` | Mettre à jour | vrai/faux | non | `true` | Décoché : un enregistrement déjà présent n'est pas modifié. |
| `remove` | Ce qui n'est plus dans la liste | choix | non | `"none"` | Dans le périmètre seulement : laisser, archiver ou supprimer. Valeurs : `"none"` (Laisser), `"archive"` (Archiver), `"delete"` (Supprimer). |
| `context` | Contexte | JSON | non |  | Contexte Odoo facultatif, par exemple {"lang": "fr_FR"} ou {"active_test": false} pour inclure les archivés. |

#### `odoo_call` : Appeler une méthode

Appelle une méthode d'Odoo, par exemple action_confirm (confirmer un devis) ou action_post (valider une facture). Compte connecté : oui.

| Réglage (`values`) | Nom dans l'éditeur | Type | Obligatoire | Par défaut | Description |
|---|---|---|---|---|---|
| `model` | Modèle | texte | oui |  | Nom technique du modèle Odoo, par exemple res.partner (contacts), product.product (articles), sale.order (commandes). |
| `method` | Méthode | texte | oui |  | Nom technique de la méthode, par exemple action_confirm. En mode développeur, survolez un bouton d'Odoo pour le voir. |
| `ids` | Identifiants | texte | non |  | Enregistrements sur lesquels appeler la méthode. Vide : méthode du modèle. |
| `kwargs` | Paramètres | JSON | non |  | Paramètres nommés de la méthode, par exemple {"date": "2026-10-17"}. |
| `context` | Contexte | JSON | non |  | Contexte Odoo facultatif, par exemple {"lang": "fr_FR"} ou {"active_test": false} pour inclure les archivés. |

#### `odoo_version` : Version d'Odoo

Version de l'Odoo connecté et protocole utilisé (JSON-2 à partir d'Odoo 19, JSON-RPC pour Odoo 18, XML-RPC quand le serveur web d'Odoo ferme JSON-RPC). Compte connecté : oui. Sortie : `major`, `minor`, `serie`, `protocol`.

Aucun réglage.

## 9. Autres connecteurs

Tous les connecteurs des étapes `action`. Hors Odoo, ils ne sont disponibles que dans la console Maillon, pas sur un serveur Sage en mode « Uniquement local ». Les actions et réglages exacts de chacun sont dans un second document, « flux-maillon-connecteurs.md », que la personne peut vous donner si le flux en utilise un.

| `connector` | Logiciel | Actions | Sur un serveur Sage (mode local) |
|---|---|---|---|
| `gmail` | Gmail | 36 | non |
| `microsoft-outlook` | Microsoft Outlook | 56 | non |
| `smtp` | SMTP | 1 | non |
| `slack` | Slack | 69 | non |
| `microsoft-teams` | Microsoft Teams | 14 | non |
| `google-sheets` | Google Sheets | 61 | non |
| `microsoft-excel-365` | Microsoft Excel 365 | 33 | non |
| `google-drive` | Google Drive | 53 | non |
| `odoo` | Odoo | 8 | oui |
| `http` | HTTP | 2 | non |
| `pennylane` | Pennylane | 4 | non |
| `qonto` | Qonto | 3 | non |

**Requête HTTP** (`connector` : `http`, `action` : `send_request`, sans compte connecté) : `values` = `method` (`GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `HEAD`), `url`, `headers` et `queryParams` (objets), `body_type` (`none`, `json`, `raw`, `form_data`) et `body` : `{ "data": { … } }` pour `json`, `{ "data": "texte" }` pour `raw`. Sa sortie est `{ "status", "headers", "body" }`. Seules les adresses publiques d'Internet sont appelées. N'écrivez jamais d'identifiants dans `authType` et `authFields` : la personne les saisira dans l'éditeur.

## 10. Flux d'un serveur Sage en mode « Uniquement local »

Un dossier Sage en mode « Uniquement local » ne passe plus par Maillon : ses flux tournent sur le serveur Sage lui-même, dans l'éditeur de la page de configuration de la passerelle, qui importe les mêmes fichiers. Dans ce cas :

- seuls les outils Sage 100 (étapes `sage`) et les actions Odoo (`connector` : `odoo`) sont disponibles ;
- un Odoo sur Internet (Odoo Online, hébergé) se lit et s'écrit, mais ne peut pas appeler le serveur : la détection en temps réel (`odooRealtime`) y est refusée, et la planification relève les changements ;
- les webhooks et points d'entrée API arrivent sur une adresse du réseau interne ;
- les étapes ne joignent que les destinations listées sur ce serveur.

## 11. Ce que Maillon vérifie à l'import

| Niveau | Cas |
|---|---|
| Refusé | Fichier illisible ou sans `format`, `version` et `flows` ; structure invalide (aucun ou plusieurs déclencheurs, lien mal formé, circuit, boucle mal fermée, filtre inconnu…) ; connecteur ou action inconnus ; référence à une étape qui n'existe pas ; dossier Sage relié à aucune société d'un compte Odoo réglé sur **Plusieurs sociétés**. |
| À vérifier | Référence à une étape qui ne s'exécute pas avant ; outil Sage absent de la passerelle du client ; compte connecté à choisir ; dossier Sage à choisir. |
| Ensuite | Nouvelle adresse de webhook ou de point d'entrée (et sa clé à créer) ; détection en temps réel à installer dans Odoo. |

Les flux importés sont désactivés. La personne les teste dans l'éditeur (bouton **Tester**), puis les active.

## 12. Recettes

Maillon ne calcule ni dates ni compteurs : pas d'addition, pas de « hier » ni de « dans 30 jours ». Voici comment faire avec ce qui existe.

- **Ne traiter que ce qui a changé (« depuis hier », « depuis la dernière fois »).** Un point de reprise sur la date de modification et l'identifiant (sections 3.5, 7.2 et 8), et une planification. La première exécution reprend tout l'historique par paquets.
- **Donner un numéro à une fiche créée.** L'identifiant de la fiche d'origine : `C{{chaque.item.id | padstart:6}}` (Odoo). Sans identifiant, la date et l'heure de réception d'un webhook ou d'un point d'entrée : `WEB{{trigger.receivedAt | digits | truncate:14}}` (17 caractères, un numéro par seconde). Évitez de compter les fiches existantes : deux exécutions proches donneraient le même numéro.
- **Ne jamais créer deux fois la même fiche.** Cherchez d'abord par une clé (`sage_search`, `odoo_search`), puis `exists` : mise à jour ou création. Côté Odoo, `odoo_upsert` le fait en une étape.
- **Marquer ce qui a été envoyé.** Écrivez la clé de l'autre logiciel dans la fiche d'origine juste après la création (numéro Sage dans `ref` ou `client_order_ref` d'Odoo), et ne lisez que les fiches sans cette clé. Si l'écriture du marqueur échoue, la fiche sera renvoyée : rapprochez aussi par une clé à la création.
- **Envoyer un résumé (e-mail, message).** Une référence à une liste dans un texte donne une ligne par élément : `"{{retards.rows}}"`. Pour choisir les colonnes, mettez d'abord la liste en forme (`map`, avec `fields` en texte : `"{{l.item.CT_Num}} : {{l.item.solde}} €"`).
- **Garder une partie d'une liste.** `map` avec `where`, ou une condition dans le corps d'une boucle.
- **Un traitement selon le type de fiche.** Une condition, ou un filtre dans la recherche (plus rapide : moins d'éléments lus).
- **S'assurer que la passerelle sait faire.** Les ajouts de la passerelle Sage 1.3.25 (`lines` de `sage_document_transform`, emplacements des lignes, entités `stock_emplacement` et `document_ligne_emplacement`) se vérifient avant d'écrire : une étape `sage_context`, puis une condition `{{contexte.features}}` `contains` `transform_lines` (ou `line_locations`, `stock_locations`). Une passerelle plus ancienne ne connaît pas ces clés.
- **Une pièce ouverte dans Sage.** Une écriture sur une pièce ouverte dans la Gestion commerciale échoue avec `locked: true` dans son message : c'est une erreur passagère, que Maillon relance dans une boucle à point de reprise, ou que l'appelant renvoie plus tard.

## 13. Exemples complets

### 13.1 Factures en retard envoyées chaque lundi par e-mail

```json
{
  "format": "maillon-flux",
  "version": 1,
  "flows": [
    {
      "name": "Factures clients en retard",
      "graph": {
        "nodes": [
          { "id": "depart", "type": "trigger", "data": { "title": "Chaque lundi à 8 h", "mode": "schedule", "schedule": { "frequency": "weekly", "time": "08:00", "weekday": 1 } } },
          { "id": "retards", "type": "sage", "data": { "title": "Échéances clients en retard", "tool": "sage_finance", "arguments": { "dossier": "BIJOU", "action": "open_items", "tiers_type": "client", "overdue_only": true, "limit": 200 } } },
          { "id": "a_relancer", "type": "condition", "data": { "title": "Des retards ?", "left": "{{retards.count}}", "operator": "greater_than", "right": "0" } },
          {
            "id": "mail",
            "type": "action",
            "data": {
              "title": "Envoyer la liste à la comptabilité",
              "connector": "smtp",
              "action": "send-email",
              "connection": "Messagerie Bijouterie",
              "values": {
                "from": "compta@bijouterie-martin.fr",
                "to": ["direction@bijouterie-martin.fr"],
                "subject": "{{retards.count}} échéances clients en retard",
                "body_type": "plain_text",
                "body": "Échéances clients en retard au {{trigger.scheduledAt | date}} :\n\n{{retards.rows}}"
              }
            }
          }
        ],
        "edges": [
          { "source": "depart", "target": "retards" },
          { "source": "retards", "target": "a_relancer" },
          { "source": "a_relancer", "target": "mail", "sourceHandle": "yes" }
        ]
      }
    }
  ]
}
```

### 13.2 Clients modifiés dans Odoo copiés dans Sage

```json
{
  "format": "maillon-flux",
  "version": 1,
  "flows": [
    {
      "name": "Clients Odoo vers Sage",
      "graph": {
        "nodes": [
          { "id": "depart", "type": "trigger", "data": { "title": "Toutes les heures", "mode": "schedule", "schedule": { "frequency": "hourly", "minute": 5 } } },
          {
            "id": "clients",
            "type": "action",
            "data": {
              "title": "Clients modifiés dans Odoo",
              "connector": "odoo",
              "action": "odoo_search",
              "connection": "Odoo production",
              "values": {
                "model": "res.partner",
                "domain": [["customer_rank", ">", 0], ["parent_id", "=", false], "|", ["write_date", ">", "{{trigger.cursor.at | default:\"1970-01-01 00:00:00\"}}"], "&", ["write_date", "=", "{{trigger.cursor.at | default:\"1970-01-01 00:00:00\"}}"], ["id", ">", "{{trigger.cursor.id | default:0}}"]],
                "fields": ["id", "name", "ref", "email", "phone", "street", "zip", "city", "write_date"],
                "order": "write_date asc, id asc",
                "limit": 50
              }
            }
          },
          { "id": "chaque", "type": "loop", "data": { "title": "Pour chaque client", "items": "{{clients}}", "max": 50, "onError": "continue", "cursor": { "at": "{{chaque.item.write_date}}", "id": "{{chaque.item.id}}" } } },
          { "id": "sans_ref", "type": "condition", "data": { "title": "Sans numéro Sage ?", "left": "{{chaque.item.ref | text}}", "operator": "is_empty" } },
          {
            "id": "creer",
            "type": "sage",
            "data": {
              "title": "Créer le client dans Sage",
              "tool": "sage_tiers",
              "arguments": {
                "dossier": "BIJOU",
                "action": "create",
                "type": "client",
                "numero": "C{{chaque.item.id | padstart:6}}",
                "fields": {
                  "intitule": "{{chaque.item.name | text | truncate:35}}",
                  "adresse": "{{chaque.item.street | text | truncate:35}}",
                  "code_postal": "{{chaque.item.zip | text | truncate:9}}",
                  "ville": "{{chaque.item.city | text | truncate:35}}",
                  "telephone": "{{chaque.item.phone | text | truncate:21}}",
                  "email": "{{chaque.item.email | text | truncate:69}}"
                }
              }
            }
          },
          { "id": "ecrire_ref", "type": "action", "data": { "title": "Écrire le numéro Sage dans Odoo", "connector": "odoo", "action": "odoo_update", "connection": "Odoo production", "values": { "model": "res.partner", "ids": "{{chaque.item.id}}", "values": { "ref": "{{creer.record.CT_Num}}" } } } },
          { "id": "chercher", "type": "sage", "data": { "title": "Chercher le client dans Sage", "tool": "sage_search", "arguments": { "dossier": "BIJOU", "entity": "client", "filters": [["CT_Num", "=", "{{chaque.item.ref | text | trim | upper}}"]], "fields": ["CT_Num"], "limit": 1 } } },
          { "id": "existe", "type": "condition", "data": { "title": "Déjà dans Sage ?", "left": "{{chercher.records}}", "operator": "exists" } },
          {
            "id": "mettre_a_jour",
            "type": "sage",
            "data": {
              "title": "Mettre à jour le client",
              "tool": "sage_tiers",
              "arguments": {
                "dossier": "BIJOU",
                "action": "update",
                "numero": "{{chaque.item.ref | text | trim | upper | truncate:17}}",
                "fields": { "intitule": "{{chaque.item.name | text | truncate:35}}", "email": "{{chaque.item.email | text | truncate:69}}", "telephone": "{{chaque.item.phone | text | truncate:21}}" }
              }
            }
          },
          {
            "id": "creer_avec_ref",
            "type": "sage",
            "data": {
              "title": "Créer le client avec sa référence Odoo",
              "tool": "sage_tiers",
              "arguments": {
                "dossier": "BIJOU",
                "action": "create",
                "type": "client",
                "numero": "{{chaque.item.ref | text | trim | upper | truncate:17}}",
                "fields": { "intitule": "{{chaque.item.name | text | truncate:35}}", "email": "{{chaque.item.email | text | truncate:69}}", "telephone": "{{chaque.item.phone | text | truncate:21}}" }
              }
            }
          }
        ],
        "edges": [
          { "source": "depart", "target": "clients" },
          { "source": "clients", "target": "chaque" },
          { "source": "chaque", "target": "sans_ref", "sourceHandle": "each" },
          { "source": "sans_ref", "target": "creer", "sourceHandle": "yes" },
          { "source": "creer", "target": "ecrire_ref" },
          { "source": "sans_ref", "target": "chercher", "sourceHandle": "no" },
          { "source": "chercher", "target": "existe" },
          { "source": "existe", "target": "mettre_a_jour", "sourceHandle": "yes" },
          { "source": "existe", "target": "creer_avec_ref", "sourceHandle": "no" }
        ]
      }
    }
  ]
}
```

### 13.3 Commande reçue par API, créée dans Sage

```json
{
  "format": "maillon-flux",
  "version": 1,
  "flows": [
    {
      "name": "Commandes du site vers Sage",
      "graph": {
        "nodes": [
          {
            "id": "depart",
            "type": "trigger",
            "data": {
              "title": "Commande reçue du site",
              "mode": "endpoint",
              "endpoint": {
                "format": "json",
                "respond": "result",
                "fields": [
                  { "key": "client", "path": "commande.client", "type": "text", "required": true },
                  { "key": "reference", "path": "commande.reference", "type": "text" },
                  { "key": "lignes", "path": "commande.lignes", "type": "list", "required": true }
                ]
              }
            }
          },
          { "id": "lignes", "type": "map", "data": { "title": "Lignes pour Sage", "items": "{{trigger.data.lignes}}", "fields": { "article": "{{lignes.item.ref}}", "quantite": "{{lignes.item.quantite}}", "prix_unitaire": "{{lignes.item.prix}}" } } },
          {
            "id": "commande",
            "type": "sage",
            "data": {
              "title": "Créer la commande dans Sage",
              "tool": "sage_document",
              "arguments": { "dossier": "BIJOU", "action": "create", "type": "commande", "tiers": "{{trigger.data.client}}", "lines": "{{lignes}}", "fields": { "reference": "{{trigger.data.reference | truncate:17}}" } }
            }
          }
        ],
        "edges": [
          { "source": "depart", "target": "lignes" },
          { "source": "lignes", "target": "commande" }
        ]
      }
    }
  ]
}
```

Le site appelle l'adresse du flux avec `{ "commande": { "client": "CARAT", "reference": "WEB-1042", "lignes": [{ "ref": "BAAR01", "quantite": 2, "prix": 45 }] } }` et reçoit la commande créée (`records.0.header.DO_Piece`).

## 14. Avant de répondre, vérifiez

- [ ] `format` vaut `"maillon-flux"`, `version` vaut `1`, chaque flux a un `name` et un `graph`.
- [ ] Chaque flux a exactement un déclencheur, et chaque étape un `id` unique.
- [ ] Chaque lien depuis une condition a `sourceHandle` `yes` ou `no`, depuis une boucle `each` ou `done`, et aucun autre lien n'en a (une branche ou la sortie `done` peut rester sans lien).
- [ ] Aucun circuit ; aucune étape du corps d'une boucle ne mène hors du corps ; pas de boucle dans une boucle.
- [ ] Chaque référence commence par `trigger` ou par l'identifiant d'une étape exécutée avant (ou par la boucle ou la mise en forme elle-même pour son `item`).
- [ ] Chaque étape `sage` a un `tool` de la section 7.6 et `dossier` dans ses `arguments`.
- [ ] Chaque étape `action` a un `connector` et une `action` existants, et le nom du compte dans `connection` quand il en faut un.
- [ ] Les valeurs écrites dans Sage respectent les longueurs (`truncate`), et les champs Odoo vides passent par `text`.
- [ ] Aucun mot de passe, clé ni jeton dans le fichier.
- [ ] Le JSON est valide : guillemets doubles, `\"` dans les textes, aucune virgule en trop, aucun commentaire.
