feat: complete fusion-woodoo WordPress plugin with portal, REST endpoints, webhooks, and admin settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
fusion-woo-odoo/fusion-woodoo/assets/css/admin.css
Normal file
74
fusion-woo-odoo/fusion-woodoo/assets/css/admin.css
Normal file
@@ -0,0 +1,74 @@
|
||||
/* Fusion WooDoo — Admin Styles */
|
||||
|
||||
.fusion-woodoo-admin h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-header {
|
||||
margin-bottom: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.fusion-woodoo-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-card h2 {
|
||||
font-size: 1.1rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* Status badges */
|
||||
.fusion-woodoo-status {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fusion-woodoo-status--ok {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.fusion-woodoo-status--error {
|
||||
background: #fce4ec;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
/* Test connection result */
|
||||
.fusion-woodoo-test-result {
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.fusion-woodoo-test-result.success {
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.fusion-woodoo-test-result.error {
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
/* Webhook table */
|
||||
.fusion-woodoo-card .widefat {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-card .widefat th,
|
||||
.fusion-woodoo-card .widefat td {
|
||||
padding: 8px 12px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
376
fusion-woo-odoo/fusion-woodoo/assets/css/my-account.css
Normal file
376
fusion-woo-odoo/fusion-woodoo/assets/css/my-account.css
Normal file
@@ -0,0 +1,376 @@
|
||||
/* Fusion WooDoo — My Account / Frontend Styles */
|
||||
|
||||
/* ── Portal wrapper ─────────────────────────────────────────── */
|
||||
.fusion-woodoo-portal {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.fusion-woodoo-portal h2 {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-portal h3 {
|
||||
font-size: 1.1rem;
|
||||
margin: 24px 0 12px;
|
||||
}
|
||||
|
||||
/* ── Tables ─────────────────────────────────────────────────── */
|
||||
.fusion-woodoo-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-table th,
|
||||
.fusion-woodoo-table td {
|
||||
padding: 10px 14px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.fusion-woodoo-table thead th {
|
||||
background: #f8f9fa;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.fusion-woodoo-table tbody tr:hover {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* ── Status badges ──────────────────────────────────────────── */
|
||||
.fusion-woodoo-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
background: #e9ecef;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.fusion-woodoo-badge--sale,
|
||||
.fusion-woodoo-badge--done,
|
||||
.fusion-woodoo-badge--posted,
|
||||
.fusion-woodoo-badge--paid {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.fusion-woodoo-badge--draft,
|
||||
.fusion-woodoo-badge--pending {
|
||||
background: #fff8e1;
|
||||
color: #f57f17;
|
||||
}
|
||||
|
||||
.fusion-woodoo-badge--cancel {
|
||||
background: #fce4ec;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.fusion-woodoo-badge--processing {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
/* ── Utility ────────────────────────────────────────────────── */
|
||||
.fusion-woodoo-empty {
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.fusion-woodoo-muted {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.fusion-woodoo-notice {
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.fusion-woodoo-notice--success {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
border-left: 4px solid #43a047;
|
||||
}
|
||||
|
||||
.fusion-woodoo-notice--error {
|
||||
background: #fce4ec;
|
||||
color: #c62828;
|
||||
border-left: 4px solid #e53935;
|
||||
}
|
||||
|
||||
.fusion-woodoo-notice--warning {
|
||||
background: #fff8e1;
|
||||
color: #f57f17;
|
||||
border-left: 4px solid #ffb300;
|
||||
}
|
||||
|
||||
/* ── PDF button ─────────────────────────────────────────────── */
|
||||
.fusion-woodoo-btn-pdf {
|
||||
font-size: 0.82rem !important;
|
||||
padding: 4px 10px !important;
|
||||
}
|
||||
|
||||
/* ── Forms ──────────────────────────────────────────────────── */
|
||||
.fusion-woodoo-form {
|
||||
max-width: 580px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-form-row {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-form-row label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.fusion-woodoo-form-row select,
|
||||
.fusion-woodoo-form-row textarea {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
font-family: inherit;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.fusion-woodoo-form-row textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Item checkboxes in return form */
|
||||
.fw-return-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.fw-return-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.fw-return-item input[type="checkbox"] {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fw-return-item-qty {
|
||||
width: 60px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Order Timeline ─────────────────────────────────────────── */
|
||||
.fusion-woodoo-timeline-wrap {
|
||||
margin: 28px 0 12px;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline-wrap h3 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__dot {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #ccc;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__step--done .fusion-woodoo-timeline__dot {
|
||||
background: #43a047;
|
||||
border-color: #43a047;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__step--done .fusion-woodoo-timeline__dot svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__step--active .fusion-woodoo-timeline__dot {
|
||||
background: #1976d2;
|
||||
border-color: #1976d2;
|
||||
box-shadow: 0 0 0 4px rgba(25, 118, 210, 0.15);
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__step--pending .fusion-woodoo-timeline__dot {
|
||||
background: #f5f5f5;
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__label {
|
||||
margin-top: 8px;
|
||||
font-size: 0.78rem;
|
||||
text-align: center;
|
||||
color: #555;
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__step--active .fusion-woodoo-timeline__label {
|
||||
color: #1976d2;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__step--done .fusion-woodoo-timeline__label {
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__tracking {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
color: #777;
|
||||
margin-top: 4px;
|
||||
max-width: 100px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__connector {
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
background: #ddd;
|
||||
margin-bottom: 28px;
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__connector--done {
|
||||
background: #43a047;
|
||||
}
|
||||
|
||||
/* ── Communication / Messages ───────────────────────────────── */
|
||||
.fusion-woodoo-communication {
|
||||
margin-top: 28px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.fusion-woodoo-messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.fusion-woodoo-message {
|
||||
border: 1px solid #ebebeb;
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.fusion-woodoo-message--email {
|
||||
border-left: 4px solid #1976d2;
|
||||
}
|
||||
|
||||
.fusion-woodoo-message--note {
|
||||
border-left: 4px solid #f9a825;
|
||||
background: #fffde7;
|
||||
}
|
||||
|
||||
.fusion-woodoo-message__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.82rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.fusion-woodoo-message__author {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.fusion-woodoo-message__type-badge {
|
||||
padding: 1px 7px;
|
||||
border-radius: 10px;
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.fusion-woodoo-message__type-badge--note {
|
||||
background: #fff8e1;
|
||||
color: #f57f17;
|
||||
}
|
||||
|
||||
.fusion-woodoo-message__body {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
/* ── Responsive ─────────────────────────────────────────────── */
|
||||
@media (max-width: 640px) {
|
||||
.fusion-woodoo-table th,
|
||||
.fusion-woodoo-table td {
|
||||
padding: 8px 8px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__step {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__connector {
|
||||
width: 2px;
|
||||
height: 24px;
|
||||
flex: none;
|
||||
margin: 0 13px 0;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.fusion-woodoo-timeline__label {
|
||||
text-align: left;
|
||||
max-width: unset;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
32
fusion-woo-odoo/fusion-woodoo/assets/js/admin.js
Normal file
32
fusion-woo-odoo/fusion-woodoo/assets/js/admin.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* Fusion WooDoo — Admin JS */
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
$(function () {
|
||||
const $btn = $('#fusion-woodoo-test-connection');
|
||||
const $result = $('#fusion-woodoo-test-result');
|
||||
|
||||
$btn.on('click', function () {
|
||||
$btn.prop('disabled', true).text('Testing…');
|
||||
$result.hide().removeClass('success error');
|
||||
|
||||
$.post(fusionWooDooAdmin.ajaxUrl, {
|
||||
action: 'fusion_woodoo_test_connection',
|
||||
nonce: fusionWooDooAdmin.nonce,
|
||||
})
|
||||
.done(function (res) {
|
||||
if (res.success) {
|
||||
$result.addClass('success').text(res.data.message).show();
|
||||
} else {
|
||||
$result.addClass('error').text(res.data.message || 'Connection failed.').show();
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
$result.addClass('error').text('Request failed. Check your network connection.').show();
|
||||
})
|
||||
.always(function () {
|
||||
$btn.prop('disabled', false).text('Test Connection');
|
||||
});
|
||||
});
|
||||
});
|
||||
}(jQuery));
|
||||
134
fusion-woo-odoo/fusion-woodoo/assets/js/my-account.js
Normal file
134
fusion-woo-odoo/fusion-woodoo/assets/js/my-account.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/* Fusion WooDoo — My Account JS */
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
/* ── Return form ──────────────────────────────────────────── */
|
||||
var ordersData = {};
|
||||
try {
|
||||
var raw = document.getElementById('fw-orders-data');
|
||||
if (raw) ordersData = JSON.parse(raw.textContent || '{}');
|
||||
} catch (e) {}
|
||||
|
||||
// Populate item list when order changes
|
||||
$(document).on('change', '#fw-order-select', function () {
|
||||
var orderId = $(this).val();
|
||||
var $container = $('#fw-items-container');
|
||||
var $list = $('#fw-items-list');
|
||||
$list.empty();
|
||||
|
||||
if (!orderId || !ordersData[orderId]) {
|
||||
$container.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var items = ordersData[orderId];
|
||||
if (!items.length) {
|
||||
$container.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach(function (item) {
|
||||
var $row = $('<div class="fw-return-item"></div>');
|
||||
$row.append(
|
||||
$('<input type="checkbox" />').attr({
|
||||
name: 'items[' + item.product_id + '][select]',
|
||||
'data-product-id': item.product_id,
|
||||
value: '1',
|
||||
})
|
||||
);
|
||||
$row.append($('<span></span>').text(item.name));
|
||||
$row.append(
|
||||
$('<input type="number" class="fw-return-item-qty" />').attr({
|
||||
name: 'items[' + item.product_id + '][qty]',
|
||||
min: 1,
|
||||
max: item.qty,
|
||||
value: item.qty,
|
||||
disabled: true,
|
||||
})
|
||||
);
|
||||
$list.append($row);
|
||||
});
|
||||
|
||||
$container.show();
|
||||
});
|
||||
|
||||
// Enable/disable qty field with checkbox
|
||||
$(document).on('change', '.fw-return-item input[type="checkbox"]', function () {
|
||||
var $qty = $(this).closest('.fw-return-item').find('.fw-return-item-qty');
|
||||
$qty.prop('disabled', !this.checked);
|
||||
});
|
||||
|
||||
// Submit return form via AJAX
|
||||
$(document).on('submit', '#fusion-woodoo-return-form', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $form = $(this);
|
||||
var $notice = $('#fusion-woodoo-return-notice');
|
||||
var $submit = $form.find('[type=submit]');
|
||||
|
||||
// Collect selected items
|
||||
var items = [];
|
||||
$form.find('.fw-return-item input[type="checkbox"]:checked').each(function () {
|
||||
var productId = $(this).data('product-id');
|
||||
var qty = parseInt($(this).closest('.fw-return-item').find('.fw-return-item-qty').val(), 10) || 1;
|
||||
items.push({ product_id: productId, qty: qty });
|
||||
});
|
||||
|
||||
var payload = {
|
||||
action: 'fusion_woodoo_submit_return',
|
||||
nonce: fusionWooDoo.nonce,
|
||||
order_id: $form.find('[name=order_id]').val(),
|
||||
reason: $form.find('[name=reason]').val(),
|
||||
items: items,
|
||||
};
|
||||
|
||||
$submit.prop('disabled', true).text('Submitting…');
|
||||
$notice.hide().removeClass('fusion-woodoo-notice--success fusion-woodoo-notice--error');
|
||||
|
||||
$.post(fusionWooDoo.ajaxUrl, payload)
|
||||
.done(function (res) {
|
||||
if (res.success) {
|
||||
$notice.addClass('fusion-woodoo-notice--success').text(res.data.message).show();
|
||||
$form[0].reset();
|
||||
$('#fw-items-container').hide();
|
||||
} else {
|
||||
$notice.addClass('fusion-woodoo-notice--error').text(res.data.message || 'Submission failed.').show();
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
$notice.addClass('fusion-woodoo-notice--error').text('Request failed. Please try again.').show();
|
||||
})
|
||||
.always(function () {
|
||||
$submit.prop('disabled', false).text('Submit Return Request');
|
||||
$('html, body').animate({ scrollTop: $notice.offset().top - 40 }, 300);
|
||||
});
|
||||
});
|
||||
|
||||
/* ── Reorder button ───────────────────────────────────────── */
|
||||
$(document).on('click', '.fusion-woodoo-reorder', function () {
|
||||
var $btn = $(this);
|
||||
var orderId = $btn.data('order-id');
|
||||
var nonce = $btn.data('nonce');
|
||||
|
||||
$btn.prop('disabled', true).text('Adding…');
|
||||
|
||||
$.post(fusionWooDoo.ajaxUrl, {
|
||||
action: 'fusion_woodoo_reorder',
|
||||
nonce: nonce,
|
||||
order_id: orderId,
|
||||
})
|
||||
.done(function (res) {
|
||||
if (res.success && res.data.cart_url) {
|
||||
window.location.href = res.data.cart_url;
|
||||
} else {
|
||||
alert(res.data.message || 'Could not reorder. Please try again.');
|
||||
$btn.prop('disabled', false).text('Reorder');
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
alert('Request failed. Please try again.');
|
||||
$btn.prop('disabled', false).text('Reorder');
|
||||
});
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
51
fusion-woo-odoo/fusion-woodoo/fusion-woodoo.php
Normal file
51
fusion-woo-odoo/fusion-woodoo/fusion-woodoo.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Fusion WooDoo
|
||||
* Plugin URI: https://fusionsoft.ca
|
||||
* Description: Seamless Odoo integration for WooCommerce — sync products, orders, invoices, and inventory.
|
||||
* Version: 1.0.0
|
||||
* Author: Fusion Central
|
||||
* Author URI: https://fusionsoft.ca
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 8.0
|
||||
* WC requires at least: 8.0
|
||||
* WC tested up to: 9.0
|
||||
* Text Domain: fusion-woodoo
|
||||
* Domain Path: /languages
|
||||
* License: GPL v2 or later
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
define('FUSION_WOODOO_VERSION', '1.0.0');
|
||||
define('FUSION_WOODOO_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('FUSION_WOODOO_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
add_action('plugins_loaded', function() {
|
||||
if (!class_exists('WooCommerce')) {
|
||||
add_action('admin_notices', function() {
|
||||
echo '<div class="error"><p><strong>Fusion WooDoo</strong> requires WooCommerce to be installed and active.</p></div>';
|
||||
});
|
||||
return;
|
||||
}
|
||||
require_once FUSION_WOODOO_PLUGIN_DIR . 'includes/class-fusion-woodoo.php';
|
||||
Fusion_WooDoo::instance();
|
||||
});
|
||||
|
||||
register_activation_hook(__FILE__, function() {
|
||||
$dirs = ['invoices', 'deliveries'];
|
||||
foreach ($dirs as $dir) {
|
||||
$path = wp_upload_dir()['basedir'] . '/fusion-woodoo/' . $dir;
|
||||
wp_mkdir_p($path);
|
||||
file_put_contents($path . '/.htaccess', 'deny from all');
|
||||
}
|
||||
flush_rewrite_rules();
|
||||
});
|
||||
|
||||
register_deactivation_hook(__FILE__, function() {
|
||||
if (file_exists(FUSION_WOODOO_PLUGIN_DIR . 'includes/class-webhooks.php')) {
|
||||
require_once FUSION_WOODOO_PLUGIN_DIR . 'includes/class-webhooks.php';
|
||||
Fusion_WooDoo_Webhooks::unregister_all();
|
||||
}
|
||||
flush_rewrite_rules();
|
||||
});
|
||||
138
fusion-woo-odoo/fusion-woodoo/includes/class-admin-settings.php
Normal file
138
fusion-woo-odoo/fusion-woodoo/includes/class-admin-settings.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?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')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
fusion-woo-odoo/fusion-woodoo/includes/class-api-client.php
Normal file
91
fusion-woo-odoo/fusion-woodoo/includes/class-api-client.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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]);
|
||||
}
|
||||
}
|
||||
104
fusion-woo-odoo/fusion-woodoo/includes/class-fusion-woodoo.php
Normal file
104
fusion-woo-odoo/fusion-woodoo/includes/class-fusion-woodoo.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
127
fusion-woo-odoo/fusion-woodoo/includes/class-my-account.php
Normal file
127
fusion-woo-odoo/fusion-woodoo/includes/class-my-account.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
189
fusion-woo-odoo/fusion-woodoo/includes/class-rest-endpoints.php
Normal file
189
fusion-woo-odoo/fusion-woodoo/includes/class-rest-endpoints.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?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]);
|
||||
}
|
||||
}
|
||||
103
fusion-woo-odoo/fusion-woodoo/includes/class-returns.php
Normal file
103
fusion-woo-odoo/fusion-woodoo/includes/class-returns.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?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'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
fusion-woo-odoo/fusion-woodoo/includes/class-webhooks.php
Normal file
106
fusion-woo-odoo/fusion-woodoo/includes/class-webhooks.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
91
fusion-woo-odoo/fusion-woodoo/readme.txt
Normal file
91
fusion-woo-odoo/fusion-woodoo/readme.txt
Normal file
@@ -0,0 +1,91 @@
|
||||
=== Fusion WooDoo ===
|
||||
Contributors: fusioncentral
|
||||
Tags: woocommerce, odoo, integration, erp, orders, invoices, inventory, sync
|
||||
Requires at least: 6.0
|
||||
Tested up to: 6.7
|
||||
Requires PHP: 8.0
|
||||
Stable tag: 1.0.0
|
||||
WC requires at least: 8.0
|
||||
WC tested up to: 9.0
|
||||
License: GPLv2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Seamless Odoo 19 integration for WooCommerce — sync orders, invoices, deliveries, and inventory in real time.
|
||||
|
||||
== Description ==
|
||||
|
||||
Fusion WooDoo bridges your WooCommerce store with an Odoo 19 instance running the `fusion_woocommerce` module. It provides:
|
||||
|
||||
* **Real-time order sync** via WooCommerce webhooks → Odoo
|
||||
* **Status & tracking push-back** from Odoo → WooCommerce order meta
|
||||
* **Customer portal tabs** in My Account: Sales Orders, Invoices, Deliveries, Returns
|
||||
* **Visual order timeline** on the order detail page (Confirmed → Processing → Shipped → Delivered → Completed)
|
||||
* **Invoice & delivery PDF storage** — Odoo pushes base64-encoded PDFs; customers download securely
|
||||
* **Return requests** — customers submit returns from their account; routed to Odoo automatically
|
||||
* **Odoo message thread** display on order detail pages
|
||||
* **Admin settings page** under WooCommerce with one-click connection test and webhook status dashboard
|
||||
|
||||
== Requirements ==
|
||||
|
||||
* WordPress 6.0 or higher
|
||||
* WooCommerce 8.0 or higher
|
||||
* PHP 8.0 or higher
|
||||
* Odoo 19 with the `fusion_woocommerce` Odoo module installed and configured
|
||||
|
||||
== Installation ==
|
||||
|
||||
1. Upload the `fusion-woodoo` folder to `/wp-content/plugins/`.
|
||||
2. Activate the plugin through the **Plugins** screen in WordPress.
|
||||
3. Go to **WooCommerce → Fusion WooDoo** in the WordPress admin.
|
||||
4. Enter your Odoo base URL (e.g. `https://erp.example.com`) and the API key generated in the Odoo `fusion_woocommerce` module settings.
|
||||
5. Save — webhooks will be registered automatically.
|
||||
6. Click **Test Connection** to verify Odoo is reachable.
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= Where do I get the API key? =
|
||||
|
||||
In Odoo, open the `fusion_woocommerce` module settings and copy the generated API key. Paste it into **WooCommerce → Fusion WooDoo → API Key**.
|
||||
|
||||
= Which WooCommerce webhooks does the plugin register? =
|
||||
|
||||
Five webhooks: `order.created`, `order.updated`, `product.updated`, `customer.created`, and `customer.updated`. All point to your Odoo instance. They are removed automatically on plugin deactivation.
|
||||
|
||||
= Where are invoice and delivery PDFs stored? =
|
||||
|
||||
Under `wp-content/uploads/fusion-woodoo/invoices/` and `.../deliveries/`. Both directories are protected by `.htaccess` (deny from all). PDFs are served only to the order owner via a signed AJAX request.
|
||||
|
||||
= Can customers reorder from the Sales Orders tab? =
|
||||
|
||||
Yes — the Reorder button adds all items from a previous order to the WooCommerce cart.
|
||||
|
||||
= How do I hide portal tabs I don't need? =
|
||||
|
||||
Go to **WooCommerce → Fusion WooDoo** and uncheck the portal tab toggles you want to hide.
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. Admin settings page — connection details and webhook status.
|
||||
2. My Account — Sales Orders tab.
|
||||
3. My Account — Invoices tab with PDF download.
|
||||
4. My Account — Deliveries tab with tracking links.
|
||||
5. My Account — Returns tab with request form.
|
||||
6. Order detail page — visual timeline.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.0 =
|
||||
* Initial release.
|
||||
* Admin settings with connection test and webhook status.
|
||||
* WP REST API endpoints: order/update, order/invoice, order/delivery, order/messages.
|
||||
* WooCommerce webhook registration for orders, products, and customers.
|
||||
* My Account portal tabs: Sales Orders, Invoices, Deliveries, Returns.
|
||||
* Visual order timeline on order detail pages.
|
||||
* Secure PDF download handler.
|
||||
* Return request form with Odoo submission.
|
||||
* Odoo message thread display.
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.0.0 =
|
||||
Initial release — no upgrade steps required.
|
||||
85
fusion-woo-odoo/fusion-woodoo/templates/admin/settings.php
Normal file
85
fusion-woo-odoo/fusion-woodoo/templates/admin/settings.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
$webhooks_active = class_exists('Fusion_WooDoo_Webhooks') ? (new Fusion_WooDoo_Webhooks())->check_stale() : false;
|
||||
?>
|
||||
<div class="wrap fusion-woodoo-admin">
|
||||
<h1><?php esc_html_e('Fusion WooDoo Settings', 'fusion-woodoo'); ?></h1>
|
||||
|
||||
<div class="fusion-woodoo-header">
|
||||
<p><?php esc_html_e('Configure your Odoo connection and customer portal settings below.', 'fusion-woodoo'); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="fusion-woodoo-card">
|
||||
<form method="post" action="options.php">
|
||||
<?php
|
||||
settings_fields('fusion_woodoo_settings');
|
||||
do_settings_sections('fusion-woodoo-settings');
|
||||
submit_button(__('Save Settings', 'fusion-woodoo'));
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="fusion-woodoo-card">
|
||||
<h2><?php esc_html_e('Connection Status', 'fusion-woodoo'); ?></h2>
|
||||
<p>
|
||||
<strong><?php esc_html_e('Odoo URL:', 'fusion-woodoo'); ?></strong>
|
||||
<?php echo esc_html(get_option('fusion_woodoo_odoo_url', __('Not configured', 'fusion-woodoo'))); ?>
|
||||
</p>
|
||||
<p>
|
||||
<strong><?php esc_html_e('Odoo Reachable:', 'fusion-woodoo'); ?></strong>
|
||||
<?php if ($webhooks_active): ?>
|
||||
<span class="fusion-woodoo-status fusion-woodoo-status--ok"><?php esc_html_e('Yes', 'fusion-woodoo'); ?></span>
|
||||
<?php else: ?>
|
||||
<span class="fusion-woodoo-status fusion-woodoo-status--error"><?php esc_html_e('Not confirmed', 'fusion-woodoo'); ?></span>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<button type="button" id="fusion-woodoo-test-connection" class="button button-secondary">
|
||||
<?php esc_html_e('Test Connection', 'fusion-woodoo'); ?>
|
||||
</button>
|
||||
<span id="fusion-woodoo-test-result" class="fusion-woodoo-test-result" style="display:none;margin-left:12px;"></span>
|
||||
</div>
|
||||
|
||||
<div class="fusion-woodoo-card">
|
||||
<h2><?php esc_html_e('Webhook Status', 'fusion-woodoo'); ?></h2>
|
||||
<p><?php esc_html_e('Webhooks push real-time order, product, and customer updates from WooCommerce to Odoo.', 'fusion-woodoo'); ?></p>
|
||||
<?php
|
||||
$data_store = WC_Data_Store::load('webhook');
|
||||
$all_ids = $data_store->get_webhooks_ids('active');
|
||||
$wh_list = [];
|
||||
foreach ($all_ids as $id) {
|
||||
$wh = wc_get_webhook($id);
|
||||
if ($wh && str_starts_with($wh->get_name(), 'fusion-woodoo-')) {
|
||||
$wh_list[] = $wh;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if (empty($wh_list)): ?>
|
||||
<p><em><?php esc_html_e('No Fusion WooDoo webhooks registered. Save a valid Odoo URL to register them automatically.', 'fusion-woodoo'); ?></em></p>
|
||||
<?php else: ?>
|
||||
<table class="widefat striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e('Name', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Topic', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Delivery URL', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Status', 'fusion-woodoo'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($wh_list as $wh): ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($wh->get_name()); ?></td>
|
||||
<td><?php echo esc_html($wh->get_topic()); ?></td>
|
||||
<td><?php echo esc_html($wh->get_delivery_url()); ?></td>
|
||||
<td>
|
||||
<span class="fusion-woodoo-status fusion-woodoo-status--<?php echo $wh->get_status() === 'active' ? 'ok' : 'error'; ?>">
|
||||
<?php echo esc_html(ucfirst($wh->get_status())); ?>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
/**
|
||||
* Renders the Odoo message thread for a given order.
|
||||
* Include from the order details page — $order must be in scope.
|
||||
*
|
||||
* @var WC_Order $order
|
||||
*/
|
||||
$messages = $order->get_meta('_odoo_messages');
|
||||
if (empty($messages) || !is_array($messages)) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="fusion-woodoo-communication">
|
||||
<h3><?php esc_html_e('Messages', 'fusion-woodoo'); ?></h3>
|
||||
<div class="fusion-woodoo-messages">
|
||||
<?php foreach ($messages as $msg):
|
||||
$type = sanitize_key($msg['type'] ?? 'note');
|
||||
?>
|
||||
<div class="fusion-woodoo-message fusion-woodoo-message--<?php echo esc_attr($type); ?>">
|
||||
<div class="fusion-woodoo-message__meta">
|
||||
<span class="fusion-woodoo-message__author"><?php echo esc_html($msg['author'] ?? __('Odoo', 'fusion-woodoo')); ?></span>
|
||||
<span class="fusion-woodoo-message__date"><?php echo esc_html($msg['date'] ?? ''); ?></span>
|
||||
<?php if ($type === 'email'): ?>
|
||||
<span class="fusion-woodoo-message__type-badge"><?php esc_html_e('Email', 'fusion-woodoo'); ?></span>
|
||||
<?php elseif ($type === 'note'): ?>
|
||||
<span class="fusion-woodoo-message__type-badge fusion-woodoo-message__type-badge--note"><?php esc_html_e('Note', 'fusion-woodoo'); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="fusion-woodoo-message__body">
|
||||
<?php echo wp_kses_post($msg['body'] ?? ''); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
$client = new Fusion_WooDoo_API_Client();
|
||||
$result = $client->request('/woo/api/delivery/list', ['customer_id' => get_current_user_id()]);
|
||||
$deliveries = $result['success'] ? ($result['data']['deliveries'] ?? []) : [];
|
||||
|
||||
// Also pull locally stored delivery PDFs
|
||||
$wc_orders = wc_get_orders([
|
||||
'customer' => get_current_user_id(),
|
||||
'limit' => 50,
|
||||
'meta_key' => '_odoo_delivery_pdf',
|
||||
]);
|
||||
?>
|
||||
<div class="fusion-woodoo-portal">
|
||||
<h2><?php esc_html_e('Deliveries', 'fusion-woodoo'); ?></h2>
|
||||
|
||||
<?php if (empty($deliveries) && empty($wc_orders)): ?>
|
||||
<p class="fusion-woodoo-empty"><?php esc_html_e('No deliveries found.', 'fusion-woodoo'); ?></p>
|
||||
<?php else: ?>
|
||||
<table class="fusion-woodoo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e('Reference', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Order', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Scheduled Date', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Carrier', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Tracking', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Status', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('PDF', 'fusion-woodoo'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($deliveries as $del): ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($del['name'] ?? '—'); ?></td>
|
||||
<td><?php echo esc_html($del['wc_order_id'] ? '#' . $del['wc_order_id'] : '—'); ?></td>
|
||||
<td><?php echo esc_html($del['scheduled_date'] ?? '—'); ?></td>
|
||||
<td><?php echo esc_html($del['carrier'] ?? '—'); ?></td>
|
||||
<td>
|
||||
<?php if (!empty($del['tracking_number'])): ?>
|
||||
<?php if (!empty($del['tracking_url'])): ?>
|
||||
<a href="<?php echo esc_url($del['tracking_url']); ?>" target="_blank" rel="noopener">
|
||||
<?php echo esc_html($del['tracking_number']); ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?php echo esc_html($del['tracking_number']); ?>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<span class="fusion-woodoo-muted"><?php esc_html_e('N/A', 'fusion-woodoo'); ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fusion-woodoo-badge fusion-woodoo-badge--<?php echo esc_attr($del['state'] ?? 'draft'); ?>">
|
||||
<?php echo esc_html(ucfirst($del['state'] ?? '—')); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($del['wc_order_id'])): ?>
|
||||
<a href="<?php echo esc_url(add_query_arg([
|
||||
'action' => 'fusion_woodoo_download_pdf',
|
||||
'type' => 'delivery',
|
||||
'order_id' => (int) $del['wc_order_id'],
|
||||
'nonce' => wp_create_nonce('fusion_woodoo_nonce'),
|
||||
], admin_url('admin-ajax.php'))); ?>" class="button fusion-woodoo-btn-pdf">
|
||||
<?php esc_html_e('Download PDF', 'fusion-woodoo'); ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span class="fusion-woodoo-muted">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php foreach ($wc_orders as $order):
|
||||
$tracking_number = $order->get_meta('_odoo_tracking_number');
|
||||
$shipping_carrier = $order->get_meta('_odoo_shipping_carrier');
|
||||
?>
|
||||
<tr>
|
||||
<td><?php esc_html_e('Delivery', 'fusion-woodoo'); ?></td>
|
||||
<td><?php echo esc_html('#' . $order->get_order_number()); ?></td>
|
||||
<td><?php echo esc_html($order->get_date_completed() ? $order->get_date_completed()->date_i18n('Y-m-d') : '—'); ?></td>
|
||||
<td><?php echo esc_html($shipping_carrier ?: '—'); ?></td>
|
||||
<td><?php echo esc_html($tracking_number ?: '—'); ?></td>
|
||||
<td><span class="fusion-woodoo-badge"><?php echo esc_html(ucfirst($order->get_status())); ?></span></td>
|
||||
<td>
|
||||
<?php if (!empty($order->get_meta('_odoo_delivery_pdf'))): ?>
|
||||
<a href="<?php echo esc_url(add_query_arg([
|
||||
'action' => 'fusion_woodoo_download_pdf',
|
||||
'type' => 'delivery',
|
||||
'order_id' => $order->get_id(),
|
||||
'nonce' => wp_create_nonce('fusion_woodoo_nonce'),
|
||||
], admin_url('admin-ajax.php'))); ?>" class="button fusion-woodoo-btn-pdf">
|
||||
<?php esc_html_e('Download PDF', 'fusion-woodoo'); ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span class="fusion-woodoo-muted">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
// Load invoices from Odoo
|
||||
$client = new Fusion_WooDoo_API_Client();
|
||||
$result = $client->request('/woo/api/invoice/list', ['customer_id' => get_current_user_id()]);
|
||||
$invoices = $result['success'] ? ($result['data']['invoices'] ?? []) : [];
|
||||
|
||||
// Also pull locally stored invoices from WC orders
|
||||
$wc_orders = wc_get_orders([
|
||||
'customer' => get_current_user_id(),
|
||||
'limit' => 50,
|
||||
'meta_key' => '_odoo_invoice_pdf',
|
||||
]);
|
||||
?>
|
||||
<div class="fusion-woodoo-portal">
|
||||
<h2><?php esc_html_e('Invoices', 'fusion-woodoo'); ?></h2>
|
||||
|
||||
<?php if (empty($invoices) && empty($wc_orders)): ?>
|
||||
<p class="fusion-woodoo-empty"><?php esc_html_e('No invoices found.', 'fusion-woodoo'); ?></p>
|
||||
<?php else: ?>
|
||||
<table class="fusion-woodoo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e('Invoice #', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Order', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Date', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Amount Due', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Status', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('PDF', 'fusion-woodoo'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($invoices as $inv): ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($inv['name'] ?? '—'); ?></td>
|
||||
<td><?php echo esc_html($inv['wc_order_id'] ?? '—'); ?></td>
|
||||
<td><?php echo esc_html($inv['invoice_date'] ?? '—'); ?></td>
|
||||
<td><?php echo '$' . number_format((float) ($inv['amount_due'] ?? 0), 2); ?></td>
|
||||
<td>
|
||||
<span class="fusion-woodoo-badge fusion-woodoo-badge--<?php echo esc_attr($inv['state'] ?? 'draft'); ?>">
|
||||
<?php echo esc_html(ucfirst($inv['state'] ?? '—')); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($inv['wc_order_id'])): ?>
|
||||
<a href="<?php echo esc_url(add_query_arg([
|
||||
'action' => 'fusion_woodoo_download_pdf',
|
||||
'type' => 'invoice',
|
||||
'order_id' => (int) $inv['wc_order_id'],
|
||||
'nonce' => wp_create_nonce('fusion_woodoo_nonce'),
|
||||
], admin_url('admin-ajax.php'))); ?>" class="button fusion-woodoo-btn-pdf">
|
||||
<?php esc_html_e('Download PDF', 'fusion-woodoo'); ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span class="fusion-woodoo-muted"><?php esc_html_e('Unavailable', 'fusion-woodoo'); ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php foreach ($wc_orders as $order):
|
||||
if (in_array($order->get_id(), array_column($invoices, 'wc_order_id'))) continue;
|
||||
$has_pdf = !empty($order->get_meta('_odoo_invoice_pdf'));
|
||||
if (!$has_pdf) continue;
|
||||
?>
|
||||
<tr>
|
||||
<td><?php esc_html_e('Invoice', 'fusion-woodoo'); ?></td>
|
||||
<td><?php echo esc_html('#' . $order->get_order_number()); ?></td>
|
||||
<td><?php echo esc_html($order->get_date_created() ? $order->get_date_created()->date_i18n('Y-m-d') : '—'); ?></td>
|
||||
<td><?php echo '$' . number_format((float) $order->get_total(), 2); ?></td>
|
||||
<td><span class="fusion-woodoo-badge"><?php echo esc_html(ucfirst($order->get_status())); ?></span></td>
|
||||
<td>
|
||||
<a href="<?php echo esc_url(add_query_arg([
|
||||
'action' => 'fusion_woodoo_download_pdf',
|
||||
'type' => 'invoice',
|
||||
'order_id' => $order->get_id(),
|
||||
'nonce' => wp_create_nonce('fusion_woodoo_nonce'),
|
||||
], admin_url('admin-ajax.php'))); ?>" class="button fusion-woodoo-btn-pdf">
|
||||
<?php esc_html_e('Download PDF', 'fusion-woodoo'); ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
/**
|
||||
* Variables available from class-order-timeline.php:
|
||||
* @var WC_Order $order
|
||||
* @var string $odoo_status
|
||||
* @var string $tracking_number
|
||||
* @var string $shipping_carrier
|
||||
* @var int $current_index
|
||||
* @var array $stage_keys (from Fusion_WooDoo_Order_Timeline::STAGES)
|
||||
*/
|
||||
$stages = Fusion_WooDoo_Order_Timeline::STAGES;
|
||||
?>
|
||||
<div class="fusion-woodoo-timeline-wrap">
|
||||
<h3><?php esc_html_e('Order Status', 'fusion-woodoo'); ?></h3>
|
||||
<div class="fusion-woodoo-timeline">
|
||||
<?php foreach ($stages as $key => $label):
|
||||
$idx = array_search($key, array_keys($stages));
|
||||
$is_done = $idx < $current_index;
|
||||
$is_active = $idx === $current_index;
|
||||
$state_class = $is_done ? 'done' : ($is_active ? 'active' : 'pending');
|
||||
?>
|
||||
<div class="fusion-woodoo-timeline__step fusion-woodoo-timeline__step--<?php echo esc_attr($state_class); ?>">
|
||||
<div class="fusion-woodoo-timeline__dot">
|
||||
<?php if ($is_done): ?>
|
||||
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 6l3 3 5-5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="fusion-woodoo-timeline__label">
|
||||
<?php echo esc_html(__($label, 'fusion-woodoo')); ?>
|
||||
<?php if ($is_active && $key === 'shipped' && ($tracking_number || $shipping_carrier)): ?>
|
||||
<span class="fusion-woodoo-timeline__tracking">
|
||||
<?php if ($shipping_carrier): ?>
|
||||
<?php echo esc_html($shipping_carrier); ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($tracking_number): ?>
|
||||
— <?php esc_html_e('Tracking:', 'fusion-woodoo'); ?>
|
||||
<strong><?php echo esc_html($tracking_number); ?></strong>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($idx < count($stages) - 1): ?>
|
||||
<div class="fusion-woodoo-timeline__connector fusion-woodoo-timeline__connector--<?php echo $is_done ? 'done' : 'pending'; ?>"></div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
$returns_handler = new Fusion_WooDoo_Returns();
|
||||
$customer_orders = $returns_handler->get_customer_orders();
|
||||
$existing = $returns_handler->get_existing_returns();
|
||||
?>
|
||||
<div class="fusion-woodoo-portal fusion-woodoo-returns">
|
||||
<h2><?php esc_html_e('Returns', 'fusion-woodoo'); ?></h2>
|
||||
|
||||
<?php if (!empty($existing)): ?>
|
||||
<h3><?php esc_html_e('Your Return Requests', 'fusion-woodoo'); ?></h3>
|
||||
<table class="fusion-woodoo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e('Return #', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Order', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Date', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Reason', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Status', 'fusion-woodoo'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($existing as $ret): ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($ret['name'] ?? '—'); ?></td>
|
||||
<td><?php echo esc_html($ret['wc_order_id'] ? '#' . $ret['wc_order_id'] : '—'); ?></td>
|
||||
<td><?php echo esc_html($ret['date'] ?? '—'); ?></td>
|
||||
<td><?php echo esc_html($ret['reason'] ?? '—'); ?></td>
|
||||
<td>
|
||||
<span class="fusion-woodoo-badge fusion-woodoo-badge--<?php echo esc_attr($ret['state'] ?? 'pending'); ?>">
|
||||
<?php echo esc_html(ucfirst($ret['state'] ?? 'Pending')); ?>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3><?php esc_html_e('Submit a New Return', 'fusion-woodoo'); ?></h3>
|
||||
|
||||
<?php if (empty($customer_orders)): ?>
|
||||
<p class="fusion-woodoo-empty"><?php esc_html_e('No eligible orders found for return.', 'fusion-woodoo'); ?></p>
|
||||
<?php else: ?>
|
||||
<div id="fusion-woodoo-return-notice" style="display:none;" class="fusion-woodoo-notice"></div>
|
||||
|
||||
<form id="fusion-woodoo-return-form" class="fusion-woodoo-form">
|
||||
<?php wp_nonce_field('fusion_woodoo_nonce', 'nonce'); ?>
|
||||
|
||||
<div class="fusion-woodoo-form-row">
|
||||
<label for="fw-order-select"><?php esc_html_e('Select Order', 'fusion-woodoo'); ?></label>
|
||||
<select id="fw-order-select" name="order_id" required>
|
||||
<option value=""><?php esc_html_e('— Choose an order —', 'fusion-woodoo'); ?></option>
|
||||
<?php foreach ($customer_orders as $order): ?>
|
||||
<option value="<?php echo esc_attr($order->get_id()); ?>">
|
||||
#<?php echo esc_html($order->get_order_number()); ?>
|
||||
— <?php echo esc_html($order->get_date_created() ? $order->get_date_created()->date_i18n('Y-m-d') : ''); ?>
|
||||
(<?php echo '$' . number_format((float) $order->get_total(), 2); ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="fw-items-container" class="fusion-woodoo-form-row" style="display:none;">
|
||||
<label><?php esc_html_e('Select Items to Return', 'fusion-woodoo'); ?></label>
|
||||
<div id="fw-items-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="fusion-woodoo-form-row">
|
||||
<label for="fw-return-reason"><?php esc_html_e('Reason for Return', 'fusion-woodoo'); ?></label>
|
||||
<textarea id="fw-return-reason" name="reason" rows="4" required placeholder="<?php esc_attr_e('Please describe why you are returning these items.', 'fusion-woodoo'); ?>"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button button-primary">
|
||||
<?php esc_html_e('Submit Return Request', 'fusion-woodoo'); ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<script type="application/json" id="fw-orders-data">
|
||||
<?php
|
||||
$orders_data = [];
|
||||
foreach ($customer_orders as $order) {
|
||||
$items = [];
|
||||
foreach ($order->get_items() as $item) {
|
||||
$items[] = [
|
||||
'product_id' => $item->get_product_id(),
|
||||
'variation_id' => $item->get_variation_id(),
|
||||
'name' => $item->get_name(),
|
||||
'qty' => $item->get_quantity(),
|
||||
];
|
||||
}
|
||||
$orders_data[$order->get_id()] = $items;
|
||||
}
|
||||
echo wp_json_encode($orders_data);
|
||||
?>
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
$client = new Fusion_WooDoo_API_Client();
|
||||
$result = $client->request('/woo/api/sales/list', ['customer_id' => get_current_user_id()]);
|
||||
$orders = $result['success'] ? ($result['data']['orders'] ?? []) : [];
|
||||
?>
|
||||
<div class="fusion-woodoo-portal">
|
||||
<h2><?php esc_html_e('Sales Orders', 'fusion-woodoo'); ?></h2>
|
||||
|
||||
<?php if (empty($orders)): ?>
|
||||
<p class="fusion-woodoo-empty"><?php esc_html_e('No sales orders found.', 'fusion-woodoo'); ?></p>
|
||||
<?php else: ?>
|
||||
<table class="fusion-woodoo-table woocommerce-orders-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e('Order #', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Date', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Status', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Total', 'fusion-woodoo'); ?></th>
|
||||
<th><?php esc_html_e('Actions', 'fusion-woodoo'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($orders as $order): ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($order['name'] ?? '—'); ?></td>
|
||||
<td><?php echo esc_html($order['date_order'] ?? '—'); ?></td>
|
||||
<td>
|
||||
<span class="fusion-woodoo-badge fusion-woodoo-badge--<?php echo esc_attr($order['state'] ?? 'draft'); ?>">
|
||||
<?php echo esc_html(ucfirst($order['state'] ?? '—')); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?php echo '$' . number_format((float) ($order['amount_total'] ?? 0), 2); ?></td>
|
||||
<td>
|
||||
<?php if (!empty($order['wc_order_id'])): ?>
|
||||
<button
|
||||
class="button fusion-woodoo-reorder"
|
||||
data-order-id="<?php echo esc_attr($order['wc_order_id']); ?>"
|
||||
data-nonce="<?php echo esc_attr(wp_create_nonce('fusion_woodoo_nonce')); ?>">
|
||||
<?php esc_html_e('Reorder', 'fusion-woodoo'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$result['success']): ?>
|
||||
<div class="fusion-woodoo-notice fusion-woodoo-notice--warning">
|
||||
<p><?php esc_html_e('Could not load sales orders from Odoo at this time. Please try again later.', 'fusion-woodoo'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
Reference in New Issue
Block a user