> For the complete documentation index, see [llms.txt](https://docs.thewalletcrew.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.thewalletcrew.io/connectors/fr/pos/cegid/donnees-client-et-fidelite/customise-the-mapping.md).

# Personnaliser le mapping

Personnalisez le mappage des champs Cegid Retail Y2 pour l’installation du Wallet, les langues, les consentements, les préférences et les données client spécifiques à la marque.

Le mapping par défaut couvre les champs d’identité et de contact. Un script mappe d’autres données Cegid, notamment les préférences, la langue, l’état d’installation de Wallet et les identifiants fiscaux. Cette page définit le contrat du script, des recettes pratiques et des modèles de mapping sûrs.

{% hint style="info" %}
Presque toutes les marques exécutant ce connecteur déploient un script de mapping. Il doit être considéré comme faisant partie de l’implémentation initiale plutôt que comme un complément facultatif.
{% endhint %}

### Le contrat

Un script de mapping enregistre deux fonctions. `MapToY2` reçoit le compte Wallet et la structure client Cegid, et s’exécute juste avant l’écriture de l’enregistrement. `MapFromY2` reçoit l’enregistrement Cegid et le compte Wallet, et s’exécute juste après la lecture d’un enregistrement. Les deux s’exécutent **après** le mapping par défaut, donc l’un ou l’autre peut le remplacer.

Les deux fonctions sont requises. N’en enregistrer qu’une seule entraîne l’échec de l’autre lorsqu’elle est appelée.

```javascript
// server/scripts/y2.mapping.js

const mapToY2 = function (account, data) { /* Wallet -> Cegid */ };
const mapFromY2 = function (data, account) { /* Cegid -> Wallet */ };

export default function (context) {
  context.register('extensions.cegid.y2.mapper', {
    MapToY2: mapToY2,
    MapFromY2: mapFromY2
  });
}
```

Le script se modifie dans l’ **onglet Mapping** de la page de configuration Cegid. La structure de l’objet client Cegid qu’il reçoit est documentée dans le [Référence de script](/connectors/fr/pos/cegid/reference-developpeur/scripting-reference.md).

```mermaid
flowchart LR
  subgraph write["Chemin d’écriture"]
    W1["Données du formulaire<br/>compte Wallet"] --> W2["Mapping par défaut"] --> W3["MapToY2"] --> W4["Écrire dans Cegid Retail Y2"]
  end
  subgraph read["Chemin de lecture"]
    R1["Enregistrement Cegid"] --> R2["Mapping par défaut"] --> R3["MapFromY2"] --> R4["Compte Wallet"]
  end
  W4 -->|"Enregistrement relu"| R1
```

{% hint style="warning" %}
Tous les scripts d’un tenant sont chargés ensemble. Une erreur de syntaxe dans n’importe quel fichier empêche le chargement de tous les endpoints de script, pas seulement du fichier contenant l’erreur, donc une erreur dans un script sans rapport désactive aussi le mapping Cegid.
{% endhint %}

#### Écrire avec des données réelles

Pendant qu’un mapping est en cours d’écriture, la réponse brute Cegid est disponible sur le compte sous `debug.y2.customer`, avec des équivalents pour la fidélité, les documents de vente et les opérations de caisse. Cela permet de voir exactement ce que Cegid a renvoyé pour un client donné. Ce sont une aide au développement disponible pour le script, **et non** des champs pouvant être utilisés dans un modèle Carte.

### Champs extensibles de Cegid

Cegid fournit plusieurs conteneurs pour les données spécifiques à la marque, et choisir le bon est la première décision dans tout mapping.

| Conteneur                            | Structure                                                                        | Emplacements                        |
| ------------------------------------ | -------------------------------------------------------------------------------- | ----------------------------------- |
| `UserDefinedData`                    | Un objet avec des propriétés `UserDefinedTable0Value` … `UserDefinedTable9Value` | 10                                  |
| `UserDefinedTexts`                   | Un tableau de `{ Id, Value }`, la valeur est du texte                            | **3**                               |
| `UserDefinedBooleans`                | Un tableau de `{ Id, Value }`, la valeur est vraie ou fausse                     | **3**                               |
| `Dates définies par l’utilisateur`   | Un tableau de `{ Id, Value }`, la valeur est une date                            | **3**                               |
| `Valeurs définies par l’utilisateur` | Un tableau de `{ Id, Value }`, la valeur est un nombre                           | **3**                               |
| `Champs définis par l’utilisateur`   | Un tableau d’entrées typées avec un identifiant numérique                        | Défini selon la configuration Cegid |

{% hint style="danger" %}
**Les identifiants commencent à zéro ; les libellés Cegid commencent à un.** Dans chaque conteneur de tableau, `Id : 0` est le champ qu’un utilisateur Cegid voit étiqueté comme le premier, `Id : 1` le deuxième, et `Id : 2` le troisième. Le même décalage s’applique à `UserDefinedTable0Value`, qui est la première table libre. C’est la cause la plus fréquente d’une valeur arrivant dans le mauvais champ Cegid, et cela mérite un commentaire sur chaque constante.
{% endhint %}

### Recettes

#### État d’installation du Wallet

Enregistrer si un client a installé la Carte, et sur quelle plateforme, est la personnalisation la plus fréquemment mise en œuvre. C’est ce qui permet à une marque de construire dans Cegid des sélections de campagnes autour de l’adoption du Wallet.

Le modèle écrit un code dans une table libre. Deux détails déterminent si les données résultantes sont utilisables. Les états de la plateforme doivent être comparés à la valeur littérale plutôt que testés pour leur véracité, car l'état d'une Carte supprimée est une chaîne non vide et serait sinon interprété comme installé. Et le cas négatif doit être écrit : un mappage qui enregistre l'installation mais n'enregistre jamais son absence produit un segment qui ne fait que croître.

```javascript
// La table libre 3 contient l'état d'installation du Wallet.
// Confirmez l'emplacement avec la marque : le même ordinal pointe vers un
// champ métier différent dans chaque base de données Cegid.
const INSTALL_SLOT = "UserDefinedTable2Value";

const NOT_INSTALLED = "000";
const APPLE_ONLY    = "001";
const GOOGLE_ONLY   = "002";
const BOTH          = "003";

function installCode(account) {
  // Comparer à l'état littéral. "Uninstalled" est une chaîne véridique,
  // ainsi, un test de vérité signale une Carte supprimée comme installée.
  const apple  = compte["neo.Carte.Apple.state"]  === "Installé";
  const google = account["neo.pass.Google.state"] === "Installed";
  if (apple && google) return BOTH;
  if (apple)           return APPLE_ONLY;
  if (google)          return GOOGLE_ONLY;
  return NOT_INSTALLED;   // explicite, pas vide
}

const mapToY2 = function (account, data) {
  data.UserDefinedData = { ...data.UserDefinedData, [INSTALL_SLOT]: installCode(account) };
};

const mapFromY2 = function (data, account) {
  account.installState = data.UserDefinedData?.[INSTALL_SLOT] || NOT_INSTALLED;
};
```

Une valeur explicite `"000"` est préférable à une valeur vide, car un champ vide ne peut pas être distingué d'un champ qui n'a jamais été renseigné, ce qui rend impossible de constituer une sélection de clients qui ont retiré la Carte.

Cette recette dépend du fait que le connecteur réécrive dans Cegid lorsqu'une Carte est installée ou retirée, ce qui est le **Mettre à jour le client lors du changement d'installation de la Carte** paramètre décrit dans [Configuration avancée](/connectors/fr/pos/cegid/installation-et-configuration/advanced-configuration.md).

#### Langue

Cegid comporte un identifiant de langue propriétaire, et il existe **pas de table de codes commune entre les bases de données Cegid**: les codes sont définis par base de données. Un tableau des langues repris d'une autre implémentation n'est qu'une hypothèse, et les codes corrects doivent provenir de la configuration Cegid propre à la marque.

La table inverse devrait être dérivée de la table directe plutôt que rédigée séparément. Des tableaux écrits indépendamment dérivent, et le défaut qui en résulte est subtil : un client enregistré dans une langue se relit dans une autre.

```javascript
// Codes côté Cegid pour CETTE base de données Cegid.
const LANGUAGES = { fr: "FRA", en: "ANG", es: "ESP", de: "ALL" };
const DEFAULT_LANGUAGE = "ANG";

// Dérivé, afin que les deux sens ne puissent pas diverger.
const LANGUAGES_REVERSE = Object.fromEntries(
  Object.entries(LANGUAGES).map(([locale, code]) => [code, locale])
);

const mapToY2 = function (account, data) {
  const locale = (account.neo?.lng || "").toLowerCase().split("-")[0];
  data.LanguageId = LANGUAGES[locale] || DEFAULT_LANGUAGE;
};

const mapFromY2 = function (data, account) {
  const locale = LANGUAGES_REVERSE[data.LanguageId];
  if (locale) account.language = locale;  // inconnu : laisser inchangé
};
```

Normaliser la locale avant la recherche, en la passant en minuscules et en supprimant la partie régionale, évite que des variantes régionales soient manquantes sans bruit. Une langue inconnue devrait revenir à une valeur par défaut documentée plutôt qu'à une valeur vide.

#### Consentements dans des booléens libres

Cegid offre deux emplacements pour enregistrer le consentement marketing. Le `Optin` champs constituent une classification à quatre valeurs plutôt qu'un oui ou non, répertoriée dans le [Référence de script](/connectors/fr/pos/cegid/reference-developpeur/scripting-reference.md). La valeur intermédiaire qui compte comme consentement est une décision métier qui doit être convenue avec la marque ; cela n'a rien d'évident.

Les booléens libres sont le choix le plus courant, et ils ne prêtent pas à confusion. Le modèle ci-dessous montre la forme générale pour écrire dans n'importe quel conteneur de tableau : **filtrer l'entrée existante, puis ajouter**.

```javascript
const CONSENT_EMAIL_ID = 0;   // booléen libre 1

function upsert(list, id, entry) {
  return [...(list || []).filter((e) => e?.Id != id), { Id: id, ...entry }];
}

const mapToY2 = function (account, data) {
  // Ignorer seulement lorsque le formulaire n'a recueilli aucune réponse du tout.
  // Quand il en a recueilli une, écrire false aussi bien que true.
  if (typeof account.consents_email === "boolean") {
    data.UserDefinedBooleans = upsert(data.UserDefinedBooleans, CONSENT_EMAIL_ID, {
      Value: account.consents_email
    });
  }
};

const mapFromY2 = function (data, account) {
  // find, pas filter : filter renvoie un tableau, dont .Value est indéfini
  const entry = (data.UserDefinedBooleans || []).find((e) => e?.Id == CONSENT_EMAIL_ID);
  if (entry) account.consents_email = !!entry.Value;
};
```

#### Textes libres

Les textes libres suivent le même schéma de tableau, et le même décalage s'applique : `Id : 2` est le champ étiqueté comme le troisième.

```javascript
const LOYALTY_NOTE_ID = 2;   // texte libre 3

const mapToY2 = function (account, data) {
  if (account.note) {
    data.UserDefinedTexts = upsert(data.UserDefinedTexts, LOYALTY_NOTE_ID, { Value: account.note });
  }
};

const mapFromY2 = function (data, account) {
  const v = (data.UserDefinedTexts || []).find((e) => e?.Id == LOYALTY_NOTE_ID)?.Value;
  if (v) account.note = v;
};
```

#### Listes de préférences

`Champs définis par l’utilisateur` associent des entrées typées à des identifiants définis dans la configuration Cegid de la marque. Un champ d'élément de liste porte une valeur issue d'une liste définie dans Cegid, et le type de valeur identifie de quel type d'entrée il s'agit.

```javascript
const PREFERENCE_IDS = [1, 2, 3];   // convenu avec la marque
const LIST_ELEMENT = 3;             // type de valeur UserField

const mapToY2 = function (account, data) {
  for (const id of PREFERENCE_IDS) {
    const answered = account["preference" + id];
    if (answered === undefined) continue;
    // Écrire NO aussi bien que YES, sinon une préférence retirée n'atteint jamais Cegid.
    data.UserFields = upsert(data.UserFields, id, {
      ValueType: LIST_ELEMENT,
      ListElementValue: answered ? "YES" : "NO"
    });
  }
};
```

#### Les champs standard que le mappage par défaut laisse intacts

Plusieurs champs standard Cegid ne font pas partie du mappage par défaut, mais sont souvent nécessaires. Le bloc d'adresse peut être absent sur un nouvel enregistrement, il faut donc le créer avant d'y écrire quoi que ce soit.

```javascript
const mapToY2 = function (account, data) {
  // AddressData peut être absent. Écrire dedans sans le vérifier provoque une exception,
  // et toute la sauvegarde échoue.
  if (!data.AddressData) data.AddressData = {};
  data.AddressData.RegionId     = account.state || "";
  data.AddressData.AddressLine3 = account.address3;

  data.PassportNumber = account.passport;
  if (account.fiscalCode) data.FiscalId = account.fiscalCode.toUpperCase();
  if (account.comment)    data.Notepad  = account.comment;

  // Les noms alternatifs comportent un deuxième script, par exemple une forme latine
  // à côté d'une forme locale.
  data.AlternateFirstName = account.firstNameLatin;
  data.AlternateLastName  = account.lastNameLatin;
};
```

### Pratiques à suivre

#### Remplacer les entrées, ne jamais leur ajouter quoi que ce soit

Les conteneurs de tableaux restituent tel quel ce qui est déjà présent sur l'enregistrement. Ajouter sans supprimer d'abord l'entrée existante crée un doublon à *chaque* sauvegarde, et le doublon que Cegid prend en compte est arbitraire.

```javascript
// Faux : grossit à chaque sauvegarde.
data.UserFields = [...(data.UserFields || []), { Id: id, /* ... */ }];

// Correct : supprimer, puis ajouter.
data.UserFields = [...(data.UserFields || []).filter((e) => e?.Id != id), { Id: id, /* ... */ }];
```

L'erreur inverse est tout aussi dommageable : affecter un nouveau tableau remplace tout le conteneur, en supprimant les champs libres que la marque maintient à la main dans Cegid.

#### Conserver la symétrie entre les deux sens

Toute table utilisée dans `MapToY2` doit être utilisée dans `MapFromY2`, et le sens inverse doit être dérivé plutôt que retapé. Deux tables écrites indépendamment finissent par diverger, et le symptôme, une valeur qui change en une autre valeur au cours d'une sauvegarde, est difficile à attribuer.

La même chose s'applique aux codes : un chemin de lecture qui reconnaît un code qu'aucun chemin d'écriture ne produit est inutile, généralement parce que le script a été copié depuis une autre implémentation.

#### Écrire le cas négatif

Un champ que le Wallet peut définir doit aussi être un champ que le Wallet peut effacer. Les mappages qui ne consignent que le cas positif produisent des données qui ne peuvent jamais être corrigées : une préférence qui ne peut pas être retirée, un segment d'adoption qui ne fait que grandir.

#### Protéger les lectures et les écritures

Les objets imbriqués peuvent être absents. Écrire dans `data.AddressData` sans le créer d'abord fait échouer toute la sauvegarde, et lire `data.UserDefinedData.X` sans la forme avec chaînage optionnel échoue sur un client qui n'a jamais eu de champ libre défini.

#### Ne jamais journaliser le compte

L'objet account contient des noms, des adresses e-mail, des numéros de téléphone et des dates de naissance. Le journaliser place des données personnelles dans les journaux de la plateforme. Lorsqu'une trace est réellement nécessaire, il faut plutôt journaliser une seule valeur dérivée.

### Anti-modèles

| Anti-modèle                                                       | Ce qui est observé                                                                                                                   |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Ajouter à un tableau sans filtrer                                 | Des entrées en double s'accumulent à chaque sauvegarde ; la valeur effective devient imprévisible.                                   |
| Affecter un tout nouveau tableau                                  | Les champs libres maintenus à la main dans Cegid disparaissent dès la première sauvegarde du Wallet.                                 |
| Utiliser `filter` à la place de `find` lorsque                    | La valeur se relit en permanence comme undefined. Aucune erreur n'est levée ; le formulaire ne se préremplit tout simplement jamais. |
| Séparer les tables de correspondance directe et inverse           | Deux valeurs échangent leur place ; un enregistrement sauvegardé comme l’un se lit comme l’autre.                                    |
| Tester la véracité d'une chaîne d'état                            | Une Carte supprimée compte comme installée ; l’attribution de la plateforme se fige à sa première valeur.                            |
| N’écrire que la valeur positive                                   | Le segment grandit et ne rétrécit jamais ; les clients ne peuvent pas en être supprimés.                                             |
| Une condition de garde qui rend la branche négative inatteignable | Les opt-ins atteignent Cegid ; les opt-outs n’y parviennent jamais.                                                                  |
| Écriture non protégée dans un objet imbriqué                      | Le mapper lance une erreur pour tout client ne disposant pas de ce bloc, et l’enregistrement entier échoue.                          |
| Journalisation de l’objet compte                                  | Données personnelles dans les journaux de la plateforme.                                                                             |
| Une copie du mapper commentée laissée en place                    | Le prochain mainteneur modifie la copie morte.                                                                                       |

### Script de départ

Le script ci-dessous combine les recettes ci-dessus et peut servir de point de départ.

```javascript
// server/scripts/y2.mapping.js
//
// Conventions utilisées ici :
//  - Les identifiants des champs libres sont à base zéro ; l’étiquette Cegid est supérieure d’une unité.
//    Chaque constante comporte un commentaire nommant le champ Cegid.
//  - Les champs de tableau sont écrits en filtrant puis en ajoutant, jamais en étant simplement ajoutés.
//  - Les tables de correspondance sont déclarées une seule fois et inversées par programmation.
//  - Tout champ qui peut être défini peut aussi être annulé.

// ------------------------------------------------------------ constantes

const INSTALL_SLOT = "UserDefinedTable2Value";  // table libre 3
const NOT_INSTALLED = "000", APPLE_ONLY = "001",
      GOOGLE_ONLY = "002", BOTH = "003";

const CONSENT_EMAIL_ID = 0;   // booléen libre 1
const PREFERENCE_IDS   = [1, 2, 3];
const LIST_ELEMENT     = 3;

const LANGUAGES = { fr: "FRA", en: "ANG", es: "ESP" };
const DEFAULT_LANGUAGE = "ANG";
const LANGUAGES_REVERSE = Object.fromEntries(
  Object.entries(LANGUAGES).map(([l, c]) => [c, l])
);

// -------------------------------------------------------------- fonctions utilitaires

function upsert(list, id, entry) {
  return [...(list || []).filter((e) => e?.Id != id), { Id: id, ...entry }];
}

function installCode(account) {
  const apple  = compte["neo.Carte.Apple.state"]  === "Installé";
  const google = account["neo.pass.Google.state"] === "Installed";
  if (apple && google) return BOTH;
  if (apple)           return APPLE_ONLY;
  if (google)          return GOOGLE_ONLY;
  return NOT_INSTALLED;
}

// -------------------------------------------------------- Wallet -> Cegid

const mapToY2 = function (account, data) {

  const locale = (account.neo?.lng || "").toLowerCase().split("-")[0];
  data.LanguageId = LANGUAGES[locale] || DEFAULT_LANGUAGE;

  if (!data.AddressData) data.AddressData = {};
  data.AddressData.RegionId = account.state || "";

  data.UserDefinedData = { ...data.UserDefinedData, [INSTALL_SLOT]: installCode(account) };

  if (typeof account.consents_email === "boolean") {
    data.UserDefinedBooleans =
      upsert(data.UserDefinedBooleans, CONSENT_EMAIL_ID, { Value: account.consents_email });
  }

  for (const id of PREFERENCE_IDS) {
    const answered = account["preference" + id];
    if (answered === undefined) continue;
    data.UserFields = upsert(data.UserFields, id, {
      ValueType: LIST_ELEMENT,
      ListElementValue: answered ? "YES" : "NO"
    });
  }
};

// -------------------------------------------------------- Cegid -> Wallet

const mapFromY2 = function (data, account) {

  const locale = LANGUAGES_REVERSE[data.LanguageId];
  if (locale) account.language = locale;

  account.state = data.AddressData?.RegionId;
  account.installState = data.UserDefinedData?.[INSTALL_SLOT] || NOT_INSTALLED;

  const consent = (data.UserDefinedBooleans || []).find((e) => e?.Id == CONSENT_EMAIL_ID);
  if (consent) account.consents_email = !!consent.Value;

  for (const id of PREFERENCE_IDS) {
    const f = (data.UserFields || []).find((e) => e?.Id == id);
    if (f) account["preference" + id] = f.ListElementValue === "YES";
  }
};

// --------------------------------------------------------- enregistrement

export default function (context) {
  context.register('extensions.cegid.y2.mapper', { MapToY2: mapToY2, MapFromY2: mapFromY2 });
}
```

### FAQ

<details>

<summary><strong>Une valeur est écrite dans le mauvais champ Cegid. Pourquoi ?</strong></summary>

Presque toujours l'identifiant basé sur zéro. Le champ qu’un utilisateur Cegid appelle le troisième texte libre est `Id : 2` dans un script. Vérifier le champ visé avec la marque grâce à son libellé Cegid, et commenter la constante en conséquence, évite cela.

</details>

<details>

<summary><strong>Un champ s’enregistre mais ne se relit jamais. Pourquoi ?</strong></summary>

La cause la plus fréquente est l’utilisation de `filter` à la place de `find` était prévu. `filter` renvoie un tableau, et lire une valeur dans un tableau ne renvoie rien, silencieusement et sans erreur.

</details>

<details>

<summary><strong>Un script de mappage peut-il remplacer le mappage par défaut ?</strong></summary>

Oui. Le script s’exécute après le mappage par défaut dans les deux sens, donc tout champ qu’il définit a priorité.

</details>

<details>

<summary><strong>Une table de langues peut-elle être copiée d’une autre implémentation ?</strong></summary>

Non. Les identifiants de langue Cegid sont définis par base de données. Les codes doivent provenir de la configuration Cegid propre à la marque.

</details>

<details>

<summary><strong>Aucune personnalisation ne semble avoir le moindre effet. Que faut-il vérifier ?</strong></summary>

Une erreur de syntaxe dans n’importe quel fichier de script, pas nécessairement le fichier de mappage, empêche le chargement de tous les scripts pour ce tenant. Examiner les autres scripts est la première étape.

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.thewalletcrew.io/connectors/fr/pos/cegid/donnees-client-et-fidelite/customise-the-mapping.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
