Files
Odoo-Modules/docs/superpowers/plans/2026-03-31-fusion-woo-odoo-plan.md
gsinghpal 126264217e Add Fusion WooOdoo implementation plan (30 tasks, 9 phases)
Comprehensive plan covering Odoo module scaffold, core models, API client,
views/menus, sync engine (webhooks + cron), wizards, OWL frontend,
WordPress plugin, order lifecycle, advanced features (tax mapping,
pricelist sync, multi-currency, refund handling), and deployment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:54:10 -04:00

60 KiB

Fusion WooOdoo Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Build a production-ready bidirectional sync system between Odoo 19 and WooCommerce with product mapping, order lifecycle management, inventory sync, and customer portal.

Architecture: Odoo module (brain) communicates with WooCommerce via REST API v3. WordPress plugin (thin layer) receives data from Odoo and displays documents in customer My Account portal. Hybrid sync: real-time webhooks + scheduled cron fallback.

Tech Stack: Python 3 / Odoo 19 OWL / WooCommerce REST API v3 / PHP 8 / WordPress Plugin API / WooCommerce hooks

Spec: docs/superpowers/specs/2026-03-31-fusion-woo-odoo-design.md

Critical Odoo 19 Rules:

  • HTTP routes: type="jsonrpc" (NOT type="json")
  • Frontend JS: Interaction class from @web/public/interaction
  • Backend OWL: standalone rpc() from @web/core/network/rpc, static props = []
  • Always read reference files from Docker before coding: docker exec odoo-dev-app cat /usr/lib/python3/dist-packages/odoo/addons/<module>/...
  • API client in lib/ directory, NOT models/

Phase 1: Foundation — Odoo Module Scaffold + Core Models

Task 1: Create module scaffold and manifest

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/__init__.py

  • Create: fusion-woo-odoo/fusion_woocommerce/__manifest__.py

  • Create: fusion-woo-odoo/fusion_woocommerce/models/__init__.py

  • Create: fusion-woo-odoo/fusion_woocommerce/controllers/__init__.py

  • Create: fusion-woo-odoo/fusion_woocommerce/wizard/__init__.py

  • Create: fusion-woo-odoo/fusion_woocommerce/lib/__init__.py

  • Create: fusion-woo-odoo/fusion_woocommerce/security/ir.model.access.csv

  • Create: fusion-woo-odoo/fusion_woocommerce/security/woo_security.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/static/description/icon.png (placeholder)

  • Step 1: Create directory structure

mkdir -p fusion-woo-odoo/fusion_woocommerce/{models,controllers,wizard,lib,views,data,security,static/{description,src/{js,css,xml}},i18n}
  • Step 2: Create __manifest__.py
{
    'name': 'Fusion WooCommerce',
    'version': '19.0.1.0.0',
    'category': 'Sales',
    'summary': 'Bidirectional WooCommerce ↔ Odoo sync for products, orders, invoices, and inventory',
    'description': 'Seamless integration between Odoo and WooCommerce. Sync products, prices, inventory, orders, invoices, customers, and documents bidirectionally.',
    'author': 'Fusion Central',
    'website': 'https://fusionsoft.ca',
    'license': 'LGPL-3',
    'depends': ['sale_management', 'stock', 'account', 'contacts', 'mail'],
    'data': [
        'security/woo_security.xml',
        'security/ir.model.access.csv',
        'data/shipping_carriers.xml',
        'data/cron.xml',
        'data/mail_template.xml',
        'views/woo_instance_views.xml',
        'views/woo_product_map_views.xml',
        'views/woo_order_views.xml',
        'views/woo_sync_log_views.xml',
        'views/woo_conflict_views.xml',
        'views/woo_customer_views.xml',
        'views/woo_return_views.xml',
        'views/woo_tax_map_views.xml',
        'views/woo_pricelist_map_views.xml',
        'views/woo_shipping_carrier_views.xml',
        'views/sale_order_views.xml',
        'views/stock_picking_views.xml',
        'views/res_config_settings.xml',
        'views/woo_dashboard.xml',
        'views/woo_menus.xml',
        'wizard/woo_setup_wizard_views.xml',
        'wizard/woo_product_fetch_views.xml',
    ],
    'assets': {
        'web.assets_backend': [
            'fusion_woocommerce/static/src/css/woo_styles.css',
            'fusion_woocommerce/static/src/js/product_mapping.js',
            'fusion_woocommerce/static/src/js/ajax_search.js',
            'fusion_woocommerce/static/src/js/dashboard.js',
            'fusion_woocommerce/static/src/xml/product_mapping.xml',
            'fusion_woocommerce/static/src/xml/dashboard.xml',
        ],
    },
    'images': ['static/description/icon.png'],
    'installable': True,
    'application': True,
    'auto_install': False,
}
  • Step 3: Create root __init__.py
from . import models
from . import controllers
from . import wizard
from . import lib
  • Step 4: Create placeholder icon

Create a simple 128x128 PNG placeholder at static/description/icon.png.

  • Step 5: Create security group XML

security/woo_security.xml:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <record id="module_category_woocommerce" model="ir.module.category">
        <field name="name">WooCommerce</field>
        <field name="sequence">50</field>
    </record>

    <record id="group_woo_user" model="res.groups">
        <field name="name">WooCommerce User</field>
        <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
    </record>

    <record id="group_woo_manager" model="res.groups">
        <field name="name">WooCommerce Manager</field>
        <field name="implied_ids" eval="[(4, ref('group_woo_user'))]"/>
    </record>
</odoo>
  • Step 6: Create ir.model.access.csv (initial — will expand as models are added)
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_woo_instance_user,woo.instance.user,model_woo_instance,group_woo_user,1,0,0,0
access_woo_instance_manager,woo.instance.manager,model_woo_instance,group_woo_manager,1,1,1,1
  • Step 7: Commit
git add fusion-woo-odoo/
git commit -m "feat: scaffold fusion_woocommerce Odoo module with manifest and security"

Task 2: WooCommerce API Client (lib/)

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/lib/__init__.py
  • Create: fusion-woo-odoo/fusion_woocommerce/lib/woo_api_client.py

This is a plain Python class, NOT an Odoo model. It wraps the WooCommerce REST API v3.

  • Step 1: Create lib/__init__.py
from .woo_api_client import WooApiClient
  • Step 2: Write woo_api_client.py
import hashlib
import hmac
import json
import logging
import time
from urllib.parse import urljoin

import requests

_logger = logging.getLogger(__name__)

