Files
WooDoo/includes/class-woodoo-invoices.php

233 lines
8.5 KiB
PHP
Raw Normal View History

<?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',
};
}
}