From 84e6195f3a33690c0d6e4d2f361486b358b26b0a Mon Sep 17 00:00:00 2001 From: Malin Date: Wed, 1 Apr 2026 17:34:34 +0200 Subject: [PATCH] fix: PDF via JSON-RPC render, full-width table with nowrap columns PDF download: - Drop HTTP Basic Auth approach (Odoo's /report/pdf/ endpoint rejects it) - Call ir.actions.report.render_qweb_pdf() via the already-working authenticated JSON-RPC connection; returns base64-encoded PDF bytes - Validate base64_decode result starts with %PDF before serving - Descriptive Spanish error messages for each failure point Table layout: - Remove table-layout:fixed which was squashing columns into WC's ~650px content column - Add min-width:820px so table never compresses below readable width (scrolls horizontally on small screens instead) - .woodoo-invoices breaks out 100px into page margins on desktop (margin: 0 -100px; width: calc(100% + 200px)) for full-width feel - Reverts to 100% width below 960px - All key columns use white-space:nowrap + min-width so invoice reference, dates and amounts never wrap to multiple lines Co-Authored-By: Claude Sonnet 4.6 --- assets/css/woodoo.css | 44 +++++++++++++++++++++--------- includes/class-woodoo-invoices.php | 41 +++++++++++++--------------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/assets/css/woodoo.css b/assets/css/woodoo.css index 72dc8d8..84a5ff8 100644 --- a/assets/css/woodoo.css +++ b/assets/css/woodoo.css @@ -50,37 +50,55 @@ .woodoo-badge--blue { background: #dbeafe; color: #1e40af; } .woodoo-badge--grey { background: #f3f4f6; color: #6b7280; } +/* ── Invoice table wrapper: break out of the narrow WC column ─────── */ +.woodoo-invoices { + /* Push 100px into the page margin on each side on wide screens */ + margin-left: -100px; + margin-right: -100px; + width: calc(100% + 200px); +} +@media (max-width: 960px) { + .woodoo-invoices { + margin-left: 0; + margin-right: 0; + width: 100%; + } +} + /* ── Table ───────────────────────────────────────────────── */ -.woodoo-table-wrap { overflow-x: auto; } +.woodoo-table-wrap { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} .woodoo-table { width: 100%; + min-width: 820px; /* never compress below this — scroll horizontally instead */ border-collapse: collapse; font-size: .875rem; - table-layout: fixed; /* fixed layout so column widths are respected */ + /* No table-layout:fixed — let the browser size columns to content */ } .woodoo-table th, .woodoo-table td { - padding: 10px 14px; + padding: 10px 16px; border-bottom: 1px solid #e5e7eb; text-align: left; vertical-align: middle; - overflow: hidden; } -.woodoo-table th { background: #f9fafb; font-weight: 600; } +.woodoo-table th { background: #f9fafb; font-weight: 600; white-space: nowrap; } .woodoo-table tr:last-child td { border-bottom: none; } .woodoo-table .woodoo-amount { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; } -/* Invoice-specific column widths */ -.woodoo-invoices-table .col-number { width: 180px; white-space: nowrap; font-weight: 600; } -.woodoo-invoices-table .col-date { width: 100px; white-space: nowrap; } -.woodoo-invoices-table .col-due { width: 130px; } -.woodoo-invoices-table .col-amount { width: 110px; text-align: right; } -.woodoo-invoices-table .col-balance { width: 130px; text-align: right; } -.woodoo-invoices-table .col-status { width: 100px; white-space: nowrap; } -.woodoo-invoices-table .col-download{ width: 70px; text-align: center; } +/* Force key invoice columns to never wrap */ +.woodoo-invoices-table .col-number { white-space: nowrap; font-weight: 600; min-width: 160px; } +.woodoo-invoices-table .col-date { white-space: nowrap; min-width: 90px; } +.woodoo-invoices-table .col-due { white-space: nowrap; min-width: 110px; } +.woodoo-invoices-table .col-amount { white-space: nowrap; min-width: 100px; text-align: right; } +.woodoo-invoices-table .col-balance { white-space: nowrap; min-width: 120px; text-align: right; } +.woodoo-invoices-table .col-status { white-space: nowrap; min-width: 90px; } +.woodoo-invoices-table .col-download{ min-width: 60px; text-align: center; } /* Utility: never wrap content in a cell */ .woodoo-nowrap { white-space: nowrap; } diff --git a/includes/class-woodoo-invoices.php b/includes/class-woodoo-invoices.php index cd1480f..0dde0c1 100644 --- a/includes/class-woodoo-invoices.php +++ b/includes/class-woodoo-invoices.php @@ -131,33 +131,30 @@ class WooDoo_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', '' ) + // 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. + $result = $api->execute_kw( + 'ir.actions.report', + 'render_qweb_pdf', + [ 'account.report_invoice', [ $invoice_id ] ] ); - $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() ) ); + if ( is_wp_error( $result ) ) { + wp_die( 'Error de Odoo al generar el PDF: ' . esc_html( $result->get_error_message() ) ); } - $http_code = wp_remote_retrieve_response_code( $pdf_response ); - $pdf_body = wp_remote_retrieve_body( $pdf_response ); + // 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.' ); + } - // 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.' ); + $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' );