From 9c56e63820fc1b74ec40fa6f2fb5116b68ef12bc Mon Sep 17 00:00:00 2001 From: jps Date: Fri, 23 Jan 2026 16:32:40 +0100 Subject: [PATCH] Test --- app/controllers/Peppol_controller.php | 147 +++++++++- app/controllers/Plugin.php | 43 ++- app/controllers/Woocommerce_controller.php | 3 + app/helpers/Woo_Helper.php | 111 ++++++++ assets/js/admin.js | 1 + exemple-timeline.html | 299 +++++++++++++++++++++ templates/admin/dashboard.php | 18 +- templates/admin/settings.php | 26 +- 8 files changed, 607 insertions(+), 41 deletions(-) create mode 100644 exemple-timeline.html diff --git a/app/controllers/Peppol_controller.php b/app/controllers/Peppol_controller.php index 98ab33f..46c4892 100644 --- a/app/controllers/Peppol_controller.php +++ b/app/controllers/Peppol_controller.php @@ -514,19 +514,6 @@ class PEPPOL_peppol_controller { $wpo_invoice_number = (string) $order->get_meta('_wcpdf_invoice_number', true); if ($wpo_invoice_number !== '') { $invoice_number = $wpo_invoice_number; - } else { - $use_backup_format = !\ESI_PEPPOL\helpers\PEPPOL_Woo_Helper::esi_has_wpo_invoice_numbers(); - if ($use_backup_format) { - $backup_format = (string) \get_option('esi_peppol_vat_number_format', ''); - if ($backup_format !== '') { - $generated = self::build_invoice_number_from_format($backup_format, $order); - if ($generated !== '') { - $invoice_number = $generated; - $order->update_meta_data('_wcpdf_invoice_number', $generated); - $order->save(); - } - } - } } $currency_code = $order->get_currency(); @@ -568,6 +555,25 @@ class PEPPOL_peppol_controller { $order ); + $payment_data = PEPPOL_Woo_Helper::esi_get_order_payment_data($order); + $payment_means_code = $payment_data['payment_means_code'] ?? '30'; + if (!empty($payment_data['payment_terms'])) { + $payment_terms = $payment_data['payment_terms']; + } + + $payment_means_code = \apply_filters( + 'esi_peppol_payment_means_code', + $payment_means_code, + $payment_data, + $order + ); + $payment_terms = \apply_filters( + 'esi_peppol_payment_terms_from_payment', + $payment_terms, + $payment_data, + $order + ); + // Données vendeur (store) - basées sur les options WooCommerce $store_name = \get_bloginfo('name'); $store_address = \get_option('woocommerce_store_address', ''); @@ -1020,6 +1026,7 @@ class PEPPOL_peppol_controller { 'currency_code' => $currency_code, 'invoice_notes' => $invoice_notes, 'payment_terms' => $payment_terms, + 'payment_means_code' => $payment_means_code, ], 'parties' => [ 'seller' => $seller, @@ -1034,6 +1041,42 @@ class PEPPOL_peppol_controller { return \apply_filters('esi_peppol_payload_from_order', $payload, $order); } + /** + * Génère et sauvegarde un numéro WPO si format de secours configuré. + */ + public static function ensure_invoice_number_meta(\WC_Order $order): void { + $wpo_invoice_number = (string) $order->get_meta('_wcpdf_invoice_number', true); + if ($wpo_invoice_number !== '') { + return; + } + + $use_backup_format = !\ESI_PEPPOL\helpers\PEPPOL_Woo_Helper::esi_has_wpo_invoice_numbers(); + if (!$use_backup_format) { + return; + } + + $backup_format = (string) \get_option('esi_peppol_invoice_number_format', ''); + if ($backup_format === '') { + $backup_format = (string) \get_option('esi_peppol_vat_number_format', ''); + } + if ($backup_format !== '') { + $generated = self::build_invoice_number_from_format($backup_format, $order); + if ($generated !== '') { + $order->update_meta_data('_wcpdf_invoice_number', $generated); + $order->save(); + } + return; + } + + $fallback = (string) $order->get_order_number(); + if ($fallback === '') { + return; + } + + $order->update_meta_data('_wcpdf_invoice_number', $fallback); + $order->save(); + } + /** * Construit un numéro de facture depuis un format de secours. * Placeholders supportés : {YYYY}, {YY}, {MM}, {DD}, {ORDER_ID}, {ORDER_NUMBER} @@ -1044,6 +1087,8 @@ class PEPPOL_peppol_controller { $month = $date ? $date->date('m') : \gmdate('m'); $day = $date ? $date->date('d') : \gmdate('d'); + $sequence = self::get_next_invoice_sequence($format, $order, $year); + $replacements = [ '{YYYY}' => $year, '{YY}' => substr($year, -2), @@ -1051,10 +1096,86 @@ class PEPPOL_peppol_controller { '{DD}' => $day, '{ORDER_ID}' => (string) $order->get_id(), '{ORDER_NUMBER}' => (string) $order->get_order_number(), + '{number}' => $sequence, ]; $result = strtr($format, $replacements); return trim($result); } + + private static function get_next_invoice_sequence(string $format, \WC_Order $order, string $current_year): string { + if (!function_exists('wc_get_orders')) { + return '1'; + } + + $orders = \wc_get_orders([ + 'limit' => 1, + 'orderby' => 'date', + 'order' => 'DESC', + 'status' => 'any', + 'meta_key' => '_wcpdf_invoice_number', + 'meta_compare' => 'EXISTS', + 'exclude' => [$order->get_id()], + 'return' => 'objects', + ]); + + if (empty($orders)) { + return '1'; + } + + $last_order = $orders[0]; + $last_invoice = (string) $last_order->get_meta('_wcpdf_invoice_number', true); + if ($last_invoice === '') { + return '1'; + } + + $matches = self::match_invoice_number_format($format, $last_invoice); + if (empty($matches['number'])) { + return '1'; + } + + $last_number = (int) $matches['number']; + $number_width = strlen((string) $matches['number']); + + $next_number = $last_number + 1; + if (strpos($format, '{YYYY}') !== false) { + $last_year = (string) ($matches['year'] ?? ''); + if ($last_year !== $current_year) { + $next_number = 1; + } + } elseif (strpos($format, '{YY}') !== false) { + $current_year2 = substr($current_year, -2); + $last_year2 = (string) ($matches['year2'] ?? ''); + if ($last_year2 !== $current_year2) { + $next_number = 1; + } + } + + $next_str = (string) $next_number; + if ($number_width > 1) { + $next_str = str_pad($next_str, $number_width, '0', STR_PAD_LEFT); + } + + return $next_str; + } + + private static function match_invoice_number_format(string $format, string $value): array { + $pattern = preg_quote($format, '/'); + $pattern = str_replace('\{YYYY\}', '(?P\d{4})', $pattern); + $pattern = str_replace('\{YY\}', '(?P\d{2})', $pattern); + $pattern = str_replace('\{MM\}', '(?P\d{2})', $pattern); + $pattern = str_replace('\{DD\}', '(?P\d{2})', $pattern); + $pattern = str_replace('\{ORDER_ID\}', '\d+', $pattern); + $pattern = str_replace('\{ORDER_NUMBER\}', '.+?', $pattern); + $pattern = str_replace('\{number\}', '(?P\d+)', $pattern); + $pattern = '/^' . $pattern . '$/'; + + $matches = []; + if (preg_match($pattern, $value, $matches) !== 1) { + return []; + } + + return $matches; + } } \ No newline at end of file diff --git a/app/controllers/Plugin.php b/app/controllers/Plugin.php index 5a37997..111ca26 100644 --- a/app/controllers/Plugin.php +++ b/app/controllers/Plugin.php @@ -155,17 +155,21 @@ class PEPPOL_Plugin { $password = isset($_POST['esi_peppol_password']) ? sanitize_text_field(wp_unslash($_POST['esi_peppol_password'])) : ''; $email = isset($_POST['esi_peppol_email']) ? sanitize_email(wp_unslash($_POST['esi_peppol_email'])) : ''; $logo_email_id = isset($_POST['esi_peppol_logo_email_id']) ? absint(wp_unslash($_POST['esi_peppol_logo_email_id'])) : 0; - $vat_number_format = get_option('esi_peppol_vat_number_format', ''); - if (isset($_POST['esi_peppol_vat_number_format'])) { - $vat_number_format = sanitize_text_field(wp_unslash($_POST['esi_peppol_vat_number_format'])); + $invoice_number_format = get_option('esi_peppol_invoice_number_format', ''); + if ($invoice_number_format === '') { + $invoice_number_format = get_option('esi_peppol_vat_number_format', ''); } + if (isset($_POST['esi_peppol_invoice_number_format'])) { + $invoice_number_format = sanitize_text_field(wp_unslash($_POST['esi_peppol_invoice_number_format'])); + } + $invoice_number_format = self::normalize_invoice_number_format($invoice_number_format); // Sauvegarde systématique des options update_option('esi_peppol_api_key', $api_key); update_option('esi_peppol_password', $password); update_option('esi_peppol_email', $email); update_option('esi_peppol_logo_email_id', $logo_email_id); - update_option('esi_peppol_vat_number_format', $vat_number_format); + update_option('esi_peppol_invoice_number_format', $invoice_number_format); if ($action === 'save') { $notice = __('Paramètres enregistrés avec succès.', 'esi_peppol'); @@ -1296,11 +1300,11 @@ class PEPPOL_Plugin { 'action_url' => admin_url('admin.php?page=esi-peppol-settings'), 'action_label' => __('Détecter le champ TVA', 'esi_peppol'), ], - 'vat_number_format' => [ + 'invoice_number_format' => [ 'completed' => false, 'value' => '', - 'label' => __('Format num TVA', 'esi_peppol'), - 'description' => __('Format du numéro de TVA (backup si aucun format détecté automatiquement)', 'esi_peppol'), + 'label' => __('Format num facture', 'esi_peppol'), + 'description' => __('Format du numéro de facture (backup si aucun format détecté automatiquement)', 'esi_peppol'), 'action_url' => admin_url('admin.php?page=esi-peppol-settings'), 'action_label' => __('Configurer le format', 'esi_peppol'), ], @@ -1350,16 +1354,19 @@ class PEPPOL_Plugin { if (\ESI_PEPPOL\helpers\PEPPOL_Woo_Helper::esi_has_wpo_invoice_numbers()) { $detected_format = \ESI_PEPPOL\helpers\PEPPOL_Woo_Helper::esi_get_wpo_invoice_number_format_label(); } - $backup_format = (string) get_option('esi_peppol_vat_number_format', ''); + $backup_format = (string) get_option('esi_peppol_invoice_number_format', ''); + if ($backup_format === '') { + $backup_format = (string) get_option('esi_peppol_vat_number_format', ''); + } if ($detected_format !== '') { - $status['vat_number_format']['completed'] = true; - $status['vat_number_format']['value'] = sprintf( + $status['invoice_number_format']['completed'] = true; + $status['invoice_number_format']['value'] = sprintf( __('Format détecté : %s', 'esi_peppol'), $detected_format ); } elseif ($backup_format !== '') { - $status['vat_number_format']['completed'] = true; - $status['vat_number_format']['value'] = $backup_format; + $status['invoice_number_format']['completed'] = true; + $status['invoice_number_format']['value'] = $backup_format; } // Vérifier les identifiants API @@ -1386,6 +1393,18 @@ class PEPPOL_Plugin { return $status; } + private static function normalize_invoice_number_format(string $format): string { + $format = trim($format); + if ($format === '') { + return ''; + } + + $format = str_replace('{number}', '', $format); + $format = trim($format); + + return $format . '{number}'; + } + public static function write_log($message, $level = 'INFO') { $message = self::format_message_for_log($message); $log_entry = sprintf("[%s] [%s] %s\n", date('Y-m-d H:i:s'), $level, $message); diff --git a/app/controllers/Woocommerce_controller.php b/app/controllers/Woocommerce_controller.php index 91053e0..e1f3445 100644 --- a/app/controllers/Woocommerce_controller.php +++ b/app/controllers/Woocommerce_controller.php @@ -91,6 +91,9 @@ class PEPPOL_Woocommerce_controller { return; } + // Générer un numéro de facture de secours si nécessaire + PEPPOL_peppol_controller::ensure_invoice_number_meta($order); + // Construire le payload JSON à partir de la commande $payload = PEPPOL_peppol_controller::build_payload_from_order($order); diff --git a/app/helpers/Woo_Helper.php b/app/helpers/Woo_Helper.php index ec5d944..d75eedc 100644 --- a/app/helpers/Woo_Helper.php +++ b/app/helpers/Woo_Helper.php @@ -323,5 +323,116 @@ class PEPPOL_Woo_Helper { return array_values($vat_fields_found); } + + public static function esi_is_wpo_wcpdf_installed(): bool { + if (!function_exists('get_plugins')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $plugins = get_plugins(); // [ 'dir/main.php' => headers... ] + foreach ($plugins as $file => $data) { + if ($file === 'woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packingslips.php') { + return true; + } + // Optionnel : fallback par Name (plus fragile, dépend de la traduction / headers) + if (!empty($data['Name']) && stripos($data['Name'], 'PDF Invoices') !== false) { + // pas ultra fiable, mais utile en dernier recours + } + } + return false; + } + + + /** + * detecte les num de facture enregistré + */ + //wpo_wcpdf_documents_settings_invoice + /* + Array +( + [enabled] => 1 + [attach_to_email_ids] => Array + ( + [customer_on_hold_order] => 1 + [customer_processing_order] => 1 + [customer_completed_order] => 1 + ) + + [my_account_buttons] => available + [display_email] => 1 + [display_customer_notes] => 1 + [display_shipping_address] => + [display_number] => invoice_number + [number_format] => Array + ( + [prefix] => 2026- + [suffix] => + [padding] => + ) + + [display_date] => +)*/ + + /** + * Vérifie la présence de numéros de facture WPO WCPDF. + * - Plugin installé + * - Paramètres facture activés + * - Au moins une commande avec meta _wcpdf_invoice_number + */ + public static function esi_has_wpo_invoice_numbers(): bool { + if ( ! self::esi_is_wpo_wcpdf_installed() ) { + return false; + } + + if ( ! function_exists( 'wc_get_orders' ) ) { + return false; + } + + $settings = get_option( 'wpo_wcpdf_documents_settings_invoice', [] ); + if ( empty( $settings ) || ! is_array( $settings ) ) { + return false; + } + if ( empty( $settings['enabled'] ) ) { + return false; + } + if ( empty( $settings['display_number'] ) || $settings['display_number'] !== 'invoice_number' ) { + return false; + } + + $orders = wc_get_orders( [ + 'limit' => 1, + 'status' => 'any', + 'meta_key' => '_wcpdf_invoice_number', + 'meta_compare' => 'EXISTS', + 'return' => 'ids', + ] ); + + return ! empty( $orders ); + } + + /** + * Retourne un libellé lisible du format des numéros WPO WCPDF. + */ + public static function esi_get_wpo_invoice_number_format_label(): string { + $settings = get_option( 'wpo_wcpdf_documents_settings_invoice', [] ); + if ( empty( $settings ) || ! is_array( $settings ) ) { + return ''; + } + + $number_format = $settings['number_format'] ?? []; + if ( ! is_array( $number_format ) ) { + return ''; + } + + $prefix = (string) ( $number_format['prefix'] ?? '' ); + $suffix = (string) ( $number_format['suffix'] ?? '' ); + $padding = (string) ( $number_format['padding'] ?? '' ); + + $format = $prefix . '{number}' . $suffix; + if ( $padding !== '' ) { + $format .= ' (padding: ' . $padding . ')'; + } + + return $format; + } } \ No newline at end of file diff --git a/assets/js/admin.js b/assets/js/admin.js index 96da828..d38db01 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -199,6 +199,7 @@ } // Toggle affichage du mot de passe sur la page de configuration + $('.esi-peppol-password-toggle').on('click', function (e) { e.preventDefault(); var $btn = $(this); diff --git a/exemple-timeline.html b/exemple-timeline.html new file mode 100644 index 0000000..22916fc --- /dev/null +++ b/exemple-timeline.html @@ -0,0 +1,299 @@ + + + + + + Exemple Timeline Verticale + + + +
+

Timeline Verticale - Exemple

+ +
+ +
+
+

Configuration du connecteur

+

Le numéro de TVA de votre boutique doit être renseigné dans WooCommerce > Réglages > Général

+
+ Valeur configurée : + BE0431.066.713 +
+
+
+ + +
+
+

Numéro de TVA de la boutique

+

Le champ utilisé pour récupérer le numéro de TVA du client dans les commandes

+
+ Champ détecté : + billing_vat_number +
+
+
+ + +
+
+

Champ TVA client

+

Le champ utilisé pour récupérer le numéro de TVA du client dans les commandes

+

Aucun champ détecté

+
+
+ + +
+
+

Identifiants API

+

Votre clé API et mot de passe pour vous connecter à la plateforme ESI Peppol

+

Aucune clé API détectée

+
+
+ + +
+
+

URL Webhook

+

URL à configurer dans votre profil d'entreprise sur la plateforme

+
+ Configurez cette URL : + https://peppol.esi-web.be/webhook/callback +
+
+
+ + +
+
+

Email de notification (optionnel)

+

Adresse email pour recevoir les notifications en cas d'erreur d'envoi des factures

+ Configurer un email +
+
+
+ +
+

Structure CSS: .step avec ::before (cercle) et ::after (ligne)

+

États: Par défaut (gris) | .is-done (bleu avec check ✓)

+
+
+ + diff --git a/templates/admin/dashboard.php b/templates/admin/dashboard.php index 0d89ba0..f46d195 100644 --- a/templates/admin/dashboard.php +++ b/templates/admin/dashboard.php @@ -64,20 +64,20 @@ $settings_status = \ESI_PEPPOL\controllers\PEPPOL_Plugin::check_settings_status( - -
+ +
-

-

- +

+

+
- +
- - + +
diff --git a/templates/admin/settings.php b/templates/admin/settings.php index c12e899..95d35c6 100644 --- a/templates/admin/settings.php +++ b/templates/admin/settings.php @@ -195,11 +195,14 @@ if ($logo_email_id) { - + +
+ + + + + + + +

- +