# 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"
```