class WooApiClient:
    """WooCommerce REST API v3 client wrapper."""

    def __init__(self, url, consumer_key, consumer_secret, api_version='wc/v3', timeout=30):
        self.base_url = url.rstrip('/')
        self.consumer_key = consumer_key
        self.consumer_secret = consumer_secret
        self.api_version = api_version
        self.timeout = timeout
        self.session = requests.Session()
        self.session.auth = (consumer_key, consumer_secret)
        self.session.headers.update({
            'Content-Type': 'application/json',
            'User-Agent': 'FusionWooCommerce/1.0',
        })

    def _url(self, endpoint):
        return f"{self.base_url}/wp-json/{self.api_version}/{endpoint.lstrip('/')}"

    def _request(self, method, endpoint, data=None, params=None, retries=3):
        url = self._url(endpoint)
        for attempt in range(retries):
            try:
                response = self.session.request(
                    method, url, json=data, params=params, timeout=self.timeout
                )
                response.raise_for_status()
                return response.json()
            except requests.exceptions.RequestException as e:
                _logger.warning(
                    "WC API %s %s attempt %d failed: %s",
                    method, endpoint, attempt + 1, str(e)
                )
                if attempt < retries - 1:
                    time.sleep(2 ** attempt)  # exponential backoff
                else:
                    raise

    def get(self, endpoint, params=None):
        return self._request('GET', endpoint, params=params)

    def post(self, endpoint, data):
        return self._request('POST', endpoint, data=data)

    def put(self, endpoint, data):
        return self._request('PUT', endpoint, data=data)

    def delete(self, endpoint):
        return self._request('DELETE', endpoint)

    # --- Product endpoints ---

    def get_products(self, page=1, per_page=100, **kwargs):
        params = {'page': page, 'per_page': per_page, **kwargs}
        return self.get('products', params=params)

    def get_product(self, product_id):
        return self.get(f'products/{product_id}')

    def get_product_variations(self, product_id, page=1, per_page=100):
        params = {'page': page, 'per_page': per_page}
        return self.get(f'products/{product_id}/variations', params=params)

    def update_product(self, product_id, data):
        return self.put(f'products/{product_id}', data)

    def create_product(self, data):
        return self.post('products', data)

    # --- Order endpoints ---

    def get_orders(self, page=1, per_page=100, **kwargs):
        params = {'page': page, 'per_page': per_page, **kwargs}
        return self.get('orders', params=params)

    def get_order(self, order_id):
        return self.get(f'orders/{order_id}')

    def update_order(self, order_id, data):
        return self.put(f'orders/{order_id}', data)

    # --- Customer endpoints ---

    def get_customers(self, page=1, per_page=100, **kwargs):
        params = {'page': page, 'per_page': per_page, **kwargs}
        return self.get('customers', params=params)

    def get_customer(self, customer_id):
        return self.get(f'customers/{customer_id}')

    def create_customer(self, data):
        return self.post('customers', data)

    def update_customer(self, customer_id, data):
        return self.put(f'customers/{customer_id}', data)

    # --- Webhook endpoints ---

    def create_webhook(self, data):
        return self.post('webhooks', data)

    def get_webhooks(self):
        return self.get('webhooks', params={'per_page': 100})

    def delete_webhook(self, webhook_id):
        return self.delete(f'webhooks/{webhook_id}')

    # --- Tax endpoints ---

    def get_tax_classes(self):
        return self.get('taxes/classes')

    # --- Utility ---

    def test_connection(self):
        """Test if the WC API is reachable and credentials are valid."""
        try:
            result = self.get('system_status')
            return True, result.get('environment', {}).get('version', 'unknown')
        except Exception as e:
            return False, str(e)

    @staticmethod
    def verify_webhook_signature(payload, signature, secret):
        """Verify WC webhook HMAC-SHA256 signature."""
        expected = hmac.new(
            secret.encode('utf-8'),
            payload,
            hashlib.sha256
        ).digest()
        import base64
        expected_b64 = base64.b64encode(expected).decode('utf-8')
        return hmac.compare_digest(expected_b64, signature)
  • Step 3: Commit
git add fusion-woo-odoo/fusion_woocommerce/lib/
git commit -m "feat: add WooCommerce REST API v3 client wrapper"

Task 3: Core Odoo models — woo.instance and woo.shipping.carrier

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py

  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_shipping_carrier.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/__init__.py

  • Step 1: Create models/__init__.py

from . import woo_shipping_carrier
from . import woo_instance
  • Step 2: Write woo_shipping_carrier.py

Model for configurable shipping carriers. Must be created before woo.instance since other models reference it.

from odoo import models, fields


class WooShippingCarrier(models.Model):
    _name = 'woo.shipping.carrier'
    _description = 'WooCommerce Shipping Carrier'
    _order = 'name'

    name = fields.Char(string='Carrier Name', required=True)
    code = fields.Char(string='Code', required=True)
    tracking_url = fields.Char(
        string='Tracking URL Template',
        help='Use {tracking} as placeholder. E.g., https://www.canadapost.ca/track/{tracking}'
    )
    active = fields.Boolean(default=True)
  • Step 3: Write woo_instance.py

Full woo.instance model per spec. Include test_connection method and _generate_api_key helper. Fields for all sync toggles, warehouse, notifications. Encrypted fields for API keys.

import secrets
import logging
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from ..lib.woo_api_client import WooApiClient

_logger = logging.getLogger(__name__)


