# Fusion WooOdoo — Design Specification

**Date:** 2026-03-31
**Project:** Bidirectional Odoo 19 ↔ WooCommerce Sync System
**Components:** Odoo module (`fusion_woocommerce`) + WordPress plugin (`fusion-woodoo`)

---

## 1. Overview

A production-ready, plug-and-play integration between Odoo 19 and WooCommerce that syncs products, prices, inventory, orders, invoices, customers, and documents bidirectionally. Designed for non-technical users — install, enter API keys, and it works.

**Two deployable components:**
- **fusion_woocommerce** (Odoo module) — the brain. All sync logic, product mapping, scheduling, conflict resolution, and dashboards.
- **fusion-woodoo** (WordPress plugin) — thin display layer. Receives data from Odoo, displays documents in customer portal, fires webhooks on events.

## 2. Architecture

### Communication Pattern: Direct REST API (Approach A)

```
Odoo ──── WooCommerce REST API ────→ WordPress
Odoo ←─── WooCommerce Webhooks ───── WordPress
Odoo ──── Custom REST endpoints ───→ WP Plugin (invoices, docs, status)
```

- **Odoo is the brain** — all sync logic, mapping, scheduling, conflict resolution
- **WordPress is the display layer** — receives data, displays to customers, fires webhooks
- **Deduplication via external IDs** — every synced record carries a unique reference
- **HTTPS only** — all API communication encrypted
- **Multi-site ready** — one Odoo instance can connect to multiple WooCommerce sites
- **Cross-server compatible** — works regardless of where Odoo and WordPress are hosted

### Sync Strategy: Hybrid (Webhooks + Cron Fallback)

- **Real-time webhooks** for orders, customer creation, product changes
- **Scheduled cron** runs at configurable intervals to catch missed webhooks
- **Manual "Sync Now" button** for on-demand sync
- No duplications — every record checked against external IDs before creation

### Authentication: Bidirectional API Keys

- **Odoo → WooCommerce:** WC REST API consumer key + consumer secret (both encrypted at rest)
- **WC Webhooks → Odoo:** Validated via HMAC signature (`X-WC-Webhook-Signature` header) using the WC webhook secret — NOT the Odoo API key
- **WP Plugin custom endpoints → Odoo:** Odoo API key stored in WP plugin settings, sent as bearer token
- These are two distinct auth flows — webhook signature validation vs. API key authentication

### WooCommerce API Version

- Target: **WC REST API v3** (current, recommended)
- All endpoints use `/wp-json/wc/v3/` prefix

## 3. Data Models

### 3.1 Odoo Models

#### `woo.instance` — WooCommerce Site Connection

| Field | Type | Purpose |
|-------|------|---------|
| `name` | Char | Display name (e.g., "Westin WooCommerce") |
| `url` | Char | WooCommerce site URL |
| `consumer_key` | Char | WC API consumer key (encrypted) |
| `consumer_secret` | Char | WC API consumer secret (encrypted) |
| `webhook_secret` | Char | WC webhook HMAC secret (encrypted) |
| `wc_api_version` | Char | WC API version (default: "wc/v3") |
| `company_id` | Many2one (res.company) | Company (multi-company support) |
| `odoo_api_key` | Char | API key for WP plugin to call back |
| `sync_interval` | Selection | 5min / 15min / 30min / 1hr |
| `sync_products` | Boolean | Enable product sync |
| `sync_orders` | Boolean | Enable order sync |
| `sync_invoices` | Boolean | Enable invoice sync |
| `sync_inventory` | Boolean | Enable inventory sync |
| `sync_customers` | Boolean | Enable customer sync |
| `default_warehouse_id` | Many2one (stock.warehouse) | Where WC orders pull stock |
| `notify_on_failure` | Boolean | Email on sync failure |
| `notify_user_ids` | Many2many (res.users) | Who gets notified |
| `state` | Selection | draft / connected / error |
| `last_sync` | Datetime | Last successful sync |

