$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 '

' . esc_html__( 'Your account is not yet linked to Odoo. Please contact us.', 'woodoo' ) . '

'; return; } $api = woodoo_api(); if ( ! $api ) { echo '

' . esc_html__( 'Odoo integration is not configured. Please contact support.', 'woodoo' ) . '

'; 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 report from Odoo $odoo_url = rtrim( get_option( 'woodoo_odoo_url', '' ), '/' ); $pdf_url = $odoo_url . '/report/pdf/account.report_invoice/' . $invoice_id; // Build auth cookie by first doing session authenticate $auth_response = wp_remote_post( $odoo_url . '/web/session/authenticate', [ 'headers' => [ 'Content-Type' => 'application/json' ], 'body' => wp_json_encode( [ 'jsonrpc' => '2.0', 'method' => 'call', 'id' => 1, 'params' => [ 'db' => get_option( 'woodoo_odoo_db' ), 'login' => get_option( 'woodoo_odoo_username' ), 'password' => get_option( 'woodoo_odoo_api_key' ), ], ] ), 'timeout' => 20, 'sslverify' => apply_filters( 'woodoo_ssl_verify', true ), ] ); if ( is_wp_error( $auth_response ) ) { wp_die( esc_html__( 'Could not authenticate with Odoo.', 'woodoo' ) ); } // Extract session cookie $raw_headers = wp_remote_retrieve_headers( $auth_response ); $session_cookie = ''; if ( isset( $raw_headers['set-cookie'] ) ) { $cookie_header = is_array( $raw_headers['set-cookie'] ) ? $raw_headers['set-cookie'][0] : $raw_headers['set-cookie']; preg_match( '/session_id=([^;]+)/', $cookie_header, $m ); if ( isset( $m[1] ) ) $session_cookie = 'session_id=' . $m[1]; } $pdf_response = wp_remote_get( $pdf_url, [ 'headers' => $session_cookie ? [ 'Cookie' => $session_cookie ] : [], 'timeout' => 60, 'sslverify' => apply_filters( 'woodoo_ssl_verify', true ), ] ); if ( is_wp_error( $pdf_response ) ) { wp_die( esc_html__( 'Could not retrieve invoice PDF.', 'woodoo' ) ); } $pdf_body = wp_remote_retrieve_body( $pdf_response ); header( 'Content-Type: application/pdf' ); header( 'Content-Disposition: attachment; filename="invoice-' . $invoice_id . '.pdf"' ); header( 'Content-Length: ' . strlen( $pdf_body ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $pdf_body; exit; } // ── Helper ──────────────────────────────────────────────────────────── public static function payment_state_label( string $state ): string { $labels = [ 'not_paid' => __( 'Unpaid', 'woodoo' ), 'partial' => __( 'Partial', 'woodoo' ), 'in_payment'=> __( 'In Payment', 'woodoo' ), 'paid' => __( 'Paid', 'woodoo' ), 'reversed' => __( 'Reversed', 'woodoo' ), ]; return $labels[ $state ] ?? ucfirst( $state ); } 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', }; } }