Split 49 modules/suites into independent git repos; untrack from monorepo
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled

Each top-level module/suite folder is now its own private repo on GitHub
(gsinghpal/<name>) and gitea (admin/<name>), with a fresh single initial
commit. The monorepo no longer tracks them (added to .gitignore + git rm
--cached); working-tree files are retained on disk and managed in their
own repos. The monorepo keeps shared root files (CLAUDE.md, docs/, scripts/,
tools/, AGENTS.md, WIP/obsolete dirs) and full history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-07 01:54:34 -04:00
parent 2a7b315e98
commit a66cdefc01
6740 changed files with 51 additions and 1277207 deletions

View File

@@ -1,138 +0,0 @@
<?php
if (!defined('ABSPATH')) exit;
class Fusion_WooDoo_Admin_Settings {
const OPTION_ODOO_URL = 'fusion_woodoo_odoo_url';
const OPTION_API_KEY = 'fusion_woodoo_api_key';
const OPTION_SHOW_ORDERS = 'fusion_woodoo_show_orders';
const OPTION_SHOW_INVOICES = 'fusion_woodoo_show_invoices';
const OPTION_SHOW_DELIVERIES = 'fusion_woodoo_show_deliveries';
const OPTION_SHOW_RETURNS = 'fusion_woodoo_show_returns';
public function __construct() {
add_action('admin_menu', [$this, 'add_settings_page']);
add_action('admin_init', [$this, 'register_settings']);
add_action('admin_post_fusion_woodoo_save', [$this, 'handle_save']);
add_action('wp_ajax_fusion_woodoo_test_connection', [$this, 'ajax_test_connection']);
add_action('update_option_' . self::OPTION_ODOO_URL, [$this, 'on_url_changed'], 10, 0);
}
public function add_settings_page(): void {
add_submenu_page(
'woocommerce',
__('Fusion WooDoo', 'fusion-woodoo'),
__('Fusion WooDoo', 'fusion-woodoo'),
'manage_woocommerce',
'fusion-woodoo-settings',
[$this, 'render_settings_page']
);
}
public function register_settings(): void {
$options = [
self::OPTION_ODOO_URL,
self::OPTION_API_KEY,
self::OPTION_SHOW_ORDERS,
self::OPTION_SHOW_INVOICES,
self::OPTION_SHOW_DELIVERIES,
self::OPTION_SHOW_RETURNS,
];
foreach ($options as $option) {
register_setting('fusion_woodoo_settings', $option, ['sanitize_callback' => 'sanitize_text_field']);
}
add_settings_section(
'fusion_woodoo_connection',
__('Odoo Connection', 'fusion-woodoo'),
null,
'fusion-woodoo-settings'
);
add_settings_field(
self::OPTION_ODOO_URL,
__('Odoo URL', 'fusion-woodoo'),
[$this, 'field_odoo_url'],
'fusion-woodoo-settings',
'fusion_woodoo_connection'
);
add_settings_field(
self::OPTION_API_KEY,
__('API Key', 'fusion-woodoo'),
[$this, 'field_api_key'],
'fusion-woodoo-settings',
'fusion_woodoo_connection'
);
add_settings_section(
'fusion_woodoo_portal',
__('Customer Portal Tabs', 'fusion-woodoo'),
null,
'fusion-woodoo-settings'
);
$toggles = [
self::OPTION_SHOW_ORDERS => __('Show Sales Orders tab', 'fusion-woodoo'),
self::OPTION_SHOW_INVOICES => __('Show Invoices tab', 'fusion-woodoo'),
self::OPTION_SHOW_DELIVERIES => __('Show Deliveries tab', 'fusion-woodoo'),
self::OPTION_SHOW_RETURNS => __('Show Returns tab', 'fusion-woodoo'),
];
foreach ($toggles as $option => $label) {
add_settings_field(
$option,
$label,
[$this, 'field_checkbox'],
'fusion-woodoo-settings',
'fusion_woodoo_portal',
['option' => $option]
);
}
}
public function field_odoo_url(): void {
$value = esc_attr(get_option(self::OPTION_ODOO_URL, ''));
echo '<input type="url" name="' . self::OPTION_ODOO_URL . '" value="' . $value . '" class="regular-text" placeholder="https://erp.example.com" />';
echo '<p class="description">' . __('Base URL of your Odoo instance — no trailing slash.', 'fusion-woodoo') . '</p>';
}
public function field_api_key(): void {
$value = esc_attr(get_option(self::OPTION_API_KEY, ''));
echo '<input type="password" name="' . self::OPTION_API_KEY . '" value="' . $value . '" class="regular-text" autocomplete="new-password" />';
echo '<p class="description">' . __('API key from the Fusion WooCommerce Odoo module.', 'fusion-woodoo') . '</p>';
}
public function field_checkbox(array $args): void {
$option = $args['option'];
$checked = checked(1, get_option($option, 1), false);
echo '<label><input type="checkbox" name="' . esc_attr($option) . '" value="1" ' . $checked . ' /> ' . __('Enabled', 'fusion-woodoo') . '</label>';
}
public function render_settings_page(): void {
$template = FUSION_WOODOO_PLUGIN_DIR . 'templates/admin/settings.php';
if (file_exists($template)) {
include $template;
}
}
public function on_url_changed(): void {
if (class_exists('Fusion_WooDoo_Webhooks')) {
Fusion_WooDoo_Webhooks::unregister_all();
(new Fusion_WooDoo_Webhooks())->register_webhooks();
}
}
public function ajax_test_connection(): void {
check_ajax_referer('fusion_woodoo_admin_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(['message' => __('Permission denied.', 'fusion-woodoo')]);
}
$client = new Fusion_WooDoo_API_Client();
$result = $client->test_connection();
if ($result['success']) {
wp_send_json_success(['message' => __('Connection successful! Odoo is reachable.', 'fusion-woodoo')]);
} else {
wp_send_json_error(['message' => $result['error'] ?: __('Connection failed.', 'fusion-woodoo')]);
}
}
}