#### `woo.product.map` — Product Mapping (1:1)

| Field | Type | Purpose |
|-------|------|---------|
| `instance_id` | Many2one (woo.instance) | Which WC site |
| `product_id` | Many2one (product.product) | Odoo product |
| `woo_product_id` | Integer | WooCommerce product ID |
| `woo_product_name` | Char | WC product name (display) |
| `woo_sku` | Char | WC SKU |
| `woo_product_type` | Selection | simple / variable / grouped / external |
| `woo_parent_id` | Integer | WC parent product ID (for variations) |
| `is_variation` | Boolean | Whether this maps a WC variation |
| `sync_price` | Boolean | Sync price for this product |
| `sync_inventory` | Boolean | Sync stock for this product |
| `sync_images` | Boolean | Sync images for this product |
| `woo_image_ids` | Char | JSON list of WC media IDs for image tracking |
| `last_synced` | Datetime | Last sync time |
| `company_id` | Many2one (res.company) | Company |
| `state` | Selection | unmapped / mapped / conflict / error |

#### `woo.order` — Synced Order Tracking

| Field | Type | Purpose |
|-------|------|---------|
| `instance_id` | Many2one (woo.instance) | Which WC site |
| `sale_order_id` | Many2one (sale.order) | Odoo sales order |
| `woo_order_id` | Integer | WooCommerce order ID |
| `woo_order_number` | Char | WC order display number |
| `woo_status` | Char | Current WC status |
| `invoice_id` | Many2one (account.move) | Linked Odoo invoice |
| `invoice_synced` | Boolean | Whether invoice PDF was pushed to WC |
| `company_id` | Many2one (res.company) | Company |
| `state` | Selection | new / confirmed / shipped / completed / cancelled |

#### `woo.shipment` — Per-Shipment Tracking (child of woo.order)

| Field | Type | Purpose |
|-------|------|---------|
| `order_id` | Many2one (woo.order) | Parent order |
| `picking_id` | Many2one (stock.picking) | Odoo delivery order |
| `carrier_id` | Many2one (woo.shipping.carrier) | Shipping carrier |
| `tracking_number` | Char | Tracking number |
| `shipped_date` | Datetime | When shipped |
| `is_backorder` | Boolean | Whether this is a backorder shipment |
| `synced_to_woo` | Boolean | Whether WC was notified |
| `company_id` | Many2one (res.company) | Company |

#### `woo.shipping.carrier` — Configurable Carrier List

| Field | Type | Purpose |
|-------|------|---------|
| `name` | Char | Carrier name (e.g., "Canada Post") |
| `code` | Char | Short code (e.g., "canada_post") |
| `tracking_url` | Char | Tracking URL template (e.g., "https://www.canadapost.ca/track/{tracking}") |
| `active` | Boolean | Whether enabled |

#### `woo.sync.log` — Sync History

| Field | Type | Purpose |
|-------|------|---------|
| `instance_id` | Many2one (woo.instance) | Which WC site |
| `sync_type` | Selection | product / order / invoice / inventory / customer |
| `direction` | Selection | odoo_to_woo / woo_to_odoo |
| `record_ref` | Char | Reference (e.g., "SO0042") |
| `state` | Selection | success / failed / conflict |
| `message` | Text | Details or error message |
| `create_date` | Datetime | Timestamp |
| `company_id` | Many2one (res.company) | Company |

#### `woo.customer` — Customer Link Tracking

| Field | Type | Purpose |
|-------|------|---------|
| `instance_id` | Many2one (woo.instance) | Which WC site |
| `partner_id` | Many2one (res.partner) | Odoo partner |
| `woo_customer_id` | Integer | WooCommerce customer/user ID |
| `woo_email` | Char | WC customer email |
| `last_synced` | Datetime | Last sync time |
| `company_id` | Many2one (res.company) | Company |

#### `woo.tax.map` — Tax Class Mapping

