Files
WooDoo/includes/class-woodoo-invoices.php
Malin 02c8fee174 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>
2026-04-01 17:30:10 +02:00

233 lines
8.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* My Account Invoices tab.
* Shows the customer's Odoo invoices and handles PDF proxy-download.
*/
defined( 'ABSPATH' ) || exit;
class WooDoo_Invoices {
const ENDPOINT = 'odoo-invoices';
public static function init(): void {
add_filter( 'woocommerce_account_menu_items', [ __CLASS__, 'add_menu_item' ], 20 );
add_action( 'woocommerce_account_' . self::ENDPOINT . '_endpoint', [ __CLASS__, 'render' ] );
add_filter( 'woocommerce_get_query_vars', [ __CLASS__, 'add_query_var' ] );
// PDF download endpoint
add_action( 'wp_ajax_woodoo_invoice_pdf', [ __CLASS__, 'ajax_download_pdf' ] );
add_action( 'wp_ajax_nopriv_woodoo_invoice_pdf', [ __CLASS__, 'ajax_download_pdf' ] );
}
public static function add_query_var( array $vars ): array {
$vars[ self::ENDPOINT ] = self::ENDPOINT;
return $vars;
}
public static function add_menu_item( array $items ): array {
$new = [];
foreach ( $items as $key => $label ) {
$new[ $key ] = $label;
if ( $key === 'orders' ) {
$new[ self::ENDPOINT ] = __( 'Odoo Invoices', 'woodoo' );
}
}
return $new;
}
// ── Render ────────────────────────────────────────────────────────────
public static function render(): void {
$user_id = get_current_user_id();
$partner_id = (int) get_user_meta( $user_id, 'woodoo_odoo_partner_id', true );
if ( ! $partner_id ) {
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;
}
$api = woodoo_api();
if ( ! $api ) {
echo '<p class="woodoo-notice woodoo-error">' .
'La integración con Odoo no está configurada. Contacta con soporte.' .
'</p>';
return;
}
// Page parameter
$paged = max( 1, (int) ( $_GET['invoice_page'] ?? 1 ) ); // phpcs:ignore
$per_page = 10;
$offset = ( $paged - 1 ) * $per_page;
$domain = [
[ 'partner_id', '=', $partner_id ],
[ 'move_type', '=', 'out_invoice' ],
[ 'state', '!=', 'cancel' ],
];
$cache_key = 'woodoo_invoices_' . $partner_id . '_' . $paged;
$invoices = get_transient( $cache_key );
if ( false === $invoices ) {
$invoices = $api->search_read(
'account.move',
$domain,
[
'id', 'name', 'invoice_date', 'invoice_date_due',
'amount_total', 'amount_residual', 'payment_state',
'currency_id', 'state',
],
$per_page,
$offset,
'invoice_date desc'
);
set_transient( $cache_key, $invoices, 60 );
}
$total = $api->search_count( 'account.move', $domain );
$num_pages = (int) ceil( $total / $per_page );
include WOODOO_DIR . 'templates/myaccount-invoices.php';
}
// ── PDF Proxy ─────────────────────────────────────────────────────────
/**
* Download an invoice PDF from Odoo and serve it to the logged-in user.
* ?action=woodoo_invoice_pdf&invoice_id=123&nonce=...
*/
public static function ajax_download_pdf(): void {
$nonce = sanitize_text_field( wp_unslash( $_GET['nonce'] ?? '' ) );
if ( ! wp_verify_nonce( $nonce, 'woodoo_invoice_pdf' ) ) {
wp_die( esc_html__( 'Security check failed.', 'woodoo' ) );
}
if ( ! is_user_logged_in() ) {
wp_die( esc_html__( 'Please log in.', 'woodoo' ) );
}
$invoice_id = absint( $_GET['invoice_id'] ?? 0 );
if ( ! $invoice_id ) wp_die( 'Invalid invoice ID.' );
// Verify the invoice belongs to this user
$user_id = get_current_user_id();
$partner_id = (int) get_user_meta( $user_id, 'woodoo_odoo_partner_id', true );
if ( ! $partner_id ) wp_die( esc_html__( 'Account not linked.', 'woodoo' ) );
$api = woodoo_api();
if ( ! $api ) wp_die( 'API not configured.' );
$invoices = $api->search(
'account.move',
[ [ 'id', '=', $invoice_id ], [ 'partner_id', '=', $partner_id ] ],
1
);
if ( empty( $invoices ) ) {
wp_die( esc_html__( 'Invoice not found.', 'woodoo' ) );
}
// 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', '' )
);
$pdf_response = wp_remote_get(
$pdf_url,
[
'headers' => [ 'Authorization' => $basic_auth ],
'timeout' => 90,
'sslverify' => apply_filters( 'woodoo_ssl_verify', true ),
]
);
if ( is_wp_error( $pdf_response ) ) {
wp_die( 'No se pudo obtener el PDF de la factura: ' . esc_html( $pdf_response->get_error_message() ) );
}
$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="factura-' . $invoice_id . '.pdf"' );
header( 'Content-Length: ' . strlen( $pdf_body ) );
header( 'Cache-Control: private' );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $pdf_body;
exit;
}
// ── Helpers ───────────────────────────────────────────────────────────
public static function payment_state_label( string $state ): string {
$labels = [
'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',
'not_paid' => 'woodoo-badge--red',
'partial' => 'woodoo-badge--orange',
'in_payment'=> 'woodoo-badge--blue',
default => 'woodoo-badge--grey',
};
}
}