View File

@@ -1,91 +0,0 @@
<?php
if (!defined('ABSPATH')) exit;
class Fusion_WooDoo_API_Client {
private string $odoo_url;
private string $api_key;
public function __construct() {
$this->odoo_url = rtrim(get_option('fusion_woodoo_odoo_url', ''), '/');
$this->api_key = get_option('fusion_woodoo_api_key', '');
}
/**
* Make a POST request to an Odoo endpoint.
*
* @param string $endpoint e.g. '/woo/api/order/status'
* @param array $data
* @return array{success: bool, data: mixed, error: string}
*/
public function request(string $endpoint, array $data = []): array {
if (empty($this->odoo_url) || empty($this->api_key)) {
return ['success' => false, 'data' => null, 'error' => 'Odoo URL or API key not configured.'];
}
$url = $this->odoo_url . $endpoint;
$response = wp_remote_post($url, [
'timeout' => 15,
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->api_key,
],
'body' => wp_json_encode($data),
'data_format' => 'body',
]);
if (is_wp_error($response)) {
return ['success' => false, 'data' => null, 'error' => $response->get_error_message()];
}
$code = wp_remote_retrieve_response_code($response);
$body = json_decode(wp_remote_retrieve_body($response), true);
if ($code >= 200 && $code < 300) {
return ['success' => true, 'data' => $body, 'error' => ''];
}
$error_msg = $body['message'] ?? $body['error'] ?? 'Unexpected response from Odoo (HTTP ' . $code . ').';
return ['success' => false, 'data' => $body, 'error' => $error_msg];
}
/**
* Test connectivity to Odoo.
*/
public function test_connection(): array {
return $this->request('/woo/api/order/status', ['ping' => true]);
}
/**
* Get order status from Odoo.
*
* @param int|string $order_id WooCommerce order ID
*/
public function get_order_status(int|string $order_id): array {
return $this->request('/woo/api/order/status', ['order_id' => $order_id]);
}
/**
* Submit a return request to Odoo.
*
* @param int|string $order_id
* @param array $items [['product_id' => ..., 'qty' => ...], ...]
* @param string $reason
*/
public function submit_return(int|string $order_id, array $items, string $reason): array {
return $this->request('/woo/api/return/create', [
'order_id' => $order_id,
'items' => $items,
'reason' => $reason,
]);
}
/**
* Fetch existing returns for a customer.
*
* @param int|string $customer_id WooCommerce user ID
*/
public function get_returns(int|string $customer_id): array {
return $this->request('/woo/api/return/list', ['customer_id' => $customer_id]);
}
}