| Field | Type | Purpose |
|-------|------|---------|
| `instance_id` | Many2one (woo.instance) | Which WC site |
| `tax_id` | Many2one (account.tax) | Odoo tax |
| `woo_tax_class` | Char | WC tax class slug |
| `woo_tax_class_name` | Char | WC tax class display name |
| `company_id` | Many2one (res.company) | Company |

#### `woo.pricelist.map` — Price Tier Mapping

| Field | Type | Purpose |
|-------|------|---------|
| `instance_id` | Many2one (woo.instance) | Which WC site |
| `pricelist_id` | Many2one (product.pricelist) | Odoo pricelist |
| `woo_role` | Char | WC customer role (e.g., "wholesale_customer") |
| `woo_role_name` | Char | WC role display name |
| `company_id` | Many2one (res.company) | Company |

#### `woo.return` — Return/RMA Tracking

| Field | Type | Purpose |
|-------|------|---------|
| `instance_id` | Many2one (woo.instance) | Which WC site |
| `order_id` | Many2one (woo.order) | Original synced order |
| `picking_id` | Many2one (stock.picking) | Odoo return picking |
| `reason` | Text | Customer's return reason |
| `line_ids` | One2many (woo.return.line) | Items being returned |
| `state` | Selection | requested / approved / received / refunded / rejected |
| `company_id` | Many2one (res.company) | Company |

#### `woo.return.line` — Return Line Items

| Field | Type | Purpose |
|-------|------|---------|
| `return_id` | Many2one (woo.return) | Parent return |
| `product_id` | Many2one (product.product) | Product being returned |
| `quantity` | Float | Quantity to return |
| `reason` | Selection | defective / wrong_item / not_needed / damaged / other |
| `company_id` | Many2one (res.company) | Company |

#### `woo.conflict` — Data Conflicts

| Field | Type | Purpose |
|-------|------|---------|
| `instance_id` | Many2one (woo.instance) | Which WC site |
| `conflict_type` | Selection | product / customer / order |
| `map_id` | Many2one (woo.product.map) | Product map (if product conflict) |
| `customer_id` | Many2one (woo.customer) | Customer (if customer conflict) |
| `order_id` | Many2one (woo.order) | Order (if order conflict) |
| `field_name` | Char | Which field conflicts |
| `odoo_value` | Char | Value in Odoo |
| `woo_value` | Char | Value in WooCommerce |
| `resolution` | Selection | pending / use_odoo / use_woo |
| `resolved_by` | Many2one (res.users) | Who resolved it |
| `company_id` | Many2one (res.company) | Company |

### 3.2 WordPress Meta (No Custom Tables)

| Meta Key | Stored On | Purpose |
|----------|-----------|---------|
| `_odoo_order_id` | Order | Linked Odoo sales order ID |
| `_odoo_invoice_id` | Order | Linked Odoo invoice ID |
| `_odoo_invoice_pdf` | Order | Invoice PDF stored in `wp-content/uploads/fusion-woodoo/invoices/` (access-controlled) |
| `_odoo_delivery_pdf` | Order | Delivery PDF stored in `wp-content/uploads/fusion-woodoo/deliveries/` (access-controlled) |
| `_odoo_tracking_number` | Order | Tracking number from Odoo |
| `_odoo_shipping_carrier` | Order | Carrier name |
| `_odoo_order_status` | Order | Odoo-side status for timeline |
| `_odoo_product_id` | Product | Linked Odoo product ID |
| `_odoo_customer_id` | User | Linked Odoo partner ID |
| `_odoo_messages` | Order | JSON array of customer-visible messages [{author, date, body}] |
| `_odoo_instance_url` | Options | Odoo server URL |
| `_odoo_api_key` | Options | API key for Odoo auth |

## 4. Sync Workflows

### 4.1 Order Lifecycle (WooCommerce → Odoo → WooCommerce)

