diff --git a/ESI_peppol.php b/ESI_peppol.php index 3961fa5..f62b927 100644 --- a/ESI_peppol.php +++ b/ESI_peppol.php @@ -152,6 +152,18 @@ add_action( [PEPPOL_Plugin::class, 'ajax_get_log_details'] ); +// AJAX admin pour détecter les champs TVA +add_action( + 'wp_ajax_esi_peppol_detect_vat_fields', + [PEPPOL_Plugin::class, 'ajax_detect_vat_fields'] +); + +// AJAX admin pour sauvegarder le champ TVA sélectionné +add_action( + 'wp_ajax_esi_peppol_save_vat_field', + [PEPPOL_Plugin::class, 'ajax_save_vat_field'] +); + // Champ TVA de la boutique dans les réglages WooCommerce > Général. add_filter( 'woocommerce_general_settings', diff --git a/app/controllers/Plugin.php b/app/controllers/Plugin.php index 69d32e4..bf4db59 100644 --- a/app/controllers/Plugin.php +++ b/app/controllers/Plugin.php @@ -421,6 +421,8 @@ class PEPPOL_Plugin { 'logs_nonce_resend' => wp_create_nonce('esi_peppol_resend_invoice'), 'logs_nonce_status' => wp_create_nonce('esi_peppol_check_invoice_status'), 'logs_nonce_details' => wp_create_nonce('esi_peppol_get_log_details'), + 'vat_detect_nonce' => wp_create_nonce('esi_peppol_detect_vat_fields'), + 'vat_save_nonce' => wp_create_nonce('esi_peppol_save_vat_field'), 'i18n_missing' => __('Veuillez renseigner l\'API Key et le Password avant de tester la connexion.', 'esi_peppol'), 'i18n_success' => __('Connexion réussie à l\'API ESIPeppol.', 'esi_peppol'), 'i18n_error' => __('La connexion a échoué. Veuillez vérifier vos identifiants.', 'esi_peppol'), @@ -681,6 +683,184 @@ class PEPPOL_Plugin { wp_send_json_error($payload_response, 200); } + /** + * Handler AJAX pour détecter les champs TVA dans les commandes WooCommerce. + * + * @return void + */ + public static function ajax_detect_vat_fields(): void { + if (!check_ajax_referer('esi_peppol_detect_vat_fields', 'nonce', false)) { + wp_send_json_error( + [ + 'message' => __('Sécurité : nonce invalide.', 'esi_peppol'), + ], + 400 + ); + } + + if (!current_user_can('manage_options')) { + wp_send_json_error( + [ + 'message' => __('Vous n\'avez pas les permissions nécessaires pour effectuer cette action.', 'esi_peppol'), + ], + 403 + ); + } + + // Récupérer les dernières commandes pour scanner les champs TVA + $orders = wc_get_orders([ + 'limit' => 50, + 'orderby' => 'date', + 'order' => 'DESC', + 'status' => ['completed', 'processing', 'on-hold'], + ]); + + $vat_fields_found = []; + + foreach ($orders as $order) { + if (!$order instanceof \WC_Order) { + continue; + } + + // Utiliser la fonction helper pour scanner tous les champs TVA possibles + $vat_candidates = []; + + // Scanner les meta keys contenant vat/tva + foreach ($order->get_meta_data() as $meta) { + $k = (string) $meta->key; + if (preg_match('/\b(vat|tva)\b/i', $k) || preg_match('/(vat|tva)/i', $k)) { + $v = $meta->value; + if (is_string($v)) { + $v = trim($v); + } + if (!empty($v)) { + if (!isset($vat_candidates[$k])) { + $vat_candidates[$k] = [ + 'key' => $k, + 'value' => $v, + 'count' => 0, + ]; + } + $vat_candidates[$k]['count']++; + } + } + } + + // Scanner les champs de checkout + if (function_exists('WC') && WC()->checkout()) { + $fields = WC()->checkout()->get_checkout_fields(); + $patterns = ['vat', 'tva', 'btw', 'ust', 'mwst', 'piva', 'moms', 'mva']; + + foreach ($fields as $group_fields) { + foreach ($group_fields as $key => $def) { + $k = strtolower($key); + $found = false; + + foreach ($patterns as $p) { + if (strpos($k, $p) !== false) { + $found = true; + break; + } + } + + if (!$found) { + $label = strtolower((string) ($def['label'] ?? '')); + foreach ($patterns as $p) { + if (preg_match('/\b' . $p . '\b/i', $label)) { + $found = true; + break; + } + } + } + + if ($found) { + foreach ([$key, '_' . $key] as $meta_key) { + $val = $order->get_meta($meta_key, true); + if (!empty($val)) { + if (!isset($vat_candidates[$meta_key])) { + $vat_candidates[$meta_key] = [ + 'key' => $meta_key, + 'value' => trim((string) $val), + 'count' => 0, + ]; + } + $vat_candidates[$meta_key]['count']++; + } + } + } + } + } + } + + // Ajouter les candidats trouvés + foreach ($vat_candidates as $candidate) { + $key = $candidate['key']; + if (!isset($vat_fields_found[$key])) { + $vat_fields_found[$key] = [ + 'key' => $key, + 'count' => 0, + 'sample_value' => $candidate['value'], + ]; + } + $vat_fields_found[$key]['count'] += $candidate['count']; + } + } + + // Trier par nombre d'occurrences (décroissant) + usort($vat_fields_found, function ($a, $b) { + return $b['count'] - $a['count']; + }); + + wp_send_json_success([ + 'fields' => array_values($vat_fields_found), + 'count' => count($vat_fields_found), + ]); + } + + /** + * Handler AJAX pour sauvegarder le champ TVA sélectionné. + * + * @return void + */ + public static function ajax_save_vat_field(): void { + if (!check_ajax_referer('esi_peppol_save_vat_field', 'nonce', false)) { + wp_send_json_error( + [ + 'message' => __('Sécurité : nonce invalide.', 'esi_peppol'), + ], + 400 + ); + } + + if (!current_user_can('manage_options')) { + wp_send_json_error( + [ + 'message' => __('Vous n\'avez pas les permissions nécessaires pour effectuer cette action.', 'esi_peppol'), + ], + 403 + ); + } + + $vat_field_key = isset($_POST['vat_field_key']) ? sanitize_text_field(wp_unslash($_POST['vat_field_key'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing + + if ($vat_field_key === '') { + wp_send_json_error( + [ + 'message' => __('Clé de champ TVA invalide.', 'esi_peppol'), + ], + 400 + ); + } + + // Sauvegarder le champ sélectionné + update_option('esi_peppol_vat_field_key', $vat_field_key); + + wp_send_json_success([ + 'message' => __('Champ TVA enregistré avec succès.', 'esi_peppol'), + 'vat_field_key' => $vat_field_key, + ]); + } + /** * Handler AJAX pour récupérer les détails complets d'un log Peppol * à partir de l'ID du log. diff --git a/app/controllers/Woocommerce_controller.php b/app/controllers/Woocommerce_controller.php index 87fb5a1..dc4c53d 100644 --- a/app/controllers/Woocommerce_controller.php +++ b/app/controllers/Woocommerce_controller.php @@ -59,9 +59,21 @@ class PEPPOL_Woocommerce_controller { // N'envoyer vers Peppol que les commandes avec un numéro de TVA saisi ET valide. // Le plugin "WooCommerce EU VAT Number" stocke le numéro validé dans la meta "_billing_vat_number" // et le statut de validation dans "_vat_number_is_valid" (valeur 'true' ou 'false'). - // Utilisation de la fonction helper pour gérer différentes variantes de clés (billing_vat_number, billing_tva, etc.) - $billing_vat_number = \ESI_PEPPOL\helpers\PEPPOL_Woo_Helper::esi_get_order_vat_number($order); - $vat_is_valid = (string) $order->get_meta('_vat_number_is_valid'); + // Utilisation du champ TVA configuré dans les settings si disponible, sinon utilisation de la fonction helper + $saved_vat_field = \get_option('esi_peppol_vat_field_key', ''); + + if (!empty($saved_vat_field)) { + // Utiliser le champ TVA sauvegardé dans les settings + $billing_vat_number = $order->get_meta($saved_vat_field, true); + if (is_string($billing_vat_number)) { + $billing_vat_number = trim($billing_vat_number); + } + } else { + // Fallback : utiliser la fonction helper pour gérer différentes variantes de clés + $billing_vat_number = \ESI_PEPPOL\helpers\PEPPOL_Woo_Helper::esi_get_order_vat_number($order); + } + + $vat_is_valid = (string) $order->get_meta('_vat_number_is_valid'); if ($billing_vat_number === '' || $vat_is_valid !== 'true') { return; diff --git a/app/helpers/Woo_Helper.php b/app/helpers/Woo_Helper.php index 1ce25bf..adaada8 100644 --- a/app/helpers/Woo_Helper.php +++ b/app/helpers/Woo_Helper.php @@ -34,7 +34,7 @@ class PEPPOL_Woo_Helper { if ( is_numeric( $order ) ) { $order = wc_get_order( $order ); } - + if ( ! $order instanceof \WC_Order ) { return ''; } @@ -93,5 +93,98 @@ class PEPPOL_Woo_Helper { // sinon le 1er trouvé return ! empty($vat_candidates) ? reset($vat_candidates) : ''; } + + public static function esi_get_order_vat( $order ) { + if ( is_numeric($order) ) { + $order = wc_get_order($order); + } + if ( ! $order instanceof WC_Order ) { + return [ + 'found' => false, + 'key' => null, + 'value' => null, + 'source' => null, + ]; + } + + $patterns = [ 'vat','tva','btw','ust','mwst','piva','moms','mva' ]; + + /* ================================================== + * 1) CHECKOUT FIELDS (si présents) + * ================================================== */ + if ( function_exists('WC') && WC()->checkout() ) { + $fields = WC()->checkout()->get_checkout_fields(); + + foreach ( $fields as $group_fields ) { + foreach ( $group_fields as $key => $def ) { + $k = strtolower($key); + + foreach ( $patterns as $p ) { + if ( strpos($k, $p) !== false ) { + + // tentatives de lecture meta + foreach ( [ $key, '_' . $key ] as $meta_key ) { + $val = $order->get_meta($meta_key, true); + if ( ! empty($val) ) { + return [ + 'found' => true, + 'key' => $meta_key, + 'value' => trim((string)$val), + 'source' => 'checkout_field', + ]; + } + } + } + } + + // fallback label + $label = strtolower((string)($def['label'] ?? '')); + if ( $label && preg_match('/\b(vat|tva|btw|mwst|ust|piva|moms|mva)\b/i', $label) ) { + foreach ( [ $key, '_' . $key ] as $meta_key ) { + $val = $order->get_meta($meta_key, true); + if ( ! empty($val) ) { + return [ + 'found' => true, + 'key' => $meta_key, + 'value' => trim((string)$val), + 'source' => 'checkout_label', + ]; + } + } + } + } + } + } + + /* ================================================== + * 2) SCAN META (fallback ultime) + * ================================================== */ + foreach ( $order->get_meta_data() as $meta ) { + $k = strtolower((string)$meta->key); + + foreach ( $patterns as $p ) { + if ( strpos($k, $p) !== false ) { + $v = $meta->value; + if ( is_string($v) ) $v = trim($v); + + if ( ! empty($v) ) { + return [ + 'found' => true, + 'key' => $meta->key, + 'value' => $v, + 'source' => 'meta_scan', + ]; + } + } + } + } + + return [ + 'found' => false, + 'key' => null, + 'value' => null, + 'source' => null, + ]; + } } \ No newline at end of file diff --git a/assets/js/admin.js b/assets/js/admin.js index a762f47..6c98ea5 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -507,6 +507,154 @@ }); } + // Gestion de la détection des champs TVA + var $detectVatBtn = $('#esi-peppol-detect-vat-fields'); + var $vatFieldsResult = $('#esi-peppol-vat-fields-result'); + + if ($detectVatBtn.length && typeof window.esiPeppolAdmin !== 'undefined') { + $detectVatBtn.on('click', function (e) { + e.preventDefault(); + + var $btn = $(this); + $btn.prop('disabled', true).addClass('updating-message'); + $vatFieldsResult.empty(); + + $.post(window.esiPeppolAdmin.ajax_url, { + action: 'esi_peppol_detect_vat_fields', + nonce: window.esiPeppolAdmin.vat_detect_nonce + }) + .done(function (response) { + if (response && response.success && response.data) { + var fields = response.data.fields || []; + var html = ''; + + if (fields.length === 0) { + html = '

