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>
This commit is contained in:
402
includes/class-woodoo-admin.php
Normal file
402
includes/class-woodoo-admin.php
Normal file
@@ -0,0 +1,402 @@
|
||||
<?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'],
|
||||
] );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user