1. Customer places order on WooCommerce
2. WC webhook fires → Odoo receives order
3. Odoo checks customer by email → match existing partner or create new
4. Odoo checks dedup by `woo_order_id` → skip if exists
5. Odoo creates `sale.order` with mapped products, quantities, prices
6. Odoo confirms sale order, creates draft invoice
7. Odoo pushes sales order details back to WC via REST API
8. User validates invoice in Odoo
9. User processes delivery in Odoo
10. User enters tracking number + selects shipping carrier
11. Odoo pushes tracking + carrier to WC → WC status: "shipped" → **Email #1: Shipping notification with tracking**
12. User marks order "Completed" in Odoo
13. Odoo pushes status to WC → WC status: "completed" → **Email #2: Order completed**
14. Odoo pushes invoice PDF + delivery receipt PDF to WC custom endpoint
15. Customer can view/download all documents from My Account

### 4.2 Product Sync

**Supported WC Product Types:**
- **Simple products** — standard 1:1 mapping to `product.product`
- **Variable products** — parent maps to `product.template`, each variation maps to a `product.product` variant
- **Grouped products** — mapped as a set of linked simple products
- **External/affiliate products** — excluded from sync (no inventory/price to sync)
- **Downloadable/virtual products** — mapped but inventory sync disabled automatically

**Initial Setup:**
1. User clicks "Fetch WooCommerce Products" in Odoo
2. Odoo calls WC REST API → GET /products (paginated, includes variations)
3. For variable products: fetch `/products/{id}/variations` to get all variants
4. Auto-match by SKU (`internal_reference` = `sku`) → auto-map (matches both products and variants)
5. Suggest by name similarity for unmatched → user confirms/rejects
6. Remaining unmatched shown with actions: "Create in Odoo" / "Create in WooCommerce" / "Ignore"

**Ongoing Sync (cron):**
1. For each mapped product with `sync_price` enabled:
   - Compare Odoo price vs WC price
   - Same → skip
   - Changed on one side → update the other
   - Changed on both → create `woo.conflict` record
2. For each mapped product with `sync_inventory` enabled:
   - Push Odoo `qty_available` to WC `stock_quantity`

**Ongoing Sync (webhook):**
1. WC fires `product.updated` webhook
2. Odoo finds mapping by `woo_product_id`
3. Updates Odoo product or creates conflict

### 4.3 Customer Sync

1. Order webhook arrives with customer email
2. Search `res.partner` by email
   - Found → link, update address if changed
   - Not found → create with name, email, phone, billing/shipping addresses, tag "WooCommerce Customer"
3. Store `odoo_customer_id` as WC user meta
4. Ongoing: address changes flow Odoo → WooCommerce (Odoo is master)

### 4.4 Inventory Sync

**Triggered by:** stock move confirmed in Odoo, or cron fallback

1. For each mapped product with `sync_inventory` enabled
2. Get Odoo `qty_available` for configured warehouse
3. PUT to WC REST API → update `stock_quantity`
4. Log to `woo.sync.log`

**Backorder Handling:**
1. Odoo creates partial delivery → push shipped items with tracking
2. Remaining items ship later → push second tracking update
3. Customer sees both shipments in portal timeline

### 4.5 Price Tier Sync

- Configured via `woo.pricelist.map` model — maps Odoo pricelists to WC customer roles
- When a WC customer with a specific role (e.g., "wholesale_customer") places an order, Odoo uses the mapped pricelist for pricing
- When syncing prices to WC, if WC supports role-based pricing plugins (e.g., "Wholesale Prices"), push pricelist-specific prices
- Fallback: if no role mapping exists, use Odoo's default public pricelist
- Configurable per `woo.instance`

### 4.6 Product Image Sync

- Images sync bidirectionally for mapped products with `sync_images` enabled
- Odoo product images push to WC media library; WC media IDs stored in `woo_image_ids` on the map record
- WC images pull into Odoo as `ir.attachment` linked to the product
- Image order preserved (first image = WC featured image)
- On sync, compare image checksums to avoid re-uploading unchanged images

