- Added comprehensive README files for all three sync modules - Implemented advanced logging system with multiple log levels (DEBUG, INFO, WARNING, ERROR) - Added automatic sensitive data masking for security in log payloads - Implemented log retention policy (90 days local, 7 years in AWS S3 Glacier) - Added AWS S3 integration for long-term log archiving - Fixed validation error with bemade_instance_id in sync model creation - Refactored model inheritance structure from _inherit to _inherits for proper delegation - Fixed duplicate sync model prevention logic - Restored Test Connection button in instance view - Fixed connection test logging to properly record success/failure - Fixed queue creation for connection test logs - Removed unnecessary Synchronized Module tab from odoo_to_odoo_bemade - Added database indexing on create_date for performance optimization - Fixed foreign key constraint violations in field mappings creation - Enhanced error handling throughout the synchronization process
236 lines
6.8 KiB
Python
236 lines
6.8 KiB
Python
# Copyright 2025 Codeium
|
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.html)
|
|
|
|
"""Synchronization Queue Management.
|
|
|
|
This module implements the queue system for managing synchronization operations
|
|
between Odoo instances. It handles the scheduling, retry logic, and status
|
|
tracking of synchronization tasks.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from datetime import timedelta
|
|
|
|
from odoo import api, fields, models
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class OdooSyncQueue(models.Model):
|
|
_name = 'odoo.sync.queue'
|
|
_description = 'Synchronization Queue'
|
|
_order = 'priority desc, retry_count, create_date'
|
|
|
|
name = fields.Char(
|
|
string='Name',
|
|
compute='_compute_name',
|
|
store=True
|
|
)
|
|
|
|
model_id = fields.Many2one(
|
|
comodel_name='odoo.sync.model',
|
|
string='Model',
|
|
required=True
|
|
)
|
|
|
|
record_id = fields.Integer(
|
|
string='Record ID',
|
|
required=True
|
|
)
|
|
|
|
operation = fields.Selection(
|
|
selection=[
|
|
('create', 'Create'),
|
|
('write', 'Update'),
|
|
('unlink', 'Delete')
|
|
],
|
|
required=True
|
|
)
|
|
|
|
state = fields.Selection(
|
|
selection=[
|
|
('draft', 'Draft'),
|
|
('pending', 'Pending'),
|
|
('processing', 'Processing'),
|
|
('done', 'Done'),
|
|
('error', 'Error'),
|
|
('cancelled', 'Cancelled')
|
|
],
|
|
default='draft',
|
|
required=True
|
|
)
|
|
|
|
priority = fields.Integer(
|
|
string='Priority',
|
|
default=1
|
|
)
|
|
|
|
retry_count = fields.Integer(
|
|
string='Retry Count',
|
|
default=0
|
|
)
|
|
|
|
max_retries = fields.Integer(
|
|
string='Max Retries',
|
|
default=3
|
|
)
|
|
|
|
next_retry = fields.Datetime(
|
|
string='Next Retry'
|
|
)
|
|
|
|
data = fields.Text(
|
|
string='Data',
|
|
help='JSON data to synchronize'
|
|
)
|
|
|
|
error_message = fields.Text(
|
|
string='Error Message'
|
|
)
|
|
|
|
log_ids = fields.One2many(
|
|
comodel_name='odoo.sync.log',
|
|
inverse_name='queue_id',
|
|
string='Logs'
|
|
)
|
|
|
|
@api.depends('model_id', 'record_id', 'operation')
|
|
def _compute_name(self):
|
|
"""Compute the display name of the queue entry.
|
|
|
|
The name is generated using the format: 'operation - model_name#record_id'
|
|
e.g., 'create - res.partner#42'
|
|
|
|
Triggered by changes to: model_id, record_id, or operation fields.
|
|
"""
|
|
for record in self:
|
|
record.name = f'{record.operation} - {record.model_id.name}#{record.record_id}'
|
|
|
|
def action_reset(self):
|
|
"""Reset a failed queue entry to pending state.
|
|
|
|
This method:
|
|
- Changes state back to 'pending'
|
|
- Resets retry count to 0
|
|
- Clears error message and next retry time
|
|
|
|
Returns:
|
|
dict: Result of the write operation
|
|
"""
|
|
return self.write({
|
|
'state': 'pending',
|
|
'retry_count': 0,
|
|
'error_message': False,
|
|
'next_retry': False
|
|
})
|
|
|
|
def action_cancel(self):
|
|
"""Cancel the queue entry.
|
|
|
|
Changes the state of the queue entry to 'cancelled',
|
|
preventing further processing attempts.
|
|
|
|
Returns:
|
|
dict: Result of the write operation
|
|
"""
|
|
return self.write({'state': 'cancelled'})
|
|
|
|
@api.model
|
|
def _process_sync_queue(self):
|
|
"""Process pending synchronization queue entries.
|
|
|
|
This method is called by the cron job to process pending queue entries.
|
|
It will:
|
|
1. Find pending entries that are ready for processing
|
|
2. Dequeue each entry and pass it to the sync manager for processing
|
|
3. Update the entry status based on the processing result
|
|
|
|
Returns:
|
|
bool: True if processing completed successfully
|
|
"""
|
|
# Find pending entries ready for processing
|
|
domain = [
|
|
('state', '=', 'pending'),
|
|
'|',
|
|
('next_retry', '=', False),
|
|
('next_retry', '<=', fields.Datetime.now())
|
|
]
|
|
entries = self.search(domain, order='priority desc, retry_count, create_date')
|
|
|
|
for entry in entries:
|
|
# Dequeue the item and process it
|
|
sync_manager = self.env['odoo.sync.manager']
|
|
sync_manager._process_dequeued_item(self.dequeue(entry.id))
|
|
|
|
return True
|
|
|
|
@api.model
|
|
def dequeue(self, queue_id):
|
|
"""Dequeue a specific queue item for processing.
|
|
|
|
This method explicitly implements the "Dequeue" step in the synchronization
|
|
sequence diagram. It marks the queue item as processing and returns it
|
|
for further processing by the sync manager.
|
|
|
|
Args:
|
|
queue_id: ID of the queue item to dequeue
|
|
|
|
Returns:
|
|
odoo.sync.queue: The dequeued queue item record or False if not found
|
|
"""
|
|
queue_item = self.browse(queue_id)
|
|
if not queue_item.exists():
|
|
_logger.error(f"[SYNC QUEUE] Cannot dequeue item {queue_id}: record not found")
|
|
return False
|
|
|
|
_logger.debug(f"[SYNC QUEUE] Dequeuing item {queue_id}")
|
|
queue_item.write({'state': 'processing'})
|
|
return queue_item
|
|
|
|
def mark_success(self):
|
|
"""Mark this queue item as successfully processed.
|
|
|
|
This method implements the 'Mark Success' step in the synchronization sequence diagram.
|
|
"""
|
|
_logger.debug(f"[SYNC QUEUE] Marking item {self.id} as done")
|
|
self.write({'state': 'done'})
|
|
|
|
# Create success log
|
|
self.env['odoo.sync.log'].create({
|
|
'queue_id': self.id,
|
|
'state': 'success',
|
|
'message': f'Synchronisation réussie'
|
|
})
|
|
|
|
def increment_retry(self, error_message):
|
|
"""Increment the retry count for this queue item.
|
|
|
|
This method implements the 'Increment Retry' step in the synchronization sequence diagram.
|
|
|
|
Args:
|
|
error_message: The error message to log
|
|
"""
|
|
_logger.debug(f"[SYNC QUEUE] Incrementing retry count for item {self.id}")
|
|
|
|
vals = {
|
|
'state': 'error',
|
|
'error_message': error_message,
|
|
'retry_count': self.retry_count + 1
|
|
}
|
|
|
|
if self.retry_count + 1 < self.max_retries:
|
|
# Exponential backoff
|
|
delay = 2 ** (self.retry_count + 1)
|
|
vals['next_retry'] = fields.Datetime.now() + timedelta(minutes=delay)
|
|
|
|
self.write(vals)
|
|
|
|
# Create error log
|
|
self.env['odoo.sync.log'].create({
|
|
'queue_id': self.id,
|
|
'state': 'error',
|
|
'message': error_message,
|
|
'details': error_message
|
|
})
|
|
|
|
return True
|