' + + 'Aucun champ TVA trouvé dans les commandes récentes.' + + '

'; + } else if (fields.length === 1) { + // Un seul champ trouvé : afficher et proposer de sauvegarder + var field = fields[0]; + html = '
'; + html += '

Champ TVA détecté : ' + + field.key + '

'; + html += '

Exemple de valeur : ' + + (field.sample_value || 'N/A') + '

'; + html += '

Nombre d\'occurrences : ' + field.count + '

'; + html += ''; + html += '
'; + } else { + // Plusieurs champs trouvés : afficher des boutons radio + html = '
'; + html += '

Plusieurs champs TVA détectés. Veuillez sélectionner celui à utiliser :

'; + html += ''; + html += ''; + html += ''; + + fields.forEach(function(field, index) { + var checked = index === 0 ? ' checked' : ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + }); + + html += '
SélectionNom du champExemple de valeurOccurrences
' + (field.sample_value || 'N/A') + '' + field.count + '
'; + html += ''; + html += '
'; + } + + $vatFieldsResult.html(html); + } else { + var errorMsg = (response && response.data && response.data.message) + ? response.data.message + : 'Erreur lors de la détection des champs TVA.'; + $vatFieldsResult.html( + '

' + errorMsg + '

' + ); + } + }) + .fail(function () { + $vatFieldsResult.html( + '

