Files
WooDoo/includes/class-woodoo-orders.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

255 lines
10 KiB
PHP
Raw Permalink 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
/**
* WooCommerce Order → Odoo Sales Order sync.
*
* When a WC order reaches "processing" status, this class:
* 1. Finds or creates the customer in Odoo (res.partner)
* 2. Creates a sale.order with all line items
* 3. Stores the Odoo SO ID in WC order meta
*
* Products are matched by SKU (default_code). Unknown SKUs become
* generic service lines so nothing is lost.
*/
defined( 'ABSPATH' ) || exit;
class WooDoo_Orders {
public static function init(): void {
if ( ! get_option( 'woodoo_sync_orders', 1 ) ) return;
// Trigger sync when order becomes "processing"
add_action( 'woocommerce_order_status_processing', [ __CLASS__, 'sync_order' ], 10, 2 );
// Also show Odoo SO reference in the WC order admin screen
add_action( 'woocommerce_admin_order_data_after_order_details', [ __CLASS__, 'show_so_meta' ] );
// Manual re-sync button (order actions)
add_filter( 'woocommerce_order_actions', [ __CLASS__, 'add_order_action' ] );
add_action( 'woocommerce_order_action_woodoo_resync', [ __CLASS__, 'manual_resync' ] );
}
// ── Main Sync Entry ───────────────────────────────────────────────────
public static function sync_order( int $order_id, WC_Order $order ): void {
// Skip if already synced
if ( $order->get_meta( '_woodoo_so_id' ) ) return;
$api = woodoo_api();
if ( ! $api ) {
$order->add_order_note( __( '[WooDoo] Odoo integration not configured order not synced.', 'woodoo' ) );
return;
}
$partner_id = self::resolve_partner( $api, $order );
if ( ! $partner_id ) {
$order->add_order_note( __( '[WooDoo] Could not find/create Odoo partner order not synced.', 'woodoo' ) );
return;
}
$so_id = self::create_sales_order( $api, $order, $partner_id );
if ( $so_id ) {
$order->update_meta_data( '_woodoo_so_id', $so_id );
$order->update_meta_data( '_woodoo_partner_id', $partner_id );
$order->save_meta_data();
$odoo_url = get_option( 'woodoo_odoo_url', '' );
$so_link = $odoo_url ? sprintf(
'<a href="%s/odoo/sales/%d" target="_blank">#%d</a>',
esc_url( $odoo_url ),
$so_id,
$so_id
) : "#$so_id";
/* translators: %s: Odoo SO link */
$order->add_order_note( sprintf( __( '[WooDoo] Sales Order created in Odoo: %s', 'woodoo' ), $so_link ) );
// If the WC customer has no partner_id set yet, save it
$wc_user_id = $order->get_customer_id();
if ( $wc_user_id && ! get_user_meta( $wc_user_id, 'woodoo_odoo_partner_id', true ) ) {
update_user_meta( $wc_user_id, 'woodoo_odoo_partner_id', $partner_id );
// Count synced orders
$count = (int) get_user_meta( $wc_user_id, 'woodoo_so_count', true );
update_user_meta( $wc_user_id, 'woodoo_so_count', $count + 1 );
} else {
$count = (int) get_user_meta( $wc_user_id, 'woodoo_so_count', true );
update_user_meta( $wc_user_id, 'woodoo_so_count', $count + 1 );
}
} else {
$order->add_order_note( __( '[WooDoo] Failed to create Odoo Sales Order. Check WooDoo logs.', 'woodoo' ) );
}
}
// ── Partner Resolution ────────────────────────────────────────────────
private static function resolve_partner( WooDoo_API $api, WC_Order $order ): ?int {
// 1. Try from WC user meta first
$wc_user_id = $order->get_customer_id();
if ( $wc_user_id ) {
$partner_id = (int) get_user_meta( $wc_user_id, 'woodoo_odoo_partner_id', true );
if ( $partner_id > 0 ) return $partner_id;
}
// 2. Search Odoo by billing email
$email = $order->get_billing_email();
if ( $email ) {
$found = $api->search( 'res.partner', [ [ 'email', '=', $email ] ], 1 );
if ( ! empty( $found ) ) return (int) $found[0];
}
// 3. Create new partner from order billing info
$name = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() );
if ( ! $name ) $name = $order->get_billing_company() ?: 'WooCommerce Customer';
$vals = [
'name' => $name,
'email' => $email ?: false,
'phone' => $order->get_billing_phone() ?: false,
'street' => $order->get_billing_address_1() ?: false,
'street2' => $order->get_billing_address_2() ?: false,
'city' => $order->get_billing_city() ?: false,
'zip' => $order->get_billing_postcode() ?: false,
'is_company' => (bool) $order->get_billing_company(),
];
// Country
$country_code = $order->get_billing_country();
if ( $country_code ) {
$countries = $api->search_read(
'res.country',
[ [ 'code', '=', $country_code ] ],
[ 'id' ],
1
);
if ( ! empty( $countries ) ) {
$vals['country_id'] = $countries[0]['id'];
}
}
// Remove false values
$vals = array_filter( $vals, fn( $v ) => $v !== false );
return $api->create( 'res.partner', $vals );
}
// ── Sales Order Creation ──────────────────────────────────────────────
private static function create_sales_order( WooDoo_API $api, WC_Order $order, int $partner_id ): ?int {
$lines = self::build_order_lines( $api, $order );
$so_vals = [
'partner_id' => $partner_id,
'client_order_ref' => 'WC-' . $order->get_order_number(),
'note' => $order->get_customer_note() ?: false,
'order_line' => $lines,
];
// If there's a WC-created date set it
$date_created = $order->get_date_created();
if ( $date_created ) {
$so_vals['date_order'] = $date_created->date( 'Y-m-d H:i:s' );
}
// Remove false values
$so_vals = array_filter( $so_vals, fn( $v ) => $v !== false );
return $api->create( 'sale.order', $so_vals );
}
// ── Order Line Builder ────────────────────────────────────────────────
private static function build_order_lines( WooDoo_API $api, WC_Order $order ): array {
$lines = [];
foreach ( $order->get_items() as $item ) {
/** @var WC_Order_Item_Product $item */
$sku = '';
$product = $item->get_product();
if ( $product ) $sku = $product->get_sku();
$odoo_product_id = $api->find_product_by_sku( $sku );
$line = [
'name' => $item->get_name(),
'product_uom_qty' => (float) $item->get_quantity(),
'price_unit' => (float) $order->get_item_subtotal( $item, false, false ),
];
if ( $odoo_product_id ) {
$line['product_id'] = $odoo_product_id;
}
// Command.create: [0, 0, vals]
$lines[] = [ 0, 0, $line ];
}
// Shipping line
$shipping_total = (float) $order->get_shipping_total();
if ( $shipping_total > 0 ) {
$lines[] = [ 0, 0, [
'name' => sprintf(
__( 'Shipping: %s', 'woodoo' ),
$order->get_shipping_method()
),
'product_uom_qty' => 1,
'price_unit' => $shipping_total,
] ];
}
// Discount line (if any coupon was applied)
$discount = (float) $order->get_discount_total();
if ( $discount > 0 ) {
$lines[] = [ 0, 0, [
'name' => __( 'Discount', 'woodoo' ),
'product_uom_qty' => 1,
'price_unit' => -$discount,
] ];
}
return $lines;
}
// ── Admin UI ──────────────────────────────────────────────────────────
public static function show_so_meta( WC_Order $order ): void {
$so_id = $order->get_meta( '_woodoo_so_id' );
$partner_id = $order->get_meta( '_woodoo_partner_id' );
if ( ! $so_id && ! $partner_id ) return;
$odoo_url = get_option( 'woodoo_odoo_url', '' );
echo '<div class="woodoo-order-meta" style="margin-top:12px;">';
echo '<h4 style="margin:0 0 4px;">' . esc_html__( 'Odoo', 'woodoo' ) . '</h4>';
if ( $so_id ) {
$link = $odoo_url
? sprintf( '<a href="%s/odoo/sales/%d" target="_blank">SO #%d</a>', esc_url( $odoo_url ), (int) $so_id, (int) $so_id )
: 'SO #' . esc_html( $so_id );
echo '<p><strong>' . esc_html__( 'Sales Order:', 'woodoo' ) . '</strong> ' . wp_kses_post( $link ) . '</p>';
}
if ( $partner_id ) {
$link = $odoo_url
? sprintf( '<a href="%s/odoo/contacts/%d" target="_blank">Partner #%d</a>', esc_url( $odoo_url ), (int) $partner_id, (int) $partner_id )
: 'Partner #' . esc_html( $partner_id );
echo '<p><strong>' . esc_html__( 'Partner:', 'woodoo' ) . '</strong> ' . wp_kses_post( $link ) . '</p>';
}
echo '</div>';
}
public static function add_order_action( array $actions ): array {
$actions['woodoo_resync'] = __( 'Re-sync to Odoo (WooDoo)', 'woodoo' );
return $actions;
}
public static function manual_resync( WC_Order $order ): void {
// Clear previous SO ID so sync runs fresh
$order->delete_meta_data( '_woodoo_so_id' );
$order->save_meta_data();
self::sync_order( $order->get_id(), $order );
}
}