{
  "info": {
    "_postman_id": "84d53fbc-c8ed-49b6-95b9-360d1e06742e",
    "name": "Commerce 360 - API externe V1",
    "description": "Collection publique pour intégrer l’API externe Commerce 360 V1.\n\nLe contrat de production actuel n’expose aucune route POST /v1. POST /auth reste disponible.\n\nImportez également l’environnement de production fourni sur developers.hbjo-online.com, puis renseignez vos propres identifiants dans les valeurs locales Postman.\n\nAucun identifiant, mot de passe, jeton ou code confidentiel n’est inclus dans ce fichier.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{token}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const requestPath = '/' + pm.request.url.getPath().replace(/^\\//, '');",
          "const publicPaths = ['/auth', '/health'];",
          "",
          "if (!publicPaths.includes(requestPath)) {",
          "    const baseUrl = String(pm.variables.get('baseUrl') || '').replace(/\\/$/, '');",
          "    const email = pm.variables.get('email');",
          "    const password = pm.variables.get('password');",
          "    const token = pm.variables.get('token');",
          "    const tokenExpiresAt = Number(pm.variables.get('tokenExpiresAt') || 0);",
          "    const refreshBefore = Date.now() + 60000;",
          "    const tokenMustBeRefreshed = !token || tokenExpiresAt <= refreshBefore;",
          "",
          "    function setRuntimeVariable(name, value) {",
          "        if (pm.environment && Object.keys(pm.environment.toObject()).length > 0) {",
          "            pm.environment.set(name, value);",
          "        } else {",
          "            pm.collectionVariables.set(name, value);",
          "        }",
          "    }",
          "",
          "    function getTokenExpiration(jwt) {",
          "        try {",
          "            const encodedPayload = jwt.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');",
          "            const paddedPayload = encodedPayload.padEnd(Math.ceil(encodedPayload.length / 4) * 4, '=');",
          "            const payload = JSON.parse(atob(paddedPayload));",
          "            return payload.exp ? payload.exp * 1000 : Date.now() + 300000;",
          "        } catch (error) {",
          "            return Date.now() + 300000;",
          "        }",
          "    }",
          "",
          "    if (!baseUrl) {",
          "        throw new Error('Variable baseUrl absente. Sélectionnez l’environnement Commerce 360 importé.');",
          "    }",
          "",
          "    if (tokenMustBeRefreshed) {",
          "        if (!email || !password) {",
          "            throw new Error('Renseignez email et password dans l’environnement Postman avant d’appeler une route protégée.');",
          "        }",
          "",
          "        pm.sendRequest({",
          "            url: baseUrl + '/auth',",
          "            method: 'POST',",
          "            header: { 'Content-Type': 'application/json' },",
          "            body: {",
          "                mode: 'raw',",
          "                raw: JSON.stringify({ email, password })",
          "            }",
          "        }, (error, response) => {",
          "            if (error) {",
          "                throw new Error('Authentification automatique impossible : ' + error.message);",
          "            }",
          "            if (response.code !== 200) {",
          "                throw new Error('Authentification automatique refusée : HTTP ' + response.code);",
          "            }",
          "",
          "            const payload = response.json();",
          "            if (!payload.token) {",
          "                throw new Error('La réponse de /auth ne contient pas de token.');",
          "            }",
          "",
          "            setRuntimeVariable('token', payload.token);",
          "            setRuntimeVariable('tokenExpiresAt', String(getTokenExpiration(payload.token)));",
          "            pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + payload.token });",
          "        });",
          "    } else {",
          "        pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + token });",
          "    }",
          "}"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.hbjo-online.com",
      "type": "string"
    },
    {
      "key": "token",
      "value": "",
      "type": "string"
    },
    {
      "key": "tokenExpiresAt",
      "value": "0",
      "type": "string"
    },
    {
      "key": "page",
      "value": "1",
      "type": "string"
    },
    {
      "key": "limit",
      "value": "50",
      "type": "string"
    },
    {
      "key": "shopId",
      "value": "",
      "type": "string"
    },
    {
      "key": "searchTerm",
      "value": "",
      "type": "string"
    },
    {
      "key": "productId",
      "value": "",
      "type": "string"
    },
    {
      "key": "itemId",
      "value": "",
      "type": "string"
    },
    {
      "key": "categoryId",
      "value": "",
      "type": "string"
    },
    {
      "key": "productTypeId",
      "value": "",
      "type": "string"
    },
    {
      "key": "supplierId",
      "value": "",
      "type": "string"
    },
    {
      "key": "characteristicId",
      "value": "",
      "type": "string"
    },
    {
      "key": "updatedSince",
      "value": "2026-01-01T00:00:00.000Z",
      "type": "string"
    },
    {
      "key": "customerId",
      "value": "",
      "type": "string"
    },
    {
      "key": "customerNumber",
      "value": "",
      "type": "string"
    },
    {
      "key": "customerEmail",
      "value": "",
      "type": "string"
    },
    {
      "key": "customerPhone",
      "value": "",
      "type": "string"
    },
    {
      "key": "customerFirstName",
      "value": "Postman",
      "type": "string"
    },
    {
      "key": "customerLastName",
      "value": "Commerce360",
      "type": "string"
    },
    {
      "key": "customerHistoryFrom",
      "value": "2025-01-01",
      "type": "string"
    },
    {
      "key": "customerHistoryTo",
      "value": "2026-12-31",
      "type": "string"
    },
    {
      "key": "customerCreditTypes",
      "value": "note,deposit",
      "type": "string"
    },
    {
      "key": "saleId",
      "value": "",
      "type": "string"
    },
    {
      "key": "saleNumber",
      "value": "",
      "type": "string"
    },
    {
      "key": "invoiceId",
      "value": "",
      "type": "string"
    },
    {
      "key": "invoiceNumber",
      "value": "",
      "type": "string"
    },
    {
      "key": "salesFrom",
      "value": "2025-01-01T00:00:00.000Z",
      "type": "string"
    },
    {
      "key": "salesTo",
      "value": "2026-12-31T23:59:59.999Z",
      "type": "string"
    },
    {
      "key": "savId",
      "value": "",
      "type": "string"
    },
    {
      "key": "savFrom",
      "value": "2025-01-01T00:00:00.000Z",
      "type": "string"
    },
    {
      "key": "savTo",
      "value": "2026-12-31T23:59:59.999Z",
      "type": "string"
    },
    {
      "key": "giftCardId",
      "value": "",
      "type": "string"
    },
    {
      "key": "giftCardInclude",
      "value": "customer,origin,usages,invoice",
      "type": "string"
    },
    {
      "key": "giftCardDebitAmount",
      "value": "5.00",
      "type": "string"
    },
    {
      "key": "giftCardUsageSaleId",
      "value": "",
      "type": "string"
    },
    {
      "key": "giftCardDebitExternalReference",
      "value": "",
      "type": "string"
    },
    {
      "key": "giftCardDebitIdempotencyKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "confirmCustomerReplacement",
      "value": "false"
    }
  ],
  "item": [
    {
      "name": "00 - Démarrage",
      "description": "Vérification du serveur, authentification explicite et réinitialisation du token.",
      "item": [
        {
          "name": "Santé de l'API",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "health"
              ]
            },
            "description": "Doit répondre HTTP 200. À utiliser en premier si Postman affiche socket hang up."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('API accessible - HTTP 200', () => {",
                  "    pm.response.to.have.status(200);",
                  "});",
                  "pm.test('Réponse en moins de 5 secondes', () => {",
                  "    pm.expect(pm.response.responseTime).to.be.below(5000);",
                  "});"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Authentification - Récupérer le token",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{email}}\",\n  \"password\": \"{{password}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/auth",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth"
              ]
            },
            "description": "Authentification manuelle. Le script de test stocke automatiquement token et tokenExpiresAt dans l’environnement actif. Cette requête est facultative : les autres requêtes savent aussi récupérer le token automatiquement."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const environmentVariables = pm.environment ? pm.environment.toObject() : {};",
                  "const environmentIsActive = Object.keys(environmentVariables).length > 0;",
                  "const resolvedEmail = pm.variables.get('email');",
                  "const resolvedPassword = pm.variables.get('password');",
                  "console.log('Configuration authentification Commerce 360', {",
                  "    environmentIsActive,",
                  "    environmentName: pm.environment ? pm.environment.name : null,",
                  "    emailConfigured: Boolean(resolvedEmail),",
                  "    passwordConfigured: Boolean(resolvedPassword)",
                  "});",
                  "if (!environmentIsActive) {",
                  "    throw new Error('Aucun environnement actif. Sélectionnez l’environnement Commerce 360 importé en haut à droite dans Postman.');",
                  "}",
                  "if (!resolvedEmail) {",
                  "    throw new Error('La variable email est vide dans l’environnement actif.');",
                  "}",
                  "if (!resolvedPassword) {",
                  "    throw new Error('La variable password est vide dans l’environnement actif. Renseignez sa valeur locale/courante puis enregistrez l’environnement.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Authentification réussie - HTTP 200', () => {",
                  "    pm.response.to.have.status(200);",
                  "});",
                  "const payload = pm.response.json();",
                  "pm.test('Un token JWT est retourné', () => {",
                  "    pm.expect(payload.token).to.be.a('string').and.not.empty;",
                  "});",
                  "function setRuntimeVariable(name, value) {",
                  "    if (pm.environment && Object.keys(pm.environment.toObject()).length > 0) pm.environment.set(name, value);",
                  "    else pm.collectionVariables.set(name, value);",
                  "}",
                  "function getTokenExpiration(jwt) {",
                  "    try {",
                  "        const encodedPayload = jwt.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');",
                  "        const paddedPayload = encodedPayload.padEnd(Math.ceil(encodedPayload.length / 4) * 4, '=');",
                  "        const jwtPayload = JSON.parse(atob(paddedPayload));",
                  "        return jwtPayload.exp ? jwtPayload.exp * 1000 : Date.now() + 300000;",
                  "    } catch (error) {",
                  "        return Date.now() + 300000;",
                  "    }",
                  "}",
                  "if (payload.token) {",
                  "    setRuntimeVariable('token', payload.token);",
                  "    setRuntimeVariable('tokenExpiresAt', String(getTokenExpiration(payload.token)));",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Réinitialiser le token",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "health"
              ]
            },
            "description": "Efface le token mémorisé. La prochaine requête protégée déclenchera une nouvelle authentification automatique."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.unset('token');",
                  "pm.collectionVariables.set('tokenExpiresAt', '0');",
                  "if (pm.environment && Object.keys(pm.environment.toObject()).length > 0) {",
                  "    pm.environment.unset('token');",
                  "    pm.environment.set('tokenExpiresAt', '0');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Token effacé et API accessible', () => {",
                  "    pm.response.to.have.status(200);",
                  "    pm.expect(pm.variables.get('token')).to.be.oneOf([undefined, null, '']);",
                  "});"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "01 - Produits",
      "description": "Routes légères de références catalogue. Les paramètres désactivés peuvent être cochés directement dans Postman.",
      "item": [
        {
          "name": "Liste des produits - Enrichie",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products?page={{page}}&limit={{limit}}&displayedOnline=true&availableOnly=false&include=media,stock,characteristics,prices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{page}}",
                  "description": "Page courante"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}",
                  "description": "1 à 500"
                },
                {
                  "key": "search",
                  "value": "{{searchTerm}}",
                  "description": "Nom, référence ou EAN",
                  "disabled": true
                },
                {
                  "key": "displayedOnline",
                  "value": "true"
                },
                {
                  "key": "availableOnly",
                  "value": "false"
                },
                {
                  "key": "updatedSince",
                  "value": "2026-07-01T00:00:00.000Z",
                  "disabled": true
                },
                {
                  "key": "supplierId",
                  "value": "{{supplierId}}",
                  "disabled": true
                },
                {
                  "key": "productTypeId",
                  "value": "{{productTypeId}}",
                  "disabled": true
                },
                {
                  "key": "categoryId",
                  "value": "{{categoryId}}",
                  "disabled": true
                },
                {
                  "key": "shopId",
                  "value": "{{shopId}}",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "media,stock,characteristics,prices"
                }
              ]
            },
            "description": "Liste légère des références produit avec les relations publiques demandées. Ne retourne jamais les articles physiques imbriqués."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Liste produits - HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('Réponse JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));",
                  "const body = pm.response.json();",
                  "pm.test('Enveloppe paginée valide', () => {",
                  "    pm.expect(body.data).to.be.an('array');",
                  "    pm.expect(body.pagination).to.include.keys('page', 'limit', 'count', 'total', 'totalPages', 'hasNextPage');",
                  "});",
                  "if (body.data.length) {",
                  "    const product = body.data[0];",
                  "    pm.test('Contrat produit léger valide', () => {",
                  "        pm.expect(product).to.include.keys('uuid', 'reference', 'name', 'displayedOnline', 'priceRange', 'promotionPriceRange', 'availableItemsCount');",
                  "        pm.expect(product).not.to.have.property('items');",
                  "        pm.expect(product.media).to.be.an('array');",
                  "        pm.expect(product.characteristics).to.be.an('array');",
                  "        pm.expect(product.stock).to.be.an('object');",
                  "    });",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('productId', product.uuid);",
                  "    if (product.category && product.category.id) target.set('categoryId', product.category.id);",
                  "    if (product.productType && product.productType.id) target.set('productTypeId', product.productType.id);",
                  "    if (!pm.variables.get('searchTerm') && product.reference) target.set('searchTerm', product.reference);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Produits disponibles uniquement",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products?page={{page}}&limit={{limit}}&availableOnly=true&include=media,stock,prices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{page}}"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}"
                },
                {
                  "key": "availableOnly",
                  "value": "true"
                },
                {
                  "key": "include",
                  "value": "media,stock,prices"
                }
              ]
            },
            "description": "Retourne uniquement les références ayant au moins un article réellement disponible."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Produits disponibles - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Tous les produits sont disponibles', () => {",
                  "    body.data.forEach(product => {",
                  "        pm.expect(product.availableItemsCount).to.be.above(0);",
                  "        pm.expect(product.stock.available).to.equal(true);",
                  "        pm.expect(product.priceRange).not.to.equal(null);",
                  "    });",
                  "});"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Rechercher un produit",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products?search={{searchTerm}}&page=1&limit={{limit}}&include=media,stock,prices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "{{searchTerm}}",
                  "description": "Renseigné automatiquement après la première liste si vide"
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}"
                },
                {
                  "key": "include",
                  "value": "media,stock,prices"
                }
              ]
            },
            "description": "Recherche sur le nom, la référence ou l'EAN. Exécutez d'abord Liste des produits - Enrichie pour initialiser automatiquement searchTerm."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Recherche - HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('Résultat paginé', () => {",
                  "    const body = pm.response.json();",
                  "    pm.expect(body.data).to.be.an('array');",
                  "    pm.expect(body.pagination).to.be.an('object');",
                  "});"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Produits disponibles dans une boutique",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products?shopId={{shopId}}&page={{page}}&limit={{limit}}&include=media,stock,prices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "shopId",
                  "value": "{{shopId}}"
                },
                {
                  "key": "page",
                  "value": "{{page}}"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}"
                },
                {
                  "key": "include",
                  "value": "media,stock,prices"
                }
              ]
            },
            "description": "Filtre les références ayant au moins un article disponible dans shopId. La collection initialise shopId depuis le catalogue complet si elle trouve un article rattaché à une boutique."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('shopId')) {",
                  "    throw new Error('shopId est vide. Renseignez-le dans l’environnement ou exécutez Catalogue complet pour le capturer.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Filtre boutique - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Chaque produit a un article disponible', () => {",
                  "    body.data.forEach(product => {",
                  "        pm.expect(product.availableItemsCount).to.be.above(0);",
                  "        pm.expect(product.stock.available).to.equal(true);",
                  "    });",
                  "});"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "02 - Catalogue complet",
      "description": "Vue catalogue avec références et articles physiques imbriqués.",
      "item": [
        {
          "name": "Catalogue produits avec articles",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/catalog/products?page={{page}}&limit={{limit}}&availableOnly=false&displayedOnline=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "catalog",
                "products"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{page}}"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}"
                },
                {
                  "key": "updatedSince",
                  "value": "2026-07-01T00:00:00.000Z",
                  "disabled": true
                },
                {
                  "key": "shopUuid",
                  "value": "{{shopId}}",
                  "disabled": true
                },
                {
                  "key": "availableOnly",
                  "value": "false"
                },
                {
                  "key": "displayedOnline",
                  "value": "true"
                }
              ]
            },
            "description": "Vue complète avec articles, prix de base, prix promotionnel séparé, caractéristiques et disponibilité."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Catalogue - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Enveloppe catalogue valide', () => {",
                  "    pm.expect(body.data).to.be.an('array');",
                  "    pm.expect(body.pagination).to.be.an('object');",
                  "    pm.expect(body.meta.currency).to.equal('EUR');",
                  "});",
                  "const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "for (const product of body.data) {",
                  "    if (product.supplier && product.supplier.id && !pm.variables.get('supplierId')) target.set('supplierId', product.supplier.id);",
                  "    const itemWithShop = (product.items || []).find(item => item.shop && item.shop.id);",
                  "    if (itemWithShop && !pm.variables.get('shopId')) target.set('shopId', itemWithShop.shop.id);",
                  "    const firstItem = (product.items || [])[0];",
                  "    if (firstItem && !pm.variables.get('itemId')) target.set('itemId', firstItem.uuid);",
                  "    for (const item of product.items || []) {",
                  "        pm.test('Le prix de base reste présent sur ' + item.uuid, () => {",
                  "            pm.expect(item.price).to.include.keys('inclTax', 'exclTax', 'currency');",
                  "            pm.expect(item).to.have.property('promotionPrice');",
                  "        });",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "03 - Détails produits et articles",
      "description": "Détail d’une référence, articles rattachés, liste globale et détail d’un article. Les UUID sont capturés automatiquement.",
      "item": [
        {
          "name": "Détail du produit",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/{{productId}}?include=characteristics,media,prices,stock,items",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "{{productId}}"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "characteristics,media,prices,stock,items"
                },
                {
                  "key": "shopId",
                  "value": "{{shopId}}",
                  "disabled": true
                }
              ]
            },
            "description": "Détail complet de la référence sélectionnée. productId est capturé par la liste des produits."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('productId')) throw new Error('productId est vide. Exécutez d’abord Liste des produits - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Détail produit - HTTP 200', () => pm.response.to.have.status(200));",
                  "const product = pm.response.json();",
                  "pm.test('Contrat du détail produit', () => {",
                  "    pm.expect(product.uuid).to.equal(pm.variables.get('productId'));",
                  "    pm.expect(product.characteristics).to.be.an('array');",
                  "    pm.expect(product.media).to.be.an('array');",
                  "    pm.expect(product.items).to.be.an('array');",
                  "});",
                  "const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "if (product.supplier && product.supplier.id) target.set('supplierId', product.supplier.id);",
                  "if (product.category && product.category.id) target.set('categoryId', product.category.id);",
                  "if (product.items && product.items.length) {",
                  "    target.set('itemId', product.items[0].uuid);",
                  "    if (product.items[0].shop && product.items[0].shop.id) target.set('shopId', product.items[0].shop.id);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Articles rattachés au produit",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/{{productId}}/items?availableOnly=false&include=characteristics,media,shop",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "{{productId}}",
                "items"
              ],
              "query": [
                {
                  "key": "availableOnly",
                  "value": "false"
                },
                {
                  "key": "shopId",
                  "value": "{{shopId}}",
                  "disabled": true
                },
                {
                  "key": "hasPromotion",
                  "value": "true",
                  "disabled": true
                },
                {
                  "key": "updatedSince",
                  "value": "{{updatedSince}}",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "characteristics,media,shop"
                }
              ]
            },
            "description": "Articles physiques rattachés à productId. Désactivez availableOnly pour visualiser aussi les articles indisponibles."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('productId')) throw new Error('productId est vide. Exécutez d’abord Liste des produits - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Articles du produit - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Liste d’articles valide', () => pm.expect(body.data).to.be.an('array'));",
                  "if (body.data.length) {",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('itemId', body.data[0].uuid);",
                  "    if (body.data[0].shop && body.data[0].shop.id) target.set('shopId', body.data[0].shop.id);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Liste globale des articles",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/product-items?page={{page}}&limit={{limit}}&availableOnly=false&include=product,characteristics,media,shop,category,supplier",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "product-items"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{page}}"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}"
                },
                {
                  "key": "availableOnly",
                  "value": "false"
                },
                {
                  "key": "updatedSince",
                  "value": "{{updatedSince}}",
                  "disabled": true
                },
                {
                  "key": "productId",
                  "value": "{{productId}}",
                  "disabled": true
                },
                {
                  "key": "shopId",
                  "value": "{{shopId}}",
                  "disabled": true
                },
                {
                  "key": "supplierId",
                  "value": "{{supplierId}}",
                  "disabled": true
                },
                {
                  "key": "productTypeId",
                  "value": "{{productTypeId}}",
                  "disabled": true
                },
                {
                  "key": "categoryId",
                  "value": "{{categoryId}}",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "{{searchTerm}}",
                  "disabled": true
                },
                {
                  "key": "priceMin",
                  "value": "0",
                  "disabled": true
                },
                {
                  "key": "priceMax",
                  "value": "1000",
                  "disabled": true
                },
                {
                  "key": "hasPromotion",
                  "value": "true",
                  "disabled": true
                },
                {
                  "key": "isSecondHand",
                  "value": "false",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "product,characteristics,media,shop,category,supplier"
                }
              ]
            },
            "description": "Liste paginée des articles. Tous les filtres documentés sont présents dans Params et désactivés par défaut."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Liste globale articles - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Enveloppe paginée articles', () => {",
                  "    pm.expect(body.data).to.be.an('array');",
                  "    pm.expect(body.pagination).to.be.an('object');",
                  "});",
                  "if (body.data.length) {",
                  "    const item = body.data[0];",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('itemId', item.uuid);",
                  "    target.set('productId', item.productId);",
                  "    if (item.shop && item.shop.id) target.set('shopId', item.shop.id);",
                  "    if (item.category && item.category.id) target.set('categoryId', item.category.id);",
                  "    if (item.supplier && item.supplier.id) target.set('supplierId', item.supplier.id);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Détail de l’article",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/product-items/{{itemId}}?include=product,characteristics,media,shop,category,supplier",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "product-items",
                "{{itemId}}"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "product,characteristics,media,shop,category,supplier"
                }
              ]
            },
            "description": "Détail complet de l’article sélectionné. itemId est capturé par les listes précédentes."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('itemId')) throw new Error('itemId est vide. Exécutez d’abord une liste d’articles.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Détail article - HTTP 200', () => pm.response.to.have.status(200));",
                  "const item = pm.response.json();",
                  "pm.test('Contrat du détail article', () => {",
                  "    pm.expect(item.uuid).to.equal(pm.variables.get('itemId'));",
                  "    pm.expect(item.price).to.include.keys('inclTax', 'exclTax', 'currency');",
                  "    pm.expect(item.promotion).to.include.keys('active', 'type', 'value', 'priceInclTax');",
                  "    pm.expect(item.availability).to.include.keys('available', 'status', 'reason');",
                  "});"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "04 - Référentiels catalogue",
      "description": "Boutiques, catégories, types produits, caractéristiques et valeurs. Les identifiants sont capturés automatiquement.",
      "item": [
        {
          "name": "Boutiques actives",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/shops?activeOnly=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "shops"
              ],
              "query": [
                {
                  "key": "activeOnly",
                  "value": "true"
                }
              ]
            },
            "description": "Liste des boutiques actives. La première boutique initialise shopId."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Boutiques - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Liste boutiques valide', () => pm.expect(body.data).to.be.an('array'));",
                  "if (body.data.length) {",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('shopId', body.data[0].id);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Catégories avec enfants",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/categories?displayRefsOnline=true&include=children",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "categories"
              ],
              "query": [
                {
                  "key": "parentId",
                  "value": "{{categoryId}}",
                  "disabled": true
                },
                {
                  "key": "productTypeId",
                  "value": "{{productTypeId}}",
                  "disabled": true
                },
                {
                  "key": "displayRefsOnline",
                  "value": "true"
                },
                {
                  "key": "isSecondHand",
                  "value": "false",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "children"
                }
              ]
            },
            "description": "Catégories exposables et enfants directs. La première catégorie initialise categoryId et productTypeId lorsque disponibles."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Catégories - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Liste catégories valide', () => pm.expect(body.data).to.be.an('array'));",
                  "if (body.data.length) {",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    const category = body.data.find(value => value.productTypeId) || body.data[0];",
                  "    target.set('categoryId', category.uuid);",
                  "    if (category.productTypeId) target.set('productTypeId', category.productTypeId);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Types produits enrichis",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/product-types?include=categories,characteristics",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "product-types"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "categories,characteristics"
                },
                {
                  "key": "displayedOnline",
                  "value": "true",
                  "disabled": true
                }
              ]
            },
            "description": "Types produits avec catégories et caractéristiques effectives héritées."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Types produits - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Liste types produits valide', () => pm.expect(body.data).to.be.an('array'));",
                  "if (body.data.length) {",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    const productType = body.data.find(value => value.characteristics && value.characteristics.length) || body.data[0];",
                  "    target.set('productTypeId', productType.id);",
                  "    if (productType.categories && productType.categories.length) target.set('categoryId', productType.categories[0].id);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Caractéristiques avec valeurs",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/characteristics?include=values",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "characteristics"
              ],
              "query": [
                {
                  "key": "productTypeId",
                  "value": "{{productTypeId}}",
                  "disabled": true
                },
                {
                  "key": "categoryId",
                  "value": "{{categoryId}}",
                  "disabled": true
                },
                {
                  "key": "display",
                  "value": "true",
                  "disabled": true
                },
                {
                  "key": "form",
                  "value": "product",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "values"
                }
              ]
            },
            "description": "Référentiel des caractéristiques avec toutes les valeurs théoriques. Capture une caractéristique possédant des valeurs."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Caractéristiques - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Liste caractéristiques valide', () => pm.expect(body.data).to.be.an('array'));",
                  "const characteristic = body.data.find(value => value.values && value.values.length) || body.data[0];",
                  "if (characteristic) {",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('characteristicId', characteristic.id);",
                  "    if (characteristic.values && characteristic.values.length) target.set('characteristicValueId', characteristic.values[0].id);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Valeurs d’une caractéristique",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/characteristics/{{characteristicId}}/values?usedOnly=false&availableOnly=false",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "characteristics",
                "{{characteristicId}}",
                "values"
              ],
              "query": [
                {
                  "key": "usedOnly",
                  "value": "false"
                },
                {
                  "key": "productTypeId",
                  "value": "{{productTypeId}}",
                  "disabled": true
                },
                {
                  "key": "categoryId",
                  "value": "{{categoryId}}",
                  "disabled": true
                },
                {
                  "key": "availableOnly",
                  "value": "false"
                }
              ]
            },
            "description": "Valeurs de characteristicId. Activez usedOnly, availableOnly ou les filtres de périmètre directement dans Params."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('characteristicId')) throw new Error('characteristicId est vide. Exécutez d’abord Caractéristiques avec valeurs.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Valeurs caractéristique - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Contrat des valeurs valide', () => {",
                  "    pm.expect(body.characteristic.id).to.equal(pm.variables.get('characteristicId'));",
                  "    pm.expect(body.data).to.be.an('array');",
                  "});",
                  "if (body.data.length) {",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('characteristicValueId', body.data[0].id);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "05 - Synchronisation",
      "description": "Synchronisation incrémentale des changements catalogue.",
      "item": [
        {
          "name": "Changements du catalogue",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/catalog/changes?since={{updatedSince}}&types=products,product-items,categories,characteristics,shops&page=1&limit=100",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "catalog",
                "changes"
              ],
              "query": [
                {
                  "key": "since",
                  "value": "{{updatedSince}}"
                },
                {
                  "key": "until",
                  "value": "2026-12-31T23:59:59.999Z",
                  "disabled": true
                },
                {
                  "key": "types",
                  "value": "products,product-items,categories,characteristics,shops"
                },
                {
                  "key": "shopId",
                  "value": "{{shopId}}",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "100"
                }
              ]
            },
            "description": "Changements du catalogue depuis updatedSince. Modifiez cette variable pour choisir le point de reprise."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('updatedSince')) throw new Error('updatedSince est vide. Renseignez une date ISO 8601 dans l’environnement.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Changements catalogue - HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('Enveloppe de synchronisation valide', () => {",
                  "    pm.expect(body).to.include.keys('since', 'until', 'changes', 'pagination');",
                  "    pm.expect(body.changes).to.be.an('array');",
                  "});"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "06 - Clients - Lecture",
      "description": "Routes de lecture Clients V1. Commencez par la liste pour initialiser customerId, customerNumber, customerEmail et customerPhone depuis le premier résultat disponible. Les données personnelles nécessitent customers:pii:read ; les commentaires internes nécessitent internal-comments:read.",
      "item": [
        {
          "name": "Liste des clients - Enrichie",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/customers?page={{page}}&limit={{limit}}&guest=false&include=shop,consents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{page}}"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}"
                },
                {
                  "key": "search",
                  "value": "{{searchTerm}}",
                  "disabled": true
                },
                {
                  "key": "typeCustomer",
                  "value": "individual",
                  "disabled": true
                },
                {
                  "key": "shopId",
                  "value": "{{shopId}}",
                  "disabled": true
                },
                {
                  "key": "guest",
                  "value": "false"
                },
                {
                  "key": "updatedSince",
                  "value": "{{updatedSince}}",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "shop,consents"
                }
              ]
            },
            "description": "Liste paginée des clients non invités. La première réponse initialise les variables client sans écraser un customerId déjà choisi. Ajoutez internalComments à la valeur du paramètre include uniquement avec le scope correspondant.\n\nScopes : customers:read ; customers:pii:read pour email et téléphones."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Liste clients - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Enveloppe paginée clients valide', () => {",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.include.keys('page', 'limit', 'count', 'total', 'totalPages', 'hasNextPage');",
                  "    });",
                  "    if (body.data.length) {",
                  "        const customer = body.data[0];",
                  "        const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "        if (!pm.variables.get('customerId')) target.set('customerId', customer.uuid);",
                  "        if (customer.number && !pm.variables.get('customerNumber')) target.set('customerNumber', customer.number);",
                  "        if (customer.email && !pm.variables.get('customerEmail')) target.set('customerEmail', customer.email);",
                  "        if (customer.phone && !pm.variables.get('customerPhone')) target.set('customerPhone', customer.phone);",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Recherche client - Par email exact",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/search?email={{customerEmail}}&matchMode=exact&page=1&limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "search"
              ],
              "query": [
                {
                  "key": "email",
                  "value": "{{customerEmail}}"
                },
                {
                  "key": "phone",
                  "value": "{{customerPhone}}",
                  "disabled": true
                },
                {
                  "key": "firstName",
                  "value": "{{customerFirstName}}",
                  "disabled": true
                },
                {
                  "key": "lastName",
                  "value": "{{customerLastName}}",
                  "disabled": true
                },
                {
                  "key": "companyName",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "customerNumber",
                  "value": "{{customerNumber}}",
                  "disabled": true
                },
                {
                  "key": "matchMode",
                  "value": "exact"
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "Recherche déterministe pour rapprocher un compte utilisateur du site partenaire d’une fiche Commerce 360. Au moins un critère métier doit rester actif.\n\nScopes : customers:read et customers:pii:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('customerEmail')) {",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('customerEmail', 'postman.missing@example.com');",
                  "    console.warn('customerEmail était vide : utilisation d’un email de test sans correspondance attendue.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Recherche client par email - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Résultats de recherche valides', () => {",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.be.an('object');",
                  "        body.data.forEach(customer => pm.expect(customer.match.matchedOn).to.include('email'));",
                  "    });",
                  "    if (body.data.length) {",
                  "        const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "        target.set('customerId', body.data[0].uuid);",
                  "        if (body.data[0].number) target.set('customerNumber', body.data[0].number);",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Recherche client - Par nom",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/search?firstName={{customerFirstName}}&lastName={{customerLastName}}&matchMode=contains&page=1&limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "search"
              ],
              "query": [
                {
                  "key": "email",
                  "value": "{{customerEmail}}",
                  "disabled": true
                },
                {
                  "key": "phone",
                  "value": "{{customerPhone}}",
                  "disabled": true
                },
                {
                  "key": "firstName",
                  "value": "{{customerFirstName}}"
                },
                {
                  "key": "lastName",
                  "value": "{{customerLastName}}"
                },
                {
                  "key": "companyName",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "customerNumber",
                  "value": "{{customerNumber}}",
                  "disabled": true
                },
                {
                  "key": "matchMode",
                  "value": "contains"
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "Recherche par prénom et nom en mode partiel. Désactivez l’un des critères pour élargir la recherche ou activez companyName pour une société.\n\nScopes : customers:read et customers:pii:read."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Recherche client par nom - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Résultats de recherche valides', () => {",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.be.an('object');",
                  "    });",
                  "    if (body.data.length && !pm.variables.get('customerId')) {",
                  "        const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "        target.set('customerId', body.data[0].uuid);",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Recherche client - Par téléphone exact",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/search?phone={{customerPhone}}&matchMode=exact&page=1&limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "search"
              ],
              "query": [
                {
                  "key": "email",
                  "value": "{{customerEmail}}",
                  "disabled": true
                },
                {
                  "key": "phone",
                  "value": "{{customerPhone}}"
                },
                {
                  "key": "firstName",
                  "value": "{{customerFirstName}}",
                  "disabled": true
                },
                {
                  "key": "lastName",
                  "value": "{{customerLastName}}",
                  "disabled": true
                },
                {
                  "key": "companyName",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "customerNumber",
                  "value": "{{customerNumber}}",
                  "disabled": true
                },
                {
                  "key": "matchMode",
                  "value": "exact"
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "Recherche par téléphone. Le numéro peut être saisi au format national français ou international E.164.\n\nScopes : customers:read et customers:pii:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('customerPhone')) {",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('customerPhone', '+33600000000');",
                  "    console.warn('customerPhone était vide : utilisation d’un numéro de test valide sans correspondance attendue.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Recherche client par téléphone - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Résultats de recherche valides', () => {",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.be.an('object');",
                  "        body.data.forEach(customer => pm.expect(customer.match.matchedOn).to.include('phone'));",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Fiche détaillée d'un client",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/{{customerId}}?include=shop,consents,billingAddress,partner",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "{{customerId}}"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "shop,consents,billingAddress,partner"
                }
              ]
            },
            "description": "Fiche client enrichie. Ajoutez internalComments à la valeur du paramètre include uniquement si le compte d’intégration possède le scope correspondant.\n\nScopes : customers:read ; customers:pii:read pour les données personnelles ; internal-comments:read pour le commentaire."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('customerId')) throw new Error('customerId est vide. Exécutez d’abord la liste, une recherche ou la création d’un client.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Fiche client - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Contrat de la fiche client valide', () => {",
                  "        pm.expect(body).to.include.keys('uuid', 'number', 'typeCustomer', 'guest', 'createdAt', 'updatedAt');",
                  "        pm.expect(body.uuid).to.equal(pm.variables.get('customerId'));",
                  "    });",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    if (body.email) target.set('customerEmail', body.email);",
                  "    if (body.phone) target.set('customerPhone', body.phone);",
                  "    if (body.number) target.set('customerNumber', body.number);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Historique commercial du client",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/{{customerId}}/history?include=sales,orders,reservations,sav,quotes,credits&limitPerType=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "{{customerId}}",
                "history"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "sales,orders,reservations,sav,quotes,credits"
                },
                {
                  "key": "from",
                  "value": "{{customerHistoryFrom}}",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "{{customerHistoryTo}}",
                  "disabled": true
                },
                {
                  "key": "limitPerType",
                  "value": "20"
                }
              ]
            },
            "description": "Historique consolidé. Les familles non autorisées par les scopes du compte d’intégration sont omises de history et hasMore. Activez from/to dans Params pour limiter la période.\n\nScopes : customers:read et au moins un parmi sales:read, orders:read, sav:read, customer-credits:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('customerId')) throw new Error('customerId est vide. Exécutez d’abord la liste, une recherche ou la création d’un client.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Historique client - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Enveloppe historique valide', () => {",
                  "        pm.expect(body).to.include.keys('customer', 'history', 'hasMore');",
                  "        pm.expect(body.customer.uuid).to.equal(pm.variables.get('customerId'));",
                  "        pm.expect(body.history).to.be.an('object');",
                  "        pm.expect(body.hasMore).to.be.an('object');",
                  "        pm.expect(Object.keys(body.history).sort()).to.eql(Object.keys(body.hasMore).sort());",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Crédits du client - Avoirs et acomptes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/{{customerId}}/credits?type={{customerCreditTypes}}&includeExpired=false&page=1&limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "{{customerId}}",
                "credits"
              ],
              "query": [
                {
                  "key": "type",
                  "value": "{{customerCreditTypes}}"
                },
                {
                  "key": "status",
                  "value": "active",
                  "disabled": true
                },
                {
                  "key": "includeExpired",
                  "value": "false"
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "Liste les avoirs et acomptes par défaut. Ajoutez gift_card à customerCreditTypes uniquement si le compte d’intégration possède aussi gift-cards:read.\n\nScopes : customers:read et customer-credits:read ; gift-cards:read si gift_card est demandé."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('customerId')) throw new Error('customerId est vide. Exécutez d’abord la liste, une recherche ou la création d’un client.');",
                  "if (!pm.variables.get('customerCreditTypes')) throw new Error('customerCreditTypes est vide. Utilisez par exemple note,deposit.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Crédits client - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Enveloppe des crédits valide', () => {",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.include.keys('page', 'limit', 'count', 'total', 'totalPages', 'hasNextPage');",
                  "        const expectedTypes = String(pm.variables.get('customerCreditTypes')).split(',').map(type => type.trim());",
                  "        body.data.forEach(credit => {",
                  "            pm.expect(credit).to.include.keys('uuid', 'type', 'amount', 'amountLeft', 'currency', 'status');",
                  "            pm.expect(expectedTypes).to.include(credit.type);",
                  "        });",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "07 - Clients - Mise à jour",
      "description": "Remplacement explicite d’une fiche client existante. Le garde-fou demande une confirmation locale avant chaque exécution.",
      "item": [
        {
          "name": "Remplacer un client",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"typeCustomer\": \"individual\",\n  \"civility\": null,\n  \"firstName\": \"{{customerFirstName}}\",\n  \"lastName\": \"{{customerLastName}}\",\n  \"email\": \"{{customerEmail}}\",\n  \"billingDifferentAddress\": false,\n  \"communication\": \"email\",\n  \"phoneCommunication\": false,\n  \"emailCommunication\": true,\n  \"personalInformations\": true,\n  \"shopId\": \"{{shopId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/customers/{{customerId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "{{customerId}}"
              ]
            },
            "description": "Remplace les informations modifiables du client indiqué par customerId. Les champs facultatifs absents sont remis à null ou à leur valeur par défaut. Vérifiez le corps complet avant d’activer confirmCustomerReplacement."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const customerId = pm.variables.get('customerId');",
                  "if (!customerId) throw new Error('customerId est vide. Sélectionnez explicitement la fiche à remplacer.');",
                  "if (String(pm.variables.get('confirmCustomerReplacement')).toLowerCase() !== 'true') throw new Error('Définissez confirmCustomerReplacement à true localement après avoir vérifié le client et le corps complet.');",
                  "if (!pm.variables.get('shopId') || !pm.variables.get('customerEmail')) throw new Error('shopId et customerEmail doivent être renseignés.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Mise à jour client - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Contrat de mise à jour client valide', () => {",
                  "        pm.expect(body).to.include.keys('uuid', 'number', 'typeCustomer', 'firstName', 'lastName', 'email', 'phone', 'address', 'postalCode', 'city', 'updatedAt');",
                  "        pm.expect(body.uuid).to.equal(pm.variables.get('customerId'));",
                  "        pm.expect(body.email).to.equal(String(pm.variables.get('customerEmail')).toLowerCase());",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "08 - Ventes et factures",
      "description": "Routes de lecture des ventes et documents de vente. Commencez par la liste enrichie : elle initialise automatiquement saleId, saleNumber, invoiceId et invoiceNumber à partir d’une vente possédant une facture lorsqu’elle en trouve une.\n\nScope requis : sales:read. Les coordonnées du client ne sont retournées qu’avec customers:pii:read.",
      "item": [
        {
          "name": "Liste des ventes - Enrichie",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sales?page={{page}}&limit={{limit}}&include=customer,shop,seller,invoice",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sales"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{page}}"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}"
                },
                {
                  "key": "include",
                  "value": "customer,shop,seller,invoice"
                },
                {
                  "key": "customerId",
                  "value": "{{customerId}}",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "completed",
                  "disabled": true
                },
                {
                  "key": "action",
                  "value": "sale",
                  "disabled": true
                },
                {
                  "key": "shopId",
                  "value": "{{shopId}}",
                  "disabled": true
                },
                {
                  "key": "number",
                  "value": "{{saleNumber}}",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "{{salesFrom}}",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "{{salesTo}}",
                  "disabled": true
                },
                {
                  "key": "updatedSince",
                  "value": "{{updatedSince}}",
                  "disabled": true
                }
              ]
            },
            "description": "Liste paginée et enrichie des ventes. Les filtres optionnels sont prêts dans Params mais désactivés. La première vente avec facture initialise les variables utilisées par les requêtes suivantes ; à défaut, la première vente initialise saleId.\n\nScopes : sales:read ; customers:pii:read pour email et téléphone."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Liste ventes - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Enveloppe paginée des ventes valide', () => {",
                  "        pm.expect(body).to.have.keys('data', 'pagination');",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.include.keys('page', 'limit', 'count', 'total', 'totalPages', 'hasNextPage');",
                  "    });",
                  "    body.data.forEach((sale) => {",
                  "        pm.expect(sale).to.include.keys('uuid', 'number', 'status', 'action', 'totalPrice', 'totalPriceExclTax', 'totalTaxes', 'totalDiscount', 'currency', 'createdAt', 'updatedAt');",
                  "        pm.expect(sale.uuid).to.be.a('string').and.not.empty;",
                  "        pm.expect(sale.currency).to.equal('EUR');",
                  "    });",
                  "    const selected = body.data.find((sale) => sale.invoice && sale.invoice.uuid) || body.data[0];",
                  "    if (selected) {",
                  "        const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "        if (!pm.variables.get('saleId')) target.set('saleId', selected.uuid);",
                  "        if (!pm.variables.get('saleNumber') && selected.number) target.set('saleNumber', selected.number);",
                  "        if (!pm.variables.get('customerId') && selected.customer && selected.customer.uuid) target.set('customerId', selected.customer.uuid);",
                  "        if (selected.invoice && selected.invoice.uuid) {",
                  "            if (!pm.variables.get('invoiceId')) target.set('invoiceId', selected.invoice.uuid);",
                  "            if (!pm.variables.get('invoiceNumber') && selected.invoice.number) target.set('invoiceNumber', selected.invoice.number);",
                  "        }",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Liste des ventes d’un client",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sales?customerId={{customerId}}&page=1&limit=20&include=customer,invoice",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sales"
              ],
              "query": [
                {
                  "key": "customerId",
                  "value": "{{customerId}}"
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "20"
                },
                {
                  "key": "include",
                  "value": "customer,invoice"
                }
              ]
            },
            "description": "Recherche les ventes rattachées au client public customerId. Exécutez d’abord une liste de clients ou la liste enrichie des ventes pour initialiser la variable.\n\nScopes : sales:read ; customers:pii:read pour email et téléphone."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('customerId')) throw new Error('customerId est vide. Exécutez d’abord 06 - Clients - Lecture / Liste des clients - Enrichie ou la liste enrichie des ventes.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Ventes du client - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Résultat client paginé', () => {",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.be.an('object');",
                  "    });",
                  "    body.data.forEach((sale) => {",
                  "        if (sale.customer) pm.expect(sale.customer.uuid).to.equal(pm.variables.get('customerId'));",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Détail complet d’une vente",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sales/{{saleId}}?include=customer,shop,seller,items,payments,discounts,invoice,origins",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sales",
                "{{saleId}}"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "customer,shop,seller,items,payments,discounts,invoice,origins"
                }
              ]
            },
            "description": "Retourne la vente consolidée avec ses relations, lignes, taxes, règlements et remises. Les coordonnées personnelles et les détails des crédits restent conditionnés par leurs scopes dédiés.\n\nScopes : sales:read ; customers:pii:read et customer-credits:read pour les informations conditionnelles."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('saleId')) throw new Error('saleId est vide. Exécutez d’abord Liste des ventes - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Détail vente - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const sale = pm.response.json();",
                  "    pm.test('Contrat du détail de vente valide', () => {",
                  "        pm.expect(sale).to.include.keys('uuid', 'number', 'status', 'action', 'totalPriceOrigin', 'totalPrice', 'totalPriceExclTax', 'totalTaxes', 'totalDiscount', 'deposit', 'currency', 'createdAt', 'updatedAt');",
                  "        pm.expect(sale.uuid).to.equal(pm.variables.get('saleId'));",
                  "        pm.expect(sale.currency).to.equal('EUR');",
                  "        pm.expect(sale.items).to.be.an('array');",
                  "        pm.expect(sale.payments).to.be.an('array');",
                  "        pm.expect(sale.discounts).to.be.an('array');",
                  "    });",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    if (sale.number) target.set('saleNumber', sale.number);",
                  "    if (sale.invoice && sale.invoice.uuid) {",
                  "        target.set('invoiceId', sale.invoice.uuid);",
                  "        if (sale.invoice.number) target.set('invoiceNumber', sale.invoice.number);",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Métadonnées de la facture",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sales/{{saleId}}/invoice",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sales",
                "{{saleId}}",
                "invoice"
              ]
            },
            "description": "Retourne les métadonnées publiques de la facture enregistrée sans générer ni télécharger le PDF.\n\nScope : sales:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('saleId')) throw new Error('saleId est vide. Exécutez d’abord Liste des ventes - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Métadonnées facture - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const invoice = pm.response.json();",
                  "    pm.test('Contrat des métadonnées de facture valide', () => {",
                  "        pm.expect(invoice).to.have.keys('uuid', 'number', 'saleId', 'type', 'status', 'issuedAt', 'totalInclTax', 'totalExclTax', 'totalTaxes', 'currency', 'downloadAvailable');",
                  "        pm.expect(invoice.saleId).to.equal(pm.variables.get('saleId'));",
                  "        pm.expect(invoice.type).to.equal('invoice');",
                  "        pm.expect(invoice.currency).to.equal('EUR');",
                  "        pm.expect(invoice.downloadAvailable).to.be.a('boolean');",
                  "    });",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('invoiceId', invoice.uuid);",
                  "    if (invoice.number) target.set('invoiceNumber', invoice.number);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Télécharger la facture - Pièce jointe",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/pdf"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/sales/{{saleId}}/invoice/download?download=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sales",
                "{{saleId}}",
                "invoice",
                "download"
              ],
              "query": [
                {
                  "key": "download",
                  "value": "true"
                }
              ]
            },
            "description": "Retourne le document officiel en PDF binaire avec Content-Disposition: attachment. Dans Postman, utilisez Save Response pour enregistrer le fichier. Une facture est prioritaire ; le ticket enregistré est utilisé lorsqu’aucune facture n’existe.\n\nScope : sales:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('saleId')) throw new Error('saleId est vide. Exécutez d’abord Liste des ventes - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Téléchargement facture - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const contentType = String(pm.response.headers.get('Content-Type') || '').toLowerCase();",
                  "    const disposition = String(pm.response.headers.get('Content-Disposition') || '').toLowerCase();",
                  "    const cacheControl = String(pm.response.headers.get('Cache-Control') || '').toLowerCase();",
                  "    const bytes = Array.from(pm.response.stream || []).slice(0, 5);",
                  "    const signature = String.fromCharCode(...bytes);",
                  "    pm.test('Réponse PDF binaire et non base64', () => {",
                  "        pm.expect(contentType).to.include('application/pdf');",
                  "        pm.expect(disposition).to.match(/^attachment;/);",
                  "        pm.expect(cacheControl).to.include('private');",
                  "        pm.expect(cacheControl).to.include('no-store');",
                  "        pm.expect(signature).to.equal('%PDF-');",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Afficher la facture - Inline",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/pdf"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/sales/{{saleId}}/invoice/download?download=false",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sales",
                "{{saleId}}",
                "invoice",
                "download"
              ],
              "query": [
                {
                  "key": "download",
                  "value": "false"
                }
              ]
            },
            "description": "Retourne le même PDF avec Content-Disposition: inline afin qu’un application web puisse l’afficher dans le navigateur.\n\nScope : sales:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('saleId')) throw new Error('saleId est vide. Exécutez d’abord Liste des ventes - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Affichage facture - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const disposition = String(pm.response.headers.get('Content-Disposition') || '').toLowerCase();",
                  "    const bytes = Array.from(pm.response.stream || []).slice(0, 5);",
                  "    const signature = String.fromCharCode(...bytes);",
                  "    pm.test('Réponse PDF inline valide', () => {",
                  "        pm.expect(disposition).to.match(/^inline;/);",
                  "        pm.expect(signature).to.equal('%PDF-');",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "09 - SAV",
      "description": "Routes de consultation et de suivi des dossiers SAV. Commencez par la liste enrichie : elle initialise automatiquement savId, savNumber et customerId pour les requêtes suivantes.\n\nScope requis : sav:read. Les coordonnées du client nécessitent customers:pii:read et les acomptes détaillés customer-credits:read.",
      "item": [
        {
          "name": "Liste des SAV - Enrichie",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sav?page={{page}}&limit={{limit}}&include=customer,shop,seller,quote,media",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sav"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{page}}"
                },
                {
                  "key": "limit",
                  "value": "{{limit}}"
                },
                {
                  "key": "include",
                  "value": "customer,shop,seller,quote,media"
                },
                {
                  "key": "customerId",
                  "value": "{{customerId}}",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "in_progress",
                  "disabled": true
                },
                {
                  "key": "shopId",
                  "value": "{{shopId}}",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "{{searchTerm}}",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "{{savFrom}}",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "{{savTo}}",
                  "disabled": true
                },
                {
                  "key": "updatedSince",
                  "value": "{{updatedSince}}",
                  "disabled": true
                }
              ]
            },
            "description": "Liste paginée des dossiers SAV avec les relations publiques disponibles. Les filtres optionnels sont prêts dans Params mais désactivés. La première fiche exploitable initialise savId, savNumber et customerId.\n\nStatuts autorisés : pending, quoting, quote, quote_accepted, refused, in_progress, ready, done, cancelled.\n\nScopes : sav:read ; customers:pii:read pour email et téléphone."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Liste SAV - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    const statuses = ['pending', 'quoting', 'quote', 'quote_accepted', 'refused', 'in_progress', 'ready', 'done', 'cancelled'];",
                  "    pm.test('Enveloppe paginée SAV valide', () => {",
                  "        pm.expect(body).to.have.keys('data', 'pagination');",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.include.keys('page', 'limit', 'count', 'total', 'totalPages', 'hasNextPage');",
                  "    });",
                  "    body.data.forEach((sav) => {",
                  "        pm.expect(sav).to.include.keys('uuid', 'number', 'status', 'typeToDo', 'nameReference', 'reference', 'code', 'serialNumber', 'price', 'deposit', 'remainingAmount', 'paid', 'currency', 'availabilityDate', 'createdAt', 'updatedAt');",
                  "        pm.expect(sav.uuid).to.be.a('string').and.not.empty;",
                  "        pm.expect(statuses).to.include(sav.status);",
                  "        pm.expect(sav.currency).to.equal('EUR');",
                  "    });",
                  "    const selected = body.data.find((sav) => sav.customer && sav.shop) || body.data[0];",
                  "    if (selected) {",
                  "        const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "        target.set('savId', selected.uuid);",
                  "        if (selected.number) target.set('savNumber', selected.number);",
                  "        if (selected.customer && selected.customer.uuid) target.set('customerId', selected.customer.uuid);",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Liste des SAV d’un client",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sav?customerId={{customerId}}&page=1&limit=20&include=customer,shop,quote",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sav"
              ],
              "query": [
                {
                  "key": "customerId",
                  "value": "{{customerId}}"
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "20"
                },
                {
                  "key": "include",
                  "value": "customer,shop,quote"
                }
              ]
            },
            "description": "Recherche les dossiers SAV rattachés au client public customerId. Exécutez d’abord la liste SAV enrichie ou une route Clients pour initialiser la variable.\n\nScopes : sav:read ; customers:pii:read pour email et téléphone."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('customerId')) throw new Error('customerId est vide. Exécutez d’abord 09 - SAV / Liste des SAV - Enrichie ou une route Clients.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('SAV du client - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Résultat SAV client paginé', () => {",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.be.an('object');",
                  "    });",
                  "    body.data.forEach((sav) => {",
                  "        if (sav.customer) pm.expect(sav.customer.uuid).to.equal(pm.variables.get('customerId'));",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Détail complet d’un SAV",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sav/{{savId}}?include=customer,shop,seller,contact,quote,credits,media",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sav",
                "{{savId}}"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "customer,shop,seller,contact,quote,credits,media"
                }
              ]
            },
            "description": "Retourne la fiche SAV consolidée, ses étapes de suivi, son produit, ses montants et les relations demandées. Les coordonnées personnelles et les acomptes détaillés sont conditionnés par leurs scopes dédiés.\n\nScopes : sav:read ; customers:pii:read et customer-credits:read pour les informations conditionnelles."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('savId')) throw new Error('savId est vide. Exécutez d’abord Liste des SAV - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Détail SAV - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const sav = pm.response.json();",
                  "    pm.test('Contrat du détail SAV valide', () => {",
                  "        pm.expect(sav).to.include.keys('uuid', 'number', 'status', 'typeToDo', 'toDo', 'product', 'price', 'priceExclTax', 'deposit', 'remainingAmount', 'paid', 'currency', 'dates', 'warranty', 'createdAt', 'updatedAt');",
                  "        pm.expect(sav.uuid).to.equal(pm.variables.get('savId'));",
                  "        pm.expect(sav.currency).to.equal('EUR');",
                  "        pm.expect(sav.product).to.include.keys('nameReference', 'reference', 'code', 'serialNumber', 'material', 'characteristics', 'weight', 'condition');",
                  "        pm.expect(sav.dates).to.include.keys('deposit', 'availability', 'delivery', 'reception', 'return', 'validation', 'warranty');",
                  "    });",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    if (sav.number) target.set('savNumber', sav.number);",
                  "    if (sav.customer && sav.customer.uuid) target.set('customerId', sav.customer.uuid);",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Journal des statuts du SAV",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sav/{{savId}}/log?page=1&limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sav",
                "{{savId}}",
                "log"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "from",
                  "value": "{{savFrom}}",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "{{savTo}}",
                  "disabled": true
                }
              ]
            },
            "description": "Retourne l’historique public paginé des changements de statut du dossier, du plus récent au plus ancien. Activez from et to dans Params pour filtrer la période.\n\nScope : sav:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('savId')) throw new Error('savId est vide. Exécutez d’abord Liste des SAV - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Journal SAV - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Contrat du journal SAV valide', () => {",
                  "        pm.expect(body).to.have.keys('sav', 'data', 'pagination');",
                  "        pm.expect(body.sav).to.include.keys('uuid', 'number', 'status');",
                  "        pm.expect(body.sav.uuid).to.equal(pm.variables.get('savId'));",
                  "        pm.expect(body.data).to.be.an('array');",
                  "        pm.expect(body.pagination).to.include.keys('page', 'limit', 'count', 'total', 'totalPages', 'hasNextPage');",
                  "    });",
                  "    body.data.forEach((event) => {",
                  "        pm.expect(event).to.have.keys('uuid', 'occurredAt', 'type', 'previousStatus', 'status', 'label');",
                  "        pm.expect(event.type).to.equal('status_changed');",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Télécharger le bon SAV - Pièce jointe",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/pdf"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/sav/{{savId}}/slip?download=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sav",
                "{{savId}}",
                "slip"
              ],
              "query": [
                {
                  "key": "download",
                  "value": "true"
                }
              ]
            },
            "description": "Génère le bon SAV natif et retourne le PDF binaire avec Content-Disposition: attachment. Dans Postman, utilisez Save Response pour enregistrer le fichier.\n\nScope : sav:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('savId')) throw new Error('savId est vide. Exécutez d’abord Liste des SAV - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Téléchargement bon SAV - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const contentType = String(pm.response.headers.get('Content-Type') || '').toLowerCase();",
                  "    const disposition = String(pm.response.headers.get('Content-Disposition') || '').toLowerCase();",
                  "    const cacheControl = String(pm.response.headers.get('Cache-Control') || '').toLowerCase();",
                  "    const contentOptions = String(pm.response.headers.get('X-Content-Type-Options') || '').toLowerCase();",
                  "    const bytes = Array.from(pm.response.stream || []).slice(0, 5);",
                  "    const signature = String.fromCharCode(...bytes);",
                  "    pm.test('Bon SAV PDF binaire sécurisé', () => {",
                  "        pm.expect(contentType).to.include('application/pdf');",
                  "        pm.expect(disposition).to.match(/^attachment;/);",
                  "        pm.expect(cacheControl).to.include('private');",
                  "        pm.expect(cacheControl).to.include('no-store');",
                  "        pm.expect(contentOptions).to.equal('nosniff');",
                  "        pm.expect(signature).to.equal('%PDF-');",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Afficher le bon SAV - Inline",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/pdf"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/sav/{{savId}}/slip?download=false",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sav",
                "{{savId}}",
                "slip"
              ],
              "query": [
                {
                  "key": "download",
                  "value": "false"
                }
              ]
            },
            "description": "Retourne le même bon SAV PDF avec Content-Disposition: inline pour permettre son affichage direct dans un navigateur.\n\nScope : sav:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('savId')) throw new Error('savId est vide. Exécutez d’abord Liste des SAV - Enrichie.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Affichage bon SAV - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const contentType = String(pm.response.headers.get('Content-Type') || '').toLowerCase();",
                  "    const disposition = String(pm.response.headers.get('Content-Disposition') || '').toLowerCase();",
                  "    const bytes = Array.from(pm.response.stream || []).slice(0, 5);",
                  "    const signature = String.fromCharCode(...bytes);",
                  "    pm.test('Bon SAV PDF inline valide', () => {",
                  "        pm.expect(contentType).to.include('application/pdf');",
                  "        pm.expect(disposition).to.match(/^inline;/);",
                  "        pm.expect(signature).to.equal('%PDF-');",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "11 - Cartes cadeaux",
      "description": "Consultation et débit d’une carte cadeau Commerce 360. Le numéro retourné est masqué. Le débit modifie réellement l’environnement visé et exige une clé d’idempotence.",
      "item": [
        {
          "name": "Consulter la carte cadeau",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/gift-cards/{{giftCardId}}?include={{giftCardInclude}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "gift-cards",
                "{{giftCardId}}"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "{{giftCardInclude}}",
                  "description": "customer,origin,usages,invoice ; les blocs sans scope complémentaire sont omis"
                }
              ]
            },
            "description": "Retourne le détail public et le solde faisant foi. Le numéro reste masqué. Les relations customer nécessitent customers:read ; origin, usages et invoice nécessitent sales:read. Scope principal : gift-cards:read."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get('giftCardId')) throw new Error('giftCardId est vide. Émettez une carte ou renseignez son UUID public.');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Détail carte cadeau - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Contrat public du détail valide', () => {",
                  "        pm.expect(body.uuid).to.equal(pm.variables.get('giftCardId'));",
                  "        pm.expect(body.type).to.equal('gift_card');",
                  "        pm.expect(body.currency).to.equal('EUR');",
                  "        pm.expect(body.number).to.include('*');",
                  "        pm.expect(body).not.to.have.property('code');",
                  "        pm.expect(body.amountLeft).to.be.a('number');",
                  "    });",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('giftCardNumber', body.number || '');",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Débiter ou rejouer la carte cadeau",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{giftCardDebitIdempotencyKey}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"operation\": \"debit\",\n  \"amount\": {{giftCardDebitAmount}},\n  \"currency\": \"EUR\",\n  \"externalReference\": \"{{giftCardDebitExternalReference}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/gift-cards/{{giftCardId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "gift-cards",
                "{{giftCardId}}"
              ]
            },
            "description": "Débite atomiquement le solde de la carte. giftCardUsageSaleId est ajouté au corps seulement lorsqu’il est renseigné et doit alors désigner la vente qui reçoit ce paiement, pas la vente d’émission conservée dans giftCardOriginSaleId.\n\nLa première exécution génère et conserve la référence et Idempotency-Key. Une nouvelle exécution identique rejoue le débit ; modifier le corps avec la même clé provoque volontairement IDEMPOTENCY_CONFLICT. Écriture financière réelle. Scope : gift-cards:write."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "const giftCardId = String(pm.variables.get('giftCardId') || '').trim();",
                  "if (!giftCardId) throw new Error('giftCardId est vide. Émettez une carte ou renseignez son UUID public.');",
                  "const amount = Number(pm.variables.get('giftCardDebitAmount'));",
                  "if (!Number.isFinite(amount) || amount <= 0 || amount > 99999999.99 || Math.abs((amount * 100) - Math.round(amount * 100)) > 1e-8) {",
                  "    throw new Error('giftCardDebitAmount doit être strictement positif et comporter au maximum deux décimales.');",
                  "}",
                  "if (!pm.variables.get('giftCardDebitIdempotencyKey')) {",
                  "    target.set('giftCardDebitIdempotencyKey', 'postman-gift-debit-' + pm.variables.replaceIn('{{$guid}}'));",
                  "}",
                  "if (!pm.variables.get('giftCardDebitExternalReference')) {",
                  "    target.set('giftCardDebitExternalReference', 'POSTMAN-GIFT-USE-' + Date.now());",
                  "}",
                  "const requestPayload = {",
                  "    operation: 'debit',",
                  "    amount,",
                  "    currency: 'EUR',",
                  "    externalReference: pm.variables.get('giftCardDebitExternalReference')",
                  "};",
                  "const saleId = String(pm.variables.get('giftCardUsageSaleId') || '').trim();",
                  "if (saleId) requestPayload.saleId = saleId;",
                  "pm.request.body.raw = JSON.stringify(requestPayload, null, 2);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Débit carte cadeau - HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "    const body = pm.response.json();",
                  "    pm.test('Contrat public du débit valide', () => {",
                  "        pm.expect(body).to.include.keys('uuid', 'number', 'type', 'debitedAmount', 'amountLeft', 'currency', 'status', 'updatedAt');",
                  "        pm.expect(body.uuid).to.equal(pm.variables.get('giftCardId'));",
                  "        pm.expect(body.type).to.equal('gift_card');",
                  "        pm.expect(body.debitedAmount).to.equal(Number(pm.variables.get('giftCardDebitAmount')));",
                  "        pm.expect(body.amountLeft).to.be.at.least(0);",
                  "        pm.expect(body.status).to.be.oneOf(['active', 'used']);",
                  "        pm.expect(body.number).to.include('*');",
                  "        pm.expect(body).not.to.have.property('code');",
                  "    });",
                  "    const target = pm.environment && Object.keys(pm.environment.toObject()).length > 0 ? pm.environment : pm.collectionVariables;",
                  "    target.set('giftCardNumber', body.number || '');",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    }
  ]
}
