Files
Odoo-Modules/fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py
2026-03-31 20:41:04 -04:00

209 lines
8.6 KiB
Python

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})