Files
WooDoo/includes/class-woodoo-invoices.php
Malin c05433689e feat: invoice email resend, smaller table, column cleanup
Email resend (replaces broken PDF download):
- New AJAX action woodoo_send_invoice_email
- Finds Odoo invoice email template via ir.model.data (cached 24h)
  with fallback search on mail.template by model
- Calls mail.template.send_mail([template_id], invoice_id, force_send=True)
  via authenticated JSON-RPC — triggers Odoo to email the invoice
- Inline success/error row appears below the invoice row, auto-hides 6s
- Button shows "Enviando…" spinner state, resets on failure

Table columns:
- Remove "Saldo pendiente" column (was amount_residual)
- Rename "Total" → "Importe"
- min-width reduced to 700px now that there are 6 columns
- Font size reduced to .8rem for denser display

JS restructured so invoice email handler always runs (no early exit),
calendar handler only runs when WooDooCalendar data is present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:43:39 +02:00

248 lines
8.5 KiB
PHP
Raw Permalink 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' ] );
// Send invoice by email (AJAX, logged-in users only)
add_action( 'wp_ajax_woodoo_send_invoice_email', [ __CLASS__, 'ajax_send_invoice_email' ] );
}
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 ] = 'Mis Facturas';
}
}
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';
}
// ── Send Invoice by Email ─────────────────────────────────────────────
/**
* Ask Odoo to resend the invoice email to the customer.
* Uses mail.template.send_mail() with the standard invoice email template.
*/
public static function ajax_send_invoice_email(): void {
check_ajax_referer( 'woodoo_invoice_email', 'nonce' );
if ( ! is_user_logged_in() ) {
wp_send_json_error( 'No autenticado.', 401 );
}
$invoice_id = absint( $_POST['invoice_id'] ?? 0 );
if ( ! $invoice_id ) {
wp_send_json_error( 'ID de factura no válido.' );
}
$user_id = get_current_user_id();
$partner_id = (int) get_user_meta( $user_id, 'woodoo_odoo_partner_id', true );
if ( ! $partner_id ) {
wp_send_json_error( 'Tu cuenta no está vinculada a Odoo.' );
}
$api = woodoo_api();
if ( ! $api ) {
wp_send_json_error( 'Integración con Odoo no configurada.' );
}
// Verify invoice belongs to this customer
$invoices = $api->search(
'account.move',
[ [ 'id', '=', $invoice_id ], [ 'partner_id', '=', $partner_id ] ],
1
);
if ( empty( $invoices ) ) {
wp_send_json_error( 'Factura no encontrada.' );
}
// Find the Odoo invoice email template ID (cached 24h)
$template_id = (int) get_transient( 'woodoo_invoice_tpl_id' );
if ( ! $template_id ) {
$ref = $api->search_read(
'ir.model.data',
[
[ 'module', '=', 'account' ],
[ 'name', '=', 'email_template_edi_invoice' ],
],
[ 'res_id' ],
1
);
$template_id = ! empty( $ref ) ? (int) $ref[0]['res_id'] : 0;
if ( ! $template_id ) {
// Fallback: search templates by model
$tpl = $api->search_read(
'mail.template',
[ [ 'model', '=', 'account.move' ] ],
[ 'id', 'name' ],
1
);
$template_id = ! empty( $tpl ) ? (int) $tpl[0]['id'] : 0;
}
if ( $template_id ) {
set_transient( 'woodoo_invoice_tpl_id', $template_id, DAY_IN_SECONDS );
}
}
if ( ! $template_id ) {
wp_send_json_error( 'No se encontró la plantilla de correo de facturas en Odoo.' );
}
// Call mail.template.send_mail([template_id], invoice_id, force_send=True)
// First arg list [[template_id]] is the recordset selector in execute_kw,
// remaining positional args are passed to the method itself.
$result = $api->execute_kw(
'mail.template',
'send_mail',
[ [ $template_id ], $invoice_id ],
[ 'force_send' => true ]
);
if ( is_wp_error( $result ) ) {
wp_send_json_error( 'Error de Odoo: ' . $result->get_error_message() );
}
wp_send_json_success( [
'message' => 'La factura ha sido enviada a tu correo electrónico.',
] );
}
// ── 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',
};
}
}