class WooInstance(models.Model):
    _name = 'woo.instance'
    _description = 'WooCommerce Instance'
    _inherit = ['mail.thread']
    _order = 'name'

    name = fields.Char(string='Instance Name', required=True, tracking=True)
    url = fields.Char(string='WooCommerce URL', required=True, tracking=True,
                      help='Full URL of the WooCommerce site, e.g., https://example.com')
    consumer_key = fields.Char(string='Consumer Key', required=True, groups='base.group_system')
    consumer_secret = fields.Char(string='Consumer Secret', required=True, groups='base.group_system')
    webhook_secret = fields.Char(string='Webhook Secret', groups='base.group_system',
                                 help='HMAC secret for verifying WooCommerce webhook signatures')
    wc_api_version = fields.Char(string='API Version', default='wc/v3')
    odoo_api_key = fields.Char(string='Odoo API Key', groups='base.group_system',
                               help='API key for the WordPress plugin to authenticate with Odoo')
    company_id = fields.Many2one('res.company', string='Company', required=True,
                                 default=lambda self: self.env.company)

    # Sync configuration
    sync_interval = fields.Selection([
        ('5', '5 Minutes'),
        ('15', '15 Minutes'),
        ('30', '30 Minutes'),
        ('60', '1 Hour'),
    ], string='Sync Interval', default='15')
    sync_products = fields.Boolean(string='Sync Products', default=True)
    sync_orders = fields.Boolean(string='Sync Orders', default=True)
    sync_invoices = fields.Boolean(string='Sync Invoices', default=True)
    sync_inventory = fields.Boolean(string='Sync Inventory', default=True)
    sync_customers = fields.Boolean(string='Sync Customers', default=True)
    default_warehouse_id = fields.Many2one('stock.warehouse', string='Default Warehouse')

    # Notifications
    notify_on_failure = fields.Boolean(string='Email on Sync Failure')
    notify_user_ids = fields.Many2many('res.users', string='Notification Recipients')

    # Status
    state = fields.Selection([
        ('draft', 'Draft'),
        ('connected', 'Connected'),
        ('error', 'Error'),
    ], string='Status', default='draft', tracking=True)
    last_sync = fields.Datetime(string='Last Successful Sync', readonly=True)

    # Relational
    product_map_ids = fields.One2many('woo.product.map', 'instance_id', string='Product Mappings')
    order_ids = fields.One2many('woo.order', 'instance_id', string='Synced Orders')
    customer_ids = fields.One2many('woo.customer', 'instance_id', string='Synced Customers')
    sync_log_ids = fields.One2many('woo.sync.log', 'instance_id', string='Sync Logs')

    # Computed
    mapped_count = fields.Integer(compute='_compute_counts', string='Mapped Products')
    unmapped_count = fields.Integer(compute='_compute_counts', string='Unmatched Products')
    error_count = fields.Integer(compute='_compute_counts', string='Errors (24h)')

    @api.depends('product_map_ids.state', 'sync_log_ids')
    def _compute_counts(self):
        for rec in self:
            rec.mapped_count = self.env['woo.product.map'].search_count([
                ('instance_id', '=', rec.id), ('state', '=', 'mapped')
            ])
            rec.unmapped_count = self.env['woo.product.map'].search_count([
                ('instance_id', '=', rec.id), ('state', '=', 'unmapped')
            ])
            twenty_four_ago = fields.Datetime.subtract(fields.Datetime.now(), hours=24)
            rec.error_count = self.env['woo.sync.log'].search_count([
                ('instance_id', '=', rec.id),
                ('state', '=', 'failed'),
                ('create_date', '>=', twenty_four_ago),
            ])

    def _get_client(self):
        """Return a WooApiClient instance for this connection."""
        self.ensure_one()
        return WooApiClient(
            url=self.url,
            consumer_key=self.consumer_key,
            consumer_secret=self.consumer_secret,
            api_version=self.wc_api_version,
        )

    def action_test_connection(self):
        """Test the WooCommerce API connection."""
        self.ensure_one()
        client = self._get_client()
        success, info = client.test_connection()
        if success:
            self.state = 'connected'
            return {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': _('Connection Successful'),
                    'message': _('Connected to WooCommerce %s') % info,
                    'type': 'success',
                    'sticky': False,
                },
            }
        else:
            self.state = 'error'
            raise UserError(_('Connection failed: %s') % info)

    def action_generate_api_key(self):
        """Generate a new Odoo API key for the WordPress plugin."""
        self.ensure_one()
        self.odoo_api_key = secrets.token_urlsafe(32)
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': _('API Key Generated'),
                'message': _('New API key generated. Copy it to the WordPress plugin settings.'),
                'type': 'info',
                'sticky': False,
            },
        }

    def _log_sync(self, sync_type, direction, record_ref, state, message=''):
        """Create a sync log entry."""
        self.ensure_one()
        self.env['woo.sync.log'].sudo().create({
            'instance_id': self.id,
            'sync_type': sync_type,
            'direction': direction,
            'record_ref': record_ref,
            'state': state,
            'message': message,
            'company_id': self.company_id.id,
        })
  • Step 4: Update ir.model.access.csv with carrier model access
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_woo_instance_user,woo.instance.user,model_woo_instance,group_woo_user,1,0,0,0
access_woo_instance_manager,woo.instance.manager,model_woo_instance,group_woo_manager,1,1,1,1
access_woo_shipping_carrier_user,woo.shipping.carrier.user,model_woo_shipping_carrier,group_woo_user,1,0,0,0
access_woo_shipping_carrier_manager,woo.shipping.carrier.manager,model_woo_shipping_carrier,group_woo_manager,1,1,1,1
  • Step 5: Create data/shipping_carriers.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
    <record id="carrier_canada_post" model="woo.shipping.carrier">
        <field name="name">Canada Post</field>
        <field name="code">canada_post</field>
        <field name="tracking_url">https://www.canadapost-postescanada.ca/track-reperage/en#/search?searchFor={tracking}</field>
    </record>
    <record id="carrier_ups" model="woo.shipping.carrier">
        <field name="name">UPS</field>
        <field name="code">ups</field>
        <field name="tracking_url">https://www.ups.com/track?tracknum={tracking}</field>
    </record>
    <record id="carrier_fedex" model="woo.shipping.carrier">
        <field name="name">FedEx</field>
        <field name="code">fedex</field>
        <field name="tracking_url">https://www.fedex.com/fedextrack/?trknbr={tracking}</field>
    </record>
    <record id="carrier_purolator" model="woo.shipping.carrier">
        <field name="name">Purolator</field>
        <field name="code">purolator</field>
        <field name="tracking_url">https://www.purolator.com/en/shipping/tracker?pin={tracking}</field>
    </record>
    <record id="carrier_dhl" model="woo.shipping.carrier">
        <field name="name">DHL</field>
        <field name="code">dhl</field>
        <field name="tracking_url">https://www.dhl.com/en/express/tracking.html?AWB={tracking}</field>
    </record>
    <record id="carrier_other" model="woo.shipping.carrier">
        <field name="name">Other</field>
        <field name="code">other</field>
        <field name="tracking_url"></field>
    </record>
</odoo>
  • Step 6: Commit
git add fusion-woo-odoo/fusion_woocommerce/
git commit -m "feat: add woo.instance and woo.shipping.carrier models with default carriers"

Task 4: Remaining Odoo models

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py
  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_order.py
  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_shipment.py
  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_customer.py
  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_sync_log.py
  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_conflict.py
  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_tax_map.py
  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_pricelist_map.py
  • Create: fusion-woo-odoo/fusion_woocommerce/models/woo_return.py
  • Modify: fusion-woo-odoo/fusion_woocommerce/models/__init__.py

Implement each model exactly per the spec data model tables. Each model must include company_id field. All fields, types, and selections from the spec.

  • Step 1: Create all model files

