Ajout modal détail transaction api

This commit is contained in:
Jean-Philippe Staelen 2025-12-16 09:51:22 +01:00
parent 86e8d49065
commit 79ba4ad2e5
7 changed files with 607 additions and 0 deletions

View File

@ -143,6 +143,12 @@ add_action(
[PEPPOL_Plugin::class, 'ajax_check_invoice_status'] [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. // Champ TVA de la boutique dans les réglages WooCommerce > Général.
add_filter( add_filter(
'woocommerce_general_settings', 'woocommerce_general_settings',

View File

@ -247,6 +247,7 @@ class PEPPOL_Plugin {
'nonce' => wp_create_nonce('esi_peppol_test_connection'), 'nonce' => wp_create_nonce('esi_peppol_test_connection'),
'logs_nonce_resend' => wp_create_nonce('esi_peppol_resend_invoice'), 'logs_nonce_resend' => wp_create_nonce('esi_peppol_resend_invoice'),
'logs_nonce_status' => wp_create_nonce('esi_peppol_check_invoice_status'), '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_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_success' => __('Connexion réussie à l\'API ESIPeppol.', 'esi_peppol'),
'i18n_error' => __('La connexion a échoué. Veuillez vérifier vos identifiants.', '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_resend_ko' => __('L\'envoi du document a échoué.', 'esi_peppol'),
'i18n_logs_status_lbl' => __('Statut actuel du document', 'esi_peppol'), 'i18n_logs_status_lbl' => __('Statut actuel du document', 'esi_peppol'),
'i18n_logs_detail_lbl' => __('Détail retour API', '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); 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() { public static function enqueue_front_assets() {
} }

View File

@ -247,4 +247,40 @@ class PEPPOL_Main_model {
return $row; 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;
}
} }

View File

@ -116,3 +116,148 @@
background-color: #6b7280; background-color: #6b7280;
color: #ffffff; 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;
}

View File

@ -271,6 +271,224 @@
$removeLogoBtn.hide(); $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 += '<div class="esi-peppol-log-details-section">';
html += '<h3>' + (window.esiPeppolAdmin.i18n_logs_general_info || 'Informations générales') + '</h3>';
html += '<table class="widefat">';
html += '<tr><th style="width: 200px;">ID Log</th><td>' + (data.id || '-') + '</td></tr>';
if (data.order_info) {
html += '<tr><th>Commande</th><td>';
html += '<a href="' + data.order_info.edit_link + '" target="_blank">';
html += '#' + data.order_info.number + '</a>';
if (data.order_info.billing_name) {
html += ' - ' + data.order_info.billing_name;
}
html += '</td></tr>';
html += '<tr><th>Statut commande</th><td>' + data.order_info.status + '</td></tr>';
html += '<tr><th>Total</th><td>' + data.order_info.total + ' €</td></tr>';
} else {
html += '<tr><th>ID Commande</th><td>' + (data.id_order || '-') + '</td></tr>';
}
html += '<tr><th>Document ID</th><td>' + (data.document_id || '-') + '</td></tr>';
html += '<tr><th>Peppol Document ID</th><td>' + (data.peppol_document_id || '-') + '</td></tr>';
html += '<tr><th>Statut</th><td><span class="statut statut-' + (data.status ? data.status.toLowerCase().replace(/\s+/g, '-') : '') + '">' + (data.status || '-') + '</span></td></tr>';
html += '<tr><th>Succès</th><td>' + (data.success ? '<span style="color:green;">Oui</span>' : '<span style="color:red;">Non</span>') + '</td></tr>';
html += '<tr><th>Code HTTP</th><td>' + (data.http_code || '-') + '</td></tr>';
html += '<tr><th>Message</th><td>' + (data.message || '-') + '</td></tr>';
html += '<tr><th>Date ajout</th><td>' + (data.date_add || '-') + '</td></tr>';
html += '<tr><th>Dernière mise à jour</th><td>' + (data.date_update || '-') + '</td></tr>';
html += '</table>';
html += '</div>';
// Totaux (HTVA, TVAC, TVA)
if (data.invoice_totals) {
html += '<div class="esi-peppol-log-details-section">';
html += '<h3>' + (window.esiPeppolAdmin.i18n_logs_totals || 'Totaux') + '</h3>';
html += '<table class="widefat">';
var totalHTVA = parseFloat(data.invoice_totals.total_amount_excluding_vat || 0);
var totalTVA = parseFloat(data.invoice_totals.total_vat_amount || 0);
var totalTVAC = parseFloat(data.invoice_totals.total_amount_including_vat || 0);
html += '<tr><th style="width: 200px;">Total HTVA</th><td><strong>' + totalHTVA.toFixed(2) + ' €</strong></td></tr>';
html += '<tr><th>Total TVA</th><td><strong>' + totalTVA.toFixed(2) + ' €</strong></td></tr>';
html += '<tr><th>Total TVAC</th><td><strong>' + totalTVAC.toFixed(2) + ' €</strong></td></tr>';
if (data.invoice_totals.total_payable_amount) {
var payableAmount = parseFloat(data.invoice_totals.total_payable_amount || 0);
html += '<tr><th>Montant à payer</th><td>' + payableAmount.toFixed(2) + ' €</td></tr>';
}
html += '</table>';
html += '</div>';
}
// Détails TVA par taux
if (data.vat_totals && Array.isArray(data.vat_totals) && data.vat_totals.length > 0) {
html += '<div class="esi-peppol-log-details-section">';
html += '<h3>' + (window.esiPeppolAdmin.i18n_logs_vat_details || 'Détails TVA') + '</h3>';
html += '<table class="widefat">';
html += '<thead><tr><th>Taux TVA</th><th>Montant HTVA</th><th>Montant TVA</th><th>Montant TVAC</th></tr></thead>';
html += '<tbody>';
data.vat_totals.forEach(function(vat) {
var vatRate = parseFloat(vat.vat_rate || 0);
var taxableAmount = parseFloat(vat.taxable_amount || 0);
var vatAmount = parseFloat(vat.vat_amount || 0);
var totalInclVat = taxableAmount + vatAmount;
html += '<tr>';
html += '<td>' + vatRate.toFixed(2) + ' %</td>';
html += '<td>' + taxableAmount.toFixed(2) + ' €</td>';
html += '<td>' + vatAmount.toFixed(2) + ' €</td>';
html += '<td>' + totalInclVat.toFixed(2) + ' €</td>';
html += '</tr>';
});
html += '</tbody>';
html += '</table>';
html += '</div>';
}
// Données client
if (data.customer_data || (data.order_info && (data.order_info.billing_name || data.order_info.billing_company))) {
html += '<div class="esi-peppol-log-details-section">';
html += '<h3>' + (window.esiPeppolAdmin.i18n_logs_customer_data || 'Données client') + '</h3>';
html += '<table class="widefat">';
// Utiliser customer_data depuis le payload si disponible, sinon order_info
var customer = data.customer_data || {};
var orderInfo = data.order_info || {};
var customerName = customer.name || orderInfo.billing_company || orderInfo.billing_name || '-';
var customerLegalName = customer.legal_name || customer.name || orderInfo.billing_company || orderInfo.billing_name || '-';
var customerVat = customer.vat_number || orderInfo.billing_vat_number || '-';
var customerEmail = (customer.contact && customer.contact.contact_email) || orderInfo.billing_email || '-';
var customerPhone = (customer.contact && customer.contact.contact_phone) || orderInfo.billing_phone || '-';
var address = customer.address || {};
var addressLine1 = address.address_line_1 || orderInfo.billing_address_1 || '-';
var addressLine2 = address.address_line_2 || orderInfo.billing_address_2 || '';
var city = address.city || orderInfo.billing_city || '-';
var postalCode = address.postal_code || orderInfo.billing_postcode || '-';
var countryCode = address.country_code || orderInfo.billing_country || '-';
html += '<tr><th style="width: 200px;">Nom / Raison sociale</th><td>' + customerName + '</td></tr>';
if (customerLegalName && customerLegalName !== customerName) {
html += '<tr><th>Nom légal</th><td>' + customerLegalName + '</td></tr>';
}
html += '<tr><th>Numéro TVA</th><td>' + customerVat + '</td></tr>';
html += '<tr><th>Email</th><td>' + customerEmail + '</td></tr>';
if (customerPhone && customerPhone !== '-') {
html += '<tr><th>Téléphone</th><td>' + customerPhone + '</td></tr>';
}
html += '<tr><th>Adresse</th><td>';
html += addressLine1;
if (addressLine2) {
html += '<br>' + addressLine2;
}
html += '<br>' + postalCode + ' ' + city;
html += '<br>' + countryCode;
html += '</td></tr>';
html += '</table>';
html += '</div>';
}
// Données envoyées
if (data.data_sent) {
html += '<div class="esi-peppol-log-details-section">';
html += '<h3>' + (window.esiPeppolAdmin.i18n_logs_data_sent || 'Données envoyées') + '</h3>';
html += '<pre class="esi-peppol-log-json">' + $('<div>').text(data.data_sent).html() + '</pre>';
html += '</div>';
}
// Données de réponse
if (data.response_data) {
html += '<div class="esi-peppol-log-details-section">';
html += '<h3>' + (window.esiPeppolAdmin.i18n_logs_response_data || 'Données de réponse') + '</h3>';
html += '<pre class="esi-peppol-log-json">' + $('<div>').text(data.response_data).html() + '</pre>';
html += '</div>';
}
$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('<p class="esi-peppol-error">' + errorMsg + '</p>').show();
}
})
.fail(function () {
$modalLoading.hide();
var errorMsg = window.esiPeppolAdmin.i18n_network || 'Erreur de communication avec le serveur WordPress.';
$modalDetailsContent.html('<p class="esi-peppol-error">' + errorMsg + '</p>').show();
});
});
}
}); });
})(jQuery); })(jQuery);

