diff --git a/docs/superpowers/plans/2026-03-31-fusion-woo-odoo-plan.md b/docs/superpowers/plans/2026-03-31-fusion-woo-odoo-plan.md new file mode 100644 index 00000000..f4b60be6 --- /dev/null +++ b/docs/superpowers/plans/2026-03-31-fusion-woo-odoo-plan.md @@ -0,0 +1,1712 @@ +# 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//...` +- 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 + + + + WooCommerce + 50 + + + + WooCommerce User + + + + + WooCommerce Manager + + + +``` + +- [ ] **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 + + + + Canada Post + canada_post + https://www.canadapost-postescanada.ca/track-reperage/en#/search?searchFor={tracking} + + + UPS + ups + https://www.ups.com/track?tracknum={tracking} + + + FedEx + fedex + https://www.fedex.com/fedextrack/?trknbr={tracking} + + + Purolator + purolator + https://www.purolator.com/en/shipping/tracker?pin={tracking} + + + DHL + dhl + https://www.dhl.com/en/express/tracking.html?AWB={tracking} + + + Other + other + + + +``` + +- [ ] **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//documents` — WP plugin fetches invoice/delivery PDFs +- `/woo/api/order//status` — WP plugin fetches order status + timeline +- `/woo/api/order//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 + + + + + WooCommerce: Sync Products + + code + model._cron_sync_products() + 15 + minutes + -1 + True + + + + + WooCommerce: Sync Orders + + code + model._cron_sync_orders() + 5 + minutes + -1 + True + + + + + WooCommerce: Sync Inventory + + code + model._cron_sync_inventory() + 15 + minutes + -1 + True + + + + + WooCommerce: Sync Customers + + code + model._cron_sync_customers() + 30 + minutes + -1 + True + + + + + WooCommerce: Cleanup Old Sync Logs + + code + model._cron_cleanup_logs() + 1 + days + -1 + True + + +``` + +- [ ] **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 +

Fusion WooDoo requires WooCommerce to be installed and active.

'; + }); + 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" +```