From 02c8fee174a0d49dce7f83ff094a378ab7b8fc60 Mon Sep 17 00:00:00 2001 From: Malin Date: Wed, 1 Apr 2026 17:30:10 +0200 Subject: [PATCH] fix: Spanish frontend, wider columns, currency symbols, PDF download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PDF fix: - Replace session-cookie auth with HTTP Basic Auth (username:api_key) which is natively supported by Odoo 17+ report endpoints - Validate response is actually a PDF (%PDF header check) before serving - Return a descriptive Spanish error if HTTP code != 200 or body is not PDF Frontend → Spanish: - All invoice template text in Spanish (Nº Factura, Vencimiento, etc.) - All calendar/booking template text in Spanish - Payment state labels: Pendiente / Parcial / En cobro / Pagado / Anulado - "Vencida" badge for overdue invoices - Error/notice messages in Spanish across both pages Currency symbols: - Add currency_symbol() helper mapping ISO codes to symbols - EUR → €, USD → $, GBP → £, etc. (25 currencies mapped) Column widths: - Switch invoice table to table-layout:fixed with explicit column widths - col-number: 180px nowrap so reference never wraps across lines - Date/due/amount/status columns all fixed-width and nowrap - Add .woodoo-nowrap utility class Co-Authored-By: Claude Sonnet 4.6 --- assets/css/woodoo.css | 23 +++++- includes/class-woodoo-calendar.php | 2 +- includes/class-woodoo-invoices.php | 115 ++++++++++++++++------------- templates/myaccount-calendar.php | 90 ++++++++++------------ templates/myaccount-invoices.php | 92 ++++++++++++----------- 5 files changed, 169 insertions(+), 153 deletions(-) diff --git a/assets/css/woodoo.css b/assets/css/woodoo.css index 3828c4a..72dc8d8 100644 --- a/assets/css/woodoo.css +++ b/assets/css/woodoo.css @@ -56,18 +56,35 @@ width: 100%; border-collapse: collapse; font-size: .875rem; + table-layout: fixed; /* fixed layout so column widths are respected */ } .woodoo-table th, .woodoo-table td { - padding: 10px 12px; + padding: 10px 14px; border-bottom: 1px solid #e5e7eb; text-align: left; vertical-align: middle; + overflow: hidden; } .woodoo-table th { background: #f9fafb; font-weight: 600; } .woodoo-table tr:last-child td { border-bottom: none; } -.woodoo-table .woodoo-amount { text-align: right; font-variant-numeric: tabular-nums; } -.woodoo-table .woodoo-inv-number { font-weight: 600; } +.woodoo-table .woodoo-amount { + text-align: right; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} +/* Invoice-specific column widths */ +.woodoo-invoices-table .col-number { width: 180px; white-space: nowrap; font-weight: 600; } +.woodoo-invoices-table .col-date { width: 100px; white-space: nowrap; } +.woodoo-invoices-table .col-due { width: 130px; } +.woodoo-invoices-table .col-amount { width: 110px; text-align: right; } +.woodoo-invoices-table .col-balance { width: 130px; text-align: right; } +.woodoo-invoices-table .col-status { width: 100px; white-space: nowrap; } +.woodoo-invoices-table .col-download{ width: 70px; text-align: center; } + +/* Utility: never wrap content in a cell */ +.woodoo-nowrap { white-space: nowrap; } + .woodoo-overdue { color: #dc2626; font-weight: 600; } /* ── Pagination ──────────────────────────────────────────── */ diff --git a/includes/class-woodoo-calendar.php b/includes/class-woodoo-calendar.php index a756c27..83a2a4f 100644 --- a/includes/class-woodoo-calendar.php +++ b/includes/class-woodoo-calendar.php @@ -41,7 +41,7 @@ class WooDoo_Calendar { $api = woodoo_api(); if ( ! $api ) { echo '

' . - esc_html__( 'Booking is not available right now. Please contact support.', 'woodoo' ) . + 'Las reservas no están disponibles en este momento. Por favor, contacta con soporte.' . '

'; return; } diff --git a/includes/class-woodoo-invoices.php b/includes/class-woodoo-invoices.php index 3707d0c..cd1480f 100644 --- a/includes/class-woodoo-invoices.php +++ b/includes/class-woodoo-invoices.php @@ -43,8 +43,8 @@ class WooDoo_Invoices { $partner_id = (int) get_user_meta( $user_id, 'woodoo_odoo_partner_id', true ); if ( ! $partner_id ) { - echo '

' . - esc_html__( 'Your account is not yet linked to Odoo. Please contact us.', 'woodoo' ) . + echo '

' . + 'Tu cuenta aún no está vinculada a Odoo. Por favor, contáctanos para activar esta funcionalidad.' . '

'; return; } @@ -52,7 +52,7 @@ class WooDoo_Invoices { $api = woodoo_api(); if ( ! $api ) { echo '

' . - esc_html__( 'Odoo integration is not configured. Please contact support.', 'woodoo' ) . + 'La integración con Odoo no está configurada. Contacta con soporte.' . '

'; return; } @@ -131,82 +131,95 @@ class WooDoo_Invoices { wp_die( esc_html__( 'Invoice not found.', 'woodoo' ) ); } - // Fetch the PDF report from Odoo - $odoo_url = rtrim( get_option( 'woodoo_odoo_url', '' ), '/' ); - $pdf_url = $odoo_url . '/report/pdf/account.report_invoice/' . $invoice_id; - - // Build auth cookie by first doing session authenticate - $auth_response = wp_remote_post( - $odoo_url . '/web/session/authenticate', - [ - 'headers' => [ 'Content-Type' => 'application/json' ], - 'body' => wp_json_encode( [ - 'jsonrpc' => '2.0', - 'method' => 'call', - 'id' => 1, - 'params' => [ - 'db' => get_option( 'woodoo_odoo_db' ), - 'login' => get_option( 'woodoo_odoo_username' ), - 'password' => get_option( 'woodoo_odoo_api_key' ), - ], - ] ), - 'timeout' => 20, - 'sslverify' => apply_filters( 'woodoo_ssl_verify', true ), - ] + // Fetch the PDF via HTTP Basic Auth (API key as password – supported in Odoo 17+). + // This avoids the fragile session-cookie approach entirely. + $odoo_url = rtrim( get_option( 'woodoo_odoo_url', '' ), '/' ); + $pdf_url = $odoo_url . '/report/pdf/account.report_invoice/' . $invoice_id; + $basic_auth = 'Basic ' . base64_encode( + get_option( 'woodoo_odoo_username', '' ) . ':' . get_option( 'woodoo_odoo_api_key', '' ) ); - if ( is_wp_error( $auth_response ) ) { - wp_die( esc_html__( 'Could not authenticate with Odoo.', 'woodoo' ) ); - } - - // Extract session cookie - $raw_headers = wp_remote_retrieve_headers( $auth_response ); - $session_cookie = ''; - if ( isset( $raw_headers['set-cookie'] ) ) { - $cookie_header = is_array( $raw_headers['set-cookie'] ) - ? $raw_headers['set-cookie'][0] - : $raw_headers['set-cookie']; - preg_match( '/session_id=([^;]+)/', $cookie_header, $m ); - if ( isset( $m[1] ) ) $session_cookie = 'session_id=' . $m[1]; - } - $pdf_response = wp_remote_get( $pdf_url, [ - 'headers' => $session_cookie ? [ 'Cookie' => $session_cookie ] : [], - 'timeout' => 60, + 'headers' => [ 'Authorization' => $basic_auth ], + 'timeout' => 90, 'sslverify' => apply_filters( 'woodoo_ssl_verify', true ), ] ); if ( is_wp_error( $pdf_response ) ) { - wp_die( esc_html__( 'Could not retrieve invoice PDF.', 'woodoo' ) ); + wp_die( 'No se pudo obtener el PDF de la factura: ' . esc_html( $pdf_response->get_error_message() ) ); } - $pdf_body = wp_remote_retrieve_body( $pdf_response ); + $http_code = wp_remote_retrieve_response_code( $pdf_response ); + $pdf_body = wp_remote_retrieve_body( $pdf_response ); + + // Guard: Odoo may return a JSON error or HTML login page instead of a PDF + if ( $http_code !== 200 || substr( $pdf_body, 0, 4 ) !== '%PDF' ) { + wp_die( 'No se pudo generar el PDF. Código HTTP: ' . esc_html( $http_code ) . '. Comprueba los permisos del usuario de la API en Odoo.' ); + } header( 'Content-Type: application/pdf' ); - header( 'Content-Disposition: attachment; filename="invoice-' . $invoice_id . '.pdf"' ); + header( 'Content-Disposition: attachment; filename="factura-' . $invoice_id . '.pdf"' ); header( 'Content-Length: ' . strlen( $pdf_body ) ); + header( 'Cache-Control: private' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $pdf_body; exit; } - // ── Helper ──────────────────────────────────────────────────────────── + // ── Helpers ─────────────────────────────────────────────────────────── public static function payment_state_label( string $state ): string { $labels = [ - 'not_paid' => __( 'Unpaid', 'woodoo' ), - 'partial' => __( 'Partial', 'woodoo' ), - 'in_payment'=> __( 'In Payment', 'woodoo' ), - 'paid' => __( 'Paid', 'woodoo' ), - 'reversed' => __( 'Reversed', 'woodoo' ), + 'not_paid' => 'Pendiente', + 'partial' => 'Parcial', + 'in_payment' => 'En cobro', + 'paid' => 'Pagado', + 'reversed' => 'Anulado', ]; return $labels[ $state ] ?? ucfirst( $state ); } + /** + * Convert an Odoo currency name (e.g. "EUR") to its symbol ("€"). + * Falls back to the original string if not in the map. + */ + public static function currency_symbol( string $currency_name ): string { + $map = [ + 'EUR' => '€', + 'USD' => '$', + 'GBP' => '£', + 'JPY' => '¥', + 'CHF' => 'Fr', + 'MXN' => '$', + 'BRL' => 'R$', + 'ARS' => '$', + 'CLP' => '$', + 'COP' => '$', + 'PEN' => 'S/', + 'DKK' => 'kr', + 'SEK' => 'kr', + 'NOK' => 'kr', + 'PLN' => 'zł', + 'CZK' => 'Kč', + 'HUF' => 'Ft', + 'RON' => 'lei', + 'BGN' => 'лв', + 'HRK' => 'kn', + 'RUB' => '₽', + 'TRY' => '₺', + 'CNY' => '¥', + 'KRW' => '₩', + 'INR' => '₹', + 'AED' => 'د.إ', + 'MAD' => 'MAD', + ]; + return $map[ strtoupper( $currency_name ) ] ?? $currency_name; + } + public static function payment_state_class( string $state ): string { return match ( $state ) { 'paid' => 'woodoo-badge--green', diff --git a/templates/myaccount-calendar.php b/templates/myaccount-calendar.php index dfbdfa5..0592f4e 100644 --- a/templates/myaccount-calendar.php +++ b/templates/myaccount-calendar.php @@ -1,11 +1,6 @@ wp_create_nonce( 'woodoo_calendar' ), 'ajax_url' => admin_url( 'admin-ajax.php' ), 'i18n' => [ - 'loading' => __( 'Loading…', 'woodoo' ), - 'no_slots' => __( 'No available slots for this day.', 'woodoo' ), - 'select_slot' => __( 'Select a time slot', 'woodoo' ), - 'booking' => __( 'Booking…', 'woodoo' ), - 'book_btn' => __( 'Book this slot', 'woodoo' ), - 'cancel_confirm' => __( 'Cancel this meeting?', 'woodoo' ), - 'cancelling' => __( 'Cancelling…', 'woodoo' ), - 'cancelled' => __( 'Meeting cancelled.', 'woodoo' ), - 'error' => __( 'Something went wrong. Please try again.', 'woodoo' ), + 'loading' => 'Cargando…', + 'no_slots' => 'No hay horarios disponibles para este día.', + 'select_slot' => 'Selecciona un horario', + 'booking' => 'Reservando…', + 'book_btn' => 'Confirmar reserva', + 'cancel_confirm' => '¿Cancelar esta reunión?', + 'cancelling' => 'Cancelando…', + 'cancelled' => 'Reunión cancelada.', + 'error' => 'Ha ocurrido un error. Inténtalo de nuevo.', ], ] ); + +$duration_mins = (int) get_option( 'woodoo_meeting_duration', 30 ); ?>
- +
-

+

Tus próximas reuniones

-

+

No tienes reuniones programadas.

- +
- + - + target="_blank" rel="noopener" + class="woodoo-meeting-card__video"> + Unirse a la videollamada @@ -71,7 +69,7 @@ wp_localize_script( 'woodoo-frontend', 'WooDooCalendar', [
@@ -81,23 +79,18 @@ wp_localize_script( 'woodoo-frontend', 'WooDooCalendar', [
- +
-

+

Reservar una nueva reunión

- + Las reuniones tienen una duración de minutos. + Selecciona una fecha para ver la disponibilidad.

-
- +
- - - - - - +
diff --git a/templates/myaccount-invoices.php b/templates/myaccount-invoices.php index e1f99c7..d5b6312 100644 --- a/templates/myaccount-invoices.php +++ b/templates/myaccount-invoices.php @@ -1,96 +1,98 @@
-

+

Tus Facturas

-

- -

+

No se han encontrado facturas.

+
- +
- - - - - - - + + + + + + + 'woodoo_invoice_pdf', 'invoice_id' => $inv['id'], 'nonce' => wp_create_nonce( 'woodoo_invoice_pdf' ), ], admin_url( 'admin-ajax.php' ) ); ?> - - - - - - - @@ -102,7 +104,7 @@ wp_enqueue_script( 'woodoo-frontend' ); 1 ) : $base_url = wc_get_account_endpoint_url( WooDoo_Invoices::ENDPOINT ); ?> -
Nº FacturaFechaVencimientoTotalSaldo pendienteEstadoPDF
- + + + + ' . // phpcs:ignore WordPress.Security.EscapeOutput - esc_html( date_i18n( get_option( 'date_format' ), $due_ts ) ) . - ''; - if ( $overdue ) echo ' ' . esc_html__( 'Overdue', 'woodoo' ) . ''; + $due_ts = strtotime( $inv['invoice_date_due'] ); + $overdue = ( $pay_state === 'not_paid' || $pay_state === 'partial' ) && $due_ts < time(); + if ( $overdue ) { + echo ''; + echo esc_html( date_i18n( 'd/m/Y', $due_ts ) ); + echo ' Vencida'; + } else { + echo esc_html( date_i18n( 'd/m/Y', $due_ts ) ); + } } else { echo '—'; } ?> - + + - + + + + - + rel="noopener" + title="Descargar factura en PDF"> + ↓ PDF