View File

@@ -1,104 +0,0 @@
<?php
if (!defined('ABSPATH')) exit;
class Fusion_WooDoo {
private static ?Fusion_WooDoo $instance = null;
public static function instance(): Fusion_WooDoo {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->load_includes();
$this->init_hooks();
}
private function load_includes(): void {
$includes = [
'class-api-client.php',
'class-admin-settings.php',
'class-rest-endpoints.php',
'class-webhooks.php',
'class-my-account.php',
'class-order-timeline.php',
'class-returns.php',
];
foreach ($includes as $file) {
$path = FUSION_WOODOO_PLUGIN_DIR . 'includes/' . $file;
if (file_exists($path)) {
require_once $path;
}
}
}
private function init_hooks(): void {
add_action('init', [$this, 'load_textdomain']);
add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_assets']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
// Instantiate components
new Fusion_WooDoo_Admin_Settings();
new Fusion_WooDoo_REST_Endpoints();
new Fusion_WooDoo_Webhooks();
new Fusion_WooDoo_My_Account();
new Fusion_WooDoo_Order_Timeline();
new Fusion_WooDoo_Returns();
}
public function load_textdomain(): void {
load_plugin_textdomain(
'fusion-woodoo',
false,
dirname(plugin_basename(FUSION_WOODOO_PLUGIN_DIR . 'fusion-woodoo.php')) . '/languages'
);
}
public function enqueue_frontend_assets(): void {
if (is_account_page() || is_woocommerce()) {
wp_enqueue_style(
'fusion-woodoo-my-account',
FUSION_WOODOO_PLUGIN_URL . 'assets/css/my-account.css',
[],
FUSION_WOODOO_VERSION
);
wp_enqueue_script(
'fusion-woodoo-my-account',
FUSION_WOODOO_PLUGIN_URL . 'assets/js/my-account.js',
['jquery'],
FUSION_WOODOO_VERSION,
true
);
wp_localize_script('fusion-woodoo-my-account', 'fusionWooDoo', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('fusion_woodoo_nonce'),
]);
}
}
public function enqueue_admin_assets(string $hook): void {
if (strpos($hook, 'fusion-woodoo') === false) {
return;
}
wp_enqueue_style(
'fusion-woodoo-admin',
FUSION_WOODOO_PLUGIN_URL . 'assets/css/admin.css',
[],
FUSION_WOODOO_VERSION
);
wp_enqueue_script(
'fusion-woodoo-admin',
FUSION_WOODOO_PLUGIN_URL . 'assets/js/admin.js',
['jquery'],
FUSION_WOODOO_VERSION,
true
);
wp_localize_script('fusion-woodoo-admin', 'fusionWooDooAdmin', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('fusion_woodoo_admin_nonce'),
]);
}
}

View File

