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

1713 lines
60 KiB
Markdown

# 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**
```bash
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`**
```python
{
'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`**
```python
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
<?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)**
```csv
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**
```bash
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`**
```python
from .woo_api_client import WooApiClient
```
- [ ] **Step 2: Write `woo_api_client.py`**
```python
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**
```bash
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`**
```python
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.
```python
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.
```python
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**
```csv
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
<?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**
```bash
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_id``woo_tax_class`
- `woo.pricelist.map`: `pricelist_id``woo_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)
```python
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**
```bash
docker exec odoo-dev-app odoo -d fusion-dev -i fusion_woocommerce --stop-after-init
```
- [ ] **Step 5: Commit**
```bash
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**
```bash
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
```
- [ ] **Step 6: Commit**
```bash
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**
```bash
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
```
- [ ] **Step 5: Commit**
```bash
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**
```bash
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
```
- [ ] **Step 3: Commit**
```bash
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`**
```python
from . import webhook
from . import api
from . import product_search
```
- [ ] **Step 3: Commit**
```bash
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**
```bash
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**
```bash
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
<?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`**
```python
@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**
```bash
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
```
- [ ] **Step 6: Commit**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_woocommerce --stop-after-init
```
- [ ] **Step 7: Commit**
```bash
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**
```bash
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
<?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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
git add fusion-woo-odoo/
git commit -m "fix: deployment adjustments for production instances"
```