diff --git a/fusion-woo-odoo/fusion_woocommerce/__manifest__.py b/fusion-woo-odoo/fusion_woocommerce/__manifest__.py
index 1370bb1f..d015db9e 100644
--- a/fusion-woo-odoo/fusion_woocommerce/__manifest__.py
+++ b/fusion-woo-odoo/fusion_woocommerce/__manifest__.py
@@ -15,6 +15,7 @@
'data/cron.xml',
'data/mail_template.xml',
'views/woo_instance_views.xml',
+ 'views/woo_category_map_views.xml',
'views/woo_product_map_views.xml',
'views/woo_order_views.xml',
'views/woo_sync_log_views.xml',
diff --git a/fusion-woo-odoo/fusion_woocommerce/models/__init__.py b/fusion-woo-odoo/fusion_woocommerce/models/__init__.py
index 0657c847..69a8d7ae 100644
--- a/fusion-woo-odoo/fusion_woocommerce/models/__init__.py
+++ b/fusion-woo-odoo/fusion_woocommerce/models/__init__.py
@@ -1,5 +1,6 @@
from . import woo_shipping_carrier
from . import woo_instance
+from . import woo_category_map
from . import woo_product_map
from . import woo_order
from . import woo_shipment
diff --git a/fusion-woo-odoo/fusion_woocommerce/models/woo_category_map.py b/fusion-woo-odoo/fusion_woocommerce/models/woo_category_map.py
new file mode 100644
index 00000000..89530f5e
--- /dev/null
+++ b/fusion-woo-odoo/fusion_woocommerce/models/woo_category_map.py
@@ -0,0 +1,14 @@
+from odoo import api, fields, models
+
+
+class WooCategoryMap(models.Model):
+ _name = 'woo.category.map'
+ _description = 'WooCommerce Category Mapping'
+ _order = 'odoo_category_id'
+
+ instance_id = fields.Many2one('woo.instance', required=True, ondelete='cascade')
+ odoo_category_id = fields.Many2one('product.category', string='Odoo Category')
+ woo_category_id = fields.Integer(string='WC Category ID', required=True)
+ woo_category_name = fields.Char(string='WC Category Name')
+ woo_category_slug = fields.Char(string='WC Category Slug')
+ company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
diff --git a/fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py b/fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py
index 08fb9480..a89e4445 100644
--- a/fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py
+++ b/fusion-woo-odoo/fusion_woocommerce/models/woo_instance.py
@@ -51,6 +51,43 @@ class WooInstance(models.Model):
customer_ids = fields.One2many('woo.customer', 'instance_id')
sync_log_ids = fields.One2many('woo.sync.log', 'instance_id')
+ # Category mapping
+ category_map_ids = fields.One2many('woo.category.map', 'instance_id', string='Category Mappings')
+
+ # AI Configuration
+ ai_provider = fields.Selection([
+ ('claude', 'Claude (Anthropic)'),
+ ('openai', 'OpenAI'),
+ ], string='AI Provider')
+ ai_api_key = fields.Char(string='AI API Key', groups='base.group_system')
+ ai_model = fields.Char(string='AI Model',
+ help='e.g., claude-sonnet-4-5-20250514 for Claude or gpt-4o for OpenAI')
+
+ # AI Prompts
+ prompt_product_title = fields.Text(string='Product Title Prompt',
+ default='Generate an SEO-optimized product title in Title Case. Keep it concise, include the brand name and key product features. Do not use ALL CAPS.')
+ prompt_short_description = fields.Text(string='Short Description Prompt',
+ default='Write a compelling 2-3 sentence product summary in HTML format. Highlight key benefits and features. Use
tags.')
+ prompt_long_description = fields.Text(string='Long Description Prompt',
+ default='Write a detailed SEO-optimized product description in HTML format. Include sections with
headings for Features, Specifications, and Benefits. Use
lists for features. Make it informative and persuasive.')
+ prompt_meta_title = fields.Text(string='Meta Title Prompt',
+ default='Generate an SEO meta title under 60 characters. Include the primary keyword and brand name.')
+ prompt_meta_description = fields.Text(string='Meta Description Prompt',
+ default='Generate an SEO meta description under 160 characters. Include a call to action and primary keyword.')
+ prompt_image_alt = fields.Text(string='Image Alt Text Prompt',
+ default='Generate descriptive alt text for this product image. Be specific about the product shown. Keep under 125 characters.')
+ prompt_image_caption = fields.Text(string='Image Caption Prompt',
+ default='Generate a short image caption for this product photo. Include the product name and key visible feature.')
+ prompt_keywords = fields.Text(string='Keywords Prompt',
+ default='Generate 5-8 SEO focus keywords for this product, comma-separated. Include long-tail keywords.')
+
+ # Company Info for Image Geo-tagging
+ geo_company_name = fields.Char(string='Company Name (Geo-tag)', compute='_compute_geo_info', store=False)
+ geo_company_address = fields.Char(string='Company Address (Geo-tag)', compute='_compute_geo_info', store=False)
+ geo_company_phone = fields.Char(string='Company Phone (Geo-tag)', compute='_compute_geo_info', store=False)
+ geo_lat = fields.Float(string='GPS Latitude', digits=(10, 7))
+ geo_lng = fields.Float(string='GPS Longitude', digits=(10, 7))
+
# Computed
mapped_count = fields.Integer(compute='_compute_counts')
unmapped_count = fields.Integer(compute='_compute_counts')
@@ -72,6 +109,64 @@ class WooInstance(models.Model):
('create_date', '>=', yesterday),
])
+ @api.depends('company_id')
+ def _compute_geo_info(self):
+ for rec in self:
+ company = rec.company_id
+ rec.geo_company_name = company.name or ''
+ rec.geo_company_address = ', '.join(filter(None, [
+ company.street,
+ company.city,
+ company.state_id.name if company.state_id else '',
+ company.zip,
+ company.country_id.name if company.country_id else '',
+ ]))
+ rec.geo_company_phone = company.phone or ''
+
+ def action_fetch_wc_categories(self):
+ """Fetch all WooCommerce categories and display for mapping."""
+ self.ensure_one()
+ client = self._get_client()
+ CategoryMap = self.env['woo.category.map']
+ page = 1
+ fetched = 0
+ while True:
+ try:
+ cats = client.get('products/categories', params={'page': page, 'per_page': 100})
+ except Exception as e:
+ raise UserError('Failed to fetch WC categories: %s' % str(e))
+ if not cats:
+ break
+ for wc_cat in cats:
+ wc_id = wc_cat['id']
+ existing = CategoryMap.search([
+ ('instance_id', '=', self.id),
+ ('woo_category_id', '=', wc_id),
+ ], limit=1)
+ if existing:
+ existing.write({
+ 'woo_category_name': wc_cat.get('name', ''),
+ 'woo_category_slug': wc_cat.get('slug', ''),
+ })
+ else:
+ # Try auto-match by name
+ odoo_cat = self.env['product.category'].search([
+ ('name', '=ilike', wc_cat.get('name', '')),
+ ], limit=1)
+ CategoryMap.create({
+ 'instance_id': self.id,
+ 'odoo_category_id': odoo_cat.id if odoo_cat else False,
+ 'woo_category_id': wc_id,
+ 'woo_category_name': wc_cat.get('name', ''),
+ 'woo_category_slug': wc_cat.get('slug', ''),
+ 'company_id': self.company_id.id,
+ })
+ fetched += 1
+ page += 1
+ if len(cats) < 100:
+ break
+ return True
+
def _get_client(self):
"""Return a WooApiClient instance for this WooCommerce connection."""
self.ensure_one()
diff --git a/fusion-woo-odoo/fusion_woocommerce/security/ir.model.access.csv b/fusion-woo-odoo/fusion_woocommerce/security/ir.model.access.csv
index 67653d82..8fe4bbde 100644
--- a/fusion-woo-odoo/fusion_woocommerce/security/ir.model.access.csv
+++ b/fusion-woo-odoo/fusion_woocommerce/security/ir.model.access.csv
@@ -23,5 +23,7 @@ access_woo_return_user,woo.return.user,model_woo_return,fusion_woocommerce.group
access_woo_return_manager,woo.return.manager,model_woo_return,fusion_woocommerce.group_woo_manager,1,1,1,1
access_woo_return_line_user,woo.return.line.user,model_woo_return_line,fusion_woocommerce.group_woo_user,1,0,0,0
access_woo_return_line_manager,woo.return.line.manager,model_woo_return_line,fusion_woocommerce.group_woo_manager,1,1,1,1
+access_woo_category_map_user,woo.category.map.user,model_woo_category_map,fusion_woocommerce.group_woo_user,1,0,0,0
+access_woo_category_map_manager,woo.category.map.manager,model_woo_category_map,fusion_woocommerce.group_woo_manager,1,1,1,1
access_woo_setup_wizard_manager,woo.setup.wizard.manager,model_woo_setup_wizard,fusion_woocommerce.group_woo_manager,1,1,1,1
access_woo_product_fetch_manager,woo.product.fetch.manager,model_woo_product_fetch,fusion_woocommerce.group_woo_manager,1,1,1,1
diff --git a/fusion-woo-odoo/fusion_woocommerce/views/woo_category_map_views.xml b/fusion-woo-odoo/fusion_woocommerce/views/woo_category_map_views.xml
new file mode 100644
index 00000000..38da3da4
--- /dev/null
+++ b/fusion-woo-odoo/fusion_woocommerce/views/woo_category_map_views.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+ woo.category.map.list
+ woo.category.map
+
+
+
+
+
+
+
+
+
+
+
+
+
+ woo.category.map.form
+ woo.category.map
+
+
+
+
+
+
diff --git a/fusion-woo-odoo/fusion_woocommerce/views/woo_instance_views.xml b/fusion-woo-odoo/fusion_woocommerce/views/woo_instance_views.xml
index ffb89ac0..0df8abb6 100644
--- a/fusion-woo-odoo/fusion_woocommerce/views/woo_instance_views.xml
+++ b/fusion-woo-odoo/fusion_woocommerce/views/woo_instance_views.xml
@@ -133,6 +133,60 @@
+
+