Each model file follows the spec tables exactly. Key points:

  • woo.product.map: includes woo_product_type, woo_parent_id, is_variation, sync_images, woo_image_ids, state with unmapped

  • woo.order: includes invoice_id (Many2one account.move), invoice_synced

  • woo.shipment: child of woo.order with carrier_id (Many2one woo.shipping.carrier), tracking_number, is_backorder

  • woo.customer: partner_id, woo_customer_id, woo_email

  • woo.sync.log: append-only log

  • woo.conflict: polymorphic with conflict_type, map_id, customer_id, order_id

  • woo.tax.map: tax_idwoo_tax_class

  • woo.pricelist.map: pricelist_idwoo_role

  • woo.return + woo.return.line: return tracking with reason codes

  • Step 2: Update models/__init__.py (only models created so far — sale_order/stock_picking/account_move/res_partner are added in Task 5)

from . import woo_shipping_carrier
from . import woo_instance
from . import woo_product_map
from . import woo_order
from . import woo_shipment
from . import woo_customer
from . import woo_sync_log
from . import woo_conflict
from . import woo_tax_map
from . import woo_pricelist_map
from . import woo_return
  • Step 3: Update ir.model.access.csv with all models

Add read access for group_woo_user and full CRUD for group_woo_manager for every new model.

  • Step 4: Install module in dev and verify no errors
docker exec odoo-dev-app odoo -d fusion-dev -i fusion_woocommerce --stop-after-init
  • Step 5: Commit
git add fusion-woo-odoo/fusion_woocommerce/
git commit -m "feat: add all core data models (product map, order, shipment, customer, sync log, conflict, tax/pricelist map, returns)"

Task 5: Odoo model extensions (sale.order, stock.picking, account.move, res.partner)

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/models/sale_order.py

  • Create: fusion-woo-odoo/fusion_woocommerce/models/stock_picking.py

  • Create: fusion-woo-odoo/fusion_woocommerce/models/account_move.py

  • Create: fusion-woo-odoo/fusion_woocommerce/models/res_partner.py

  • Step 1: Write sale_order.py

Extend sale.order with:

  • woo_order_id (Many2one to woo.order) — reverse link

  • woo_bind_ids (One2many to woo.order) — all WC bindings

  • Helper method _woo_push_status() to push status changes to WC

  • Step 2: Write stock_picking.py

Extend stock.picking with:

  • woo_shipment_ids (One2many to woo.shipment)

  • Override button_validate() to trigger WC shipping update when delivery is done

  • Fields for tracking number and carrier on the picking form

  • Step 3: Write account_move.py

Extend account.move with:

  • woo_order_id (Many2one to woo.order) — link to synced order

  • Method to push invoice PDF to WC when posted

  • Step 4: Write res_partner.py

Extend res.partner with:

  • woo_customer_ids (One2many to woo.customer)

  • is_woo_customer (Boolean computed field)

  • Step 5: Update module, verify installation

docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
  • Step 6: Commit
git add fusion-woo-odoo/fusion_woocommerce/models/
git commit -m "feat: extend sale.order, stock.picking, account.move, res.partner with WooCommerce fields"

Phase 2: Views, Settings, and Menus

Task 6: Instance views and settings page

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_instance_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/res_config_settings.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_menus.xml

  • Step 1: Create woo_instance_views.xml

Form view with:

  • Connection section: URL, consumer key/secret, webhook secret, API version
  • "Test Connection" button, "Generate API Key" button
  • Status indicator (statusbar widget)
  • Sync Configuration tab: interval, toggles, warehouse, price sync direction
  • Shipping Carriers tab (inline tree of woo.shipping.carrier)
  • Notifications tab: toggle + user selector
  • Stats section: mapped/unmapped/errors computed fields

Tree view with: name, URL, state, last_sync, mapped_count

  • Step 2: Create woo_menus.xml

Top-level menu "WooCommerce" with submenus:

  • Dashboard

  • Instances

  • Product Mapping

  • Orders

  • Customers

  • Sync Logs

  • Conflicts

  • Returns

  • Configuration (carriers, tax mapping, pricelist mapping)

  • Step 3: Create res_config_settings.xml

Note: All sync settings live on the woo.instance form view (not in Odoo General Settings). The res_config_settings.xml is a simple redirect action to the instance list, not a full settings model — this avoids needing a res.config.settings Python model.

  • Step 4: Update module and test views load
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
  • Step 5: Commit
git add fusion-woo-odoo/fusion_woocommerce/views/
git commit -m "feat: add instance form/tree views, menus, and settings"

Task 7: All remaining views (product map, orders, sync log, conflicts, etc.)

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_product_map_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_order_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_sync_log_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_conflict_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_customer_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_return_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_tax_map_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_pricelist_map_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_shipping_carrier_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/sale_order_views.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/stock_picking_views.xml

  • Step 1: Create views for each model

Each model gets a tree view and form view. Key views:

  • Product map: tree with Odoo product, WC product, SKUs, price comparison, status badges

  • Orders: tree with WC order #, SO reference, status, shipment count. Form with shipments tab, invoice link

  • Sync log: tree (read-only list), filterable by type/direction/state. Search view with group-by

  • Conflicts: tree with resolution actions, form with "Use Odoo" / "Use WC" buttons

  • Returns: form with order link, return lines, status workflow buttons

  • Sale order: inherit form to add WooCommerce tab showing linked WC order, shipments, tracking

  • Stock picking: inherit form to add tracking number + carrier fields

  • Step 2: Update module and verify all views

docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
  • Step 3: Commit
git add fusion-woo-odoo/fusion_woocommerce/views/
git commit -m "feat: add all model views (product map, orders, sync log, conflicts, returns, tax/pricelist maps)"

Phase 3: Sync Engine — Webhooks and Cron

Task 8: Webhook controller (receive WC events)

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/controllers/webhook.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/controllers/__init__.py

  • Step 1: Write webhook.py

Odoo HTTP controller with type="jsonrpc" endpoints:

  • /woo/webhook/order — receives new order webhooks from WC
  • /woo/webhook/product — receives product update webhooks
  • /woo/webhook/customer — receives customer creation/update webhooks

Each endpoint:

  1. Validates HMAC signature using WooApiClient.verify_webhook_signature()
  2. Finds the matching woo.instance by webhook secret
  3. Checks dedup (order ID, product ID, etc.)
  4. Delegates to the appropriate sync method on the model
  5. Returns success/failure response

Important — Webhook format conflict: WooCommerce webhooks send raw JSON POST bodies, NOT JSON-RPC format. Since Odoo 19 deprecates type="json", use type="http" with auth="none" and csrf=False for webhook endpoints. Parse the raw request body with json.loads(request.httprequest.get_data()). This is the one exception to the type="jsonrpc" rule — external webhook receivers cannot use JSON-RPC because the sender (WooCommerce) controls the payload format. Read Odoo 19 reference controller from Docker first to verify type="http" is still supported.

  • Step 2: Update controllers/__init__.py
