# Quotation ↔ Odoo Sync Structure

## Overview
This document details the data structure and flow for quotation synchronization between the portal and Odoo.

---

## 📤 Portal → Odoo (Creating Quotation)

### Portal Sends:
```php
[
    'partner_id' => 3558,                    // Customer's Odoo partner ID
    'reference' => 'QT-TEST-1770367980',     // Portal quotation reference
    'notes' => 'Customer notes...',          // Quotation notes
    'valid_until' => '2026-03-08',           // Validity date
    'order_lines' => [                       // Line items (optional)
        [
            'product_id' => 123,
            'quantity' => 1,
            'unit_price' => 500.00,
            'description' => 'Magellan Metro-VPN'
        ]
    ]
]
```

### Odoo Receives & Stores:
```php
// Creates 'sale.order' record with:
[
    'partner_id' => 3558,
    'state' => 'draft',                      // Always starts as draft
    'validity_date' => '2026-03-08',
    'note' => '<p>Customer notes...</p>',    // HTML formatted
    'client_order_ref' => 'QT-TEST-...',     // Portal reference
    'name' => 'DFAZ-SO-1479',                // Auto-generated by Odoo
    'date_order' => '2026-02-06 10:54:16',   // Auto-set by Odoo
    'amount_total' => 0.00,                  // Calculated by Odoo
    'amount_untaxed' => 0.00,
    'amount_tax' => 0.00,
    'invoice_status' => 'no',
    'delivery_status' => false,
    'order_line' => []                       // Line items (if permissions allow)
]
```

### Portal Stores Back:
```php
// Updates quotation record with:
[
    'odoo_quotation_id' => '1515',           // Odoo sale.order ID
    'odoo_synced' => true,
    'odoo_state' => 'draft',
    'odoo_last_sync' => '2026-02-06 10:54:16'
]
```

---

## 📥 Odoo → Portal (Status Updates)

### Portal Requests:
```php
// Calls: getQuotationStatus($quotationId)
// Searches: sale.order where id = 1515
// Fields: ['id', 'name', 'state', 'partner_id', 'amount_total', 
//          'validity_date', 'date_order', 'order_line']
```

### Odoo Returns:
```php
[
    'id' => 1515,
    'name' => 'DFAZ-SO-1479',                // Quotation number
    'partner_id' => [3558, 'Tawona N Rwatida'], // [ID, Name]
    'state' => 'draft',                      // Current state
    'date_order' => '2026-02-06 10:54:16',
    'validity_date' => '2026-03-08',
    'amount_total' => 0.00,
    'amount_untaxed' => 0.00,
    'amount_tax' => 0.00,
    'invoice_status' => 'no',
    'delivery_status' => false,
    'order_line' => [],                      // Array of line item IDs
    'note' => '<p>Customer notes...</p>',
    'origin' => 'Portal Quotation #38'      // If set
]
```

### Portal Updates:
```php
// Maps Odoo state to portal status:
$stateMapping = [
    'draft' => 'pending',
    'sent' => 'sent',
    'sale' => 'accepted',
    'done' => 'completed',
    'cancel' => 'declined',
];

// Updates quotation:
[
    'odoo_state' => 'draft',
    'status' => 'pending',                   // Mapped from odoo_state
    'odoo_quotation_number' => 'DFAZ-SO-1479',
    'amount' => 0.00,
    'odoo_last_sync' => now()
]
```

---

## 🔄 Quotation Lifecycle States

### Odoo States:
1. **draft** - Initial quotation state
2. **sent** - Quotation sent to customer
3. **sale** - Quotation accepted, becomes sales order
4. **done** - Sales order completed
5. **cancel** - Quotation/order cancelled

### Portal Status Mapping:
| Odoo State | Portal Status | Description |
|------------|---------------|-------------|
| `draft` | `pending` | Quotation created, awaiting action |
| `sent` | `sent` | Quotation sent to customer |
| `sale` | `accepted` | Customer accepted, now a sales order |
| `done` | `completed` | Order fulfilled and completed |
| `cancel` | `declined` | Quotation/order cancelled |

---

## 📊 Database Structure

### Portal `quotations` Table:
```sql
- id (primary key)
- reference (unique, e.g., 'QT-TEST-1770367980')
- customer_id (foreign key to customers)
- package_id (foreign key to product_packages)
- status (draft/pending/sent/accepted/declined/expired)
- monthly_price
- installation_fee
- extension_cost
- equipment_cost
- total_amount
- valid_until
- notes
- service_address
- contact_email
- contact_phone
- odoo_quotation_id (Odoo sale.order ID)
- odoo_synced (boolean)
- odoo_state (draft/sent/sale/done/cancel)
- odoo_quotation_number (e.g., 'DFAZ-SO-1479')
- odoo_last_sync (timestamp)
- created_at
- updated_at
```

### Odoo `sale.order` Table (Simplified):
```sql
- id (primary key)
- name (quotation/order number, e.g., 'DFAZ-SO-1479')
- partner_id (foreign key to res.partner)
- state (draft/sent/sale/done/cancel)
- date_order (order date)
- validity_date (quotation validity)
- amount_total (total amount)
- amount_untaxed (subtotal)
- amount_tax (tax amount)
- note (HTML formatted notes)
- client_order_ref (portal reference)
- origin (source reference)
- invoice_status (no/to invoice/invoiced)
- delivery_status (pending/partial/done)
- order_line (one-to-many to sale.order.line)
```