@@ -1,127 +0,0 @@
<?php
if (!defined('ABSPATH')) exit;
class Fusion_WooDoo_My_Account {
const ENDPOINTS = [
'odoo-sales-orders' => 'Sales Orders',
'odoo-invoices' => 'Invoices',
'odoo-deliveries' => 'Deliveries',
'odoo-returns' => 'Returns',
];
const OPTION_MAP = [
'odoo-sales-orders' => 'fusion_woodoo_show_orders',
'odoo-invoices' => 'fusion_woodoo_show_invoices',
'odoo-deliveries' => 'fusion_woodoo_show_deliveries',
'odoo-returns' => 'fusion_woodoo_show_returns',
];
const TEMPLATE_MAP = [
'odoo-sales-orders' => 'sales-orders.php',
'odoo-invoices' => 'invoices.php',
'odoo-deliveries' => 'deliveries.php',
'odoo-returns' => 'returns.php',
];
public function __construct() {
add_action('init', [$this, 'add_rewrite_endpoints']);
add_filter('woocommerce_account_menu_items', [$this, 'add_menu_items']);
foreach (array_keys(self::ENDPOINTS) as $endpoint) {
add_action('woocommerce_account_' . $endpoint . '_endpoint', [$this, 'render_endpoint_content']);
}
// AJAX handler for PDF downloads
add_action('wp_ajax_fusion_woodoo_download_pdf', [$this, 'ajax_download_pdf']);
}
public function add_rewrite_endpoints(): void {
foreach (array_keys(self::ENDPOINTS) as $endpoint) {
add_rewrite_endpoint($endpoint, EP_ROOT | EP_PAGES);
}
}
public function add_menu_items(array $items): array {
$new_items = [];
foreach ($items as $key => $label) {
$new_items[$key] = $label;
// Insert Odoo tabs after 'orders'
if ($key === 'orders') {
foreach (self::ENDPOINTS as $endpoint => $title) {
if (get_option(self::OPTION_MAP[$endpoint], 1)) {
$new_items[$endpoint] = __($title, 'fusion-woodoo');
}
}
}
}
return $new_items;
}
/**
* Generic renderer — dispatches to the correct template.
*/
public function render_endpoint_content(): void {
// Determine which endpoint triggered this action
$current = null;
foreach (array_keys(self::ENDPOINTS) as $endpoint) {
if (did_action('woocommerce_account_' . $endpoint . '_endpoint')) {
$current = $endpoint;
break;
}
}
if (!$current || !isset(self::TEMPLATE_MAP[$current])) {
return;
}
$template = FUSION_WOODOO_PLUGIN_DIR . 'templates/my-account/' . self::TEMPLATE_MAP[$current];
if (file_exists($template)) {
include $template;
}
}
/**
* AJAX: serve a stored PDF file to the logged-in customer who owns the order.
*/
public function ajax_download_pdf(): void {
check_ajax_referer('fusion_woodoo_nonce', 'nonce');
$order_id = (int) ($_GET['order_id'] ?? 0);
$type = sanitize_key($_GET['type'] ?? 'invoice');
if (!$order_id || !is_user_logged_in()) {
wp_die(__('Access denied.', 'fusion-woodoo'), 403);
}
$order = wc_get_order($order_id);
if (!$order || $order->get_customer_id() !== get_current_user_id()) {
wp_die(__('Access denied.', 'fusion-woodoo'), 403);
}
$meta_key = $type === 'delivery' ? '_odoo_delivery_pdf' : '_odoo_invoice_pdf';
$rel_path = $order->get_meta($meta_key);
if (empty($rel_path)) {
wp_die(__('PDF not found.', 'fusion-woodoo'), 404);
}
$upload = wp_upload_dir();
$file_path = $upload['basedir'] . $rel_path;
if (!file_exists($file_path) || !is_file($file_path)) {
wp_die(__('PDF file not found on server.', 'fusion-woodoo'), 404);
}
// Security: ensure path stays within uploads dir
$real_base = realpath($upload['basedir']);
$real_file = realpath($file_path);
if (!$real_file || strpos($real_file, $real_base) !== 0) {
wp_die(__('Access denied.', 'fusion-woodoo'), 403);
}
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($real_file) . '"');
header('Content-Length: ' . filesize($real_file));
readfile($real_file);
exit;
}
}

View File

@@ -1,42 +0,0 @@
<?php
if (!defined('ABSPATH')) exit;
class Fusion_WooDoo_Order_Timeline {
/**
* Timeline stages in order. Key = _odoo_order_status value(s) that map to this stage.
*/
const STAGES = [
'confirmed' => 'Confirmed',
'processing' => 'Processing',
'shipped' => 'Shipped',
'delivered' => 'Delivered',
'done' => 'Completed',
];
public function __construct() {
add_action('woocommerce_order_details_after_order_table', [$this, 'render_timeline']);
}
public function render_timeline(WC_Order $order): void {
$odoo_status = strtolower((string) $order->get_meta('_odoo_order_status'));
if (empty($odoo_status)) {
return;
}
$tracking_number = $order->get_meta('_odoo_tracking_number');
$shipping_carrier = $order->get_meta('_odoo_shipping_carrier');
$stage_keys = array_keys(self::STAGES);
$current_index = array_search($odoo_status, $stage_keys);
if ($current_index === false) {
$current_index = 0;
}
$template = FUSION_WOODOO_PLUGIN_DIR . 'templates/my-account/order-timeline.php';
if (file_exists($template)) {
include $template;
}
}
}

View File