from . import webhook
from . import api
from . import product_search
  • Step 3: Commit
git add fusion-woo-odoo/fusion_woocommerce/controllers/
git commit -m "feat: add webhook controller for receiving WC order/product/customer events"

Task 9: API controller (endpoints for WP plugin)

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/controllers/api.py

  • Step 1: Write api.py

REST-like endpoints that the WordPress plugin calls:

  • /woo/api/order/<int:order_id>/documents — WP plugin fetches invoice/delivery PDFs
  • /woo/api/order/<int:order_id>/status — WP plugin fetches order status + timeline
  • /woo/api/order/<int:order_id>/messages — WP plugin fetches customer-visible messages
  • /woo/api/return/create — WP plugin submits return request

Each endpoint validates the Odoo API key (bearer token in Authorization header).

Important: Use type="jsonrpc" per Odoo 19 rules.

  • Step 2: Commit
git add fusion-woo-odoo/fusion_woocommerce/controllers/api.py
git commit -m "feat: add API controller for WordPress plugin communication"

Task 10: AJAX search controller (product mapping)

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py

  • Step 1: Write product_search.py

Endpoints for live AJAX search in the product mapping UI:

  • /woo/search/odoo_products — search Odoo products by name/SKU/internal_reference
  • /woo/search/woo_products — search fetched WC products by name/SKU
  • /woo/search/mapped — search mapped products

Returns JSON results, debounced on the client side (300ms).

Important: Use type="jsonrpc".

  • Step 2: Commit
git add fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py
git commit -m "feat: add AJAX search controller for product mapping UI"

Task 11: Cron jobs and sync engine

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/data/cron.xml

  • Add sync methods to: fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py

  • Step 1: Create data/cron.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
    <!-- Product sync cron -->
    <record id="cron_woo_sync_products" model="ir.cron">
        <field name="name">WooCommerce: Sync Products</field>
        <field name="model_id" ref="model_woo_instance"/>
        <field name="state">code</field>
        <field name="code">model._cron_sync_products()</field>
        <field name="interval_number">15</field>
        <field name="interval_type">minutes</field>
        <field name="numbercall">-1</field>
        <field name="active">True</field>
    </record>

    <!-- Order sync cron -->
    <record id="cron_woo_sync_orders" model="ir.cron">
        <field name="name">WooCommerce: Sync Orders</field>
        <field name="model_id" ref="model_woo_instance"/>
        <field name="state">code</field>
        <field name="code">model._cron_sync_orders()</field>
        <field name="interval_number">5</field>
        <field name="interval_type">minutes</field>
        <field name="numbercall">-1</field>
        <field name="active">True</field>
    </record>

    <!-- Inventory sync cron -->
    <record id="cron_woo_sync_inventory" model="ir.cron">
        <field name="name">WooCommerce: Sync Inventory</field>
        <field name="model_id" ref="model_woo_instance"/>
        <field name="state">code</field>
        <field name="code">model._cron_sync_inventory()</field>
        <field name="interval_number">15</field>
        <field name="interval_type">minutes</field>
        <field name="numbercall">-1</field>
        <field name="active">True</field>
    </record>

    <!-- Customer sync cron -->
    <record id="cron_woo_sync_customers" model="ir.cron">
        <field name="name">WooCommerce: Sync Customers</field>
        <field name="model_id" ref="model_woo_instance"/>
        <field name="state">code</field>
        <field name="code">model._cron_sync_customers()</field>
        <field name="interval_number">30</field>
        <field name="interval_type">minutes</field>
        <field name="numbercall">-1</field>
        <field name="active">True</field>
    </record>

    <!-- Sync log cleanup cron -->
    <record id="cron_woo_cleanup_logs" model="ir.cron">
        <field name="name">WooCommerce: Cleanup Old Sync Logs</field>
        <field name="model_id" ref="model_woo_sync_log"/>
        <field name="state">code</field>
        <field name="code">model._cron_cleanup_logs()</field>
        <field name="interval_number">1</field>
        <field name="interval_type">days</field>
        <field name="numbercall">-1</field>
        <field name="active">True</field>
    </record>
</odoo>
  • Step 2: Add sync methods to woo_instance.py

Add class methods:

  • _cron_sync_products() — iterates connected instances, syncs prices/inventory for mapped products
  • _cron_sync_orders() — fetches recent WC orders not yet synced, creates SOs
  • _cron_sync_inventory() — pushes Odoo stock levels to WC for mapped products
  • _cron_sync_customers() — syncs address updates Odoo → WC

Each method:

  1. Gets all connected instances
  2. For each instance, calls the WC API
  3. Applies dedup checks
  4. Creates/updates records
  5. Logs to woo.sync.log
  6. On failure: logs error, sends notification email if enabled
  • Step 3: Add log cleanup to woo_sync_log.py
@api.model
def _cron_cleanup_logs(self):
    """Purge sync logs older than 90 days (180 for errors)."""
    cutoff_success = fields.Datetime.subtract(fields.Datetime.now(), days=90)
    cutoff_error = fields.Datetime.subtract(fields.Datetime.now(), days=180)
    self.search([
        '|',
        '&', ('state', '!=', 'failed'), ('create_date', '<', cutoff_success),
        '&', ('state', '=', 'failed'), ('create_date', '<', cutoff_error),
    ]).unlink()
  • Step 4: Create email notification templates

data/mail_template.xml with templates for:

  • Sync failure notification

  • New WC order received notification

  • Step 5: Update module and verify crons registered

docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
  • Step 6: Commit
git add fusion-woo-odoo/fusion_woocommerce/
git commit -m "feat: add cron jobs, sync engine methods, log cleanup, and email templates"

Phase 4: Wizards (Setup + Product Fetch)

Task 12: Setup wizard

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/wizard/woo_setup_wizard.py

  • Create: fusion-woo-odoo/fusion_woocommerce/wizard/woo_setup_wizard_views.xml

  • Modify: fusion-woo-odoo/fusion_woocommerce/wizard/__init__.py

  • Step 1: Write woo_setup_wizard.py

Multi-step transient model wizard:

  1. Enter WC URL + consumer key/secret
  2. Test connection
  3. Select default warehouse
  4. Generate Odoo API key
  5. Trigger product fetch
  • Step 2: Write woo_setup_wizard_views.xml

Form view with step indicator, fields for each step, navigation buttons.

  • Step 3: Commit
git add fusion-woo-odoo/fusion_woocommerce/wizard/
git commit -m "feat: add first-time setup wizard"

Task 13: Product fetch and auto-match wizard

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/wizard/woo_product_fetch.py

  • Create: fusion-woo-odoo/fusion_woocommerce/wizard/woo_product_fetch_views.xml

  • Step 1: Write woo_product_fetch.py