### 4.7 Tax Mapping

- Configured via `woo.tax.map` model — maps Odoo `account.tax` records to WC tax class slugs
- Supports Canadian HST/GST/PST configurations
- When creating orders from WC: look up WC tax class → find mapped Odoo tax → apply to SO lines
- When pushing products to WC: look up Odoo product's tax → find mapped WC tax class → set on WC product
- Configurable mapping table per `woo.instance`

### 4.8 Return/RMA

1. Customer clicks "Request Return" in WP My Account
2. Fills return form: selects order, selects items + quantities, selects reason (defective / wrong_item / not_needed / damaged / other), submits
3. WP plugin sends return request to Odoo custom endpoint
4. Odoo creates `woo.return` record (state: requested)
5. Odoo user reviews and approves/rejects the return
6. If approved: Odoo creates a reverse `stock.picking` (NOT `stock.return.picking` which is a transient wizard) linked to the `woo.return`
7. Status updates push back to WC → customer sees progress: requested → approved → received → refunded (or rejected)
8. When return is received and refund issued, Odoo creates credit note → maps to WC refund

### 4.9 Refund Handling

- WooCommerce native refunds (partial or full) sync to Odoo as credit notes on the linked invoice
- Odoo credit notes sync back to WC as refunds on the order
- Refund amounts and line items are tracked to prevent double-refunding
- Dedup key: `woo_order_id` + refund ID

### 4.10 Communication History Sync

- When Odoo pushes order status updates, it also pushes recent `mail.message` records from the sale.order's chatter
- Messages are sent as part of the order status push (same REST endpoint, included in payload)
- WP plugin stores messages as order meta (`_odoo_messages` — JSON array of {author, date, body})
- Only internal notes marked "visible to customer" are synced — private internal notes stay in Odoo
- New messages pushed on each status update (incremental, not full history each time)

### 4.11 Deduplication Rules

| Record | Unique Key | Check Before Creating |
|--------|-----------|----------------------|
| Order | `woo_order_id` per instance | Always check `woo.order` |
| Customer | `email` | Always search `res.partner` by email |
| Product map | `woo_product_id` per instance | Always check `woo.product.map` |
| Invoice | `sale_order_id` | One invoice per SO |
| Sync log | N/A | Append-only |

## 5. Product Mapping UI (Odoo)

### Design Principles
- Modern, intuitive, production-grade
- Live AJAX search — instant filtering as user types (debounced ~300ms)
- No page reloads for search/filter operations

### Layout

**Top Bar:**
- Instance selector dropdown (which WC site)
- "Fetch Products" button (initial import)
- "Sync Now" button
- Stats bar: X mapped / Y unmatched / Z conflicts

**Three Tabs:**

**Tab 1: Mapped Products**
- Table: Odoo Product | SKU | WC Product | WC SKU | Price (Odoo) | Price (WC) | Stock | Status | Actions
- Live search bar filters both Odoo and WC product names/SKUs in real time
- Bulk actions: Unmap selected, Sync selected, Enable/disable price sync, Enable/disable inventory sync
- Conflict indicator (amber icon) on rows with price conflicts
- Click row to expand details

**Tab 2: Unmatched Products**
- Split view: Odoo products on left, WC products on right
- Each side has its own live search bar
- Click to select from each side and map together (v1); drag-and-drop as a v2 enhancement
- Action buttons per product: "Create in WooCommerce" / "Create in Odoo" / "Ignore"
- Auto-suggest section at top showing name-based matches with confidence score

**Tab 3: Conflicts**
- Table: Product | Field | Odoo Value | WC Value | Actions
- Actions: "Use Odoo Value" / "Use WC Value" / "Ignore"
- Bulk resolve: "Use all Odoo values" / "Use all WC values"

### Search Behaviour
- Debounced at 300ms — starts filtering after user stops typing briefly
- Filters product name, SKU, and internal reference simultaneously
- No enter key required
- Results update in-place without page reload
- Built using Odoo 19 OWL framework with RPC calls to controller endpoints

