Files
gh-jamshu-jamshi-marketplac…/skills/odoo-connector-module-creator/references/troubleshooting.md
2025-11-29 18:50:04 +08:00

15 KiB

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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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
<record id="queue_job_function_sync_products" model="queue.job.function">
    <field name="name">myconnector.backend.sync_products</field>
    <field name="channel_id" ref="queue_job.channel_root"/>
</record>

# 4. Use with_delay correctly
backend.with_delay().sync_products()  # Correct
backend.sync_products()  # Wrong - runs synchronously

Problem: Jobs failing silently

Solution:

# 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
<record id="queue_job_function_sync_products" model="queue.job.function">
    <field name="retry_pattern">{1: 60, 5: 300, 10: 600}</field>
    <!-- Retry after 60s, 300s, 600s -->
</record>

6. Webhook Issues

Problem: Webhooks not received

Diagnosis:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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:

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:

# 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:

# 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

# In code:
import logging
_logger = logging.getLogger(__name__)
_logger.setLevel(logging.DEBUG)

# In odoo.conf:
log_level = debug
log_handler = :DEBUG

Use Odoo Shell

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

def sync_products(self):
    import pdb; pdb.set_trace()  # Debugger will pause here
    products = adapter.get_products()
    ...

Monitor API Calls

# 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