Transient model that:

  1. Fetches all WC products via paginated API calls (including variations for variable products)
  2. Auto-match phase 1: SKU match (internal_reference = sku)
  3. Auto-match phase 2: name similarity suggestions (using difflib.SequenceMatcher)
  4. Creates woo.product.map records:
    • SKU matches → state mapped automatically
    • Name suggestions → state unmapped with suggested match stored
    • No match → state unmapped
  5. Returns action to open the product mapping view
  • Step 2: Write view with progress indicator

  • Step 3: Commit

git add fusion-woo-odoo/fusion_woocommerce/wizard/
git commit -m "feat: add product fetch wizard with SKU auto-match and name suggestions"

Phase 5: OWL Frontend (Product Mapping UI + Dashboard)

Task 14: Product mapping OWL component

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js
  • Create: fusion-woo-odoo/fusion_woocommerce/static/src/js/ajax_search.js
  • Create: fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml
  • Create: fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css

Important Odoo 19 rules:

  • Use standalone rpc() from @web/core/network/rpc

  • static props = [] not {}

  • Read reference OWL components from Docker first

  • Step 1: Read reference OWL component from Odoo 19

docker exec odoo-dev-app cat /usr/lib/python3/dist-packages/odoo/addons/web/static/src/core/network/rpc.js
  • Step 2: Write ajax_search.js

Reusable search component with:

  • Debounced input (300ms)

  • Calls product_search controller endpoint via rpc()

  • Emits results to parent component

  • No enter key required

  • Step 3: Write product_mapping.js

Main OWL component with three tabs:

  • Mapped Products: table with live search, bulk actions

  • Unmatched Products: split view with search bars on each side, click-to-map

  • Conflicts: resolution table with action buttons

  • Step 4: Write product_mapping.xml (OWL template)

  • Step 5: Write woo_styles.css

Modern styling for the mapping UI: clean table styles, status badges, search bar styling, tab navigation.

  • Step 6: Update module and verify UI loads
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
  • Step 7: Commit
git add fusion-woo-odoo/fusion_woocommerce/static/
git commit -m "feat: add OWL product mapping UI with live AJAX search"

Task 15: Dashboard widget

Files:

  • Create: fusion-woo-odoo/fusion_woocommerce/static/src/js/dashboard.js

  • Create: fusion-woo-odoo/fusion_woocommerce/static/src/xml/dashboard.xml

  • Create: fusion-woo-odoo/fusion_woocommerce/views/woo_dashboard.xml

  • Step 1: Write dashboard OWL component

Shows at a glance:

  • Orders pending sync (count + link)

  • Last sync time

  • Errors in last 24h (count + link)

  • Products mapped/unmapped (progress bar)

  • Quick actions: Sync Now, View Conflicts, Open Mapping

  • Step 2: Register as action in woo_dashboard.xml

  • Step 3: Commit

git add fusion-woo-odoo/fusion_woocommerce/
git commit -m "feat: add WooCommerce sync dashboard widget"

Phase 6: WordPress Plugin — fusion-woodoo

Task 16: WordPress plugin scaffold

Files:

  • Create: fusion-woo-odoo/fusion-woodoo/fusion-woodoo.php

  • Create: fusion-woo-odoo/fusion-woodoo/includes/class-fusion-woodoo.php

  • Create: fusion-woo-odoo/fusion-woodoo/includes/class-admin-settings.php

  • Create: fusion-woo-odoo/fusion-woodoo/templates/admin/settings.php

  • Create: fusion-woo-odoo/fusion-woodoo/assets/css/admin.css

  • Create: fusion-woo-odoo/fusion-woodoo/assets/js/admin.js

  • Create: fusion-woo-odoo/fusion-woodoo/assets/images/icon.png (placeholder)

  • Create: fusion-woo-odoo/fusion-woodoo/readme.txt

  • Create: fusion-woo-odoo/fusion-woodoo/languages/fusion-woodoo.pot

  • Step 1: Create fusion-woodoo.php

Plugin header, activation/deactivation hooks, main class bootstrap. Requires WooCommerce.

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

// Check WooCommerce dependency
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();
});

// Activation hook
register_activation_hook(__FILE__, function() {
    // Create upload directories with .htaccess protection
    $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');
    }
});

// Deactivation hook — unregister WC webhooks
register_deactivation_hook(__FILE__, function() {
    require_once FUSION_WOODOO_PLUGIN_DIR . 'includes/class-webhooks.php';
    Fusion_WooDoo_Webhooks::unregister_all();
});
  • Step 2: Create class-fusion-woodoo.php

Singleton main class, loads all includes, registers hooks.

  • Step 3: Create admin settings page

WP admin page under WooCommerce menu:

  • Odoo Instance URL field

  • API Key field

  • "Test Connection" button (AJAX)

  • Portal toggle settings

  • Webhook status display

  • Step 4: Commit

git add fusion-woo-odoo/fusion-woodoo/
git commit -m "feat: scaffold fusion-woodoo WordPress plugin with admin settings"

Task 17: REST endpoints (receive from Odoo)

Files:

  • Create: fusion-woo-odoo/fusion-woodoo/includes/class-rest-endpoints.php

  • Step 1: Write REST endpoints

Register WP REST API endpoints that Odoo calls:

  • POST /wp-json/fusion-woodoo/v1/order/update — receives order status + tracking + documents from Odoo
  • POST /wp-json/fusion-woodoo/v1/order/invoice — receives invoice PDF
  • POST /wp-json/fusion-woodoo/v1/order/delivery — receives delivery PDF
  • POST /wp-json/fusion-woodoo/v1/order/messages — receives customer-visible messages

Each endpoint:

  1. Validates Odoo API key from Authorization header
  2. Stores data as WC order meta
  3. Saves PDFs to protected upload directory
  4. Returns success/error
  • Step 2: Commit
git add fusion-woo-odoo/fusion-woodoo/includes/class-rest-endpoints.php
git commit -m "feat: add REST endpoints for receiving Odoo data (orders, invoices, deliveries)"

Task 18: Webhook registration

Files:

  • Create: fusion-woo-odoo/fusion-woodoo/includes/class-webhooks.php

  • Step 1: Write webhook management class

On plugin activation / settings save:

  1. Register WC webhooks pointing to Odoo:
    • order.created{odoo_url}/woo/webhook/order
    • order.updated{odoo_url}/woo/webhook/order
    • product.updated{odoo_url}/woo/webhook/product
    • customer.created{odoo_url}/woo/webhook/customer
    • customer.updated{odoo_url}/woo/webhook/customer
  2. On deactivation: unregister all webhooks
  3. On URL change: re-register with new URL
  4. Display webhook status in admin settings
  • Step 2: Commit