Erreur de communication avec le serveur WordPress.

' + ); + }) + .always(function () { + $btn.prop('disabled', false).removeClass('updating-message'); + }); + }); + + // Gestion du bouton "Enregistrer" pour le champ TVA + $(document).on('click', '.esi-peppol-save-vat-field', function (e) { + e.preventDefault(); + + var $btn = $(this); + var vatFieldKey = $btn.data('vat-field-key'); + + // Si pas de clé dans data-attribute, récupérer depuis le radio sélectionné + if (!vatFieldKey || vatFieldKey === '') { + var $selectedRadio = $('input[name="esi_peppol_vat_field_radio"]:checked'); + if ($selectedRadio.length) { + vatFieldKey = $selectedRadio.val(); + } else { + alert('Veuillez sélectionner un champ TVA.'); + return; + } + } + + if (!vatFieldKey) { + alert('Erreur : aucun champ TVA sélectionné.'); + return; + } + + $btn.prop('disabled', true).addClass('updating-message'); + + $.post(window.esiPeppolAdmin.ajax_url, { + action: 'esi_peppol_save_vat_field', + nonce: window.esiPeppolAdmin.vat_save_nonce, + vat_field_key: vatFieldKey + }) + .done(function (response) { + if (response && response.success) { + var message = (response.data && response.data.message) + ? response.data.message + : 'Champ TVA enregistré avec succès.'; + + $vatFieldsResult.html( + '

' + message + '

' + ); + + // Recharger la page après 1 seconde pour afficher le champ sauvegardé + setTimeout(function () { + window.location.reload(); + }, 1000); + } else { + var errorMsg = (response && response.data && response.data.message) + ? response.data.message + : 'Erreur lors de l\'enregistrement du champ TVA.'; + $vatFieldsResult.html( + '

' + errorMsg + '

' + ); + } + }) + .fail(function () { + $vatFieldsResult.html( + '

Erreur de communication avec le serveur WordPress.

' + ); + }) + .always(function () { + $btn.prop('disabled', false).removeClass('updating-message'); + }); + }); + } + }); })(jQuery); diff --git a/templates/admin/settings.php b/templates/admin/settings.php index d73804b..ad39882 100644 --- a/templates/admin/settings.php +++ b/templates/admin/settings.php @@ -151,6 +151,39 @@ if ($logo_email_id) { + + + + + + +
+ +
+ +

+ ' . esc_html($saved_vat_field) . '' + ); + ?> +

+ +
+

+ +

+ + +