View File

@ -73,6 +73,13 @@ if (!defined('ABSPATH')) {
<td><?php echo esc_html($row->date_add); ?></td> <td><?php echo esc_html($row->date_add); ?></td>
<td><?php echo esc_html($row->date_update); ?></td> <td><?php echo esc_html($row->date_update); ?></td>
<td> <td>
<button
type="button"
class="button button-secondary esi-peppol-log-detail"
data-log-id="<?php echo esc_attr($row->id); ?>"
>
<?php esc_html_e('Détail', 'esi_peppol'); ?>
</button>
<button <button
type="button" type="button"
class="button button-secondary esi-peppol-log-resend" class="button button-secondary esi-peppol-log-resend"
@ -95,4 +102,11 @@ if (!defined('ABSPATH')) {
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php
// Inclusion de la modal de détails
$modal_template = ESI_PEPPOL_DIR . 'templates/modules/logs_details_modal.php';
if (file_exists($modal_template)) {
include $modal_template;
}
?>

View File

@ -0,0 +1,28 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
?>
<!-- Modal pour afficher les détails d'un log -->
<div id="esi-peppol-log-details-modal" class="esi-peppol-modal" style="display: none;">
<div class="esi-peppol-modal-overlay"></div>
<div class="esi-peppol-modal-content">
<div class="esi-peppol-modal-header">
<h2><?php esc_html_e('Détails du log', 'esi_peppol'); ?></h2>
<button type="button" class="esi-peppol-modal-close" aria-label="<?php esc_attr_e('Fermer', 'esi_peppol'); ?>">
<span class="dashicons dashicons-no-alt"></span>
</button>
</div>
<div class="esi-peppol-modal-body">
<div id="esi-peppol-log-details-loading" class="esi-peppol-loading">
<span class="spinner is-active"></span>
<p><?php esc_html_e('Chargement des détails...', 'esi_peppol'); ?></p>
</div>
<div id="esi-peppol-log-details-content" style="display: none;">
<!-- Contenu chargé dynamiquement -->
</div>
</div>
</div>
</div>