git add fusion-woo-odoo/fusion-woodoo/includes/class-webhooks.php
git commit -m "feat: add WC webhook registration/lifecycle management"

Task 19: My Account portal — tabs and templates

Files:

  • Create: fusion-woo-odoo/fusion-woodoo/includes/class-my-account.php

  • Create: fusion-woo-odoo/fusion-woodoo/includes/class-order-timeline.php

  • Create: fusion-woo-odoo/fusion-woodoo/includes/class-returns.php

  • Create: fusion-woo-odoo/fusion-woodoo/includes/class-api-client.php

  • Create: fusion-woo-odoo/fusion-woodoo/templates/my-account/sales-orders.php

  • Create: fusion-woo-odoo/fusion-woodoo/templates/my-account/invoices.php

  • Create: fusion-woo-odoo/fusion-woodoo/templates/my-account/deliveries.php

  • Create: fusion-woo-odoo/fusion-woodoo/templates/my-account/returns.php

  • Create: fusion-woo-odoo/fusion-woodoo/templates/my-account/order-timeline.php

  • Create: fusion-woo-odoo/fusion-woodoo/templates/my-account/communication.php

  • Create: fusion-woo-odoo/fusion-woodoo/assets/css/my-account.css

  • Create: fusion-woo-odoo/fusion-woodoo/assets/js/my-account.js

  • Step 1: Write class-my-account.php

Register new WC My Account tabs via woocommerce_account_menu_items filter:

  • Sales Orders
  • Invoices
  • Deliveries
  • Returns

Register endpoints via woocommerce_account_{slug}_endpoint. Load templates.

  • Step 2: Write sales orders template

Table of Odoo-synced sales orders. Columns: Order #, Date, Items, Total, Status. "Reorder" button using stored WC product IDs.

  • Step 3: Write invoices template

Table of Odoo invoices. "Download PDF" button serving file through access-controlled PHP handler that validates user ownership.

  • Step 4: Write deliveries template

Table of deliveries with carrier, tracking # (clickable link to carrier tracking URL), status. "Download PDF" for delivery receipt.

  • Step 5: Write class-order-timeline.php and template

Visual timeline component:

● Confirmed → ● Processing → ● Shipped → ● Delivered → ● Completed

CSS-driven, shows dates, tracking info at shipping stage.

  • Step 6: Write returns tab and class-returns.php

Return request form: select order → select items + quantities → select reason → submit. Sends to Odoo API. Displays existing returns with status.

  • Step 7: Write communication history template

Threaded conversation view from _odoo_messages meta.

  • Step 8: Write class-api-client.php

PHP client for calling Odoo API endpoints (for return submission, status checks).

  • Step 9: Write CSS and JS for portal

my-account.css: modern styling for tabs, tables, timeline, return form. my-account.js: AJAX for return submission, reorder button.

  • Step 10: Commit
git add fusion-woo-odoo/fusion-woodoo/
git commit -m "feat: add My Account portal with sales orders, invoices, deliveries, returns, timeline, and communication history"

Phase 7: Order Lifecycle Integration

Task 20: Order sync — WC order → Odoo SO + Invoice

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py (add _sync_order_from_wc method)

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_order.py (add processing methods)

  • Step 1: Write _sync_order_from_wc on woo.instance

Method that receives WC order data and:

  1. Finds/creates customer by email (via woo.customer)
  2. Checks dedup by woo_order_id
  3. Maps WC line items to Odoo products via woo.product.map
  4. Creates sale.order with correct products, quantities, prices, taxes (via woo.tax.map)
  5. Confirms the SO
  6. Creates draft invoice
  7. Creates woo.order tracking record
  8. Pushes SO details back to WC
  9. Logs everything to woo.sync.log
  • Step 2: Add shipping/completion push methods to woo.order

  • action_push_shipping() — pushes tracking + carrier to WC, triggers WC shipping email

  • action_push_completed() — pushes completed status to WC, triggers WC completion email

  • action_push_invoice_pdf() — renders Odoo invoice PDF, pushes to WP plugin REST endpoint

  • action_push_delivery_pdf() — renders delivery receipt PDF, pushes to WP plugin

  • Step 3: Commit

git add fusion-woo-odoo/fusion_woocommerce/models/
git commit -m "feat: implement order sync (WC → Odoo SO/invoice) and status push (Odoo → WC)"

Task 21: Shipping and completion workflow on stock.picking

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/stock_picking.py

  • Step 1: Extend button_validate override

When a delivery linked to a WC order is validated:

  1. Create woo.shipment record with tracking + carrier
  2. If tracking number is set: automatically push shipping update to WC
  3. Handle partial deliveries (backorders): create additional shipment records
  • Step 2: Add tracking fields to picking form

Add woo_tracking_number and woo_carrier_id fields to stock.picking, visible when the picking is linked to a WC order.

  • Step 3: Commit
git add fusion-woo-odoo/fusion_woocommerce/models/stock_picking.py
git commit -m "feat: integrate shipping workflow with WC status push and backorder handling"

Phase 8: Advanced Features

Task 22: Product price and inventory sync logic

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py

  • Step 1: Implement price sync in cron

For each mapped product with sync_price:

  • Fetch WC price via API

  • Compare to Odoo list_price

  • If different on one side only → update the other

  • If different on both → create woo.conflict

  • Step 2: Implement inventory sync

For each mapped product with sync_inventory:

  • Get qty_available from Odoo (filtered by configured warehouse)

  • Push to WC stock_quantity via API

  • Step 3: Implement image sync

For mapped products with sync_images:

  • Compare image checksums

  • Push new/changed images to WC media library

  • Pull WC images to Odoo ir.attachment

  • Track WC media IDs in woo_image_ids

  • Step 4: Commit

git add fusion-woo-odoo/fusion_woocommerce/models/
git commit -m "feat: implement price, inventory, and image sync logic"

Task 23: Conflict resolution

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_conflict.py

  • Step 1: Add resolution methods

  • action_use_odoo() — pushes Odoo value to WC, resolves conflict

  • action_use_woo() — pulls WC value to Odoo, resolves conflict

  • action_bulk_resolve_odoo() — resolves all selected conflicts with Odoo values

  • action_bulk_resolve_woo() — resolves all selected conflicts with WC values

  • Step 2: Commit

git add fusion-woo-odoo/fusion_woocommerce/models/woo_conflict.py
git commit -m "feat: implement conflict resolution actions"

Task 24: Return/RMA and refund handling

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_return.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/controllers/api.py

  • Step 1: Implement return workflow

woo.return methods:

  • action_approve() — creates reverse stock.picking, pushes status to WC

  • action_reject() — rejects return, pushes status to WC

  • action_receive() — marks return received

  • action_refund() — creates credit note on linked invoice, syncs refund to WC

  • Step 2: Add return endpoint to API controller