## 6. Customer Portal (WordPress — My Account)

### New Tabs in My Account

**Tab: Sales Orders**
- List of all Odoo-created sales orders
- Columns: Order #, Date, Items, Total, Status
- Click to view full order details
- "Reorder" button → creates new WC cart with same products (Odoo pushes WC product IDs per line in the order data so the plugin can build the cart directly)

**Tab: Invoices**
- List of all Odoo invoices
- Columns: Invoice #, Date, Amount, Status (Draft/Paid)
- "Download PDF" button for each invoice
- PDF is the actual Odoo-generated invoice

**Tab: Deliveries**
- List of all delivery/inventory transfer receipts
- Columns: Delivery #, Date, Carrier, Tracking #, Status
- "Download PDF" button for delivery receipt
- Tracking number is a clickable link to carrier's tracking page

**Tab: Returns**
- List of return requests
- Columns: Return #, Date, Items, Status
- "Request Return" button to initiate new return
- Return form: select order, select items, reason, submit

### Order Status Timeline

Visual timeline/tracker on each order detail page:

```
● Confirmed → ● Processing → ● Shipped → ● Delivered → ● Completed
     ✓              ✓             ✓            ○              ○
```

- Green checkmark for completed stages
- Blue dot for current stage
- Grey dot for upcoming stages
- Each stage shows date/time when it occurred
- Shipping stage shows carrier + tracking number with link

### Communication History

- Messages/notes exchanged between customer and team about an order
- Displayed as a threaded conversation view
- Synced from Odoo chatter (`mail.message` on `sale.order`) — only messages marked visible to customer
- Stored as `_odoo_messages` order meta (JSON array)
- Pushed incrementally with each order status update

## 7. Settings & Configuration

### 7.1 Odoo Settings Page (fusion_woocommerce)

**Connection Section:**
- WooCommerce URL (with "Test Connection" button)
- Consumer Key + Consumer Secret
- Auto-generated Odoo API Key (with "Regenerate" button)
- Connection status indicator (green/red)

**Sync Configuration:**
- Sync interval dropdown (5min / 15min / 30min / 1hr)
- Toggle switches for each sync type: Products, Orders, Invoices, Inventory, Customers
- Default warehouse selector
- Price sync direction: Odoo → WC / WC → Odoo / Bidirectional

**Shipping Carriers:**
- Enabled carriers checklist: Canada Post, UPS, FedEx, Purolator, DHL, Other
- "Other" allows custom carrier name

**Tax Mapping:**
- Table: Odoo Tax | WC Tax Class | Actions
- Add/edit/remove mappings

**Notifications:**
- Toggle: Email on sync failure
- User selector: who receives notifications
- Toggle: Email on new WC order received

**Setup Wizard:**
- First-time setup walks user through: enter URL → enter API keys → test connection → select warehouse → fetch products → initial mapping
- No technical knowledge required

### 7.2 WordPress Settings Page (fusion-woodoo)

**Connection Section:**
- Odoo Instance URL
- API Key
- "Test Connection" button
- Status indicator

**Portal Settings:**
- Toggle: Show Sales Orders tab
- Toggle: Show Invoices tab
- Toggle: Show Deliveries tab
- Toggle: Show Returns tab
- Toggle: Show Order Timeline
- Toggle: Enable Reorder button

**Webhook Configuration:**
- Auto-configured on activation (registers WC webhooks pointing to Odoo)
- Display registered webhooks with status
- On plugin deactivation: unregister all WC webhooks pointing to Odoo
- On Odoo URL change: automatically re-register webhooks with new URL
- Stale webhook detection: if webhooks point to an unreachable Odoo URL, show warning

## 8. Dashboard Widget (Odoo)

An Odoo dashboard widget showing sync health at a glance:

