import logging import secrets from odoo import api, fields, models from odoo.exceptions import UserError from ..lib.woo_api_client import WooApiClient _logger = logging.getLogger(__name__) class WooInstance(models.Model): _name = 'woo.instance' _description = 'WooCommerce Instance' _inherit = ['mail.thread'] name = fields.Char(required=True, tracking=True) url = fields.Char(required=True, tracking=True) consumer_key = fields.Char(groups='base.group_system') consumer_secret = fields.Char(groups='base.group_system') webhook_secret = fields.Char(groups='base.group_system') wc_api_version = fields.Char(default='wc/v3') odoo_api_key = fields.Char(groups='base.group_system') company_id = fields.Many2one( 'res.company', required=True, default=lambda self: self.env.company, ) sync_interval = fields.Selection( [('5', '5 Min'), ('15', '15 Min'), ('30', '30 Min'), ('60', '1 Hour')], default='15', ) sync_products = fields.Boolean(default=True) sync_orders = fields.Boolean(default=True) sync_invoices = fields.Boolean(default=True) sync_inventory = fields.Boolean(default=True) sync_customers = fields.Boolean(default=True) default_warehouse_id = fields.Many2one('stock.warehouse') notify_on_failure = fields.Boolean() notify_user_ids = fields.Many2many('res.users') state = fields.Selection( [('draft', 'Draft'), ('connected', 'Connected'), ('error', 'Error')], default='draft', tracking=True, ) last_sync = fields.Datetime(readonly=True) # Relational product_map_ids = fields.One2many('woo.product.map', 'instance_id') order_ids = fields.One2many('woo.order', 'instance_id') customer_ids = fields.One2many('woo.customer', 'instance_id') sync_log_ids = fields.One2many('woo.sync.log', 'instance_id') # Computed mapped_count = fields.Integer(compute='_compute_counts') unmapped_count = fields.Integer(compute='_compute_counts') error_count = fields.Integer(compute='_compute_counts') @api.depends('product_map_ids.state', 'sync_log_ids') def _compute_counts(self): for rec in self: rec.mapped_count = self.env['woo.product.map'].search_count([ ('instance_id', '=', rec.id), ('state', '=', 'mapped'), ]) rec.unmapped_count = self.env['woo.product.map'].search_count([ ('instance_id', '=', rec.id), ('state', '=', 'unmapped'), ]) yesterday = fields.Datetime.subtract(fields.Datetime.now(), hours=24) rec.error_count = self.env['woo.sync.log'].search_count([ ('instance_id', '=', rec.id), ('state', '=', 'failed'), ('create_date', '>=', yesterday), ]) def _get_client(self): """Return a WooApiClient instance for this WooCommerce connection.""" self.ensure_one() if not self.consumer_key or not self.consumer_secret: raise UserError("Consumer key and secret are required.") return WooApiClient( url=self.url, consumer_key=self.consumer_key, consumer_secret=self.consumer_secret, api_version=self.wc_api_version or 'wc/v3', ) def action_test_connection(self): """Test the WooCommerce connection and update state.""" self.ensure_one() try: client = self._get_client() success, info = client.test_connection() if success: self.state = 'connected' self.message_post(body=f"Connection successful. WooCommerce version: {info}") else: self.state = 'error' self.message_post(body=f"Connection failed: {info}") except Exception as exc: self.state = 'error' self.message_post(body=f"Connection error: {exc}") _logger.exception("WooCommerce connection test failed for %s", self.name) def action_generate_api_key(self): """Generate a random API key for the Odoo webhook endpoint.""" self.ensure_one() self.odoo_api_key = secrets.token_urlsafe(32) def _log_sync(self, sync_type, direction, record_ref, state, message=''): """Create a woo.sync.log record for this instance.""" self.ensure_one() self.env['woo.sync.log'].sudo().create({ 'instance_id': self.id, 'sync_type': sync_type, 'direction': direction, 'record_ref': record_ref, 'state': state, 'message': message, 'company_id': self.company_id.id, }) # ------------------------------------------------------------------ # Cron entry points # ------------------------------------------------------------------ @api.model def _cron_sync_products(self): """Sync product prices and inventory for all connected instances.""" instances = self.search([('state', '=', 'connected'), ('sync_products', '=', True)]) for instance in instances: try: instance._sync_products() except Exception as e: _logger.error("Product sync failed for %s: %s", instance.name, str(e)) instance._log_sync('product', 'woo_to_odoo', instance.name, 'failed', str(e)) instance._notify_failure('product', str(e)) @api.model def _cron_sync_orders(self): """Fetch new orders from all connected WooCommerce instances.""" instances = self.search([('state', '=', 'connected'), ('sync_orders', '=', True)]) for instance in instances: try: instance._sync_orders() except Exception as e: _logger.error("Order sync failed for %s: %s", instance.name, str(e)) instance._log_sync('order', 'woo_to_odoo', instance.name, 'failed', str(e)) instance._notify_failure('order', str(e)) @api.model def _cron_sync_inventory(self): """Push inventory levels to WooCommerce for all connected instances.""" instances = self.search([('state', '=', 'connected'), ('sync_inventory', '=', True)]) for instance in instances: try: instance._sync_inventory() except Exception as e: _logger.error("Inventory sync failed for %s: %s", instance.name, str(e)) instance._log_sync('inventory', 'odoo_to_woo', instance.name, 'failed', str(e)) instance._notify_failure('inventory', str(e)) @api.model def _cron_sync_customers(self): """Sync customer address updates to WooCommerce.""" instances = self.search([('state', '=', 'connected'), ('sync_customers', '=', True)]) for instance in instances: try: instance._sync_customers() except Exception as e: _logger.error("Customer sync failed for %s: %s", instance.name, str(e)) instance._log_sync('customer', 'odoo_to_woo', instance.name, 'failed', str(e)) instance._notify_failure('customer', str(e)) # ------------------------------------------------------------------ # Sync method placeholders (filled in during later tasks) # ------------------------------------------------------------------ def _sync_products(self): """Sync products — implemented in Task 22.""" self.ensure_one() _logger.info("Product sync for %s — not yet implemented", self.name) def _sync_orders(self): """Sync orders — implemented in Task 20.""" self.ensure_one() _logger.info("Order sync for %s — not yet implemented", self.name) def _sync_inventory(self): """Sync inventory — implemented in Task 22.""" self.ensure_one() _logger.info("Inventory sync for %s — not yet implemented", self.name) def _sync_customers(self): """Sync customers — implemented in Task 25.""" self.ensure_one() _logger.info("Customer sync for %s — not yet implemented", self.name) def _notify_failure(self, sync_type, error_message): """Send email notification on sync failure.""" self.ensure_one() if not self.notify_on_failure or not self.notify_user_ids: return template = self.env.ref( 'fusion_woocommerce.woo_sync_failure_notification', raise_if_not_found=False, ) if template: for user in self.notify_user_ids: template.with_context( sync_type=sync_type, error_message=error_message, ).send_mail(self.id, force_send=True, email_values={'email_to': user.email})