Définition de table Model-Driven
Identifiant du schéma : model-driven-entity@1
Schéma lisible par machine : model-driven-entity.schema.json
La représentation canonique, en texte brut, d’une table Model-Driven Apps. C’est la référence selon laquelle une table est validée — la même définition que produit le concepteur, que soumet un appel d’API, que génère un assistant IA et que transporte une importation de solution. La validation s’applique de manière identique quelle que soit l’origine : un fichier écrit à la main ou généré, conforme à ce schéma, peut être importé en toute sécurité.
Structure
Une définition est un objet JSON comportant ces membres :
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
logicalName | string | oui | Préfixé par l’éditeur, en snake-case minuscule (p. ex. par_invoice). |
displayName | string | oui | Libellé au singulier (p. ex. Invoice). |
displayCollectionName | string | oui | Libellé au pluriel (p. ex. Invoices). |
icon | string | non | Clé d’icône issue du jeu d’icônes de la plateforme, affichée à côté de la table dans la navigation d’une application (§4.4). À omettre pour le glyphe par défaut. |
publisherPrefix | string | oui | Préfixe court en minuscules (p. ex. par). |
primaryField | string | oui | Nom logique du champ principal (nom) ; doit figurer dans fields. |
primaryImage | string | non | Nom logique de l’image de l’enregistrement (§6.16) — l’image affichée à côté d’un enregistrement dans les grilles et les recherches. Doit désigner un champ de type Image dans fields. À omettre pour une table sans image. |
secondaryField | string | null | non | Nom logique du champ affiché comme texte secondaire de l’enregistrement — la seconde ligne, plus petite, sous son nom partout où les enregistrements sont listés sans grille, comme dans un panneau de recherche (§6.13.5). Doit désigner un champ de fields qui n’est ni le champ principal ni une colonne File/Image. |
hierarchyParentField | string | null | non | Nom logique de la recherche qui fait de cette table une hiérarchie — le champ contenant le parent de chaque enregistrement, pointant vers cette même table (la catégorie parente d’une catégorie, l’unité parente d’une unité). Doit désigner un champ de fields de type Lookup dont la targetEntity est cette table. Il n’accorde rien et n’impose rien : la table s’affiche toujours sous forme de grille tant qu’aucune vue n’imbrique ses enregistrements. Il sert à ce que le fait d’être une hiérarchie soit une propriété des données plutôt que d’une seule vue — le niveau supérieur d’une vue arborescente utilise par défaut ce champ comme lien enfant. |
personField | string | null | non |
constraints | array | non | Des règles appliquées par la base de données, et non par l’application (voir ci-dessous) — une colonne unique, ou une période qui ne peut pas en chevaucher une autre dans la même portée. |
fields | array | oui | Une ou plusieurs définitions de champ (voir ci-dessous). |
relationships | array | non | Des relations un-à-plusieurs reposant sur des champs de recherche. |
isAuditEnabled | boolean | non | Active l’audit des modifications pour la table (section 6.5). Vaut false par défaut. |
states | array | non | Les états dans lesquels peut se trouver un enregistrement de cette table (§6.14) — son cycle de vie. Voir ci-dessous. Absent dans un document écrit avant l’existence des états, ce qui permet à un importateur de le distinguer d’une table qui n’en a aucun. |
reasons | array | non | Les raisons, chacune appartenant à exactement un état. Voir ci-dessous. |
statusValueBase | integer | non | Le plancher de la plage à partir de laquelle sont numérotées les options d’état et de raison ajoutées à cette table — la base des valeurs d’option de l’éditeur propriétaire (§6.14). Les valeurs Actif/Inactif créées d’office se situent hors de cette plage, à 0 et 1 : chaque table les possède et elles signifient partout la même chose, elles n’ont donc besoin d’aucun espace de noms. |
nextStatusValue | integer | non | Le niveau le plus haut atteint par les statuts de la table — la prochaine valeur qu’elle attribuera. Stocké plutôt que déduit des options, pour la même raison que pour un groupe d’options : le numéro d’une option supprimée est retiré, et le recalculer l’attribuerait à la prochaine nouvelle option alors que des enregistrements peuvent encore le contenir. |
Champ
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
logicalName | string | oui | En snake-case minuscule. |
displayName | string | oui | |
dataType | string | oui | L’un de Text, MultilineText, Integer, Decimal, Boolean, DateTime, Lookup, PolymorphicLookup, File, Image, OptionSet, State, Reason. |
isRequired | boolean | non | |
isPrimary | boolean | non | Vrai pour l’unique champ principal. |
maxLength | integer | non | Pour Text (1–4000). |
precision, scale | integer | non | Pour Decimal (scale ≤ precision ≤ 38). |
targetEntity | string | conditionnel | Obligatoire lorsque dataType vaut Lookup ; le nom logique de la table référencée. |
targetEntities | string[] | conditionnel | Obligatoire lorsque dataType vaut PolymorphicLookup ; les tables vers lesquelles une valeur peut pointer, dans l’ordre du sélecteur. Au moins deux, sans répétition. |
isAuditExcluded | boolean | non | Exclut le champ de l’audit des modifications lorsque la table est auditée (section 6.5). Vaut false par défaut. |
Les champs
PolymorphicLookupsont provisionnés sous forme de deux colonnes : la colonneuuidpropre au champ, qui contient l’id de l’enregistrement, et une colonne sœur<column>_typede typetext, qui contient le nom logique de la table cible. Aucune ne porte de clé étrangère — une même colonne ne peut pas référencer plusieurs tables — l’intégrité relève donc du chemin d’écriture : il vérifie que la cible existe, et une cible supprimée ensuite se lit comme une référence non résolue.Sur le réseau, les deux moitiés voyagent sous la forme d’un seul scalaire,
<table>:<id>, de sorte que lesvaluesd’un enregistrement restent une table plate associant le nom logique du champ à un scalaire, et que la colonne discriminante n’apparaisse jamais comme un champ. C’est la convention existante de la plateforme pour une référence autodescriptive (recordfile:<id>,webresource:par_/app.js).C’est le nom logique de la table qui est stocké, et non un nombre, car un discriminant numérique serait un index dans
targetEntities— réordonner les cibles dans le concepteur réorienterait alors silencieusement chaque ligne stockée vers une autre table. Les colonnes de propriétaire d’un enregistrement utilisent au contraire une énumération numérique, ce qui est cohérent et non contradictoire : leurs cinq valeurs sont des concepts de la plateforme fixés dans le code, et non une liste qu’un créateur modifie.Chaque entrée de
targetEntitiesreçoit sa propre relation sous-jacente, ce qui permet au formulaire de la table référencée de proposer un onglet Associés pour ces enregistrements.
Les champs
File/Imagesont provisionnés sous forme d’une colonne texte contenant une référence qualifiée par un schéma —recordfile:<id>pour un fichier conservé dans le schéma propre de l’environnement, ouobjectstore:<key>pour un fichier assez volumineux pour avoir débordé vers le stockage d’objets. Les octets sont téléversés vers le point de terminaison de fichier propre au champ plutôt qu’écrits via l’API d’enregistrement, et sont servis sous l’autorisation propre à l’enregistrement. Lequel des deux magasins contient un fichier est déterminé par sa taille, et par rien de ce qu’un créateur définit.
Relation
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
logicalName | string | oui | En snake-case minuscule. |
type | string | oui | OneToMany. |
referencedEntity | string | oui | Le côté « un ». |
referencingEntity | string | oui | Le côté « plusieurs » (qui porte la recherche). |
lookupField | string | oui | Le champ de recherche de la table référençante sur lequel repose la relation. |
État et raison
Un état indique où en est un enregistrement dans son cycle de vie ; une raison précise de quelle variante de cet état il s’agit. Ils voyagent avec la table parce qu’ils font partie de ce que le créateur a conçu.
🔴 Ce ne sont pas un groupe d’options. Active sur une facture et Active sur un contact
ne sont pas le même choix, et les modéliser comme une seule liste partagée ferait qu’en
renommer un renommerait l’autre.
État :
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
value | integer | oui | La valeur stockée, conservée lors d’une importation : les enregistrements de l’environnement source la contiennent déjà, Dev et Test doivent donc s’accorder sur sa signification. |
displayName | string | oui | Le libellé. |
color | string | non | La couleur dans laquelle son badge est dessiné. |
icon | string | non | Le glyphe de son badge. |
defaultReason | integer | non | Laquelle des raisons de cet état reçoit un enregistrement qui y entre. Absent signifie la première, ce qui rend ce réglage facultatif. |
Raison :
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
state | integer | oui | La value de l’état auquel appartient cette raison. Indexée par valeur plutôt que par id de ligne, car une valeur est ce que stocke un enregistrement et ce qui survit à une exportation. |
value | integer | oui | La valeur stockée. |
displayName | string | oui | Le libellé. |
color · icon | string | non | Comme pour un état. |
L’appartenance est tout l’enjeu : Won et Lost sont toutes deux des raisons pour
lesquelles une affaire est Inactive, et les proposer alors qu’elle est Active n’aurait aucun
sens.
Exemple
{
"logicalName": "par_invoice",
"displayName": "Invoice",
"displayCollectionName": "Invoices",
"publisherPrefix": "par",
"primaryField": "par_name",
"fields": [
{ "logicalName": "par_name", "displayName": "Name", "dataType": "Text", "isPrimary": true, "isRequired": true, "maxLength": 200 },
{ "logicalName": "par_total", "displayName": "Total", "dataType": "Decimal", "precision": 18, "scale": 2 },
{ "logicalName": "par_account", "displayName": "Account", "dataType": "Lookup", "targetEntity": "par_account" }
],
"relationships": [
{ "logicalName": "par_account_invoices", "type": "OneToMany", "referencedEntity": "par_account", "referencingEntity": "par_invoice", "lookupField": "par_account" }
]
}
Gestion des versions
Les anciennes versions du schéma sont conservées à côté des nouvelles, afin que les
solutions déjà déployées continuent d’être validées après l’évolution du schéma. Lorsqu’une
modification incompatible est nécessaire, une nouvelle version (model-driven-entity@2) est
publiée plutôt que de modifier celle-ci.