@@ -1,189 +0,0 @@
<?php
if (!defined('ABSPATH')) exit;
class Fusion_WooDoo_REST_Endpoints {
public function __construct() {
add_action('rest_api_init', [$this, 'register_routes']);
}
public function register_routes(): void {
$namespace = 'fusion-woodoo/v1';
register_rest_route($namespace, '/order/update', [
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'handle_order_update'],
'permission_callback' => [$this, 'verify_api_key'],
'args' => [
'order_id' => ['required' => true, 'validate_callback' => 'is_numeric'],
'status' => ['required' => false, 'sanitize_callback' => 'sanitize_text_field'],
'tracking_number' => ['required' => false, 'sanitize_callback' => 'sanitize_text_field'],
'shipping_carrier' => ['required' => false, 'sanitize_callback' => 'sanitize_text_field'],
],
]);
register_rest_route($namespace, '/order/invoice', [
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'handle_order_invoice'],
'permission_callback' => [$this, 'verify_api_key'],
'args' => [
'order_id' => ['required' => true, 'validate_callback' => 'is_numeric'],
'pdf_data' => ['required' => true],
'filename' => ['required' => false, 'sanitize_callback' => 'sanitize_file_name'],
],
]);
register_rest_route($namespace, '/order/delivery', [
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'handle_order_delivery'],
'permission_callback' => [$this, 'verify_api_key'],
'args' => [
'order_id' => ['required' => true, 'validate_callback' => 'is_numeric'],
'pdf_data' => ['required' => true],
'filename' => ['required' => false, 'sanitize_callback' => 'sanitize_file_name'],
],
]);
register_rest_route($namespace, '/order/messages', [
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'handle_order_messages'],
'permission_callback' => [$this, 'verify_api_key'],
'args' => [
'order_id' => ['required' => true, 'validate_callback' => 'is_numeric'],
'messages' => ['required' => true],
],
]);
}
/**
* Validate Bearer token from Authorization header.
*/
public function verify_api_key(WP_REST_Request $request): bool|WP_Error {
$auth_header = $request->get_header('authorization');
if (empty($auth_header) || !str_starts_with($auth_header, 'Bearer ')) {
return new WP_Error('missing_auth', __('Authorization header missing or malformed.', 'fusion-woodoo'), ['status' => 401]);
}
$provided_key = trim(substr($auth_header, 7));
$stored_key = get_option('fusion_woodoo_api_key', '');
if (empty($stored_key) || !hash_equals($stored_key, $provided_key)) {
return new WP_Error('invalid_api_key', __('Invalid API key.', 'fusion-woodoo'), ['status' => 403]);
}
return true;
}
/**
* POST /order/update — update order status and tracking info.
*/
public function handle_order_update(WP_REST_Request $request): WP_REST_Response|WP_Error {
$order_id = (int) $request->get_param('order_id');
$order = wc_get_order($order_id);
if (!$order) {
return new WP_Error('order_not_found', __('Order not found.', 'fusion-woodoo'), ['status' => 404]);
}
if ($status = $request->get_param('status')) {
$order->update_meta_data('_odoo_order_status', sanitize_text_field($status));
}
if ($tracking = $request->get_param('tracking_number')) {
$order->update_meta_data('_odoo_tracking_number', sanitize_text_field($tracking));
}
if ($carrier = $request->get_param('shipping_carrier')) {
$order->update_meta_data('_odoo_shipping_carrier', sanitize_text_field($carrier));
}
$order->save();
return rest_ensure_response(['success' => true, 'order_id' => $order_id]);
}
/**
* POST /order/invoice — store base64-encoded invoice PDF.
*/
public function handle_order_invoice(WP_REST_Request $request): WP_REST_Response|WP_Error {
return $this->save_pdf($request, 'invoices', '_odoo_invoice_pdf');
}
/**
* POST /order/delivery — store base64-encoded delivery PDF.
*/
public function handle_order_delivery(WP_REST_Request $request): WP_REST_Response|WP_Error {
return $this->save_pdf($request, 'deliveries', '_odoo_delivery_pdf');
}
/**
* POST /order/messages — store Odoo messages array against the order.
*/
public function handle_order_messages(WP_REST_Request $request): WP_REST_Response|WP_Error {
$order_id = (int) $request->get_param('order_id');
$order = wc_get_order($order_id);
if (!$order) {
return new WP_Error('order_not_found', __('Order not found.', 'fusion-woodoo'), ['status' => 404]);
}
$messages = $request->get_param('messages');
if (!is_array($messages)) {
return new WP_Error('invalid_messages', __('Messages must be a JSON array.', 'fusion-woodoo'), ['status' => 400]);
}
// Sanitize each message entry
$sanitized = array_map(function($msg) {
return [
'author' => sanitize_text_field($msg['author'] ?? ''),
'date' => sanitize_text_field($msg['date'] ?? ''),
'body' => wp_kses_post($msg['body'] ?? ''),
'type' => sanitize_text_field($msg['type'] ?? 'note'),
];
}, $messages);
$order->update_meta_data('_odoo_messages', $sanitized);
$order->save();
return rest_ensure_response(['success' => true, 'count' => count($sanitized)]);
}
/**
* Shared logic to decode and store a PDF file.
*/
private function save_pdf(WP_REST_Request $request, string $folder, string $meta_key): WP_REST_Response|WP_Error {
$order_id = (int) $request->get_param('order_id');
$order = wc_get_order($order_id);
if (!$order) {
return new WP_Error('order_not_found', __('Order not found.', 'fusion-woodoo'), ['status' => 404]);
}
$pdf_data = $request->get_param('pdf_data');
$decoded = base64_decode($pdf_data, true);
if ($decoded === false) {
return new WP_Error('invalid_pdf', __('Invalid base64-encoded PDF data.', 'fusion-woodoo'), ['status' => 400]);
}
$filename = $request->get_param('filename') ?: ($folder . '-' . $order_id . '-' . time() . '.pdf');
$filename = sanitize_file_name($filename);
$upload = wp_upload_dir();
$save_dir = $upload['basedir'] . '/fusion-woodoo/' . $folder . '/';
if (!file_exists($save_dir)) {
wp_mkdir_p($save_dir);
file_put_contents($save_dir . '.htaccess', 'deny from all');
}
$file_path = $save_dir . $filename;
$bytes = file_put_contents($file_path, $decoded);
if ($bytes === false) {
return new WP_Error('file_write_error', __('Could not save PDF to disk.', 'fusion-woodoo'), ['status' => 500]);
}
$relative = str_replace($upload['basedir'], '', $file_path);
$order->update_meta_data($meta_key, $relative);
$order->save();
return rest_ensure_response(['success' => true, 'path' => $relative]);
}
}

