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

403 lines
20 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
/**
* Admin: settings page, customer linking, connection test.
*/
defined( 'ABSPATH' ) || exit;
class WooDoo_Admin {
public static function init(): void {
add_action( 'admin_menu', [ __CLASS__, 'add_menu' ] );
add_action( 'admin_init', [ __CLASS__, 'register_settings' ] );
add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_admin_assets' ] );
// User profile: add Odoo Partner ID field
add_action( 'show_user_profile', [ __CLASS__, 'user_profile_fields' ] );
add_action( 'edit_user_profile', [ __CLASS__, 'user_profile_fields' ] );
add_action( 'personal_options_update',[ __CLASS__, 'save_user_profile_fields' ] );
add_action( 'edit_user_profile_update',[ __CLASS__, 'save_user_profile_fields' ] );
// AJAX handlers (admin only)
add_action( 'wp_ajax_woodoo_test_connection', [ __CLASS__, 'ajax_test_connection' ] );
add_action( 'wp_ajax_woodoo_search_partners', [ __CLASS__, 'ajax_search_partners' ] );
add_action( 'wp_ajax_woodoo_link_customer', [ __CLASS__, 'ajax_link_customer' ] );
// WC customer list column
add_filter( 'manage_users_columns', [ __CLASS__, 'add_users_column' ] );
add_filter( 'manage_users_custom_column', [ __CLASS__, 'render_users_column' ], 10, 3 );
}
// ── Menu & Settings ───────────────────────────────────────────────────
public static function add_menu(): void {
add_submenu_page(
'woocommerce',
__( 'WooDoo Odoo Integration', 'woodoo' ),
__( 'Odoo Integration', 'woodoo' ),
'manage_woocommerce',
'woodoo-settings',
[ __CLASS__, 'render_settings_page' ]
);
}
public static function register_settings(): void {
$fields = [
'woodoo_odoo_url' => __( 'Odoo URL (e.g. https://odoo.example.com)', 'woodoo' ),
'woodoo_odoo_db' => __( 'Database Name', 'woodoo' ),
'woodoo_odoo_username' => __( 'Username / Login', 'woodoo' ),
'woodoo_odoo_api_key' => __( 'API Key', 'woodoo' ),
];
foreach ( $fields as $key => $label ) {
register_setting( 'woodoo_settings', $key, [ 'sanitize_callback' => 'sanitize_text_field' ] );
}
// Meeting settings
$meeting_fields = [
'woodoo_meeting_duration' => 30, // minutes
'woodoo_available_days' => [ 1, 2, 3, 4, 5 ], // Mon-Fri
'woodoo_available_from' => '09:00',
'woodoo_available_to' => '17:00',
'woodoo_meeting_title_prefix'=> 'Meeting via WooDoo',
];
foreach ( $meeting_fields as $key => $default ) {
register_setting( 'woodoo_settings', $key );
}
// Order sync toggle
register_setting( 'woodoo_settings', 'woodoo_sync_orders', [ 'sanitize_callback' => 'rest_sanitize_boolean' ] );
}
public static function enqueue_admin_assets( string $hook ): void {
if ( strpos( $hook, 'woodoo-settings' ) === false
&& $hook !== 'user-edit.php'
&& $hook !== 'profile.php' ) {
return;
}
wp_enqueue_style(
'woodoo-admin',
WOODOO_URL . 'assets/css/woodoo-admin.css',
[],
WOODOO_VERSION
);
wp_enqueue_script(
'woodoo-admin',
WOODOO_URL . 'assets/js/woodoo-admin.js',
[ 'jquery' ],
WOODOO_VERSION,
true
);
wp_localize_script( 'woodoo-admin', 'WooDooAdmin', [
'nonce' => wp_create_nonce( 'woodoo_admin' ),
'ajax_url' => admin_url( 'admin-ajax.php' ),
'i18n' => [
'testing' => __( 'Testing…', 'woodoo' ),
'searching' => __( 'Searching…', 'woodoo' ),
'link_done' => __( 'Linked!', 'woodoo' ),
'error' => __( 'Error. Check console.', 'woodoo' ),
],
] );
}
// ── Settings Page HTML ────────────────────────────────────────────────
public static function render_settings_page(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'Access denied.', 'woodoo' ) );
}
$days_map = [
1 => __( 'Monday', 'woodoo' ),
2 => __( 'Tuesday', 'woodoo' ),
3 => __( 'Wednesday', 'woodoo' ),
4 => __( 'Thursday', 'woodoo' ),
5 => __( 'Friday', 'woodoo' ),
6 => __( 'Saturday', 'woodoo' ),
7 => __( 'Sunday', 'woodoo' ),
];
$saved_days = (array) get_option( 'woodoo_available_days', [ 1, 2, 3, 4, 5 ] );
?>
<div class="wrap woodoo-settings">
<h1><?php esc_html_e( 'WooDoo Odoo Integration', 'woodoo' ); ?></h1>
<nav class="nav-tab-wrapper">
<a href="#tab-connection" class="nav-tab nav-tab-active"><?php esc_html_e( 'Connection', 'woodoo' ); ?></a>
<a href="#tab-meetings" class="nav-tab"><?php esc_html_e( 'Meetings', 'woodoo' ); ?></a>
<a href="#tab-orders" class="nav-tab"><?php esc_html_e( 'Order Sync', 'woodoo' ); ?></a>
<a href="#tab-customers" class="nav-tab"><?php esc_html_e( 'Customers', 'woodoo' ); ?></a>
</nav>
<form method="post" action="options.php">
<?php settings_fields( 'woodoo_settings' ); ?>
<!-- Connection Tab -->
<div id="tab-connection" class="woodoo-tab active">
<h2><?php esc_html_e( 'Odoo Connection', 'woodoo' ); ?></h2>
<table class="form-table">
<tr>
<th><?php esc_html_e( 'Odoo URL', 'woodoo' ); ?></th>
<td>
<input type="url" name="woodoo_odoo_url" value="<?php echo esc_attr( get_option( 'woodoo_odoo_url' ) ); ?>" class="regular-text" placeholder="https://odoo.yourcompany.com">
<p class="description"><?php esc_html_e( 'Include https://, no trailing slash.', 'woodoo' ); ?></p>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Database Name', 'woodoo' ); ?></th>
<td><input type="text" name="woodoo_odoo_db" value="<?php echo esc_attr( get_option( 'woodoo_odoo_db' ) ); ?>" class="regular-text"></td>
</tr>
<tr>
<th><?php esc_html_e( 'Username', 'woodoo' ); ?></th>
<td><input type="text" name="woodoo_odoo_username" value="<?php echo esc_attr( get_option( 'woodoo_odoo_username' ) ); ?>" class="regular-text" autocomplete="off"></td>
</tr>
<tr>
<th><?php esc_html_e( 'API Key', 'woodoo' ); ?></th>
<td>
<input type="password" name="woodoo_odoo_api_key" value="<?php echo esc_attr( get_option( 'woodoo_odoo_api_key' ) ); ?>" class="regular-text" autocomplete="off">
<p class="description"><?php esc_html_e( 'Generate in Odoo: Settings → Users → Your User → Account Security → New API Key.', 'woodoo' ); ?></p>
</td>
</tr>
</table>
<p>
<button type="button" id="woodoo-test-connection" class="button button-secondary">
<?php esc_html_e( 'Test Connection', 'woodoo' ); ?>
</button>
<span id="woodoo-test-result" style="margin-left:12px;"></span>
</p>
</div>
<!-- Meetings Tab -->
<div id="tab-meetings" class="woodoo-tab" style="display:none;">
<h2><?php esc_html_e( 'Meeting / Booking Settings', 'woodoo' ); ?></h2>
<table class="form-table">
<tr>
<th><?php esc_html_e( 'Slot Duration (minutes)', 'woodoo' ); ?></th>
<td>
<input type="number" name="woodoo_meeting_duration" value="<?php echo esc_attr( get_option( 'woodoo_meeting_duration', 30 ) ); ?>" min="15" max="240" step="15" class="small-text">
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Available Days', 'woodoo' ); ?></th>
<td>
<?php foreach ( $days_map as $num => $label ) : ?>
<label style="margin-right:12px;">
<input type="checkbox" name="woodoo_available_days[]" value="<?php echo esc_attr( $num ); ?>"
<?php checked( in_array( $num, $saved_days, true ) ); ?>>
<?php echo esc_html( $label ); ?>
</label>
<?php endforeach; ?>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Available From', 'woodoo' ); ?></th>
<td><input type="time" name="woodoo_available_from" value="<?php echo esc_attr( get_option( 'woodoo_available_from', '09:00' ) ); ?>"></td>
</tr>
<tr>
<th><?php esc_html_e( 'Available To', 'woodoo' ); ?></th>
<td><input type="time" name="woodoo_available_to" value="<?php echo esc_attr( get_option( 'woodoo_available_to', '17:00' ) ); ?>"></td>
</tr>
<tr>
<th><?php esc_html_e( 'Meeting Title Prefix', 'woodoo' ); ?></th>
<td><input type="text" name="woodoo_meeting_title_prefix" value="<?php echo esc_attr( get_option( 'woodoo_meeting_title_prefix', 'Meeting via WooDoo' ) ); ?>" class="regular-text"></td>
</tr>
</table>
</div>
<!-- Order Sync Tab -->
<div id="tab-orders" class="woodoo-tab" style="display:none;">
<h2><?php esc_html_e( 'Order → Odoo Sales Order Sync', 'woodoo' ); ?></h2>
<table class="form-table">
<tr>
<th><?php esc_html_e( 'Enable Order Sync', 'woodoo' ); ?></th>
<td>
<label>
<input type="checkbox" name="woodoo_sync_orders" value="1" <?php checked( get_option( 'woodoo_sync_orders', 1 ) ); ?>>
<?php esc_html_e( 'Automatically create a Sales Order in Odoo when a WooCommerce order is placed (status: processing).', 'woodoo' ); ?>
</label>
</td>
</tr>
</table>
<div class="woodoo-info-box">
<strong><?php esc_html_e( 'How it works:', 'woodoo' ); ?></strong>
<ul>
<li><?php esc_html_e( 'When a WooCommerce order reaches "Processing" status, WooDoo finds or creates the customer in Odoo.', 'woodoo' ); ?></li>
<li><?php esc_html_e( 'Products are matched by SKU (default_code). Unmatched items are added as generic lines.', 'woodoo' ); ?></li>
<li><?php esc_html_e( 'The Odoo Sales Order is left in "Quotation" (draft) state, ready for invoicing.', 'woodoo' ); ?></li>
<li><?php esc_html_e( 'The Odoo SO reference is saved as order meta and visible in the WC order screen.', 'woodoo' ); ?></li>
</ul>
</div>
</div>
<!-- Customer Linking Tab -->
<div id="tab-customers" class="woodoo-tab" style="display:none;">
<h2><?php esc_html_e( 'Customer Linking', 'woodoo' ); ?></h2>
<p><?php esc_html_e( 'Link WooCommerce customers to their Odoo partner record. You can also edit this from each user\'s profile page.', 'woodoo' ); ?></p>
<div class="woodoo-customer-linker">
<label><?php esc_html_e( 'Search WooCommerce Customer:', 'woodoo' ); ?></label>
<input type="text" id="woo-customer-search" class="regular-text" placeholder="<?php esc_attr_e( 'Customer name or email…', 'woodoo' ); ?>">
<div id="woo-customer-results"></div>
</div>
</div>
<?php submit_button(); ?>
</form>
</div>
<?php
}
// ── User Profile Fields ───────────────────────────────────────────────
public static function user_profile_fields( WP_User $user ): void {
if ( ! current_user_can( 'edit_users' ) ) return;
$partner_id = get_user_meta( $user->ID, 'woodoo_odoo_partner_id', true );
$partner_name = get_user_meta( $user->ID, 'woodoo_odoo_partner_name', true );
?>
<h2><?php esc_html_e( 'Odoo Integration (WooDoo)', 'woodoo' ); ?></h2>
<table class="form-table">
<tr>
<th><?php esc_html_e( 'Odoo Partner ID', 'woodoo' ); ?></th>
<td>
<input type="number" id="woodoo_odoo_partner_id" name="woodoo_odoo_partner_id"
value="<?php echo esc_attr( $partner_id ); ?>"
class="small-text" min="1">
<?php if ( $partner_name ) : ?>
<span id="woodoo-partner-name-display" style="margin-left:8px;color:#666;">
<?php echo esc_html( $partner_name ); ?>
</span>
<?php endif; ?>
<p class="description">
<?php esc_html_e( 'Leave blank to auto-match by email on next sync. Or type an ID and the name will resolve automatically.', 'woodoo' ); ?>
</p>
<input type="hidden" name="woodoo_nonce" value="<?php echo esc_attr( wp_create_nonce( 'woodoo_user_profile_' . $user->ID ) ); ?>">
<p>
<strong><?php esc_html_e( 'Search Odoo partners:', 'woodoo' ); ?></strong><br>
<input type="text" id="woodoo-partner-search-input" class="regular-text"
placeholder="<?php esc_attr_e( 'Name or email…', 'woodoo' ); ?>"
data-user-id="<?php echo esc_attr( $user->ID ); ?>">
<button type="button" id="woodoo-partner-search-btn" class="button button-secondary" style="margin-left:4px;">
<?php esc_html_e( 'Search', 'woodoo' ); ?>
</button>
<div id="woodoo-partner-search-results" style="margin-top:8px;"></div>
</p>
</td>
</tr>
<?php
$odoo_so_count = get_user_meta( $user->ID, 'woodoo_so_count', true );
if ( $odoo_so_count ) : ?>
<tr>
<th><?php esc_html_e( 'Odoo Sales Orders', 'woodoo' ); ?></th>
<td>
<span><?php echo esc_html( $odoo_so_count ); ?> <?php esc_html_e( 'orders synced to Odoo', 'woodoo' ); ?></span>
</td>
</tr>
<?php endif; ?>
</table>
<?php
}
public static function save_user_profile_fields( int $user_id ): void {
if ( ! isset( $_POST['woodoo_nonce'] )
|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['woodoo_nonce'] ) ), 'woodoo_user_profile_' . $user_id )
) {
return;
}
if ( ! current_user_can( 'edit_user', $user_id ) ) return;
$partner_id = isset( $_POST['woodoo_odoo_partner_id'] )
? absint( $_POST['woodoo_odoo_partner_id'] )
: 0;
if ( $partner_id > 0 ) {
update_user_meta( $user_id, 'woodoo_odoo_partner_id', $partner_id );
// Resolve name from Odoo
$api = woodoo_api();
if ( $api ) {
$partners = $api->read( 'res.partner', [ $partner_id ], [ 'name', 'email' ] );
if ( ! empty( $partners[0]['name'] ) ) {
update_user_meta( $user_id, 'woodoo_odoo_partner_name', sanitize_text_field( $partners[0]['name'] ) );
}
}
} else {
delete_user_meta( $user_id, 'woodoo_odoo_partner_id' );
delete_user_meta( $user_id, 'woodoo_odoo_partner_name' );
}
}
// ── Users List Column ─────────────────────────────────────────────────
public static function add_users_column( array $columns ): array {
$columns['woodoo_odoo'] = __( 'Odoo Partner', 'woodoo' );
return $columns;
}
public static function render_users_column( string $value, string $column, int $user_id ): string {
if ( $column !== 'woodoo_odoo' ) return $value;
$partner_id = get_user_meta( $user_id, 'woodoo_odoo_partner_id', true );
$partner_name = get_user_meta( $user_id, 'woodoo_odoo_partner_name', true );
if ( $partner_id ) {
return '<span style="color:#2271b1;">' . esc_html( $partner_name ?: "#$partner_id" ) . '</span>';
}
return '<span style="color:#999;">—</span>';
}
// ── AJAX Handlers ─────────────────────────────────────────────────────
public static function ajax_test_connection(): void {
check_ajax_referer( 'woodoo_admin', 'nonce' );
if ( ! current_user_can( 'manage_woocommerce' ) ) wp_send_json_error( 'Forbidden', 403 );
$api = woodoo_api();
if ( ! $api ) {
wp_send_json_error( 'Credentials not saved yet. Save settings first.' );
}
wp_send_json_success( $api->test_connection() );
}
public static function ajax_search_partners(): void {
check_ajax_referer( 'woodoo_admin', 'nonce' );
if ( ! current_user_can( 'edit_users' ) ) wp_send_json_error( 'Forbidden', 403 );
$q = sanitize_text_field( wp_unslash( $_POST['query'] ?? '' ) );
$api = woodoo_api();
if ( ! $api || empty( $q ) ) wp_send_json_success( [] );
$results = $api->search_read(
'res.partner',
[ '|', [ 'name', 'ilike', $q ], [ 'email', 'ilike', $q ] ],
[ 'id', 'name', 'email', 'is_company' ],
10
);
wp_send_json_success( $results );
}
public static function ajax_link_customer(): void {
check_ajax_referer( 'woodoo_admin', 'nonce' );
if ( ! current_user_can( 'edit_users' ) ) wp_send_json_error( 'Forbidden', 403 );
$user_id = absint( $_POST['user_id'] ?? 0 );
$partner_id = absint( $_POST['partner_id'] ?? 0 );
if ( ! $user_id || ! $partner_id ) wp_send_json_error( 'Invalid IDs' );
$api = woodoo_api();
if ( ! $api ) wp_send_json_error( 'API not configured' );
$partners = $api->read( 'res.partner', [ $partner_id ], [ 'name', 'email' ] );
if ( empty( $partners ) ) wp_send_json_error( 'Partner not found in Odoo' );
update_user_meta( $user_id, 'woodoo_odoo_partner_id', $partner_id );
update_user_meta( $user_id, 'woodoo_odoo_partner_name', sanitize_text_field( $partners[0]['name'] ) );
wp_send_json_success( [
'partner_id' => $partner_id,
'partner_name' => $partners[0]['name'],
] );
}
}