fix: Spanish frontend, wider columns, currency symbols, PDF download

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 <noreply@anthropic.com>
This commit is contained in:
Malin
2026-04-01 17:30:10 +02:00
parent 38d1352e9a
commit 02c8fee174
5 changed files with 169 additions and 153 deletions

View File

@@ -43,8 +43,8 @@ class WooDoo_Invoices {
$partner_id = (int) get_user_meta( $user_id, 'woodoo_odoo_partner_id', true );
if ( ! $partner_id ) {
echo '<p class="woodoo-notice">' .
esc_html__( 'Your account is not yet linked to Odoo. Please contact us.', 'woodoo' ) .
echo '<p class="woodoo-notice woodoo-error">' .
'Tu cuenta aún no está vinculada a Odoo. Por favor, contáctanos para activar esta funcionalidad.' .
'</p>';
return;
}
@@ -52,7 +52,7 @@ class WooDoo_Invoices {
$api = woodoo_api();
if ( ! $api ) {
echo '<p class="woodoo-notice woodoo-error">' .
esc_html__( 'Odoo integration is not configured. Please contact support.', 'woodoo' ) .
'La integración con Odoo no está configurada. Contacta con soporte.' .
'</p>';
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',