Files
WooDoo/includes/class-woodoo-invoices.php
Malin 68c1ff4455 feat: initial WooDoo plugin – WooCommerce & Odoo 19 integration
- Odoo JSON-RPC client (no Composer, uses wp_remote_post)
- Admin settings page under WooCommerce with connection test
- Customer linking: search Odoo partners from WP user profile
- My Account: Odoo Invoices tab with PDF proxy download
- My Account: Book a Meeting tab (slot calculator + calendar.event)
- WC order → Odoo sale.order auto-sync on processing status
- Products matched by SKU; partner auto-created from billing info
- Uninstall cleanup (options, user meta, order meta, DB table)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 13:58:27 +02:00

220 lines
8.2 KiB
PHP
Raw 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' ] );
// 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' ] );
}
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 ] = __( '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 '<p class="woodoo-notice">' .
esc_html__( 'Your account is not yet linked to Odoo. Please contact us.', 'woodoo' ) .
'</p>';
return;
}
$api = woodoo_api();
if ( ! $api ) {
echo '<p class="woodoo-notice woodoo-error">' .
esc_html__( 'Odoo integration is not configured. Please contact support.', 'woodoo' ) .
'</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';
}
// ── 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',
};
}
}