- **Orders pending sync:** count with link to list
- **Last sync time:** timestamp + "X minutes ago"
- **Errors in last 24h:** count with link to sync log (filtered)
- **Products mapped / unmapped:** ratio with progress bar
- **Inventory sync status:** last run + items synced
- **Quick actions:** Sync Now, View Conflicts, Open Mapping

## 9. Folder Structure

```
fusion-woo-odoo/
├── fusion_woocommerce/                 # Odoo 19 module
│   ├── __init__.py
│   ├── __manifest__.py
│   ├── controllers/
│   │   ├── __init__.py
│   │   ├── webhook.py                  # Receive WC webhooks
│   │   ├── api.py                      # REST API for WP plugin
│   │   └── product_search.py           # AJAX search endpoints
│   ├── lib/
│   │   ├── __init__.py
│   │   └── woo_api_client.py           # WooCommerce REST API wrapper (NOT an Odoo model)
│   ├── models/
│   │   ├── __init__.py
│   │   ├── woo_instance.py             # WC site connection config
│   │   ├── woo_product_map.py          # Product mapping
│   │   ├── woo_order.py                # Order sync tracking
│   │   ├── woo_shipment.py             # Per-shipment tracking
│   │   ├── woo_shipping_carrier.py     # Configurable carrier list
│   │   ├── woo_customer.py             # Customer link tracking
│   │   ├── woo_tax_map.py              # Tax class mapping
│   │   ├── woo_pricelist_map.py        # Price tier mapping
│   │   ├── woo_return.py               # Return/RMA tracking + lines
│   │   ├── woo_sync_log.py             # Sync history
│   │   ├── woo_conflict.py             # Conflict resolution
│   │   ├── sale_order.py               # sale.order extensions
│   │   ├── stock_picking.py            # Delivery extensions (tracking)
│   │   ├── account_move.py             # Invoice extensions
│   │   └── res_partner.py              # Partner extensions (WC link)
│   ├── views/
│   │   ├── woo_instance_views.xml      # Settings / connection config
│   │   ├── woo_product_map_views.xml   # Product mapping UI
│   │   ├── woo_order_views.xml         # Order tracking views
│   │   ├── woo_sync_log_views.xml      # Sync log views
│   │   ├── woo_conflict_views.xml      # Conflict resolution views
│   │   ├── woo_dashboard.xml           # Dashboard widget
│   │   ├── sale_order_views.xml        # SO view extensions
│   │   ├── stock_picking_views.xml     # Delivery view extensions
│   │   └── res_config_settings.xml     # Settings page
│   ├── wizard/
│   │   ├── __init__.py
│   │   ├── woo_setup_wizard.py         # First-time setup wizard
│   │   ├── woo_setup_wizard_views.xml
│   │   ├── woo_product_fetch.py        # Fetch & auto-match wizard
│   │   └── woo_product_fetch_views.xml
│   ├── data/
│   │   ├── cron.xml                    # Scheduled sync jobs + log cleanup cron
│   │   ├── mail_template.xml           # Notification email templates
│   │   └── shipping_carriers.xml       # Default carrier data (Canada Post, UPS, FedEx, Purolator, DHL)
│   ├── security/
│   │   ├── ir.model.access.csv         # Access rights
│   │   └── woo_security.xml            # Record rules & groups
│   ├── static/
│   │   ├── description/
│   │   │   └── icon.png                # Module icon (placeholder)
│   │   └── src/
│   │       ├── js/
│   │       │   ├── product_mapping.js  # OWL component: mapping UI
│   │       │   ├── ajax_search.js      # Live search component
│   │       │   └── dashboard.js        # Dashboard widget
│   │       ├── css/
│   │       │   └── woo_styles.css      # Custom styles
│   │       └── xml/
│   │           ├── product_mapping.xml # OWL templates
│   │           └── dashboard.xml       # Dashboard template
│   └── i18n/
│       └── (translation files)
│
└── fusion-woodoo/                      # WordPress plugin
    ├── fusion-woodoo.php               # Plugin entry point
    ├── includes/
    │   ├── class-fusion-woodoo.php     # Main plugin class
    │   ├── class-api-client.php        # Odoo API client
    │   ├── class-rest-endpoints.php    # Custom REST endpoints (receive from Odoo)
    │   ├── class-webhooks.php          # WC webhook registration
    │   ├── class-my-account.php        # My Account tab registration
    │   ├── class-order-timeline.php    # Order status timeline
    │   ├── class-returns.php           # Return/RMA handling
    │   └── class-admin-settings.php    # WP admin settings page
    ├── templates/
    │   ├── my-account/
    │   │   ├── sales-orders.php        # Sales orders tab
    │   │   ├── invoices.php            # Invoices tab
    │   │   ├── deliveries.php          # Deliveries tab
    │   │   ├── returns.php             # Returns tab
    │   │   ├── order-timeline.php      # Status timeline component
    │   │   └── communication.php       # Communication history
    │   └── admin/
    │       └── settings.php            # Admin settings page
    ├── assets/
    │   ├── css/
    │   │   ├── my-account.css          # Customer portal styles
    │   │   └── admin.css               # Admin styles
    │   ├── js/
    │   │   ├── my-account.js           # Portal interactions
    │   │   └── admin.js                # Admin JS (test connection, etc.)
    │   └── images/
    │       └── icon.png                # Plugin icon (placeholder)
    ├── languages/
    │   └── fusion-woodoo.pot           # Translation template
    └── readme.txt                      # WordPress plugin readme
```

