[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"theme-db":3,"$f9yZicxDnGVUiV0Dne4_3u9_jeudeUYciELqfy-xDJDI":22,"$fKnz2vuX4bZz1LbUTiuFsvSZ3e07l5_5fqNYp4Tzdhi8":60,"megamenu":141,"header-db":197,"footer-db":210,"$fiEjmlWTdS1jYa4ToaMebWM2sGOB2u1Fwlbn-n5mng38":226},{"theme":4},{"colors":5,"typography":13,"ui":17,"defaultColorMode":21},{"primary":6,"secondary":7,"background":8,"foreground":9,"muted":10,"headerBg":11,"footerBg":12,"topBarBg":9,"topBarText":11},"#4F46E5","#0D9488","#F9FAFB","#111827","#6B7280","#ffffff","#020617",{"fontFamily":14,"fontUrl":15,"baseFontSize":16},"Inter, system-ui, sans-serif","https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Inter:wght@400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;0,800;0,900;1,400;1,700&display=swap","16px",{"borderRadius":18,"contentWidth":19,"shadow":20},"lg","7xl",true,"light",{"title":23,"slug":24,"metaDescription":25,"category":26,"tags":27,"difficulty":34,"psVersions":35,"content":38,"faq":39,"tldr":55,"readingTime":56,"generatedAt":57,"publishDate":57,"relatedArticles":58,"sourceCategory":59},"Personnaliser les produits sur la facture PrestaShop : numéro de série et champs custom","personnaliser-produits-facture-prestashop-numero-serie","Ajoutez des numéros de série, codes ou informations personnalisées aux produits sur vos factures PrestaShop. Guide technique complet avec override PDF.","commandes",[28,29,30,31,32,33],"facture","PDF","numéro de série","override","HTMLTemplateInvoice","customization","avance",[36,37],"1.7","8.x","\u003Ch1>Personnaliser les produits sur la facture PrestaShop : numéro de série et champs custom\u003C\u002Fh1>\n\u003Cp>Dans le cadre de certaines activités (électronique, logiciels, équipements industriels), il est indispensable d'afficher des informations spécifiques à chaque produit directement sur la facture : numéro de série, code de licence, lot de fabrication, etc. PrestaShop ne propose pas cette fonctionnalité nativement, mais l'architecture du système de facturation permet de l'implémenter proprement.\u003C\u002Fp>\n\u003Ch2>Comprendre l'architecture de facturation PrestaShop\u003C\u002Fh2>\n\u003Cp>La génération des factures PDF dans PrestaShop repose sur la classe \u003Ccode>HTMLTemplateInvoice\u003C\u002Fcode>, située dans \u003Ccode>classes\u002Fpdf\u002FHTMLTemplateInvoice.php\u003C\u002Fcode>. Cette classe utilise des templates Smarty stockés dans \u003Ccode>pdf\u002F\u003C\u002Fcode> pour construire le rendu HTML qui sera ensuite converti en PDF.\u003C\u002Fp>\n\u003Cp>Le fichier clé pour la liste des produits est :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>**PrestaShop 1.7** : `pdf\u002Finvoice.product-tab.tpl`\u003C\u002Fli>\n\u003Cli>**PrestaShop 8.x** : même emplacement, avec quelques ajustements de variables Smarty\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>La méthode \u003Ccode>getContent()\u003C\u002Fcode> de \u003Ccode>HTMLTemplateInvoice\u003C\u002Fcode> prépare les données produits via \u003Ccode>$order->getProducts()\u003C\u002Fcode>, puis les injecte dans le template Smarty.\u003C\u002Fp>\n\u003Ch2>Étape 1 : Choisir la bonne approche pour stocker l'information\u003C\u002Fh2>\n\u003Cp>Avant de modifier la facture, il faut déterminer \u003Cstrong>où\u003C\u002Fstrong> stocker le numéro de série ou le code personnalisé. Plusieurs approches existent :\u003C\u002Fp>\n\u003Ch3>Option A : Utiliser le champ référence produit\u003C\u002Fh3>\n\u003Cp>Si votre numéro de série est unique par produit (pas par commande), le champ \u003Ccode>reference\u003C\u002Fcode> de \u003Ccode>ps_product\u003C\u002Fcode> peut suffire. C'est la solution la plus simple : ce champ est déjà disponible dans le template de facture via \u003Ccode>$product.product_reference\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch3>Option B : Utiliser les personnalisations produit (customizations)\u003C\u002Fh3>\n\u003Cp>PrestaShop gère nativement les personnalisations produit (champs texte ou fichier). Si le numéro de série doit être saisi au moment de la commande, cette approche est pertinente. Les personnalisations sont stockées dans \u003Ccode>ps_customization\u003C\u002Fcode> et \u003Ccode>ps_customized_data\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch3>Option C : Ajouter un champ custom sur le détail de commande\u003C\u002Fh3>\n\u003Cp>Pour les numéros de série attribués \u003Cstrong>après\u003C\u002Fstrong> la commande (cas le plus fréquent), il faut étendre la table \u003Ccode>ps_order_detail\u003C\u002Fcode> avec une colonne supplémentaire. C'est l'approche la plus robuste.\u003C\u002Fp>\n\u003Ch2>Étape 2 : Étendre la table order_detail (Option C)\u003C\u002Fh2>\n\u003Cp>Créez un module qui ajoute la colonne nécessaire :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\n\u002F\u002F modules\u002Fmyserials\u002Fmyserials.php\npublic function install()\n{\n    return parent::install()\n        && $this-&gt;registerHook('actionOrderDetail')\n        && $this-&gt;registerHook('displayAdminOrderContentOrder')\n        && Db::getInstance()-&gt;execute('\n            ALTER TABLE `' . _DB_PREFIX_ . 'order_detail`\n            ADD `serial_number` VARCHAR(255) DEFAULT NULL\n        ');\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Étape 3 : Interface d'administration pour saisir le numéro de série\u003C\u002Fh2>\n\u003Cp>Hookez-vous sur \u003Ccode>displayAdminOrderContentOrder\u003C\u002Fcode> (PrestaShop 1.7) ou \u003Ccode>displayAdminOrderMain\u003C\u002Fcode> (PrestaShop 8.x) pour ajouter un formulaire de saisie dans le back-office :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\npublic function hookDisplayAdminOrderMain(array $params)\n{\n    $orderId = $params['id_order'];\n    $orderDetails = OrderDetail::getList((int) $orderId);\n    \n    foreach ($orderDetails as &$detail) {\n        $detail['serial_number'] = Db::getInstance()-&gt;getValue(\n            'SELECT serial_number FROM `' . _DB_PREFIX_ . 'order_detail`\n            WHERE id_order_detail = ' . (int) $detail['id_order_detail']\n        );\n    }\n    \n    $this-&gt;context-&gt;smarty-&gt;assign([\n        'order_details' =&gt; $orderDetails,\n        'serial_save_url' =&gt; $this-&gt;context-&gt;link-&gt;getAdminLink('AdminModules', true, [], [\n            'configure' =&gt; $this-&gt;name,\n            'action' =&gt; 'saveSerial',\n        ]),\n    ]);\n    \n    return $this-&gt;display(__FILE__, 'views\u002Ftemplates\u002Fhook\u002Fadmin-order-serials.tpl');\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Étape 4 : Override du template de facture PDF\u003C\u002Fh2>\n\u003Cp>Copiez le template dans votre thème pour l'overrider proprement :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">\n# PrestaShop 8.x\ncp pdf\u002Finvoice.product-tab.tpl themes\u002Fvotre-theme\u002Fpdf\u002Finvoice.product-tab.tpl\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Modifiez le template pour afficher le numéro de série :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-smarty\">\n{* Dans la boucle des produits *}\n{foreach $order_details as $order_detail}\n&lt;tr&gt;\n    &lt;td&gt;{$order_detail.product_name}&lt;\u002Ftd&gt;\n    &lt;td&gt;{$order_detail.product_reference}&lt;\u002Ftd&gt;\n    {* Ajout du numéro de série *}\n    &lt;td&gt;\n        {if isset($order_detail.serial_number) && $order_detail.serial_number}\n            {$order_detail.serial_number}\n        {else}\n            -\n        {\u002Fif}\n    &lt;\u002Ftd&gt;\n    &lt;td&gt;{$order_detail.unit_price_tax_excl_including_ecotax}&lt;\u002Ftd&gt;\n    &lt;td&gt;{$order_detail.product_quantity}&lt;\u002Ftd&gt;\n    &lt;td&gt;{$order_detail.total_price_tax_excl_including_ecotax}&lt;\u002Ftd&gt;\n&lt;\u002Ftr&gt;\n{\u002Fforeach}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>N'oubliez pas d'ajouter l'en-tête de colonne correspondant dans la partie \u003Ccode>\u003Cthead>\u003C\u002Fcode> du tableau.\u003C\u002Fp>\n\u003Ch2>Étape 5 : Override de HTMLTemplateInvoice\u003C\u002Fh2>\n\u003Cp>Pour injecter les données de numéro de série dans le template, créez un override de la classe :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\n\u002F\u002F override\u002Fclasses\u002Fpdf\u002FHTMLTemplateInvoice.php\nclass HTMLTemplateInvoice extends HTMLTemplateInvoiceCore\n{\n    public function getContent()\n    {\n        \u002F\u002F Récupérer le contenu parent\n        $content = parent::getContent();\n        \n        \u002F\u002F Enrichir les order_details avec les numéros de série\n        $orderDetails = $this-&gt;order-&gt;getProductsDetail();\n        foreach ($orderDetails as &$detail) {\n            $detail['serial_number'] = Db::getInstance()-&gt;getValue(\n                'SELECT serial_number FROM `' . _DB_PREFIX_ . 'order_detail`\n                WHERE id_order_detail = ' . (int) $detail['id_order_detail']\n            );\n        }\n        \n        $this-&gt;smarty-&gt;assign('order_details_with_serials', $orderDetails);\n        \n        return $content;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>Important sur PrestaShop 8.x\u003C\u002Fstrong> : le système d'override classique fonctionne toujours, mais Symfony encourage l'utilisation de services décorés. Pour un module, l'override reste l'approche la plus pragmatique pour modifier la génération PDF.\u003C\u002Fp>\n\u003Ch2>Cas particulier : génération automatique de codes\u003C\u002Fh2>\n\u003Cp>Si vos numéros de série ou codes de licence sont générés automatiquement (produits dématérialisés, licences logicielles), hookez-vous sur \u003Ccode>actionOrderStatusPostUpdate\u003C\u002Fcode> pour déclencher la génération au moment du paiement :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\npublic function hookActionOrderStatusPostUpdate(array $params)\n{\n    if ((int) $params['newOrderStatus']-&gt;id === (int) Configuration::get('PS_OS_PAYMENT')) {\n        $order = new Order((int) $params['id_order']);\n        $details = $order-&gt;getProductsDetail();\n        \n        foreach ($details as $detail) {\n            $serial = $this-&gt;generateSerialNumber($detail['product_id']);\n            Db::getInstance()-&gt;update('order_detail', [\n                'serial_number' =&gt; pSQL($serial),\n            ], 'id_order_detail = ' . (int) $detail['id_order_detail']);\n        }\n    }\n}\n\nprivate function generateSerialNumber(int $productId): string\n{\n    return strtoupper(\n        substr(md5($productId . time() . random_bytes(8)), 0, 4) . '-' .\n        substr(md5(uniqid('', true)), 0, 4) . '-' .\n        substr(md5(random_bytes(16)), 0, 4) . '-' .\n        substr(md5(time() . $productId), 0, 4)\n    );\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Bonnes pratiques et pièges à éviter\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>**Ne modifiez jamais les fichiers core directement** : utilisez toujours le système d'override ou un module. Une mise à jour PrestaShop écraserait vos modifications.\u003C\u002Fli>\n\u003Cli>**Videz le cache** après chaque modification de template PDF : supprimez le contenu de `var\u002Fcache\u002F` (PS 8.x) ou `cache\u002Fsmarty\u002Fcompile\u002F` (PS 1.7).\u003C\u002Fli>\n\u003Cli>**Testez avec `Tools::enableCache(false)`** pendant le développement pour voir vos modifications en temps réel.\u003C\u002Fli>\n\u003Cli>**Les factures déjà générées ne changent pas** : si vous ajoutez un numéro de série après coup, il faudra régénérer la facture (possible via le back-office en modifiant l'état de commande).\u003C\u002Fli>\n\u003Cli>**Échappez systématiquement** les valeurs avec `pSQL()` côté PHP et `{$variable|escape:'html':'UTF-8'}` côté Smarty.\u003C\u002Fli>\n\u003C\u002Ful>",[40,43,46,49,52],{"q":41,"a":42},"Comment ajouter un numéro de série sur la facture PDF PrestaShop ?","Il faut étendre la table ps_order_detail avec un champ serial_number, créer une interface de saisie dans le back-office via un hook d'administration, puis overrider le template PDF (invoice.product-tab.tpl) et la classe HTMLTemplateInvoice pour afficher cette donnée sur la facture générée.",{"q":44,"a":45},"Peut-on modifier la facture PDF PrestaShop sans toucher aux fichiers core ?","Oui, PrestaShop offre deux mécanismes propres : copier les templates PDF dans le dossier de votre thème (themes\u002Fvotre-theme\u002Fpdf\u002F) pour les templates Smarty, et utiliser le système d'override (override\u002Fclasses\u002Fpdf\u002F) pour la logique PHP. Cette approche survit aux mises à jour.",{"q":47,"a":48},"Comment générer automatiquement un code de licence à la commande sur PrestaShop ?","Utilisez le hook actionOrderStatusPostUpdate dans un module custom. Détectez le passage au statut 'Paiement accepté', puis générez le code via une fonction cryptographiquement sûre (random_bytes) et stockez-le dans ps_order_detail. Le code apparaîtra automatiquement sur la facture si le template est configuré.",{"q":50,"a":51},"Les modifications du template de facture s'appliquent-elles aux anciennes commandes ?","Le nouveau template s'applique à toutes les factures générées après la modification. Les factures déjà créées conservent leur format d'origine. Pour régénérer une ancienne facture, vous pouvez modifier temporairement le statut de la commande dans le back-office, ce qui déclenche une nouvelle génération du PDF.",{"q":53,"a":54},"Quelle est la différence entre override PDF et module pour personnaliser les factures PrestaShop 8 ?","Les deux approches fonctionnent sur PrestaShop 8.x. L'override (override\u002Fclasses\u002Fpdf\u002F) est rapide à mettre en place mais limité à un seul override par classe. Un module dédié est plus maintenable, permet la distribution, et offre une interface d'administration. Pour un besoin ponctuel, l'override suffit ; pour une fonctionnalité complète avec UI, privilégiez le module.","Pour afficher des numéros de série ou codes personnalisés sur les factures PrestaShop, étendez ps_order_detail avec un champ custom, créez un module avec interface d'administration pour la saisie, puis overridez le template PDF et la classe HTMLTemplateInvoice pour injecter ces données dans le rendu.",5,"2026-03-21T14:44:45.000Z",[],"PrestaShop pour les développeurs",{"columns":61},[62,78,108,129],{"title":63,"links":64},"Plateforme",[65,69,72,75],{"label":66,"href":67,"external":68},"Offre Starter (2 500 €)","\u002Foffre-starter",false,{"label":70,"href":71,"external":68},"Devenir Ambassadeur","\u002Fambassadeur",{"label":73,"href":74,"external":68},"Modules PrestaShop","\u002Fmodules",{"label":76,"href":77,"external":20},"CodeMyShop.com","https:\u002F\u002Fcodemyshop.com",{"title":79,"links":80},"Le Synedre",[81,84,87,90,93,96,99,102,105],{"label":82,"href":83,"external":68},"L'histoire","\u002Fsynedre",{"label":85,"href":86,"external":68},"Constitution","\u002Fsynedre\u002Fconstitution",{"label":88,"href":89,"external":68},"L'équipe","\u002Fequipe",{"label":91,"href":92,"external":68},"Le réacteur en direct","\u002Freacteur",{"label":94,"href":95,"external":68},"Le Drill (entraînement)","\u002Fdrill",{"label":97,"href":98,"external":68},"Protocole de réunion","\u002Fsynedre\u002Freunion",{"label":100,"href":101,"external":68},"Les agents IA","\u002Fagents-ia",{"label":103,"href":104,"external":68},"La Conduite","\u002Fsynedre\u002Fconduite",{"label":106,"href":107,"external":68},"Charte plateforme","\u002Fsynedre\u002Fcharte",{"title":109,"links":110},"Ressources",[111,114,117,120,123,126],{"label":112,"href":113,"external":68},"Blog","\u002Fblog",{"label":115,"href":116,"external":68},"Academy","\u002Facademy",{"label":118,"href":119,"external":68},"Dictionnaire","\u002Fdictionnaire",{"label":121,"href":122,"external":68},"Expertise PrestaShop","\u002Fexpertise",{"label":124,"href":125,"external":68},"Flywheel","\u002Fflywheel",{"label":127,"href":128,"external":68},"Manifeste","\u002Fmanifeste",{"title":130,"links":131},"À propos",[132,135,138],{"label":133,"href":134,"external":68},"Alexandre Carette","\u002Fa-propos",{"label":136,"href":137,"external":68},"Dossier de presse","\u002Fpresse",{"label":139,"href":140,"external":68},"Contact","\u002Fcontact",{"items":142},[143,152,158,164,172,180,185,191],{"id":144,"type":145,"label":146,"href":122,"icon":148,"description":148,"badge":148,"groupTitle":148,"style":148,"gridColumns":148,"cssClass":148,"psCategoryId":148,"showPsChildren":68,"position":149,"children":150,"psChildren":151},41,"link",{"fr":147},"Expertise",null,0,[],[],{"id":153,"type":145,"label":154,"href":113,"icon":148,"description":148,"badge":148,"groupTitle":148,"style":148,"gridColumns":148,"cssClass":148,"psCategoryId":148,"showPsChildren":68,"position":155,"children":156,"psChildren":157},42,{"fr":112},1,[],[],{"id":159,"type":145,"label":160,"href":74,"icon":148,"description":148,"badge":148,"groupTitle":148,"style":148,"gridColumns":148,"cssClass":148,"psCategoryId":148,"showPsChildren":68,"position":161,"children":162,"psChildren":163},43,{"fr":73},2,[],[],{"id":165,"type":145,"label":166,"href":168,"icon":148,"description":148,"badge":148,"groupTitle":148,"style":148,"gridColumns":148,"cssClass":148,"psCategoryId":148,"showPsChildren":68,"position":169,"children":170,"psChildren":171},44,{"fr":167},"Outils IA","\u002Foutils-ia",3,[],[],{"id":173,"type":145,"label":174,"href":67,"icon":148,"description":148,"badge":148,"groupTitle":148,"style":176,"gridColumns":148,"cssClass":148,"psCategoryId":148,"showPsChildren":68,"position":177,"children":178,"psChildren":179},45,{"fr":175},"Offre Starter ✨",{"highlight":20},4,[],[],{"id":181,"type":145,"label":182,"href":116,"icon":148,"description":148,"badge":148,"groupTitle":148,"style":148,"gridColumns":148,"cssClass":148,"psCategoryId":148,"showPsChildren":68,"position":56,"children":183,"psChildren":184},46,{"fr":115},[],[],{"id":186,"type":145,"label":187,"href":134,"icon":148,"description":148,"badge":148,"groupTitle":148,"style":148,"gridColumns":148,"cssClass":148,"psCategoryId":148,"showPsChildren":68,"position":188,"children":189,"psChildren":190},47,{"fr":130},6,[],[],{"id":192,"type":145,"label":193,"href":140,"icon":148,"description":148,"badge":148,"groupTitle":148,"style":148,"gridColumns":148,"cssClass":148,"psCategoryId":148,"showPsChildren":68,"position":194,"children":195,"psChildren":196},48,{"fr":139},7,[],[],{"header":198},{"logo":199,"topBar":204,"contactEmail":207,"features":208,"navBar":148},{"src":200,"alt":201,"text":133,"href":202,"class":203},"\u002Flogo-ac.svg","Alexandre Carette — Architecte E-commerce Souverain","\u002F","h-10 w-10",{"message":148,"showLanguages":68,"align":205,"languages":206},"left",[],"contact@alexandrecarette.fr",{"showSearch":68,"showWishlist":68,"showLogin":20,"showContact":68,"showCart":68,"stickyHeader":20,"headerLayout":209},"inline",{"footer":211},{"theme":212,"description":148,"hours":148,"logo":213,"contact":214,"social":215,"bottomBar":225},"dark",{"src":200,"href":202,"alt":133},{"email":148,"phone":148,"address":148,"cta":148},[216,219,222],{"platform":217,"href":218,"label":217},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-carette\u002F",{"platform":220,"href":221,"label":220},"malt","https:\u002F\u002Fwww.malt.fr\u002Fprofile\u002Falexandrecarette",{"platform":223,"href":224,"label":223},"github","https:\u002F\u002Fgithub.com\u002Fprest4cafe",{"copyright":148},{"academy":227,"blog":228,"expertise":239},[],[229,233,236],{"title":230,"url":231,"score":155,"type":232},"PrestaShop headless avec Nuxt 3 : pourquoi séparer back et front","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-separation-front-back","blog",{"title":234,"url":235,"score":155,"type":232},"PrestaShop headless : Nuxt 3, pas Next.js — le choix souverain","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-nextjs-souverainete",{"title":237,"url":238,"score":155,"type":232},"Sylius rachète PrestaShop : ce que ça change pour vous","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fsylius-rachat-prestashop-headless-souverainete",[]]