Personnaliser les produits sur la facture PrestaShop : numéro de série et champs custom
Ajoutez des numéros de série, codes ou informations personnalisées aux produits sur vos factures PrestaShop. Guide technique complet avec override PDF.
En bref : 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.
Personnaliser les produits sur la facture PrestaShop : numéro de série et champs custom
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.
Comprendre l'architecture de facturation PrestaShop
La génération des factures PDF dans PrestaShop repose sur la classe HTMLTemplateInvoice, située dans classes/pdf/HTMLTemplateInvoice.php. Cette classe utilise des templates Smarty stockés dans pdf/ pour construire le rendu HTML qui sera ensuite converti en PDF.
Le fichier clé pour la liste des produits est :
- **PrestaShop 1.7** : `pdf/invoice.product-tab.tpl`
- **PrestaShop 8.x** : même emplacement, avec quelques ajustements de variables Smarty
La méthode getContent() de HTMLTemplateInvoice prépare les données produits via $order->getProducts(), puis les injecte dans le template Smarty.
Étape 1 : Choisir la bonne approche pour stocker l'information
Avant de modifier la facture, il faut déterminer où stocker le numéro de série ou le code personnalisé. Plusieurs approches existent :
Option A : Utiliser le champ référence produit
Si votre numéro de série est unique par produit (pas par commande), le champ reference de ps_product peut suffire. C'est la solution la plus simple : ce champ est déjà disponible dans le template de facture via $product.product_reference.
Option B : Utiliser les personnalisations produit (customizations)
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 ps_customization et ps_customized_data.
Option C : Ajouter un champ custom sur le détail de commande
Pour les numéros de série attribués après la commande (cas le plus fréquent), il faut étendre la table ps_order_detail avec une colonne supplémentaire. C'est l'approche la plus robuste.
Étape 2 : Étendre la table order_detail (Option C)
Créez un module qui ajoute la colonne nécessaire :
// modules/myserials/myserials.php
public function install()
{
return parent::install()
&& $this->registerHook('actionOrderDetail')
&& $this->registerHook('displayAdminOrderContentOrder')
&& Db::getInstance()->execute('
ALTER TABLE `' . _DB_PREFIX_ . 'order_detail`
ADD `serial_number` VARCHAR(255) DEFAULT NULL
');
}
Étape 3 : Interface d'administration pour saisir le numéro de série
Hookez-vous sur displayAdminOrderContentOrder (PrestaShop 1.7) ou displayAdminOrderMain (PrestaShop 8.x) pour ajouter un formulaire de saisie dans le back-office :
public function hookDisplayAdminOrderMain(array $params)
{
$orderId = $params['id_order'];
$orderDetails = OrderDetail::getList((int) $orderId);
foreach ($orderDetails as &$detail) {
$detail['serial_number'] = Db::getInstance()->getValue(
'SELECT serial_number FROM `' . _DB_PREFIX_ . 'order_detail`
WHERE id_order_detail = ' . (int) $detail['id_order_detail']
);
}
$this->context->smarty->assign([
'order_details' => $orderDetails,
'serial_save_url' => $this->context->link->getAdminLink('AdminModules', true, [], [
'configure' => $this->name,
'action' => 'saveSerial',
]),
]);
return $this->display(__FILE__, 'views/templates/hook/admin-order-serials.tpl');
}
Étape 4 : Override du template de facture PDF
Copiez le template dans votre thème pour l'overrider proprement :
# PrestaShop 8.x
cp pdf/invoice.product-tab.tpl themes/votre-theme/pdf/invoice.product-tab.tpl
Modifiez le template pour afficher le numéro de série :
{* Dans la boucle des produits *}
{foreach $order_details as $order_detail}
<tr>
<td>{$order_detail.product_name}</td>
<td>{$order_detail.product_reference}</td>
{* Ajout du numéro de série *}
<td>
{if isset($order_detail.serial_number) && $order_detail.serial_number}
{$order_detail.serial_number}
{else}
-
{/if}
</td>
<td>{$order_detail.unit_price_tax_excl_including_ecotax}</td>
<td>{$order_detail.product_quantity}</td>
<td>{$order_detail.total_price_tax_excl_including_ecotax}</td>
</tr>
{/foreach}
N'oubliez pas d'ajouter l'en-tête de colonne correspondant dans la partie Pour injecter les données de numéro de série dans le template, créez un override de la classe : Important sur PrestaShop 8.x : 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. 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 Tout ce que vous devez savoir sur ce sujet. Un projet PrestaShop ? Discutons-en directement. 193 projets livrés Alexandre Carette Expert PrestaShop & Architecture E-commerce Développeur PrestaShop depuis 2014, 193 projets livrés. Je conçois des architectures headless Nuxt + PrestaShop et des outils d'automatisation IA pour les e-commerçants. du tableau.
Étape 5 : Override de HTMLTemplateInvoice
// override/classes/pdf/HTMLTemplateInvoice.php
class HTMLTemplateInvoice extends HTMLTemplateInvoiceCore
{
public function getContent()
{
// Récupérer le contenu parent
$content = parent::getContent();
// Enrichir les order_details avec les numéros de série
$orderDetails = $this->order->getProductsDetail();
foreach ($orderDetails as &$detail) {
$detail['serial_number'] = Db::getInstance()->getValue(
'SELECT serial_number FROM `' . _DB_PREFIX_ . 'order_detail`
WHERE id_order_detail = ' . (int) $detail['id_order_detail']
);
}
$this->smarty->assign('order_details_with_serials', $orderDetails);
return $content;
}
}
Cas particulier : génération automatique de codes
actionOrderStatusPostUpdate pour déclencher la génération au moment du paiement :
public function hookActionOrderStatusPostUpdate(array $params)
{
if ((int) $params['newOrderStatus']->id === (int) Configuration::get('PS_OS_PAYMENT')) {
$order = new Order((int) $params['id_order']);
$details = $order->getProductsDetail();
foreach ($details as $detail) {
$serial = $this->generateSerialNumber($detail['product_id']);
Db::getInstance()->update('order_detail', [
'serial_number' => pSQL($serial),
], 'id_order_detail = ' . (int) $detail['id_order_detail']);
}
}
}
private function generateSerialNumber(int $productId): string
{
return strtoupper(
substr(md5($productId . time() . random_bytes(8)), 0, 4) . '-' .
substr(md5(uniqid('', true)), 0, 4) . '-' .
substr(md5(random_bytes(16)), 0, 4) . '-' .
substr(md5(time() . $productId), 0, 4)
);
}
Bonnes pratiques et pièges à éviter
Questions fréquentes
Lire sur le blog
