feat: add woo.instance and woo.shipping.carrier models with default carriers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-03-31 20:08:41 -04:00
parent 2cc146b253
commit cd710065c6
4 changed files with 162 additions and 0 deletions

View File

@@ -11,6 +11,7 @@
'data': [
'security/woo_security.xml',
'security/ir.model.access.csv',
'data/shipping_carriers.xml',
],
'assets': {
'web.assets_backend': [

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="carrier_canada_post" model="woo.shipping.carrier">
<field name="name">Canada Post</field>
<field name="code">canada_post</field>
<field name="tracking_url">https://www.canadapost-postescanada.ca/track-reperage/en#/search?searchFor={tracking}</field>
</record>
<record id="carrier_ups" model="woo.shipping.carrier">
<field name="name">UPS</field>
<field name="code">ups</field>
<field name="tracking_url">https://www.ups.com/track?tracknum={tracking}</field>
</record>
<record id="carrier_fedex" model="woo.shipping.carrier">
<field name="name">FedEx</field>
<field name="code">fedex</field>
<field name="tracking_url">https://www.fedex.com/fedextrack/?trknbr={tracking}</field>
</record>
<record id="carrier_purolator" model="woo.shipping.carrier">
<field name="name">Purolator</field>
<field name="code">purolator</field>
<field name="tracking_url">https://www.purolator.com/en/shipping/tracker?pin={tracking}</field>
</record>
<record id="carrier_dhl" model="woo.shipping.carrier">
<field name="name">DHL</field>
<field name="code">dhl</field>
<field name="tracking_url">https://www.dhl.com/en/express/tracking.html?AWB={tracking}</field>
</record>
<record id="carrier_other" model="woo.shipping.carrier">
<field name="name">Other</field>
<field name="code">other</field>
<field name="tracking_url"></field>
</record>
</odoo>

View File

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

View File

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