## 10. Error Handling & Reliability

- **Retry logic:** Failed API calls retry up to 3 times with exponential backoff
- **Queue system:** Failed syncs are queued and retried on next cron run
- **Graceful degradation:** If WC site is down, Odoo continues working normally; queued syncs process when connection restores
- **Sync log:** Every operation logged with success/failure status and error details
- **Email alerts:** Configurable notifications on sync failures
- **Connection health check:** Periodic ping to verify WC site is reachable
- **Sync log retention:** Cron purges logs older than 90 days (configurable). Keeps error logs for 180 days.
- **PDF file access control:** Invoice and delivery PDFs stored in protected directories with `.htaccess` deny rules. Served via PHP handler that validates the requesting user owns the order.

## 11. Security

- API keys stored encrypted in both systems
- HTTPS required for all API communication
- Odoo API key validated on every incoming request from WP
- WC consumer key/secret validated on every outgoing request from Odoo
- WordPress nonce verification on all admin AJAX calls
- Rate limiting on webhook endpoints to prevent abuse
- Input sanitization on all data received from external systems
- WC webhook signature validation via HMAC-SHA256 (separate from API key auth)
- PDF files served through access-controlled PHP handler, not direct file URLs

## 12. Odoo 19 Technical Requirements

- All HTTP controller routes must use `type="jsonrpc"` (NOT `type="json"` which is deprecated)
- Frontend JS uses `Interaction` class from `@web/public/interaction`
- Backend OWL components use standalone `rpc()` from `@web/core/network/rpc`
- OWL components: `static props = []` not `{}`
- API client class in `lib/` directory (NOT in `models/` — would cause ORM registration errors). Import via `from ..lib.woo_api_client import WooApiClient`
- All custom models include `company_id` field for multi-company support

## 13. Multi-Currency Support

- Odoo currency syncs to WooCommerce product prices
- Exchange rates managed in Odoo (existing currency rate feature)
- Products can have different prices per currency
- Invoice amounts respect the order's original currency

## 14. Target Deployments

| Pairing | Odoo | WordPress |
|---------|------|-----------|
| Westin | odoo-westin (192.168.1.40, VM101) | westinhealthcare.ca (192.168.1.152, VM301) |
| Mobility | odoo-mobility (192.168.1.102, VM115) | mobilityspecialties.ca (192.168.1.153, VM305) |
| Any third party | Any Odoo 19 instance | Any WooCommerce site |
