{
  "openapi": "3.1.0",
  "info": {
    "title": "Naskel API",
    "version": "1.0.0",
    "description": "API REST v1 de Naskel. Lecture des ressources (factures, devis, contacts, entreprises, opportunités, produits) et écritures ciblées (création de documents, ajustement et mouvements de stock). Spécification dérivée de la documentation publique (https://naskel.io/docs/api). Pas de PATCH ni de DELETE dans l'API publique.",
    "contact": { "name": "Documentation Naskel", "url": "https://naskel.io/docs/api/introduction" }
  },
  "servers": [
    { "url": "https://app.naskel.io/api/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Factures" },
    { "name": "Entreprises" },
    { "name": "Devis" },
    { "name": "Contacts" },
    { "name": "Opportunités" },
    { "name": "Produits" },
    { "name": "Mouvements de stock" }
  ],
  "paths": {
    "/invoices": {
      "get": {
        "tags": ["Factures"],
        "summary": "Lister les factures",
        "description": "Scope requis : `read:invoices`.",
        "parameters": [
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/offset" },
          {
            "name": "status",
            "in": "query",
            "schema": { "$ref": "#/components/schemas/InvoiceStatus" }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste paginée de factures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Invoice" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Factures"],
        "summary": "Créer une facture",
        "description": "Crée une facture au statut `DRAFT`. Son numéro est attribué automatiquement et son échéance fixée à 30 jours. Scope requis : `write:invoices`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/InvoiceCreate" } }
          }
        },
        "responses": {
          "201": {
            "description": "Facture créée.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Invoice" } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/invoices/{id}": {
      "get": {
        "tags": ["Factures"],
        "summary": "Lire une facture",
        "description": "Scope requis : `read:invoices`.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "La facture.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Invoice" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/companies": {
      "get": {
        "tags": ["Entreprises"],
        "summary": "Lister les entreprises",
        "description": "Scope requis : `read:contacts` (les entreprises n'ont pas de scope propre).",
        "parameters": [
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/offset" },
          { "$ref": "#/components/parameters/q" }
        ],
        "responses": {
          "200": {
            "description": "Liste paginée d'entreprises.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Company" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Entreprises"],
        "summary": "Créer une entreprise",
        "description": "Scope requis : `write:contacts`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CompanyCreate" } }
          }
        },
        "responses": {
          "201": {
            "description": "Entreprise créée.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Company" } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/companies/{id}": {
      "get": {
        "tags": ["Entreprises"],
        "summary": "Lire une entreprise",
        "description": "Scope requis : `read:contacts`.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "L'entreprise.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Company" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/quotes": {
      "get": {
        "tags": ["Devis"],
        "summary": "Lister les devis",
        "description": "Scope requis : `read:quotes`.",
        "parameters": [
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/offset" },
          { "name": "status", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Liste paginée de devis.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Quote" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Devis"],
        "summary": "Créer un devis",
        "description": "Scope requis : `write:quotes`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/QuoteCreate" } }
          }
        },
        "responses": {
          "201": {
            "description": "Devis créé.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Quote" } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/quotes/{id}": {
      "get": {
        "tags": ["Devis"],
        "summary": "Lire un devis",
        "description": "Renvoie le devis avec ses lignes. Scope requis : `read:quotes`.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "Le devis détaillé.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/QuoteDetail" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/quotes/{id}/send": {
      "post": {
        "tags": ["Devis"],
        "summary": "Envoyer un devis",
        "description": "Envoie le devis au client. Le corps est vide. Le devis passe de `DRAFT` à `SENT`. Scope requis : `write:quotes`.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "Devis envoyé.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Quote" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/contacts": {
      "get": {
        "tags": ["Contacts"],
        "summary": "Lister les contacts",
        "description": "Scope requis : `read:contacts`.",
        "parameters": [
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/offset" },
          { "$ref": "#/components/parameters/q" }
        ],
        "responses": {
          "200": {
            "description": "Liste paginée de contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Contact" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Contacts"],
        "summary": "Créer un contact",
        "description": "Scope requis : `write:contacts`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/ContactCreate" } }
          }
        },
        "responses": {
          "201": {
            "description": "Contact créé.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Contact" } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/contacts/{id}": {
      "get": {
        "tags": ["Contacts"],
        "summary": "Lire un contact",
        "description": "Renvoie le contact avec un objet `company` enrichi. Scope requis : `read:contacts`.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "Le contact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/ContactDetail" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/opportunities": {
      "get": {
        "tags": ["Opportunités"],
        "summary": "Lister les opportunités",
        "description": "Scope requis : `read:opportunities`.",
        "parameters": [
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/offset" },
          { "name": "status", "in": "query", "schema": { "$ref": "#/components/schemas/OpportunityStatus" } }
        ],
        "responses": {
          "200": {
            "description": "Liste paginée d'opportunités.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Opportunity" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Opportunités"],
        "summary": "Créer une opportunité",
        "description": "Scope requis : `write:opportunities`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/OpportunityCreate" } }
          }
        },
        "responses": {
          "201": {
            "description": "Opportunité créée.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Opportunity" } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/opportunities/{id}": {
      "get": {
        "tags": ["Opportunités"],
        "summary": "Lire une opportunité",
        "description": "Ajoute `quoteSentAt` aux champs de liste. Scope requis : `read:opportunities`.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "L'opportunité.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Opportunity" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/products": {
      "get": {
        "tags": ["Produits"],
        "summary": "Lister les produits",
        "description": "Scope requis : `read:products`.",
        "parameters": [
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/offset" },
          { "name": "trackStock", "in": "query", "schema": { "type": "boolean" }, "description": "Filtre les produits suivis (ou non) en stock." },
          { "name": "supplierId", "in": "query", "schema": { "type": "string" }, "description": "Filtre par fournisseur." },
          { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Recherche sur nom, référence et code-barres." }
        ],
        "responses": {
          "200": {
            "description": "Liste paginée de produits.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/products/{id}": {
      "get": {
        "tags": ["Produits"],
        "summary": "Lire un produit",
        "description": "Scope requis : `read:products`.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "Le produit.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Product" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/products/{id}/adjust": {
      "post": {
        "tags": ["Produits"],
        "summary": "Ajuster un stock (inventaire physique)",
        "description": "Transmet la quantité physiquement constatée ; le serveur calcule le delta. Idempotent (en-tête `Idempotency-Key`). Émet l'événement webhook `stock.adjusted`. Scope requis : `write:products`.",
        "parameters": [
          { "$ref": "#/components/parameters/id" },
          { "$ref": "#/components/parameters/idempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/ProductAdjust" } }
          }
        },
        "responses": {
          "200": {
            "description": "Ajustement appliqué.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/AdjustResult" } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/stock-movements": {
      "post": {
        "tags": ["Mouvements de stock"],
        "summary": "Enregistrer des mouvements de stock",
        "description": "Enregistre un lot de 1 à 200 mouvements. Quantité signée (`+` entrée, `−` sortie). Traitement partiel : voir `applied` et `errors`. Idempotent (en-tête `Idempotency-Key`). Émet `stock.movement` pour chaque mouvement appliqué. Scope requis : `write:products`.",
        "parameters": [{ "$ref": "#/components/parameters/idempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/StockMovementsRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "Lot traité. Chaque mouvement réussit ou échoue indépendamment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/StockMovementsResult" } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clé API publique, préfixée `nsk_live_`, dans l'en-tête `Authorization: Bearer <clé>`."
      }
    },
    "parameters": {
      "id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "description": "Nombre d'éléments par page (défaut 20, maximum 100).",
        "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 100 }
      },
      "offset": {
        "name": "offset",
        "in": "query",
        "description": "Nombre d'éléments à sauter (défaut 0).",
        "schema": { "type": "integer", "default": 0, "minimum": 0 }
      },
      "q": {
        "name": "q",
        "in": "query",
        "description": "Recherche plein texte (champs selon la ressource).",
        "schema": { "type": "string" }
      },
      "idempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Clé unique d'idempotence. Rejouer la même clé renvoie le même résultat sans ré-exécuter l'opération.",
        "schema": { "type": "string" }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Requête invalide (`invalid_request`) ou corps invalide (`invalid_body`, voir `details`).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Authentification absente ou invalide (`missing_authorization`, `invalid_key_format`, `invalid_key`, `revoked_key`, `expired_key`).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "Scope requis absent (`insufficient_scope`).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Ressource introuvable (`not_found`).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Conflict": {
        "description": "Action impossible dans l'état actuel de la ressource (`conflict`).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "UnprocessableEntity": {
        "description": "Validation métier échouée (par exemple `stock_adjust_failed`).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Limite de débit dépassée (`rate_limited`). Voir l'en-tête `Retry-After`.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Pagination": {
        "type": "object",
        "properties": {
          "total": { "type": "integer" },
          "limit": { "type": "integer" },
          "offset": { "type": "integer" }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" },
              "details": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "path": { "type": "string" },
                    "message": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      },
      "CompanyRef": {
        "type": ["object", "null"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" }
        }
      },
      "InvoiceStatus": {
        "type": "string",
        "enum": ["DRAFT", "SENT", "PARTIAL", "PAID", "OVERDUE", "CANCELLED"]
      },
      "OpportunityStatus": {
        "type": "string",
        "enum": ["DRAFT", "QUOTE_SENT", "WAITING_REPLY", "REPLIED", "HOT", "WON", "LOST"]
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "number": { "type": "string" },
          "title": { "type": "string" },
          "status": { "$ref": "#/components/schemas/InvoiceStatus" },
          "type": { "type": "string" },
          "clientName": { "type": "string" },
          "clientCompany": { "type": ["string", "null"] },
          "clientEmail": { "type": "string" },
          "taxRate": { "type": "number" },
          "discountPercent": { "type": "number" },
          "reverseCharge": { "type": "boolean" },
          "issuedAt": { "type": ["string", "null"], "format": "date-time" },
          "dueAt": { "type": ["string", "null"], "format": "date-time" },
          "paidAt": { "type": ["string", "null"], "format": "date-time" },
          "paidAmount": { "type": "number" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "LineCreate": {
        "type": "object",
        "required": ["title", "quantity", "unitPrice"],
        "properties": {
          "title": { "type": "string", "minLength": 1, "maxLength": 500 },
          "quantity": { "type": "number", "exclusiveMinimum": 0 },
          "unitPrice": { "type": "number", "minimum": 0 },
          "taxRate": { "type": "number", "minimum": 0, "maximum": 100 }
        }
      },
      "InvoiceCreate": {
        "type": "object",
        "required": ["clientName", "clientEmail", "lines"],
        "properties": {
          "clientName": { "type": "string", "minLength": 1, "maxLength": 200 },
          "clientEmail": { "type": "string", "format": "email", "maxLength": 200 },
          "currency": { "type": "string", "minLength": 3, "maxLength": 3, "default": "EUR" },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": { "$ref": "#/components/schemas/LineCreate" }
          }
        }
      },
      "Company": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "siret": { "type": ["string", "null"] },
          "vatNumber": { "type": ["string", "null"] },
          "address": { "type": ["string", "null"] },
          "phone": { "type": ["string", "null"] },
          "isSupplier": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "CompanyCreate": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "minLength": 1, "maxLength": 200 },
          "siret": { "type": "string", "maxLength": 32 },
          "vatNumber": { "type": "string", "maxLength": 32 },
          "address": { "type": "string", "maxLength": 1000 }
        }
      },
      "Quote": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "number": { "type": ["string", "null"] },
          "title": { "type": "string" },
          "status": { "type": "string" },
          "clientName": { "type": "string" },
          "clientCompany": { "type": ["string", "null"] },
          "clientEmail": { "type": "string" },
          "taxRate": { "type": "number" },
          "validityDays": { "type": "integer" },
          "currency": { "type": "string" },
          "opportunityId": { "type": ["string", "null"] },
          "issuedAt": { "type": ["string", "null"], "format": "date-time" },
          "sentAt": { "type": ["string", "null"], "format": "date-time" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "QuoteLine": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "position": { "type": "integer" },
          "description": { "type": "string" },
          "quantity": { "type": "number" },
          "unitPrice": { "type": "number" },
          "unit": { "type": ["string", "null"] },
          "discountPercent": { "type": "number" },
          "taxRate": { "type": "number" }
        }
      },
      "QuoteDetail": {
        "allOf": [
          { "$ref": "#/components/schemas/Quote" },
          {
            "type": "object",
            "properties": {
              "clientAddress": { "type": ["string", "null"] },
              "clientSiret": { "type": ["string", "null"] },
              "clientVatNumber": { "type": ["string", "null"] },
              "introText": { "type": ["string", "null"] },
              "terms": { "type": ["string", "null"] },
              "notes": { "type": ["string", "null"] },
              "signedAt": { "type": ["string", "null"], "format": "date-time" },
              "lines": { "type": "array", "items": { "$ref": "#/components/schemas/QuoteLine" } }
            }
          }
        ]
      },
      "QuoteCreate": {
        "type": "object",
        "required": ["clientName", "clientEmail", "lines"],
        "properties": {
          "clientName": { "type": "string", "minLength": 1, "maxLength": 200 },
          "clientEmail": { "type": "string", "format": "email", "maxLength": 200 },
          "currency": { "type": "string", "minLength": 3, "maxLength": 3, "default": "EUR" },
          "opportunityId": { "type": "string" },
          "contactId": { "type": "string" },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": { "$ref": "#/components/schemas/LineCreate" }
          }
        }
      },
      "Contact": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "email": { "type": "string" },
          "name": { "type": "string" },
          "phone": { "type": ["string", "null"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "company": { "$ref": "#/components/schemas/CompanyRef" }
        }
      },
      "ContactDetail": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "email": { "type": "string" },
          "name": { "type": "string" },
          "phone": { "type": ["string", "null"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "company": {
            "type": ["object", "null"],
            "properties": {
              "id": { "type": "string" },
              "name": { "type": "string" },
              "address": { "type": ["string", "null"] },
              "phone": { "type": ["string", "null"] },
              "siret": { "type": ["string", "null"] },
              "vatNumber": { "type": ["string", "null"] },
              "isSupplier": { "type": "boolean" }
            }
          }
        }
      },
      "ContactCreate": {
        "type": "object",
        "required": ["name", "email"],
        "properties": {
          "name": { "type": "string", "minLength": 1, "maxLength": 200 },
          "email": { "type": "string", "format": "email", "maxLength": 200 },
          "phone": { "type": "string", "maxLength": 32 },
          "companyId": { "type": "string" }
        }
      },
      "Opportunity": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string" },
          "status": { "$ref": "#/components/schemas/OpportunityStatus" },
          "amount": { "type": ["number", "null"] },
          "priority": { "type": ["string", "null"] },
          "contactEmail": { "type": ["string", "null"] },
          "companyId": { "type": ["string", "null"] },
          "company": { "$ref": "#/components/schemas/CompanyRef" },
          "lastContactAt": { "type": ["string", "null"], "format": "date-time" },
          "nextFollowUpAt": { "type": ["string", "null"], "format": "date-time" },
          "wonAt": { "type": ["string", "null"], "format": "date-time" },
          "quoteSentAt": { "type": ["string", "null"], "format": "date-time" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "OpportunityCreate": {
        "type": "object",
        "required": ["title"],
        "properties": {
          "title": { "type": "string", "minLength": 1, "maxLength": 200 },
          "amount": { "type": "number", "minimum": 0 },
          "contactId": { "type": "string" },
          "companyId": { "type": "string" },
          "status": { "$ref": "#/components/schemas/OpportunityStatus" }
        }
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "reference": { "type": ["string", "null"] },
          "barcode": { "type": ["string", "null"] },
          "unit": { "type": ["string", "null"] },
          "unitPrice": { "type": "number", "description": "Prix de vente unitaire, dans l'unité monétaire de l'espace (euros)." },
          "costPrice": { "type": "number", "description": "Coût d'achat unitaire, même unité." },
          "taxRate": { "type": "number" },
          "supplierId": { "type": ["string", "null"] },
          "trackStock": { "type": "boolean" },
          "stockQuantity": { "type": ["number", "null"] },
          "stockThreshold": { "type": ["number", "null"] },
          "stockState": { "type": "string", "enum": ["ok", "low", "out", "untracked"] },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "ProductAdjust": {
        "type": "object",
        "required": ["countedQuantity"],
        "properties": {
          "countedQuantity": { "type": "number", "minimum": 0, "description": "Quantité physiquement constatée." },
          "note": { "type": "string", "maxLength": 280 }
        }
      },
      "AdjustResult": {
        "type": "object",
        "properties": {
          "productId": { "type": "string" },
          "before": { "type": "number" },
          "after": { "type": "number" },
          "delta": { "type": "number" },
          "movementId": { "type": "string" }
        }
      },
      "StockMovementsRequest": {
        "type": "object",
        "required": ["movements"],
        "properties": {
          "movements": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "object",
              "required": ["productId", "quantity"],
              "properties": {
                "productId": { "type": "string" },
                "quantity": { "type": "number", "description": "Signée : positive pour une entrée, négative pour une sortie." },
                "reason": { "type": "string", "maxLength": 280 },
                "refKind": { "type": "string", "maxLength": 60 },
                "refId": { "type": "string", "maxLength": 120 },
                "unitCost": { "type": "number" }
              }
            }
          }
        }
      },
      "StockMovementsResult": {
        "type": "object",
        "properties": {
          "applied": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "productId": { "type": "string" },
                "before": { "type": "number" },
                "after": { "type": "number" },
                "delta": { "type": "number" }
              }
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "productId": { "type": "string" },
                "error": { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}
