# Troubleshooting Guide for Odoo Connectors ## Common Issues and Solutions ### 1. Connection Issues #### Problem: "Connection test failed" or timeout errors **Possible Causes**: - Incorrect API URL - Invalid API credentials - Network/firewall blocking requests - API endpoint not accessible **Solutions**: ```python # 1. Verify API URL format api_url = backend.api_url print(f"Testing connection to: {api_url}") # 2. Test with curl/requests directly import requests response = requests.get(f"{api_url}/health", timeout=10) print(f"Status: {response.status_code}") # 3. Check credentials adapter = backend.get_adapter('backend.adapter') headers = adapter.get_api_headers() print(f"Headers: {headers}") # Don't log in production! # 4. Add detailed logging import logging logging.getLogger('requests').setLevel(logging.DEBUG) ``` #### Problem: SSL Certificate verification failed **Solution**: ```python # Temporary: Disable SSL verification (NOT for production!) def make_request(self, method, endpoint, **kwargs): kwargs['verify'] = False return super().make_request(method, endpoint, **kwargs) # Production: Add CA certificate import certifi kwargs['verify'] = certifi.where() ``` ### 2. Authentication Issues #### Problem: 401 Unauthorized **Diagnosis**: ```python # Check token expiry if backend.token_expires_at: from datetime import datetime is_expired = datetime.now() >= backend.token_expires_at print(f"Token expired: {is_expired}") # Check authentication header adapter = backend.get_adapter('backend.adapter') headers = adapter.get_api_headers() print(f"Auth header: {headers.get('Authorization', 'MISSING')}") ``` **Solutions**: ```python # 1. Refresh OAuth token backend.refresh_access_token() # 2. Re-authenticate backend.action_start_oauth_flow() # 3. Verify API key is correct # Go to backend form and re-enter API key ``` #### Problem: OAuth callback not working **Common Issues**: - Redirect URI mismatch - State parameter validation failed - CORS issues **Solutions**: ```python # 1. Check redirect URI matches exactly print(f"Configured: {backend.oauth_redirect_uri}") print(f"Expected: https://yourodoo.com/myconnector/oauth/callback") # 2. Disable state validation temporarily for debugging def exchange_code_for_token(self, code, state): # Skip state validation # if state != stored_state: # raise ValueError('Invalid OAuth state') ... # 3. Add CORS headers in controller @http.route('/myconnector/oauth/callback', cors='*') ``` ### 3. Import/Sync Issues #### Problem: Records not importing **Diagnosis**: ```python # 1. Check if importer is registered with backend.work_on('myconnector.product.template') as work: try: importer = work.component(usage='record.importer') print(f"Importer found: {importer._name}") except ComponentNotFound: print("ERROR: Importer component not registered!") # 2. Check adapter methods adapter = work.component(usage='backend.adapter') products = adapter.get_products() print(f"Fetched {len(products)} products from API") # 3. Test mapper mapper = work.component(usage='import.mapper') if products: mapped = mapper.map_record(products[0]) print(f"Mapped data: {mapped.values()}") ``` **Solutions**: ```python # 1. Register component properly class ProductImporter(GenericImporter): _name = 'myconnector.product.importer' _inherit = 'generic.importer' _apply_on = 'myconnector.product.template' # Must match model! _usage = 'record.importer' # Required! # 2. Check model name consistency # Backend: myconnector.backend # Binding: myconnector.product.template # Component: _apply_on = 'myconnector.product.template' # 3. Add logging def _import_record(self, external_id, force=False): _logger.info("Importing product %s", external_id) # ... import logic _logger.info("Successfully imported product %s", external_id) ``` #### Problem: Duplicate records created **Cause**: External ID not properly set or constraint not working **Solution**: ```python # 1. Verify SQL constraint class ProductBinding(models.Model): _sql_constraints = [ ('backend_external_uniq', 'unique(backend_id, external_id)', 'Product must be unique per backend') ] # 2. Check external ID is set def _import_record(self, external_id, force=False): # Always set external_id in mapped data mapped_data = mapper.map_record(external_data).values() if 'external_id' not in mapped_data: mapped_data['external_id'] = str(external_id) # 3. Search for existing binding before creating binding = self.env['myconnector.product.template'].search([ ('backend_id', '=', backend.id), ('external_id', '=', str(external_id)) ], limit=1) if binding: binding.write(mapped_data) else: binding = self.env['myconnector.product.template'].create(mapped_data) ``` ### 4. Export Issues #### Problem: Records not exporting to external system **Diagnosis**: ```python # 1. Check exporter is registered with backend.work_on('myconnector.product.template') as work: exporter = work.component(usage='record.exporter') # 2. Test export mapper mapper = work.component(usage='export.mapper') external_data = mapper.map_record(binding).values() print(f"Export data: {json.dumps(external_data, indent=2)}") # 3. Test adapter create method adapter = work.component(usage='backend.adapter') result = adapter.create_product(external_data) print(f"Created external ID: {result.get('id')}") ``` **Solutions**: ```python # 1. Check no_export flag binding.write({'no_export': False}) # 2. Ensure export mapper returns correct format class ProductExportMapper(GenericExportMapper): direct = [ ('name', 'title'), # Odoo field -> External field ('list_price', 'price'), ] # 3. Handle API response correctly def _export_record(self, binding): mapper = self.component(usage='export.mapper') data = mapper.map_record(binding).values() adapter = self.component(usage='backend.adapter') if binding.external_id: adapter.update_product(binding.external_id, data) else: result = adapter.create_product(data) # Save external ID! binding.write({'external_id': str(result['id'])}) ``` ### 5. Queue Job Issues #### Problem: Queue jobs not running **Diagnosis**: ```bash # 1. Check queue job workers are running ps aux | grep odoo # 2. Check queued jobs # Go to Queue > Jobs in Odoo UI # 3. Check job configuration # Settings > Technical > Queue Jobs > Functions ``` **Solutions**: ```python # 1. Ensure queue_job is installed and loaded # In odoo.conf: # server_wide_modules = base,web,queue_job # 2. Start job runner # odoo-bin -c odoo.conf --workers=2 # 3. Register job functions # In data/queue_job_function_data.xml myconnector.backend.sync_products # 4. Use with_delay correctly backend.with_delay().sync_products() # Correct backend.sync_products() # Wrong - runs synchronously ``` #### Problem: Jobs failing silently **Solution**: ```python # 1. Check job logs # Queue > Jobs > Failed # Click on job to see error details # 2. Add try/except with logging @job def sync_products(self): try: # Sync logic _logger.info("Product sync completed successfully") except Exception as e: _logger.exception("Product sync failed") raise # Re-raise to mark job as failed # 3. Configure retry pattern {1: 60, 5: 300, 10: 600} ``` ### 6. Webhook Issues #### Problem: Webhooks not received **Diagnosis**: ```bash # 1. Check route is registered # In Odoo shell: routes = request.env['ir.http']._get_routes() webhook_routes = [r for r in routes if 'webhook' in r] print(webhook_routes) # 2. Test webhook endpoint manually curl -X POST https://yourodoo.com/myconnector/webhook \ -H "Content-Type: application/json" \ -H "X-API-Key: your_key" \ -d '{"test": "data"}' # 3. Check webhook URL configuration print(f"Webhook URL: {backend.webhook_url}") # Ensure this matches the URL configured in external system ``` **Solutions**: ```python # 1. Ensure controller is registered class WebhookController(http.Controller): @http.route('/myconnector/webhook', type='json', auth='none', csrf=False) def webhook(self): # IMPORTANT: auth='none', csrf=False for external calls ... # 2. Update module to load controllers # In __init__.py: from . import controllers # 3. Check firewall/reverse proxy allows POST to webhook URL # 4. Add debug logging @http.route('/myconnector/webhook', type='json', auth='none', csrf=False) def webhook(self): _logger.info("Webhook received: %s", request.jsonrequest) ... ``` #### Problem: Webhook signature verification failing **Diagnosis**: ```python # In controller: payload = request.httprequest.get_data(as_text=True) signature = request.httprequest.headers.get('X-Signature') secret = backend.webhook_secret expected = hmac.new( secret.encode('utf-8'), payload.encode('utf-8'), hashlib.sha256 ).hexdigest() print(f"Received signature: {signature}") print(f"Expected signature: {expected}") print(f"Match: {signature == expected}") ``` **Solutions**: ```python # 1. Ensure secret matches between Odoo and external system # 2. Check signature algorithm matches # Some systems use base64, others hex # 3. Verify payload encoding # Use raw payload, not parsed JSON # 4. Check header name # Could be X-Signature, X-Webhook-Signature, etc. # 5. Temporarily disable verification for debugging if not self._verify_signature(...): _logger.warning("Signature verification failed, but processing anyway") # return {'error': 'Invalid signature'}, 401 ``` ### 7. Data Mapping Issues #### Problem: Fields not mapping correctly **Diagnosis**: ```python # Test mapper in isolation mapper = ProductImportMapper(work) external_data = { 'id': 123, 'title': 'Test Product', 'price': 99.99, } mapped = mapper.map_record(external_data) print(f"Mapped values: {mapped.values()}") # Check each mapping method for method_name in dir(mapper): if hasattr(getattr(mapper, method_name), '_mapping'): result = getattr(mapper, method_name)(external_data) print(f"{method_name}: {result}") ``` **Solutions**: ```python # 1. Use @mapping decorator @mapping def product_name(self, record): return {'name': record['title']} # Must return dict! # 2. Handle missing fields @mapping def category(self, record): category_name = record.get('category', {}).get('name') if not category_name: return {} # Return empty dict, not None # 3. Use only_create for default values @only_create def default_code(self, record): return {'default_code': record.get('sku', 'SKU_MISSING')} ``` ### 8. Performance Issues #### Problem: Sync takes too long **Diagnosis**: ```python import time def sync_products(self): start = time.time() # Time each step t1 = time.time() products = adapter.get_products() print(f"Fetch: {time.time() - t1:.2f}s for {len(products)} products") t2 = time.time() for product in products: self.import_product(product['id']) print(f"Import: {time.time() - t2:.2f}s") print(f"Total: {time.time() - start:.2f}s") ``` **Solutions**: ```python # 1. Use batch operations def sync_products(self): # Fetch all products at once (if API supports) products = adapter.get_all_products() # Process in batches batch_size = 100 for i in range(0, len(products), batch_size): batch = products[i:i+batch_size] self.with_delay().import_product_batch(batch) # 2. Reduce database queries # Use search_read instead of browse products = env['product.template'].search_read( [('id', 'in', product_ids)], ['name', 'list_price'] ) # 3. Use SQL for bulk operations self.env.cr.execute(""" UPDATE myconnector_product_template SET sync_status = 'success' WHERE backend_id = %s """, (backend.id,)) # 4. Disable expensive computations during import # Use context flags binding.with_context(skip_compute=True).write(values) ``` ### 9. Module Installation Issues #### Problem: Module won't install/upgrade **Common Errors**: ``` ParseError: Invalid XML SyntaxError: Invalid Python syntax ProgrammingError: relation does not exist ``` **Solutions**: ```bash # 1. Check logs tail -f /var/log/odoo/odoo.log # 2. Validate XML syntax xmllint --noout views/*.xml # 3. Check Python syntax python3 -m py_compile models/*.py # 4. Drop and recreate database (dev only!) dropdb test_db createdb test_db odoo-bin -c odoo.conf -d test_db -i myconnector # 5. Update with stop-after-init to see errors odoo-bin -c odoo.conf -d test_db -u myconnector --stop-after-init # 6. Check dependencies # In __manifest__.py, ensure all 'depends' modules are installed ``` ### 10. Debugging Tips #### Enable Debug Logging ```python # In code: import logging _logger = logging.getLogger(__name__) _logger.setLevel(logging.DEBUG) # In odoo.conf: log_level = debug log_handler = :DEBUG ``` #### Use Odoo Shell ```bash odoo-bin shell -c odoo.conf -d your_db >>> backend = env['myconnector.backend'].browse(1) >>> backend.sync_products() >>> env.cr.rollback() # Rollback changes ``` #### Use pdb Debugger ```python def sync_products(self): import pdb; pdb.set_trace() # Debugger will pause here products = adapter.get_products() ... ``` #### Monitor API Calls ```python # Add request/response logging def make_request(self, method, endpoint, **kwargs): _logger.debug("Request: %s %s", method, endpoint) _logger.debug("Params: %s", kwargs.get('params')) _logger.debug("Data: %s", kwargs.get('data')) response = super().make_request(method, endpoint, **kwargs) _logger.debug("Response status: %s", response.status_code if hasattr(response, 'status_code') else 'N/A') _logger.debug("Response data: %s", response[:500] if isinstance(response, str) else str(response)[:500]) return response ``` ## Error Reference | Error | Cause | Solution | |-------|-------|----------| | ComponentNotFound | Component not registered | Check `_name`, `_apply_on`, `_usage` | | MissingError | Record deleted | Check `exists()` before operations | | AccessError | Permission denied | Check security rules and groups | | ValidationError | Constraint violated | Check required fields and constraints | | HTTPError 401 | Invalid credentials | Refresh tokens or re-authenticate | | HTTPError 429 | Rate limited | Implement rate limiting and backoff | | HTTPError 500 | Server error | Retry with exponential backoff | | TypeError in mapper | Wrong return type | Mappers must return dict | | IntegrityError | Duplicate key | Check SQL constraints | | JSONDecodeError | Invalid JSON | Check API response format |