Files
WooDoo/includes/class-woodoo-orders.php

255 lines
10 KiB
PHP
Raw Permalink Normal View History

<?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 );
}
}