735 lines
13 KiB
Markdown
735 lines
13 KiB
Markdown
Complete API reference for the Odoo client and cache stores in generated PWAs.
|
|
|
|
## What this command does:
|
|
- Provides comprehensive API documentation
|
|
- Lists all available methods and functions
|
|
- Shows parameter types and return values
|
|
- Includes code examples for each method
|
|
- Helps developers use the generated code effectively
|
|
|
|
---
|
|
|
|
## Odoo API Client Reference 🔌
|
|
|
|
The Odoo API client (`src/lib/odoo.js` or equivalent) provides methods for interacting with Odoo Studio models.
|
|
|
|
### Configuration
|
|
|
|
#### Environment Variables
|
|
```bash
|
|
VITE_ODOO_URL=https://yourcompany.odoo.com
|
|
VITE_ODOO_DB=yourcompany-main
|
|
ODOO_API_KEY=your_api_key
|
|
ODOO_USERNAME=your.email@company.com
|
|
VITE_MODEL_NAME=x_expense
|
|
```
|
|
|
|
---
|
|
|
|
## CRUD Operations
|
|
|
|
### createRecord()
|
|
Create a new record in Odoo.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function createRecord(model, fields)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `model` (string) - Odoo model name (e.g., 'x_expense')
|
|
- `fields` (object) - Field values to set
|
|
|
|
**Returns:** Promise<number> - ID of created record
|
|
|
|
**Example:**
|
|
```javascript
|
|
const newId = await odoo.createRecord('x_expense', {
|
|
x_studio_description: 'Team lunch',
|
|
x_studio_amount: 45.50,
|
|
x_studio_date: '2025-01-15',
|
|
x_studio_category: 'meal',
|
|
x_studio_employee: odoo.formatMany2one(12)
|
|
});
|
|
|
|
console.log(`Created expense with ID: ${newId}`);
|
|
```
|
|
|
|
**Error Handling:**
|
|
```javascript
|
|
try {
|
|
const id = await odoo.createRecord('x_expense', fields);
|
|
} catch (error) {
|
|
console.error('Failed to create:', error.message);
|
|
// Handle error (show message to user, retry, etc.)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### searchRecords()
|
|
Search and read records from Odoo.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function searchRecords(model, domain, fields, limit = null, offset = null)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `model` (string) - Odoo model name
|
|
- `domain` (array) - Odoo domain filter (e.g., [['id', '>', 100]])
|
|
- `fields` (array) - List of field names to fetch
|
|
- `limit` (number, optional) - Maximum number of records
|
|
- `offset` (number, optional) - Number of records to skip
|
|
|
|
**Returns:** Promise<Array<Object>> - Array of record objects
|
|
|
|
**Example:**
|
|
```javascript
|
|
// Get all expenses
|
|
const allExpenses = await odoo.searchRecords(
|
|
'x_expense',
|
|
[],
|
|
['x_studio_description', 'x_studio_amount', 'x_studio_date']
|
|
);
|
|
|
|
// Get expenses > $100
|
|
const largeExpenses = await odoo.searchRecords(
|
|
'x_expense',
|
|
[['x_studio_amount', '>', 100]],
|
|
['x_studio_description', 'x_studio_amount']
|
|
);
|
|
|
|
// Get recent 10 expenses
|
|
const recentExpenses = await odoo.searchRecords(
|
|
'x_expense',
|
|
[],
|
|
['x_studio_description', 'x_studio_date'],
|
|
10 // limit
|
|
);
|
|
|
|
// Get expenses with pagination
|
|
const page2 = await odoo.searchRecords(
|
|
'x_expense',
|
|
[],
|
|
fields,
|
|
20, // limit: 20 per page
|
|
20 // offset: skip first 20 (page 2)
|
|
);
|
|
```
|
|
|
|
**Domain Syntax:**
|
|
```javascript
|
|
// Equals
|
|
[['x_studio_status', '=', 'draft']]
|
|
|
|
// Greater than
|
|
[['x_studio_amount', '>', 100]]
|
|
|
|
// In list
|
|
[['x_studio_category', 'in', ['meal', 'travel']]]
|
|
|
|
// Multiple conditions (AND)
|
|
[
|
|
['x_studio_amount', '>', 50],
|
|
['x_studio_status', '=', 'draft']
|
|
]
|
|
|
|
// OR conditions
|
|
['|',
|
|
['x_studio_amount', '>', 100],
|
|
['x_studio_category', '=', 'travel']
|
|
]
|
|
|
|
// Complex: (amount > 100 OR category = travel) AND status = draft
|
|
['&',
|
|
'|',
|
|
['x_studio_amount', '>', 100],
|
|
['x_studio_category', '=', 'travel'],
|
|
['x_studio_status', '=', 'draft']
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
### updateRecord()
|
|
Update an existing record.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function updateRecord(model, id, values)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `model` (string) - Odoo model name
|
|
- `id` (number) - Record ID to update
|
|
- `values` (object) - Fields to update
|
|
|
|
**Returns:** Promise<boolean> - true if successful
|
|
|
|
**Example:**
|
|
```javascript
|
|
await odoo.updateRecord('x_expense', 123, {
|
|
x_studio_status: 'approved',
|
|
x_studio_amount: 55.00
|
|
});
|
|
|
|
// Update multiple fields
|
|
await odoo.updateRecord('x_expense', 123, {
|
|
x_studio_description: 'Updated description',
|
|
x_studio_date: '2025-01-20',
|
|
x_studio_notes: 'Added receipt'
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### deleteRecord()
|
|
Delete a record from Odoo.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function deleteRecord(model, id)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `model` (string) - Odoo model name
|
|
- `id` (number) - Record ID to delete
|
|
|
|
**Returns:** Promise<boolean> - true if successful
|
|
|
|
**Example:**
|
|
```javascript
|
|
await odoo.deleteRecord('x_expense', 123);
|
|
|
|
// With confirmation
|
|
if (confirm('Are you sure you want to delete this expense?')) {
|
|
await odoo.deleteRecord('x_expense', expenseId);
|
|
}
|
|
```
|
|
|
|
**Error Handling:**
|
|
```javascript
|
|
try {
|
|
await odoo.deleteRecord('x_expense', id);
|
|
console.log('Deleted successfully');
|
|
} catch (error) {
|
|
console.error('Failed to delete:', error.message);
|
|
alert('Could not delete: ' + error.message);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Helper Methods
|
|
|
|
### fetchPartners()
|
|
Fetch partner (res.partner) records.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function fetchPartners(ids = null)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `ids` (array, optional) - Specific partner IDs to fetch. If null, fetches all.
|
|
|
|
**Returns:** Promise<Array<Object>> - Array of partner objects
|
|
|
|
**Example:**
|
|
```javascript
|
|
// Fetch all partners
|
|
const allPartners = await odoo.fetchPartners();
|
|
|
|
// Fetch specific partners
|
|
const somePartners = await odoo.fetchPartners([1, 2, 3]);
|
|
|
|
// Use in dropdown
|
|
const partners = await odoo.fetchPartners();
|
|
// Display: partners.map(p => ({ value: p.id, label: p.name }))
|
|
```
|
|
|
|
---
|
|
|
|
### formatMany2one()
|
|
Format a Many2one field value for Odoo.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
function formatMany2one(id)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `id` (number | null) - Partner/record ID
|
|
|
|
**Returns:** Array<number, boolean> | false - Odoo-formatted value
|
|
|
|
**Example:**
|
|
```javascript
|
|
// Set employee field
|
|
const fields = {
|
|
x_studio_employee: odoo.formatMany2one(12)
|
|
// Result: [12, false]
|
|
};
|
|
|
|
// Clear employee field
|
|
const fields = {
|
|
x_studio_employee: odoo.formatMany2one(null)
|
|
// Result: false
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
### formatMany2many()
|
|
Format a Many2many field value for Odoo.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
function formatMany2many(ids)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `ids` (array) - Array of record IDs
|
|
|
|
**Returns:** Array - Odoo command format
|
|
|
|
**Example:**
|
|
```javascript
|
|
// Set tags (replace all)
|
|
const fields = {
|
|
x_studio_tags: odoo.formatMany2many([1, 2, 3])
|
|
// Result: [[6, 0, [1, 2, 3]]]
|
|
};
|
|
|
|
// Clear tags
|
|
const fields = {
|
|
x_studio_tags: odoo.formatMany2many([])
|
|
// Result: [[6, 0, []]]
|
|
};
|
|
```
|
|
|
|
**Odoo Many2many Commands:**
|
|
```javascript
|
|
// (6, 0, [ids]) - Replace all (what formatMany2many uses)
|
|
// (4, id) - Add link to id
|
|
// (3, id) - Remove link to id
|
|
// (5, 0) - Remove all links
|
|
```
|
|
|
|
---
|
|
|
|
## Cache Store API Reference 💾
|
|
|
|
The cache store provides reactive state management with offline-first capabilities.
|
|
|
|
### Properties
|
|
|
|
#### records
|
|
**Type:** Reactive Array<Object>
|
|
|
|
Current cached records.
|
|
|
|
**Example:**
|
|
```javascript
|
|
// SvelteKit
|
|
$: totalAmount = $expenseCache.reduce((sum, e) => sum + e.x_studio_amount, 0);
|
|
|
|
// React
|
|
const totalAmount = useMemo(() =>
|
|
records.reduce((sum, e) => sum + e.x_studio_amount, 0),
|
|
[records]
|
|
);
|
|
|
|
// Vue
|
|
const totalAmount = computed(() =>
|
|
expenseStore.records.reduce((sum, e) => sum + e.x_studio_amount, 0)
|
|
);
|
|
```
|
|
|
|
#### isLoading
|
|
**Type:** Reactive Boolean
|
|
|
|
Loading state indicator.
|
|
|
|
**Example:**
|
|
```javascript
|
|
{#if $expenseCache.isLoading}
|
|
<LoadingSpinner />
|
|
{:else}
|
|
<ExpenseList />
|
|
{/if}
|
|
```
|
|
|
|
#### error
|
|
**Type:** Reactive String | null
|
|
|
|
Current error message, if any.
|
|
|
|
**Example:**
|
|
```javascript
|
|
{#if $expenseCache.error}
|
|
<ErrorAlert message={$expenseCache.error} />
|
|
{/if}
|
|
```
|
|
|
|
#### lastSync
|
|
**Type:** Reactive Number (timestamp)
|
|
|
|
Timestamp of last successful sync.
|
|
|
|
**Example:**
|
|
```javascript
|
|
const timeSinceSync = Date.now() - $expenseCache.lastSync;
|
|
const minutes = Math.floor(timeSinceSync / 60000);
|
|
// Display: "Last synced ${minutes} minutes ago"
|
|
```
|
|
|
|
---
|
|
|
|
### Methods
|
|
|
|
### load()
|
|
Load records from cache and trigger background sync.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function load()
|
|
```
|
|
|
|
**Returns:** Promise<void>
|
|
|
|
**Behavior:**
|
|
1. Loads from cache immediately (instant UI update)
|
|
2. Checks if cache is stale (> 5 minutes)
|
|
3. If stale, syncs in background
|
|
4. Updates UI when new data arrives
|
|
|
|
**Example:**
|
|
```javascript
|
|
// SvelteKit
|
|
$effect(() => {
|
|
expenseCache.load();
|
|
});
|
|
|
|
// React
|
|
useEffect(() => {
|
|
expenseCache.load();
|
|
}, []);
|
|
|
|
// Vue
|
|
onMounted(() => {
|
|
expenseStore.load();
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### create()
|
|
Create a new record with optimistic update.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function create(data)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `data` (object) - Field values for new record
|
|
|
|
**Returns:** Promise<number> - ID of created record
|
|
|
|
**Behavior:**
|
|
1. Generates temporary ID
|
|
2. Adds to cache immediately (optimistic)
|
|
3. Creates in Odoo (background)
|
|
4. Replaces temp ID with real ID
|
|
5. Rolls back on error
|
|
|
|
**Example:**
|
|
```javascript
|
|
try {
|
|
const newId = await expenseCache.create({
|
|
x_studio_description: 'Lunch meeting',
|
|
x_studio_amount: 45.50,
|
|
x_studio_date: '2025-01-15',
|
|
x_studio_category: 'meal'
|
|
});
|
|
|
|
console.log('Created:', newId);
|
|
navigate(`/expenses/${newId}`);
|
|
} catch (error) {
|
|
alert('Failed to create: ' + error.message);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### update()
|
|
Update an existing record.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function update(id, data)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `id` (number) - Record ID
|
|
- `data` (object) - Fields to update
|
|
|
|
**Returns:** Promise<boolean>
|
|
|
|
**Behavior:**
|
|
1. Updates cache immediately (optimistic)
|
|
2. Updates in Odoo (background)
|
|
3. Rolls back on error
|
|
|
|
**Example:**
|
|
```javascript
|
|
await expenseCache.update(123, {
|
|
x_studio_amount: 50.00,
|
|
x_studio_status: 'submitted'
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### delete()
|
|
Delete a record.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function remove(id)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `id` (number) - Record ID to delete
|
|
|
|
**Returns:** Promise<boolean>
|
|
|
|
**Behavior:**
|
|
1. Removes from cache immediately
|
|
2. Deletes from Odoo (background)
|
|
3. Restores on error
|
|
|
|
**Example:**
|
|
```javascript
|
|
if (confirm('Delete this expense?')) {
|
|
try {
|
|
await expenseCache.remove(123);
|
|
navigate('/expenses');
|
|
} catch (error) {
|
|
alert('Failed to delete: ' + error.message);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### refresh()
|
|
Force refresh from Odoo.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
async function refresh()
|
|
```
|
|
|
|
**Returns:** Promise<void>
|
|
|
|
**Behavior:**
|
|
1. Fetches all records from Odoo
|
|
2. Replaces cache
|
|
3. Updates UI
|
|
|
|
**Example:**
|
|
```javascript
|
|
// Manual refresh button
|
|
<button onclick={() => expenseCache.refresh()}>
|
|
Refresh
|
|
</button>
|
|
```
|
|
|
|
---
|
|
|
|
### clearCache()
|
|
Clear all cached data.
|
|
|
|
**Signature:**
|
|
```javascript
|
|
function clearCache()
|
|
```
|
|
|
|
**Returns:** void
|
|
|
|
**Behavior:**
|
|
1. Clears localStorage
|
|
2. Clears IndexedDB
|
|
3. Resets records to empty array
|
|
|
|
**Example:**
|
|
```javascript
|
|
// Logout function
|
|
async function logout() {
|
|
expenseCache.clearCache();
|
|
// Clear other caches
|
|
navigate('/login');
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Advanced Patterns
|
|
|
|
### Custom Filters
|
|
```javascript
|
|
// Derived store (SvelteKit)
|
|
import { derived } from 'svelte/store';
|
|
|
|
export const draftExpenses = derived(
|
|
expenseCache,
|
|
$cache => $cache.filter(e => e.x_studio_status === 'draft')
|
|
);
|
|
|
|
// Hook (React)
|
|
function useDraftExpenses() {
|
|
const { records } = useExpense();
|
|
return useMemo(
|
|
() => records.filter(e => e.x_studio_status === 'draft'),
|
|
[records]
|
|
);
|
|
}
|
|
|
|
// Computed (Vue)
|
|
const draftExpenses = computed(() =>
|
|
expenseStore.records.filter(e => e.x_studio_status === 'draft')
|
|
);
|
|
```
|
|
|
|
### Sorting
|
|
```javascript
|
|
export const sortedExpenses = derived(
|
|
expenseCache,
|
|
$cache => [...$cache].sort((a, b) =>
|
|
b.x_studio_date.localeCompare(a.x_studio_date)
|
|
)
|
|
);
|
|
```
|
|
|
|
### Search
|
|
```javascript
|
|
function searchExpenses(query) {
|
|
return records.filter(e =>
|
|
e.x_studio_description.toLowerCase().includes(query.toLowerCase())
|
|
);
|
|
}
|
|
```
|
|
|
|
### Grouping
|
|
```javascript
|
|
function groupByCategory(records) {
|
|
return records.reduce((groups, record) => {
|
|
const category = record.x_studio_category;
|
|
if (!groups[category]) groups[category] = [];
|
|
groups[category].push(record);
|
|
return groups;
|
|
}, {});
|
|
}
|
|
```
|
|
|
|
### Aggregation
|
|
```javascript
|
|
function getTotalByCategory(records) {
|
|
return records.reduce((totals, record) => {
|
|
const cat = record.x_studio_category;
|
|
totals[cat] = (totals[cat] || 0) + record.x_studio_amount;
|
|
return totals;
|
|
}, {});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Server Route API Reference 🔐
|
|
|
|
The server route (`src/routes/api/odoo/+server.js`) handles Odoo communication.
|
|
|
|
### Endpoint
|
|
**URL:** `/api/odoo`
|
|
**Method:** POST
|
|
**Content-Type:** application/json
|
|
|
|
### Request Format
|
|
```json
|
|
{
|
|
"action": "create|search|update|delete",
|
|
"model": "x_expense",
|
|
...parameters
|
|
}
|
|
```
|
|
|
|
### Actions
|
|
|
|
#### create
|
|
```json
|
|
{
|
|
"action": "create",
|
|
"model": "x_expense",
|
|
"fields": {
|
|
"x_studio_description": "Lunch",
|
|
"x_studio_amount": 45.50
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:** `{ "id": 123 }`
|
|
|
|
#### search
|
|
```json
|
|
{
|
|
"action": "search",
|
|
"model": "x_expense",
|
|
"domain": [["id", ">", 100]],
|
|
"fields": ["x_studio_description", "x_studio_amount"]
|
|
}
|
|
```
|
|
|
|
**Response:** `{ "records": [...] }`
|
|
|
|
#### update
|
|
```json
|
|
{
|
|
"action": "update",
|
|
"model": "x_expense",
|
|
"id": 123,
|
|
"values": {
|
|
"x_studio_amount": 50.00
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:** `{ "success": true }`
|
|
|
|
#### delete
|
|
```json
|
|
{
|
|
"action": "delete",
|
|
"model": "x_expense",
|
|
"id": 123
|
|
}
|
|
```
|
|
|
|
**Response:** `{ "success": true }`
|
|
|
|
---
|
|
|
|
## Example prompts to use this command:
|
|
- `/api-reference` - Show complete API documentation
|
|
- User: "What methods are available in the Odoo client?"
|
|
- User: "How do I use the cache store?"
|
|
- User: "Show me API examples"
|
|
|
|
## Next Steps:
|
|
- Try the methods in your project
|
|
- Review `/examples` for practical use cases
|
|
- See `/architecture` for design patterns
|
|
- Check `/help` for more information
|