From cd710065c6839027f20b7d77184aff84b93d5e90 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Tue, 31 Mar 2026 20:08:41 -0400 Subject: [PATCH] feat: add woo.instance and woo.shipping.carrier models with default carriers Co-Authored-By: Claude Opus 4.6 (1M context) --- .../fusion_woocommerce/__manifest__.py | 1 + .../data/shipping_carriers.xml | 33 +++++ .../fusion_woocommerce/models/woo_instance.py | 117 ++++++++++++++++++ .../models/woo_shipping_carrier.py | 11 ++ 4 files changed, 162 insertions(+) create mode 100644 fusion-woo-odoo/fusion_woocommerce/data/shipping_carriers.xml create mode 100644 fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py create mode 100644 fusion-woo-odoo/fusion_woocommerce/models/woo_shipping_carrier.py diff --git a/fusion-woo-odoo/fusion_woocommerce/__manifest__.py b/fusion-woo-odoo/fusion_woocommerce/__manifest__.py index f45d0e7b..ef9ecb0e 100644 --- a/fusion-woo-odoo/fusion_woocommerce/__manifest__.py +++ b/fusion-woo-odoo/fusion_woocommerce/__manifest__.py @@ -11,6 +11,7 @@ 'data': [ 'security/woo_security.xml', 'security/ir.model.access.csv', + 'data/shipping_carriers.xml', ], 'assets': { 'web.assets_backend': [ diff --git a/fusion-woo-odoo/fusion_woocommerce/data/shipping_carriers.xml b/fusion-woo-odoo/fusion_woocommerce/data/shipping_carriers.xml new file mode 100644 index 00000000..fe01434d --- /dev/null +++ b/fusion-woo-odoo/fusion_woocommerce/data/shipping_carriers.xml @@ -0,0 +1,33 @@ + + + + Canada Post + canada_post + https://www.canadapost-postescanada.ca/track-reperage/en#/search?searchFor={tracking} + + + UPS + ups + https://www.ups.com/track?tracknum={tracking} + + + FedEx + fedex + https://www.fedex.com/fedextrack/?trknbr={tracking} + + + Purolator + purolator + https://www.purolator.com/en/shipping/tracker?pin={tracking} + + + DHL + dhl + https://www.dhl.com/en/express/tracking.html?AWB={tracking} + + + Other + other + + + diff --git a/fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py b/fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py new file mode 100644 index 00000000..936f2de6 --- /dev/null +++ b/fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py @@ -0,0 +1,117 @@ +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, + }) diff --git a/fusion-woo-odoo/fusion_woocommerce/models/woo_shipping_carrier.py b/fusion-woo-odoo/fusion_woocommerce/models/woo_shipping_carrier.py new file mode 100644 index 00000000..1fd70582 --- /dev/null +++ b/fusion-woo-odoo/fusion_woocommerce/models/woo_shipping_carrier.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class WooShippingCarrier(models.Model): + _name = 'woo.shipping.carrier' + _description = 'WooCommerce Shipping Carrier' + + name = fields.Char(required=True) + code = fields.Char(required=True) + tracking_url = fields.Char(help='Use {tracking} as placeholder') + active = fields.Boolean(default=True)