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:
@@ -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 ───────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user