View File

@@ -1,103 +0,0 @@
<?php
if (!defined('ABSPATH')) exit;
class Fusion_WooDoo_Returns {
public function __construct() {
add_action('wp_ajax_fusion_woodoo_submit_return', [$this, 'ajax_submit_return']);
}
/**
* Get the last N orders for the current customer.
*
* @param int $limit
* @return WC_Order[]
*/
public function get_customer_orders(int $limit = 20): array {
if (!is_user_logged_in()) {
return [];
}
return wc_get_orders([
'customer' => get_current_user_id(),
'limit' => $limit,
'status' => ['completed', 'processing', 'on-hold'],
'orderby' => 'date',
'order' => 'DESC',
]);
}
/**
* Fetch existing returns for the current customer from Odoo.
*
* @return array
*/
public function get_existing_returns(): array {
if (!is_user_logged_in()) {
return [];
}
$client = new Fusion_WooDoo_API_Client();
$result = $client->get_returns(get_current_user_id());
return $result['success'] ? ($result['data']['returns'] ?? []) : [];
}
/**
* AJAX handler for return form submission.
*/
public function ajax_submit_return(): void {
check_ajax_referer('fusion_woodoo_nonce', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error(['message' => __('You must be logged in to submit a return.', 'fusion-woodoo')]);
}
$order_id = (int) ($_POST['order_id'] ?? 0);
$reason = sanitize_textarea_field($_POST['reason'] ?? '');
$items = $_POST['items'] ?? [];
if (!$order_id) {
wp_send_json_error(['message' => __('Please select an order.', 'fusion-woodoo')]);
}
if (empty($items) || !is_array($items)) {
wp_send_json_error(['message' => __('Please select at least one item to return.', 'fusion-woodoo')]);
}
if (empty($reason)) {
wp_send_json_error(['message' => __('Please provide a reason for the return.', 'fusion-woodoo')]);
}
// Verify the order belongs to the current user
$order = wc_get_order($order_id);
if (!$order || $order->get_customer_id() !== get_current_user_id()) {
wp_send_json_error(['message' => __('Order not found or access denied.', 'fusion-woodoo')]);
}
// Sanitize and validate items
$sanitized_items = [];
foreach ($items as $item) {
$product_id = (int) ($item['product_id'] ?? 0);
$qty = max(1, (int) ($item['qty'] ?? 1));
if ($product_id > 0) {
$sanitized_items[] = ['product_id' => $product_id, 'qty' => $qty];
}
}
if (empty($sanitized_items)) {
wp_send_json_error(['message' => __('No valid items selected for return.', 'fusion-woodoo')]);
}
$client = new Fusion_WooDoo_API_Client();
$result = $client->submit_return($order_id, $sanitized_items, $reason);
if ($result['success']) {
wp_send_json_success([
'message' => __('Return request submitted successfully. Our team will review it shortly.', 'fusion-woodoo'),
'return_id' => $result['data']['return_id'] ?? null,
]);
} else {
wp_send_json_error([
'message' => $result['error'] ?: __('Failed to submit return. Please try again or contact support.', 'fusion-woodoo'),
]);
}
}
}

