feat: add category mapping model and AI settings on woo.instance

Category mapping between Odoo product categories and WC categories with
auto-match by name and manual mapping UI. AI settings for Claude/OpenAI
with customizable prompts for product content generation. GPS coordinates
for image geo-tagging pulled from company settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-01 14:30:02 -04:00
parent 3179cc1f7b
commit 3493c43916
7 changed files with 210 additions and 0 deletions

View File

@@ -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 <p> tags.')
prompt_long_description = fields.Text(string='Long Description Prompt',
default='Write a detailed SEO-optimized product description in HTML format. Include sections with <h3> headings for Features, Specifications, and Benefits. Use <ul> 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()