@kodeme-io/next-core-odoo-api
v0.8.4
Published
Odoo adapter implementing @kodeme-io/next-core-client interfaces with full JSON-RPC support
Maintainers
Readme
@kodeme-io/next-core-odoo-api
Backend-agnostic Odoo adapter implementing @kodeme-io/next-core-client interfaces with full JSON-RPC support.
Features
- ✅ Backend Agnostic - Implements standard IDataClient and IAuthClient interfaces
- ✅ Easy Backend Switching - Can migrate to FastAPI/GraphQL without code changes
- ✅ Pure JSON-RPC - Direct Odoo API calls, no custom REST endpoints
- ✅ Zero Dependencies - Lightweight, only depends on @kodeme-io/next-core-client
- ✅ Type Safety - Full TypeScript support with generic types
- ✅ Auto Filter Conversion - Generic filters → Odoo domains automatically
- ✅ CRUD Operations - Full create, read, update, delete support
- ✅ Session Management - Persistent authentication with auto-refresh
- ✅ Error Handling - Comprehensive error handling with ClientError
Installation
npm install @kodeme-io/next-core-client @kodeme-io/next-core-odoo-apiQuick Start
1. Create OdooAdapter Instance
import { OdooAdapter } from '@kodeme-io/next-core-odoo-api'
const odoo = new OdooAdapter({
url: 'https://your-odoo-server.com',
database: 'production'
})2. Authentication
// Login
const authResult = await odoo.login('[email protected]', 'password')
console.log('Logged in as:', authResult.user.name)
// Check session
const isValid = await odoo.checkSession()
// Logout
await odoo.logout()3. CRUD Operations
// Search with generic filters (no Odoo domain syntax needed!)
const partners = await odoo.searchRead({
model: 'res.partner',
filters: [
{ field: 'is_company', operator: 'eq', value: true },
{ field: 'country_id', operator: 'in', value: [1, 2, 3] }
],
fields: ['id', 'name', 'email'],
limit: 20,
orderBy: [{ field: 'name', direction: 'asc' }]
})
// Create
const partnerId = await odoo.create('res.partner', {
name: 'ABC Corporation',
email: '[email protected]'
})
// Update
await odoo.update('res.partner', partnerId, {
phone: '+1234567890'
})
// Delete
await odoo.delete('res.partner', partnerId)CRUD Operations
Search & Read
// Get all customers
const customers = await odoo.searchRead(
'res.partner',
[['is_company', '=', true]],
['name', 'email', 'phone']
)
// With pagination
const page1 = await odoo.searchRead(
'res.partner',
[],
['name'],
{ limit: 10, offset: 0, order: 'name ASC' }
)Search (IDs only)
// Get customer IDs
const customerIds = await odoo.search(
'res.partner',
[['customer_type', '=', 'canvas']],
{ limit: 100 }
)
// Returns: [1, 2, 3, ...]Read
// Read specific records
const customers = await odoo.read(
'res.partner',
[1, 2, 3],
['name', 'email', 'phone']
)Create
// Create new record
const customerId = await odoo.create('res.partner', {
name: 'New Customer',
email: '[email protected]',
phone: '+1234567890',
customer_type: 'canvas'
})
// Returns: new record ID (e.g., 123)Update (Write)
// Update existing records
const success = await odoo.write(
'res.partner',
[123],
{ phone: '+0987654321' }
)
// Returns: true if successfulDelete (Unlink)
// Delete records
const success = await odoo.unlink('res.partner', [123, 456])
// Returns: true if successfulCall Custom Methods
// Call any Odoo model method
const result = await odoo.callMethod(
'sale.order',
'action_confirm',
[orderId]
)
// With keyword arguments
const result = await odoo.callMethod(
'res.partner',
'mobile_search',
[],
{ query: 'ABC', limit: 10 }
)Odoo Domains
Domains use Odoo's Polish notation syntax:
// Simple conditions
const domain = [['name', '=', 'ABC']]
// Multiple conditions (AND)
const domain = [
['name', 'ilike', 'ABC'],
['active', '=', true]
]
// OR conditions
const domain = [
'|',
['name', 'ilike', 'ABC'],
['email', 'ilike', 'ABC']
]
// Complex nested conditions
const domain = [
'&',
['active', '=', true],
'|',
['customer_type', '=', 'canvas'],
['customer_type', '=', 'taking_order']
]Domain Operators
=,!=- Equals, not equals>,>=,<,<=- Comparisonslike,ilike- Pattern matching (case-sensitive/insensitive)in,not in- List membership=?- Unset or equals (for optional filters)
OCA Addon Support
Operating Unit (Multi-Branch)
// Filter by operating unit
const customers = await odoo.searchRead(
'res.partner',
[['operating_unit_id', '=', 5]],
['name', 'operating_unit_id']
)
// Get user's operating units
const user = await odoo.read('res.users', [userId], [
'operating_unit_ids',
'default_operating_unit_id'
])Tier Validation (Approval Workflow)
// Check validation status
const customer = await odoo.searchRead(
'res.partner',
[['id', '=', customerId]],
['name', 'validated', 'need_validation', 'validation_status']
)
// Request validation
await odoo.callMethod('res.partner', 'request_validation', [customerId])
// Approve/Reject
await odoo.callMethod('tier.validation', 'validate_tier', [reviewId])
await odoo.callMethod('tier.validation', 'reject_tier', [reviewId])SFA-Specific Methods
The client includes helper methods for common SFA operations:
// Get visits by date range
const visits = await odoo.getVisitsByDate(userId, '2024-01-01', '2024-01-31')
// Get customers by type
const canvasCustomers = await odoo.getCustomersByType('canvas')
const allCustomers = await odoo.getCustomersByType()
// Create visit
const visitId = await odoo.createVisit({
partner_id: 123,
user_id: userId,
visit_date: '2024-01-15',
visit_mode: 'canvas',
state: 'planned'
})
// Update visit
await odoo.updateVisit(visitId, {
state: 'completed',
check_out_time: '2024-01-15 16:30:00'
})
// Upload photo
await odoo.uploadPhoto({
visit_id: visitId,
photo_type: 'checkin',
file_data: base64String,
file_name: 'checkin.jpg',
gps_latitude: -6.2088,
gps_longitude: 106.8456
})
// Validate location (geofence check)
const isValid = await odoo.validateLocation(
partnerId,
-6.2088,
106.8456
)
// Mobile sync
const syncData = await odoo.mobileSync('2024-01-15T10:00:00Z')Error Handling
import { OdooJSONRPCError } from '@next-odoo/odoo-api'
try {
await odoo.create('res.partner', { name: 'Test' })
} catch (error) {
if (error instanceof OdooJSONRPCError) {
console.error('Odoo Error:', {
code: error.code,
message: error.message,
data: error.data
})
} else {
console.error('Network Error:', error)
}
}Session Management
// Use existing session
const odoo = createOdooClient({
url: 'https://odoo.yourdomain.com',
database: 'production_db',
sessionId: existingSessionId
})
// Get session ID
const sessionId = odoo.getSessionId()
// Set session ID
odoo.setSessionId(newSessionId)
// Check if authenticated
if (odoo.isAuthenticated()) {
// Make API calls
}Environment Variables
NEXT_PUBLIC_ODOO_URL=https://odoo.yourdomain.com
NEXT_PUBLIC_ODOO_DB=production_dbAPI Routes Example
// app/api/customers/route.ts
import { createOdooClient } from '@next-odoo/odoo-api'
import { NextResponse } from 'next/server'
export async function GET() {
const odoo = createOdooClient({
url: process.env.ODOO_URL!,
database: process.env.ODOO_DB!,
sessionId: 'user-session-id' // From request headers/cookies
})
const customers = await odoo.searchRead(
'res.partner',
[['is_company', '=', true]],
['name', 'email']
)
return NextResponse.json(customers)
}Best Practices
- Use environment variables for Odoo credentials
- Reuse client instances - Create once, use throughout app
- Handle errors - Always wrap calls in try/catch
- Specify fields - Only request fields you need
- Use pagination - For large result sets, use limit/offset
- Session persistence - Store and reuse sessionId
- Type safety - Leverage TypeScript types
License
MIT