View File

@@ -1,106 +0,0 @@
<?php
if (!defined('ABSPATH')) exit;
class Fusion_WooDoo_Webhooks {
const WEBHOOK_PREFIX = 'fusion-woodoo-';
public function __construct() {
add_action('woocommerce_settings_saved', [$this, 'maybe_register']);
}
/**
* Register all required WooCommerce webhooks pointing at the configured Odoo URL.
*/
public function register_webhooks(): void {
$odoo_url = rtrim(get_option('fusion_woodoo_odoo_url', ''), '/');
if (empty($odoo_url)) {
return;
}
$webhooks = [
[
'name' => self::WEBHOOK_PREFIX . 'order-created',
'topic' => 'order.created',
'api_url' => $odoo_url . '/woo/webhook/order',
],
[
'name' => self::WEBHOOK_PREFIX . 'order-updated',
'topic' => 'order.updated',
'api_url' => $odoo_url . '/woo/webhook/order',
],
[
'name' => self::WEBHOOK_PREFIX . 'product-updated',
'topic' => 'product.updated',
'api_url' => $odoo_url . '/woo/webhook/product',
],
[
'name' => self::WEBHOOK_PREFIX . 'customer-created',
'topic' => 'customer.created',
'api_url' => $odoo_url . '/woo/webhook/customer',
],
[
'name' => self::WEBHOOK_PREFIX . 'customer-updated',
'topic' => 'customer.updated',
'api_url' => $odoo_url . '/woo/webhook/customer',
],
];
// Remove stale webhooks first to avoid duplicates
self::unregister_all();
foreach ($webhooks as $wh_data) {
$webhook = new WC_Webhook();
$webhook->set_name($wh_data['name']);
$webhook->set_topic($wh_data['topic']);
$webhook->set_delivery_url($wh_data['api_url']);
$webhook->set_secret(get_option('fusion_woodoo_api_key', wp_generate_password(32)));
$webhook->set_status('active');
$webhook->save();
}
}
/**
* Delete all fusion-woodoo webhooks from WooCommerce.
*/
public static function unregister_all(): void {
$data_store = WC_Data_Store::load('webhook');
$all_ids = $data_store->get_webhooks_ids('active');
foreach ($all_ids as $id) {
$webhook = wc_get_webhook($id);
if ($webhook && str_starts_with($webhook->get_name(), self::WEBHOOK_PREFIX)) {
$webhook->delete(true);
}
}
}
/**
* Ping the Odoo URL and return whether it is reachable.
*/
public function check_stale(): bool {
$odoo_url = get_option('fusion_woodoo_odoo_url', '');
if (empty($odoo_url)) {
return false;
}
$response = wp_remote_get(rtrim($odoo_url, '/') . '/web/health', [
'timeout' => 10,
'sslverify' => apply_filters('fusion_woodoo_sslverify', true),
]);
return !is_wp_error($response) && wp_remote_retrieve_response_code($response) < 500;
}
/**
* Re-register webhooks when Odoo URL changes (triggered from settings).
*/
public function maybe_register(): void {
if (
isset($_POST[Fusion_WooDoo_Admin_Settings::OPTION_ODOO_URL]) &&
!empty($_POST[Fusion_WooDoo_Admin_Settings::OPTION_ODOO_URL])
) {
$this->register_webhooks();
}
}
}