Process incoming return requests from WP plugin.

  • Step 3: Commit
git add fusion-woo-odoo/fusion_woocommerce/
git commit -m "feat: implement return/RMA workflow and refund handling"

Task 25: Customer sync logic

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_customer.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py

  • Step 1: Implement customer matching and creation

  • Match by email (res.partner search)

  • Create partner with billing/shipping addresses, phone, tag

  • Create woo.customer link record

  • Push odoo_customer_id back to WC user meta

  • Step 2: Implement address sync (Odoo → WC)

Cron method to push Odoo address changes to WC for mapped customers.

  • Step 3: Commit
git add fusion-woo-odoo/fusion_woocommerce/models/
git commit -m "feat: implement customer sync with email matching and address updates"

Task 26: Tax mapping, pricelist sync, and multi-currency logic

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_tax_map.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_pricelist_map.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py

  • Step 1: Implement tax mapping application

Add helper methods:

  • _get_odoo_tax_from_wc_class(instance, wc_tax_class) — looks up woo.tax.map to find matching account.tax

  • _get_wc_tax_class_from_odoo_tax(instance, tax_id) — reverse lookup for pushing products to WC

  • Apply tax mapping in _sync_order_from_wc when creating SO lines

  • Apply tax mapping when pushing products to WC

  • Step 2: Implement pricelist sync logic

  • When WC order arrives: check customer's WC role → find mapped pricelist via woo.pricelist.map → apply pricelist to SO

  • When syncing prices to WC: if role-based pricing plugin is detected, push pricelist-specific prices per role

  • Fallback to default public pricelist if no mapping

  • Step 3: Implement multi-currency support

  • When syncing products: include currency in WC product update

  • When receiving WC orders: respect order currency, set on SO

  • Invoice amounts honour the original order currency

  • Leverage Odoo's existing res.currency.rate for exchange rates

  • Step 4: Commit

git add fusion-woo-odoo/fusion_woocommerce/models/
git commit -m "feat: implement tax mapping, pricelist sync, and multi-currency support"

Task 27: Communication history push and invoice trigger

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_order.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/account_move.py

  • Step 1: Implement communication history push

Add method on woo.order:

  • _push_messages_to_wc() — extracts mail.message records from linked sale.order chatter where subtype_id indicates "visible to customer" (not internal notes)

  • Sends as JSON array [{author, date, body}] to WP plugin REST endpoint

  • Called from action_push_shipping() and action_push_completed() automatically

  • Incremental: tracks last pushed message ID, only sends new messages

  • Step 2: Add invoice push trigger on account.move

Override action_post() on account.move:

  • After posting, check if the invoice is linked to a woo.order (via woo_order_id)

  • If so, automatically call woo_order.action_push_invoice_pdf()

  • Mark invoice_synced = True on the woo.order

  • Step 3: Handle incoming WC refund webhooks

Add webhook handler for order.refunded events:

  • Receive WC refund data (partial or full)

  • Find linked woo.order by woo_order_id

  • Create Odoo credit note on the linked invoice

  • Dedup by woo_order_id + WC refund ID (stored as reference on the credit note)

  • Step 4: Commit

git add fusion-woo-odoo/fusion_woocommerce/models/
git commit -m "feat: add communication history push, invoice auto-trigger, and WC refund webhook handler"

Task 28: Connection health check, rate limiting, and readme

Files:

  • Modify: fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py

  • Modify: fusion-woo-odoo/fusion_woocommerce/data/cron.xml

  • Create: fusion-woo-odoo/fusion-woodoo/readme.txt

  • Create: fusion-woo-odoo/fusion-woodoo/languages/fusion-woodoo.pot

  • Step 1: Add connection health check cron

New cron job that runs hourly: pings each connected WC instance, updates state to error if unreachable, sends notification.

  • Step 2: Add rate limiting to webhook endpoints

Simple in-memory rate limiter on Odoo webhook controller: max 100 requests per minute per source IP. Returns 429 if exceeded.

  • Step 3: Add stale webhook detection to WP plugin

In admin settings, check if registered webhook delivery URLs are reachable. Show warning if Odoo URL is unreachable.

  • Step 4: Write WordPress plugin readme.txt

Standard WordPress plugin readme with description, installation instructions, FAQ, changelog.

  • Step 5: Generate .pot translation template

  • Step 6: Commit

git add fusion-woo-odoo/
git commit -m "feat: add health checks, rate limiting, stale webhook detection, and plugin readme"

Note on parallelisation: The following task groups are independent and can run concurrently if multiple agents are available:

  • Phases 1-5 (Odoo module) and Phase 6 (WordPress plugin) are fully independent
  • Tasks 8, 9, 10 (controllers) can run in parallel
  • Tasks 14 and 15 (OWL components) can run in parallel

Phase 9: Testing and Polish

Task 29: End-to-end testing on dev instances

  • Step 1: Install Odoo module on dev
docker exec odoo-dev-app odoo -d fusion-dev -i fusion_woocommerce --stop-after-init
  • Step 2: Create a test WooCommerce instance connection

Configure connection to a test WC site, test connection, generate API key.

  • Step 3: Test product fetch and mapping

Run the product fetch wizard, verify auto-match, test manual mapping with search.

  • Step 4: Test order lifecycle end-to-end

Place a test WC order, verify it syncs to Odoo SO, process delivery, push shipping, push completion.

  • Step 5: Test customer portal

Verify invoices, deliveries, returns, timeline display in WP My Account.

  • Step 6: Test inventory sync

Change stock in Odoo, verify WC stock updates.

  • Step 7: Fix any issues found

  • Step 8: Commit fixes

git add fusion-woo-odoo/
git commit -m "fix: address issues found during end-to-end testing"

Task 30: Deploy to Westin and Mobility

  • Step 1: Install on odoo-westin (VM101)

SSH to odoo-westin, deploy module, configure connection to westinhealthcare.ca.

  • Step 2: Install WP plugin on westinhealthcare.ca (VM301)

SSH to westin-wp, upload plugin, activate, configure Odoo URL + API key.

  • Step 3: Install on odoo-mobility (VM115)

SSH to odoo-mobility, deploy module, configure connection to mobilityspecialties.ca.

  • Step 4: Install WP plugin on mobilityspecialties.ca (VM305)

SSH to wordpress-mobility, upload plugin, activate, configure.

  • Step 5: Run initial product fetch on both instances

  • Step 6: Verify sync is working on both sites

  • Step 7: Commit any deployment fixes

git add fusion-woo-odoo/
git commit -m "fix: deployment adjustments for production instances"