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>
This commit is contained in:
Malin
2026-04-01 17:43:39 +02:00
parent d1597731c5
commit c05433689e
4 changed files with 221 additions and 167 deletions

View File

@@ -15,9 +15,8 @@ class WooDoo_Invoices {
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' ] );
// 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 {
@@ -93,78 +92,97 @@ class WooDoo_Invoices {
include WOODOO_DIR . 'templates/myaccount-invoices.php';
}
// ── PDF Proxy ─────────────────────────────────────────────────────────
// ── Send Invoice by Email ─────────────────────────────────────────────
/**
* Download an invoice PDF from Odoo and serve it to the logged-in user.
* ?action=woodoo_invoice_pdf&invoice_id=123&nonce=...
* 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_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' ) );
}
public static function ajax_send_invoice_email(): void {
check_ajax_referer( 'woodoo_invoice_email', 'nonce' );
if ( ! is_user_logged_in() ) {
wp_die( esc_html__( 'Please log in.', 'woodoo' ) );
wp_send_json_error( 'No autenticado.', 401 );
}
$invoice_id = absint( $_GET['invoice_id'] ?? 0 );
if ( ! $invoice_id ) wp_die( 'Invalid invoice ID.' );
$invoice_id = absint( $_POST['invoice_id'] ?? 0 );
if ( ! $invoice_id ) {
wp_send_json_error( 'ID de factura no válido.' );
}
// 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' ) );
if ( ! $partner_id ) {
wp_send_json_error( 'Tu cuenta no está vinculada a Odoo.' );
}
$api = woodoo_api();
if ( ! $api ) wp_die( 'API not configured.' );
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_die( esc_html__( 'Invoice not found.', 'woodoo' ) );
wp_send_json_error( 'Factura no encontrada.' );
}
// Use the authenticated JSON-RPC connection to render the PDF.
// Odoo's /report/pdf/ HTTP endpoint only accepts session cookies, not API keys.
// Calling ir.actions.report.render_qweb_pdf() via execute_kw works with any
// valid authenticated user and returns the PDF content base64-encoded.
// 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(
'ir.actions.report',
'render_qweb_pdf',
[ 'account.report_invoice', [ $invoice_id ] ]
'mail.template',
'send_mail',
[ [ $template_id ], $invoice_id ],
[ 'force_send' => true ]
);
if ( is_wp_error( $result ) ) {
wp_die( 'Error de Odoo al generar el PDF: ' . esc_html( $result->get_error_message() ) );
wp_send_json_error( 'Error de Odoo: ' . $result->get_error_message() );
}
// Result is [base64_pdf_string, 'pdf']
$b64 = is_array( $result ) ? ( $result[0] ?? '' ) : $result;
if ( empty( $b64 ) ) {
wp_die( 'Odoo devolvió una respuesta vacía al generar el PDF.' );
}
$pdf_body = base64_decode( $b64, true );
if ( $pdf_body === false || substr( $pdf_body, 0, 4 ) !== '%PDF' ) {
wp_die( 'El contenido recibido de Odoo no es un PDF válido. Comprueba que el usuario de la API tenga permisos de impresión 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;
wp_send_json_success( [
'message' => 'La factura ha sido enviada a tu correo electrónico.',
] );
}
// ── Helpers ───────────────────────────────────────────────────────────