diff --git a/ESI_peppol.php b/ESI_peppol.php index 85d9b9a..e03edaf 100644 --- a/ESI_peppol.php +++ b/ESI_peppol.php @@ -143,6 +143,12 @@ add_action( [PEPPOL_Plugin::class, 'ajax_check_invoice_status'] ); +// AJAX admin pour récupérer les détails d'un log Peppol +add_action( + 'wp_ajax_esi_peppol_get_log_details', + [PEPPOL_Plugin::class, 'ajax_get_log_details'] +); + // 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 aabe403..ad7313f 100644 --- a/app/controllers/Plugin.php +++ b/app/controllers/Plugin.php @@ -247,6 +247,7 @@ class PEPPOL_Plugin { 'nonce' => wp_create_nonce('esi_peppol_test_connection'), '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'), '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'), @@ -257,6 +258,13 @@ class PEPPOL_Plugin { 'i18n_logs_resend_ko' => __('L\'envoi du document a échoué.', 'esi_peppol'), 'i18n_logs_status_lbl' => __('Statut actuel du document', 'esi_peppol'), 'i18n_logs_detail_lbl' => __('Détail retour API', 'esi_peppol'), + 'i18n_logs_general_info' => __('Informations générales', 'esi_peppol'), + 'i18n_logs_totals' => __('Totaux', 'esi_peppol'), + 'i18n_logs_vat_details' => __('Détails TVA', 'esi_peppol'), + 'i18n_logs_customer_data' => __('Données client', 'esi_peppol'), + 'i18n_logs_data_sent' => __('Données envoyées', 'esi_peppol'), + 'i18n_logs_response_data' => __('Données de réponse', 'esi_peppol'), + 'i18n_logs_error' => __('Erreur lors du chargement des détails.', 'esi_peppol'), ] ); } @@ -500,6 +508,158 @@ class PEPPOL_Plugin { wp_send_json_error($payload_response, 200); } + /** + * Handler AJAX pour récupérer les détails complets d'un log Peppol + * à partir de l'ID du log. + * + * @return void + */ + public static function ajax_get_log_details(): void { + if (!check_ajax_referer('esi_peppol_get_log_details', 'nonce', false)) { + wp_send_json_error( + [ + 'message' => __('Sécurité : nonce invalide.', 'esi_peppol'), + ], + 400 + ); + } + + if (!current_user_can('manage_woocommerce') && !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 + ); + } + + $log_id = isset($_POST['log_id']) ? (int) $_POST['log_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing + + if ($log_id <= 0) { + wp_send_json_error( + [ + 'message' => __('Identifiant de log invalide.', 'esi_peppol'), + ], + 400 + ); + } + + $row = \ESI_PEPPOL\models\PEPPOL_Main_model::get_by_id($log_id); + + if (!$row) { + wp_send_json_error( + [ + 'message' => __('Aucun enregistrement Peppol trouvé pour cet ID.', 'esi_peppol'), + ], + 404 + ); + } + + // Préparer les données pour l'affichage + $data_sent = $row->data_sent ?? null; + $response_data = $row->response_data ?? null; + + // Formater les données JSON si possible + $data_sent_formatted = ''; + if ($data_sent) { + if (is_array($data_sent) || is_object($data_sent)) { + $data_sent_formatted = wp_json_encode($data_sent, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } else { + $data_sent_formatted = (string) $data_sent; + } + } + + $response_data_formatted = ''; + if ($response_data) { + if (is_array($response_data) || is_object($response_data)) { + $response_data_formatted = wp_json_encode($response_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } else { + $response_data_formatted = (string) $response_data; + } + } + + // Extraire les informations financières depuis data_sent + $invoice_totals = null; + $vat_totals = null; + $customer_data = null; + + if ($data_sent && (is_array($data_sent) || is_object($data_sent))) { + // Convertir en tableau si c'est un objet + $data_array = is_object($data_sent) ? (array) $data_sent : $data_sent; + + // Extraire invoice_totals + if (isset($data_array['invoice_totals']) && is_array($data_array['invoice_totals'])) { + $invoice_totals = $data_array['invoice_totals']; + } + + // Extraire vat_totals + if (isset($data_array['vat_totals']) && is_array($data_array['vat_totals'])) { + $vat_totals = $data_array['vat_totals']; + } + + // Extraire les données client (buyer) + if (isset($data_array['parties']['buyer']) && is_array($data_array['parties']['buyer'])) { + $customer_data = $data_array['parties']['buyer']; + } + } + + // Récupérer les informations de la commande si disponible + $order_info = null; + $order_totals = null; + if (!empty($row->id_order)) { + $order = wc_get_order($row->id_order); + if ($order) { + $order_info = [ + 'id' => $order->get_id(), + 'number' => $order->get_order_number(), + 'status' => $order->get_status(), + 'total' => $order->get_total(), + 'billing_name' => trim($order->get_billing_first_name() . ' ' . $order->get_billing_last_name()), + 'billing_email' => $order->get_billing_email(), + 'billing_phone' => $order->get_billing_phone(), + 'billing_company' => $order->get_billing_company(), + 'billing_address_1' => $order->get_billing_address_1(), + 'billing_address_2' => $order->get_billing_address_2(), + 'billing_city' => $order->get_billing_city(), + 'billing_postcode' => $order->get_billing_postcode(), + 'billing_country' => $order->get_billing_country(), + 'billing_vat_number' => $order->get_meta('_billing_vat_number') ?: '', + 'edit_link' => get_edit_post_link($row->id_order), + ]; + + // Calculer les totaux depuis la commande si invoice_totals n'est pas disponible + if (!$invoice_totals) { + $order_totals = [ + 'total_amount_excluding_vat' => $order->get_total() - $order->get_total_tax(), + 'total_vat_amount' => $order->get_total_tax(), + 'total_amount_including_vat' => $order->get_total(), + ]; + } + } + } + + $payload_response = [ + 'id' => $row->id, + 'id_order' => $row->id_order ?? 0, + 'order_info' => $order_info, + 'document_id' => $row->document_id ?? '', + 'peppol_document_id' => $row->peppol_document_id ?? '', + 'status' => $row->status ?? '', + 'success' => !empty($row->success), + 'message' => $row->message ?? '', + 'http_code' => $row->http_code ?? null, + 'date_add' => $row->date_add ?? '', + 'date_update' => $row->date_update ?? '', + 'invoice_totals' => $invoice_totals ?? $order_totals, + 'vat_totals' => $vat_totals, + 'customer_data' => $customer_data, + 'data_sent' => $data_sent_formatted, + 'response_data' => $response_data_formatted, + ]; + + wp_send_json_success($payload_response); + } + public static function enqueue_front_assets() { } diff --git a/app/models/Main_model.php b/app/models/Main_model.php index 071f7a4..7b3814a 100644 --- a/app/models/Main_model.php +++ b/app/models/Main_model.php @@ -247,4 +247,40 @@ class PEPPOL_Main_model { return $row; } + + /** + * Récupère un enregistrement PEPPOL par ID (id). + * + * La propriété response_data est désérialisée automatiquement. + * La propriété data_sent est désérialisée automatiquement. + * + * @param int $id ID de l'enregistrement. + * @return object|null + */ + public static function get_by_id(int $id): ?object { + global $wpdb; + + $table_name = self::get_table_name(); + + $row = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$table_name} WHERE id = %d LIMIT 1", + $id + ) + ); + + if (!$row) { + return null; + } + + // Désérialiser la réponse API et le payload si nécessaire + if (isset($row->response_data)) { + $row->response_data = \maybe_unserialize($row->response_data); + } + if (isset($row->data_sent)) { + $row->data_sent = \maybe_unserialize($row->data_sent); + } + + return $row; + } } \ No newline at end of file diff --git a/assets/css/admin.css b/assets/css/admin.css index 9a4b666..9440396 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -116,3 +116,148 @@ background-color: #6b7280; color: #ffffff; } + +/* Styles pour la modal de détails des logs */ +.esi-peppol-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100000; + display: flex; + align-items: center; + justify-content: center; +} + +.esi-peppol-modal-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + cursor: pointer; +} + +.esi-peppol-modal-content { + position: relative; + background: #ffffff; + border-radius: 8px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + max-width: 90%; + max-height: 90vh; + width: 800px; + display: flex; + flex-direction: column; + z-index: 1; +} + +.esi-peppol-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + border-bottom: 1px solid #ddd; +} + +.esi-peppol-modal-header h2 { + margin: 0; + font-size: 20px; + font-weight: 600; +} + +.esi-peppol-modal-close { + background: none; + border: none; + cursor: pointer; + padding: 4px; + color: #666; + font-size: 20px; + line-height: 1; + transition: color 0.2s; +} + +.esi-peppol-modal-close:hover { + color: #000; +} + +.esi-peppol-modal-close .dashicons { + width: 20px; + height: 20px; + font-size: 20px; +} + +.esi-peppol-modal-body { + padding: 24px; + overflow-y: auto; + flex: 1; +} + +.esi-peppol-loading { + text-align: center; + padding: 40px 20px; +} + +.esi-peppol-loading .spinner { + float: none; + margin: 0 auto 10px; +} + +.esi-peppol-log-details-section { + margin-bottom: 30px; +} + +.esi-peppol-log-details-section:last-child { + margin-bottom: 0; +} + +.esi-peppol-log-details-section h3 { + margin-top: 0; + margin-bottom: 15px; + font-size: 16px; + font-weight: 600; + color: #1d2327; + border-bottom: 2px solid #2271b1; + padding-bottom: 8px; +} + +.esi-peppol-log-details-section table { + margin: 0; +} + +.esi-peppol-log-details-section table th { + background-color: #f6f7f7; + font-weight: 600; + padding: 12px; + text-align: left; + vertical-align: top; +} + +.esi-peppol-log-details-section table td { + padding: 12px; + vertical-align: top; +} + +.esi-peppol-log-json { + background-color: #f6f7f7; + border: 1px solid #ddd; + border-radius: 4px; + padding: 15px; + overflow-x: auto; + font-family: 'Courier New', Courier, monospace; + font-size: 13px; + line-height: 1.6; + max-height: 400px; + overflow-y: auto; + white-space: pre-wrap; + word-wrap: break-word; +} + +.esi-peppol-error { + color: #d63638; + padding: 15px; + background-color: #fcf0f1; + border-left: 4px solid #d63638; + margin: 0; +} diff --git a/assets/js/admin.js b/assets/js/admin.js index f9e0f2f..e0c69a5 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -271,6 +271,224 @@ $removeLogoBtn.hide(); }); } + + // Gestion de la modal de détails des logs + var $modal = $('#esi-peppol-log-details-modal'); + var $modalOverlay = $modal.find('.esi-peppol-modal-overlay'); + var $modalClose = $modal.find('.esi-peppol-modal-close'); + var $modalContent = $modal.find('.esi-peppol-modal-content'); + var $modalLoading = $('#esi-peppol-log-details-loading'); + var $modalDetailsContent = $('#esi-peppol-log-details-content'); + + // Fonction pour ouvrir la modal + function openLogDetailsModal() { + $modal.fadeIn(200); + $('body').css('overflow', 'hidden'); + } + + // Fonction pour fermer la modal + function closeLogDetailsModal() { + $modal.fadeOut(200); + $('body').css('overflow', ''); + $modalDetailsContent.hide().empty(); + $modalLoading.show(); + } + + // Fermer la modal en cliquant sur l'overlay ou le bouton de fermeture + $modalOverlay.on('click', closeLogDetailsModal); + $modalClose.on('click', closeLogDetailsModal); + + // Fermer la modal avec la touche Escape + $(document).on('keydown', function (e) { + if (e.key === 'Escape' && $modal.is(':visible')) { + closeLogDetailsModal(); + } + }); + + // Empêcher la fermeture en cliquant sur le contenu de la modal + $modalContent.on('click', function (e) { + e.stopPropagation(); + }); + + // Gestion du clic sur le bouton "Détail" + if ($logsTable.length && typeof window.esiPeppolAdmin !== 'undefined') { + $logsTable.on('click', '.esi-peppol-log-detail', function (e) { + e.preventDefault(); + + var $btn = $(this); + var logId = $btn.data('log-id'); + + if (!logId) { + return; + } + + // Ouvrir la modal + openLogDetailsModal(); + $modalLoading.show(); + $modalDetailsContent.hide().empty(); + + // Charger les détails via AJAX + $.post(window.esiPeppolAdmin.ajax_url, { + action: 'esi_peppol_get_log_details', + nonce: window.esiPeppolAdmin.logs_nonce_details, + log_id: logId + }) + .done(function (response) { + $modalLoading.hide(); + + if (response && response.success && response.data) { + var data = response.data; + var html = ''; + + // Informations générales + html += '
| ID Log | ' + (data.id || '-') + ' |
|---|---|
| Commande | '; + html += ''; + html += '#' + data.order_info.number + ''; + if (data.order_info.billing_name) { + html += ' - ' + data.order_info.billing_name; + } + html += ' |
| Statut commande | ' + data.order_info.status + ' |
| Total | ' + data.order_info.total + ' € |
| ID Commande | ' + (data.id_order || '-') + ' |
| Document ID | ' + (data.document_id || '-') + ' |
| Peppol Document ID | ' + (data.peppol_document_id || '-') + ' |
| Statut | ' + (data.status || '-') + ' |
| Succès | ' + (data.success ? 'Oui' : 'Non') + ' |
| Code HTTP | ' + (data.http_code || '-') + ' |
| Message | ' + (data.message || '-') + ' |
| Date ajout | ' + (data.date_add || '-') + ' |
| Dernière mise à jour | ' + (data.date_update || '-') + ' |
| Total HTVA | ' + totalHTVA.toFixed(2) + ' € |
|---|---|
| Total TVA | ' + totalTVA.toFixed(2) + ' € |
| Total TVAC | ' + totalTVAC.toFixed(2) + ' € |
| Montant à payer | ' + payableAmount.toFixed(2) + ' € |
| Taux TVA | Montant HTVA | Montant TVA | Montant TVAC |
|---|---|---|---|
| ' + vatRate.toFixed(2) + ' % | '; + html += '' + taxableAmount.toFixed(2) + ' € | '; + html += '' + vatAmount.toFixed(2) + ' € | '; + html += '' + totalInclVat.toFixed(2) + ' € | '; + html += '
| Nom / Raison sociale | ' + customerName + ' |
|---|---|
| Nom légal | ' + customerLegalName + ' |
| Numéro TVA | ' + customerVat + ' |
| ' + customerEmail + ' | |
| Téléphone | ' + customerPhone + ' |
| Adresse | ';
+ html += addressLine1;
+ if (addressLine2) {
+ html += ' ' + addressLine2; + } + html += ' ' + postalCode + ' ' + city; + html += ' ' + countryCode; + html += ' |
' + $('').text(data.data_sent).html() + '';
+ html += '';
+ }
+
+ // Données de réponse
+ if (data.response_data) {
+ html += '';
+ html += '' + (window.esiPeppolAdmin.i18n_logs_response_data || 'Données de réponse') + '
';
+ html += '' + $('').text(data.response_data).html() + '';
+ html += '';
+ }
+
+ $modalDetailsContent.html(html).show();
+ } else {
+ var errorMsg = (response && response.data && response.data.message)
+ ? response.data.message
+ : (window.esiPeppolAdmin.i18n_logs_error || 'Erreur lors du chargement des détails.');
+ $modalDetailsContent.html('' + errorMsg + '
').show();
+ }
+ })
+ .fail(function () {
+ $modalLoading.hide();
+ var errorMsg = window.esiPeppolAdmin.i18n_network || 'Erreur de communication avec le serveur WordPress.';
+ $modalDetailsContent.html('' + errorMsg + '
').show();
+ });
+ });
+ }
});
})(jQuery);
diff --git a/templates/admin/logs.php b/templates/admin/logs.php
index 117c109..ef05be8 100644
--- a/templates/admin/logs.php
+++ b/templates/admin/logs.php
@@ -73,6 +73,13 @@ if (!defined('ABSPATH')) {
date_add); ?>
date_update); ?>
+