---

## 🔗 Bidirectional Sync Flow

### 1. Create Quotation (Portal → Odoo)
```
Portal                          Odoo
------                          ----
1. Customer requests quotation
2. Create quotation record
3. Call OdooService::createQuotation()
                        →       4. Create sale.order (state='draft')
                        ←       5. Return quotation ID (1515)
6. Store odoo_quotation_id
7. Set odoo_synced = true
```

### 2. Status Sync (Odoo → Portal)
```
Portal                          Odoo
------                          ----
1. Check if sync needed
   (>30 min since last sync)
2. Call OdooService::getQuotationStatus()
                        →       3. Search sale.order by ID
                        ←       4. Return quotation data
5. Map odoo_state to portal status
6. Update quotation record
7. Set odoo_last_sync = now()
```

### 3. Convert to Order (Portal → Odoo)
```
Portal                          Odoo
------                          ----
1. Customer accepts quotation
2. Call OdooService::convertQuotationToSaleOrder()
                        →       3. Update sale.order state='sale'
                        ←       4. Return success
5. Create sales_order record
6. Link to quotation
7. Update odoo_state = 'sale'
```

---

## ⚙️ OdooService Methods

### createQuotation($quotationData)
**Purpose**: Create a new quotation in Odoo

**Input**:
```php
[
    'partner_id' => int,
    'reference' => string,
    'notes' => string,
    'valid_until' => date,
    'order_lines' => array (optional)
]
```

**Output**: `int` (Odoo quotation ID)

**Odoo Call**: `sale.order::create`

---

### getQuotationStatus($quotationId)
**Purpose**: Get current quotation status from Odoo

**Input**: `int` (Odoo quotation ID)

**Output**:
```php
[
    'id' => int,
    'name' => string,
    'state' => string,
    'partner_id' => [int, string],
    'amount_total' => float,
    'validity_date' => date,
    'date_order' => datetime,
    'order_line' => array
]
```

**Odoo Call**: `sale.order::search_read`

---

### convertQuotationToSaleOrder($quotationId)
**Purpose**: Convert draft quotation to confirmed sales order

**Input**: `int` (Odoo quotation ID)

**Output**: `int` (Same ID, state changed to 'sale')

**Odoo Call**: `sale.order::write` (state='sale')

---

### updateQuotationPricing($quotationId)
**Purpose**: Recalculate quotation pricing

**Input**: `int` (Odoo quotation ID)

**Output**: Updated quotation data

**Odoo Call**: `sale.order::action_update_prices`

---

### getCustomerQuotations($partnerId, $limit)
**Purpose**: Get all quotations for a customer

**Input**: 
- `int` (Odoo partner ID)
- `int` (limit, default 10)

**Output**: Array of quotation records

**Odoo Call**: `sale.order::search_read`

---

## 🔍 Current Implementation Status

### ✅ Working:
- ✅ Create quotation in Odoo
- ✅ Store Odoo quotation ID in portal
- ✅ Retrieve quotation status from Odoo
- ✅ Map Odoo states to portal statuses
- ✅ Bidirectional reference (portal ID ↔ Odoo ID)
- ✅ Auto-sync every 30 minutes

### ⚠️ Limitations:
- ⚠️ Order lines may not sync due to Odoo permissions
- ⚠️ Amount calculations done by Odoo (may be $0 without lines)
- ⚠️ HTML formatting in notes (Odoo adds `<p>` tags)
- ⚠️ Partner ID returned as array `[id, name]` not just ID

### 🔧 Recommendations:
1. **Add order line sync verification** - Check if lines were created
2. **Handle amount mismatches** - Portal vs Odoo totals
3. **Strip HTML from notes** - When displaying in portal
4. **Add webhook support** - Real-time updates instead of polling
5. **Implement retry logic** - For failed syncs
6. **Add sync status indicator** - Show users if quotation is synced

---

## 📝 Example Workflow

### Complete Quotation Flow:
```
1. Customer completes feasibility check
   ↓
2. Portal creates quotation (status='pending')
   ↓
3. Sync to Odoo (state='draft')
   ↓
4. Odoo assigns number 'DFAZ-SO-1479'
   ↓
5. Portal stores odoo_quotation_id=1515
   ↓
6. Admin reviews in Odoo, changes state='sent'
   ↓
7. Portal polls Odoo (every 30 min)
   ↓
8. Portal updates status='sent'
   ↓
9. Customer accepts quotation
   ↓
10. Portal calls convertQuotationToSaleOrder()
    ↓
11. Odoo changes state='sale'
    ↓
12. Portal creates sales_order record
    ↓
13. Order processing begins...
```

---

## 🎯 Key Takeaways

1. **Quotations ARE stored in Odoo** as `sale.order` records with `state='draft'`
2. **Odoo is the source of truth** for quotation numbers and amounts
3. **Portal maintains local copy** for fast access and offline capability
4. **Sync is bidirectional** but Odoo-initiated changes require polling
5. **State mapping is consistent** and well-defined
6. **Order lines are optional** due to permission constraints
7. **30-minute sync interval** balances freshness vs API load

---

**Last Updated**: February 6, 2026  
**Status**: ✅ Verified and Working  
**Test Results**: See `test-quotation-odoo-workflow.php`
