feat: AI-powered product creation wizard with SEO and image geo-tagging

4-step wizard: Basic Info → Images → Content & SEO → Review & Create.
AI generates product titles, descriptions, meta data using Claude or OpenAI.
Image geo-tagging with company EXIF data. SEO meta pushed to Rank Math,
Yoast, AIOSEO, and SEOPress simultaneously. Products created with CAPS
name in Odoo, Title Case in WooCommerce.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-01 14:44:48 -04:00
parent f759bf558f
commit 4efd5066d0
6 changed files with 732 additions and 1 deletions

View File

@@ -32,6 +32,7 @@
'views/woo_menus.xml',
'wizard/woo_setup_wizard_views.xml',
'wizard/woo_product_fetch_views.xml',
'wizard/woo_product_create_views.xml',
],
'assets': {
'web.assets_backend': [

View File

@@ -27,3 +27,4 @@ access_woo_category_map_user,woo.category.map.user,model_woo_category_map,fusion
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
access_woo_product_create_wizard_manager,woo.product.create.wizard.manager,model_woo_product_create_wizard,fusion_woocommerce.group_woo_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
27 access_woo_category_map_manager woo.category.map.manager model_woo_category_map fusion_woocommerce.group_woo_manager 1 1 1 1
28 access_woo_setup_wizard_manager woo.setup.wizard.manager model_woo_setup_wizard fusion_woocommerce.group_woo_manager 1 1 1 1
29 access_woo_product_fetch_manager woo.product.fetch.manager model_woo_product_fetch fusion_woocommerce.group_woo_manager 1 1 1 1
30 access_woo_product_create_wizard_manager woo.product.create.wizard.manager model_woo_product_create_wizard fusion_woocommerce.group_woo_manager 1 1 1 1

View File

@@ -410,7 +410,20 @@ export class ProductMapping extends Component {
}
async createInWC(odooProductId) {
this.notification.add("Create in WooCommerce queued.", { type: "info" });
if (!this.state.instanceId) {
this.notification.add("Please select an instance first.", { type: "warning" });
return;
}
this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'woo.product.create.wizard',
views: [[false, 'form']],
target: 'new',
context: {
default_instance_id: this.state.instanceId,
default_odoo_product_id: odooProductId,
},
});
}
async createInOdoo(wooMapId) {

View File

@@ -1,2 +1,3 @@
from . import woo_setup_wizard
from . import woo_product_fetch
from . import woo_product_create

View File

@@ -0,0 +1,509 @@
import base64
import json
import logging
from odoo import api, fields, models
from odoo.exceptions import UserError
from ..lib.ai_service import AIService
from ..lib.image_processor import ImageProcessor
_logger = logging.getLogger(__name__)
class WooProductCreateWizard(models.TransientModel):
_name = 'woo.product.create.wizard'
_description = 'Create Product in WooCommerce'
# Step tracking
step = fields.Selection([
('basic', 'Basic Info'),
('images', 'Images'),
('content', 'Content & SEO'),
('review', 'Review & Create'),
], default='basic')
instance_id = fields.Many2one('woo.instance', required=True, string='WooCommerce Instance')
odoo_product_id = fields.Many2one('product.product', string='Odoo Product')
# Step 1: Basic Info
product_name = fields.Char(string='Product Name (Odoo - CAPS)')
wc_product_name = fields.Char(string='Product Name (WC - Title Case)')
odoo_category_id = fields.Many2one('product.category', string='Odoo Category')
wc_category_id = fields.Integer(string='WC Category ID')
wc_category_name = fields.Char(string='WC Category', readonly=True)
sale_price = fields.Float(string='Sale Price', digits='Product Price')
cost_price = fields.Float(string='Cost Price', digits='Product Price')
sku = fields.Char(string='Internal Reference / SKU')
sales_tax_id = fields.Many2one('account.tax', string='Sales Tax',
domain="[('type_tax_use', '=', 'sale')]")
purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax',
domain="[('type_tax_use', '=', 'purchase')]")
wc_tax_class = fields.Char(string='WC Tax Class')
# Step 2: Images
image_1 = fields.Binary(string='Image 1 (Featured)')
image_1_filename = fields.Char()
image_2 = fields.Binary(string='Image 2')
image_2_filename = fields.Char()
image_3 = fields.Binary(string='Image 3')
image_3_filename = fields.Char()
image_4 = fields.Binary(string='Image 4')
image_4_filename = fields.Char()
image_5 = fields.Binary(string='Image 5')
image_5_filename = fields.Char()
# Image AI metadata (stored as JSON text)
image_metadata = fields.Text(string='Image Metadata', default='[]')
images_geotagged = fields.Boolean(string='Images Geo-tagged')
# Step 3: Content & SEO
raw_product_info = fields.Text(string='Product Information',
help='Type everything you know about this product. The AI will use this to generate descriptions.')
ai_keywords = fields.Char(string='Keywords')
wc_title = fields.Char(string='WC Product Title')
short_description = fields.Html(string='Short Description')
long_description = fields.Html(string='Long Description')
meta_title = fields.Char(string='Meta Title')
meta_description = fields.Text(string='Meta Description')
seo_keywords = fields.Char(string='SEO Focus Keywords')
# Step 4 is review - uses fields above
# --- Onchange / Defaults ---
@api.onchange('odoo_product_id')
def _onchange_odoo_product(self):
if self.odoo_product_id:
product = self.odoo_product_id
self.product_name = product.name.upper() if product.name else ''
self.wc_product_name = product.name.title() if product.name else ''
self.sale_price = product.list_price
self.cost_price = product.standard_price
self.sku = product.default_code or ''
self.odoo_category_id = product.categ_id.id if product.categ_id else False
# Auto-set sales tax
if product.taxes_id:
self.sales_tax_id = product.taxes_id[0].id
# Auto-set image
if product.image_1920:
self.image_1 = product.image_1920
@api.onchange('odoo_category_id')
def _onchange_category(self):
"""Auto-map Odoo category to WC category using woo.category.map."""
if self.odoo_category_id and self.instance_id:
mapping = self.env['woo.category.map'].search([
('instance_id', '=', self.instance_id.id),
('odoo_category_id', '=', self.odoo_category_id.id),
], limit=1)
if mapping:
self.wc_category_id = mapping.woo_category_id
self.wc_category_name = mapping.woo_category_name
@api.onchange('sales_tax_id')
def _onchange_sales_tax(self):
"""Auto-map sales tax to WC tax class and match purchase tax."""
if self.sales_tax_id and self.instance_id:
# Map to WC tax class
tax_map = self.env['woo.tax.map'].search([
('instance_id', '=', self.instance_id.id),
('tax_id', '=', self.sales_tax_id.id),
], limit=1)
if tax_map:
self.wc_tax_class = tax_map.woo_tax_class
# Match purchase tax by amount
purchase_tax = self.env['account.tax'].search([
('type_tax_use', '=', 'purchase'),
('amount', '=', self.sales_tax_id.amount),
('company_id', '=', self.instance_id.company_id.id),
], limit=1)
if purchase_tax:
self.purchase_tax_id = purchase_tax.id
# --- Navigation ---
def action_next(self):
self.ensure_one()
steps = ['basic', 'images', 'content', 'review']
idx = steps.index(self.step)
if idx < len(steps) - 1:
self.step = steps[idx + 1]
return self._reopen()
def action_back(self):
self.ensure_one()
steps = ['basic', 'images', 'content', 'review']
idx = steps.index(self.step)
if idx > 0:
self.step = steps[idx - 1]
return self._reopen()
def _reopen(self):
return {
'type': 'ir.actions.act_window',
'res_model': self._name,
'res_id': self.id,
'views': [(False, 'form')],
'target': 'new',
}
# --- AI Methods ---
def _get_ai_service(self):
"""Get AIService instance from the woo.instance settings."""
self.ensure_one()
inst = self.instance_id
if not inst.ai_provider or not inst.ai_api_key:
raise UserError(
"Please configure AI settings (Provider + API Key) on the WooCommerce instance first."
)
return AIService(inst.ai_provider, inst.ai_api_key, inst.ai_model or None)
def action_ai_generate_all(self):
"""Generate all content fields using AI."""
self.ensure_one()
ai = self._get_ai_service()
inst = self.instance_id
product_info = {
'name': self.product_name or '',
'category': self.odoo_category_id.complete_name if self.odoo_category_id else '',
'price': self.sale_price,
'sku': self.sku or '',
'raw_description': self.raw_product_info or '',
'keywords': self.ai_keywords or '',
}
prompts = {
'title': inst.prompt_product_title or '',
'short_desc': inst.prompt_short_description or '',
'long_desc': inst.prompt_long_description or '',
'meta_title': inst.prompt_meta_title or '',
'meta_desc': inst.prompt_meta_description or '',
'keywords': inst.prompt_keywords or '',
}
result = ai.generate_product_content(product_info, prompts)
self.wc_title = result.get('title', self.wc_product_name)
self.short_description = result.get('short_description', '')
self.long_description = result.get('long_description', '')
self.meta_title = result.get('meta_title', '')
self.meta_description = result.get('meta_description', '')
self.seo_keywords = result.get('keywords', self.ai_keywords or '')
return self._reopen()
def action_ai_generate_title(self):
self.ensure_one()
ai = self._get_ai_service()
product_info = {
'name': self.product_name,
'category': self.odoo_category_id.complete_name if self.odoo_category_id else '',
'raw_description': self.raw_product_info or '',
}
result = ai.generate_single_field(
product_info,
self.instance_id.prompt_product_title or 'Generate SEO product title in Title Case',
'title',
)
self.wc_title = result
return self._reopen()
def action_ai_generate_short_desc(self):
self.ensure_one()
ai = self._get_ai_service()
product_info = {
'name': self.product_name,
'category': self.odoo_category_id.complete_name if self.odoo_category_id else '',
'raw_description': self.raw_product_info or '',
}
result = ai.generate_single_field(
product_info,
self.instance_id.prompt_short_description or 'Write a short HTML product description',
'short_description',
)
self.short_description = result
return self._reopen()
def action_ai_generate_long_desc(self):
self.ensure_one()
ai = self._get_ai_service()
product_info = {
'name': self.product_name,
'category': self.odoo_category_id.complete_name if self.odoo_category_id else '',
'raw_description': self.raw_product_info or '',
'keywords': self.ai_keywords or '',
}
result = ai.generate_single_field(
product_info,
self.instance_id.prompt_long_description or 'Write a detailed HTML product description',
'long_description',
)
self.long_description = result
return self._reopen()
def action_ai_generate_meta(self):
self.ensure_one()
ai = self._get_ai_service()
product_info = {
'name': self.wc_title or self.product_name,
'category': self.odoo_category_id.complete_name if self.odoo_category_id else '',
'keywords': self.ai_keywords or '',
}
self.meta_title = ai.generate_single_field(
product_info,
self.instance_id.prompt_meta_title or 'Generate SEO meta title under 60 chars',
'meta_title',
)
self.meta_description = ai.generate_single_field(
product_info,
self.instance_id.prompt_meta_description or 'Generate SEO meta description under 160 chars',
'meta_description',
)
return self._reopen()
def action_ai_generate_keywords(self):
self.ensure_one()
ai = self._get_ai_service()
product_info = {
'name': self.wc_title or self.product_name,
'category': self.odoo_category_id.complete_name if self.odoo_category_id else '',
'raw_description': self.raw_product_info or '',
}
self.seo_keywords = ai.generate_single_field(
product_info,
self.instance_id.prompt_keywords or 'Generate SEO focus keywords',
'keywords',
)
return self._reopen()
# --- Image AI ---
def action_ai_tag_images(self):
"""Generate AI metadata for all uploaded images."""
self.ensure_one()
ai = self._get_ai_service()
inst = self.instance_id
category_name = self.odoo_category_id.complete_name if self.odoo_category_id else ''
metadata = []
for i in range(1, 6):
img = getattr(self, f'image_{i}', None)
if img:
meta = ai.generate_image_metadata(
self.product_name or '',
category_name,
inst.prompt_image_alt or 'Descriptive alt text under 125 chars',
inst.prompt_image_caption or 'Short product image caption',
)
meta['index'] = i
metadata.append(meta)
self.image_metadata = json.dumps(metadata)
return self._reopen()
def action_geo_tag_images(self):
"""Write EXIF geo-tag data to all uploaded images."""
self.ensure_one()
inst = self.instance_id
for i in range(1, 6):
img = getattr(self, f'image_{i}', None)
if img:
tagged = ImageProcessor.geo_tag_image(
img.decode('utf-8') if isinstance(img, bytes) else img,
inst.geo_company_name,
inst.geo_company_address,
inst.geo_company_phone,
inst.geo_lat,
inst.geo_lng,
)
setattr(self, f'image_{i}', tagged)
self.images_geotagged = True
return self._reopen()
# --- Create Product ---
def action_create_product(self):
"""Create the product in WooCommerce and update Odoo."""
self.ensure_one()
inst = self.instance_id
client = inst._get_client()
# --- Update/Create Odoo product ---
odoo_product = self.odoo_product_id
odoo_vals = {
'name': self.product_name or (self.wc_title or '').upper(),
'list_price': self.sale_price,
'standard_price': self.cost_price,
'default_code': self.sku or '',
'type': 'consu',
}
if self.odoo_category_id:
odoo_vals['categ_id'] = self.odoo_category_id.id
if self.sales_tax_id:
odoo_vals['taxes_id'] = [(6, 0, [self.sales_tax_id.id])]
if self.purchase_tax_id:
odoo_vals['supplier_taxes_id'] = [(6, 0, [self.purchase_tax_id.id])]
if odoo_product:
odoo_product.write(odoo_vals)
else:
odoo_product = self.env['product.product'].create(odoo_vals)
# Set product image
if self.image_1:
odoo_product.image_1920 = self.image_1
# --- Prepare WC product data ---
wc_data = {
'name': self.wc_title or (self.product_name or '').title(),
'type': 'simple',
'regular_price': str(self.sale_price),
'sku': self.sku or '',
'short_description': self.short_description or '',
'description': self.long_description or '',
'manage_stock': False,
'status': 'publish',
}
# Category
if self.wc_category_id:
wc_data['categories'] = [{'id': self.wc_category_id}]
# Tax class
if self.wc_tax_class:
wc_data['tax_class'] = self.wc_tax_class
# SEO meta data — compatible with Rank Math, Yoast, AIOSEO, SEOPress
seo_meta = []
if self.meta_title:
seo_meta.extend([
{'key': 'rank_math_title', 'value': self.meta_title},
{'key': '_yoast_wpseo_title', 'value': self.meta_title},
{'key': '_aioseo_title', 'value': self.meta_title},
{'key': '_seopress_titles_title', 'value': self.meta_title},
])
if self.meta_description:
seo_meta.extend([
{'key': 'rank_math_description', 'value': self.meta_description},
{'key': '_yoast_wpseo_metadesc', 'value': self.meta_description},
{'key': '_aioseo_description', 'value': self.meta_description},
{'key': '_seopress_titles_desc', 'value': self.meta_description},
])
if self.seo_keywords:
seo_meta.extend([
{'key': 'rank_math_focus_keyword', 'value': self.seo_keywords},
{'key': '_yoast_wpseo_focuskw', 'value': self.seo_keywords},
{'key': '_aioseo_keywords', 'value': self.seo_keywords},
{'key': '_seopress_analysis_target_kw', 'value': self.seo_keywords},
])
if seo_meta:
wc_data['meta_data'] = seo_meta
# --- Upload images to WC ---
image_metadata = []
try:
image_metadata = json.loads(self.image_metadata or '[]')
except (json.JSONDecodeError, TypeError):
pass
wc_images = []
for i in range(1, 6):
img_data = getattr(self, f'image_{i}', None)
if not img_data:
continue
filename = getattr(self, f'image_{i}_filename', '') or f'product_image_{i}.jpg'
# Find AI metadata for this image
img_meta = next((m for m in image_metadata if m.get('index') == i), {})
# Upload image via WordPress REST API
try:
img_bytes = base64.b64decode(
img_data if isinstance(img_data, str) else img_data.decode('utf-8')
)
import requests
wp_url = inst.url.rstrip('/')
upload_url = f"{wp_url}/wp-json/wp/v2/media"
headers = {
'Content-Disposition': f'attachment; filename="{filename}"',
'Content-Type': 'image/jpeg',
}
resp = requests.post(
upload_url,
auth=(inst.consumer_key, inst.consumer_secret),
headers=headers,
data=img_bytes,
timeout=60,
)
if resp.status_code in (200, 201):
media = resp.json()
wc_img = {
'id': media['id'],
'alt': img_meta.get('alt_text', ''),
'name': img_meta.get('title', filename),
'caption': img_meta.get('caption', ''),
'description': img_meta.get('description', ''),
}
# First image is featured
if i == 1:
wc_img['position'] = 0
wc_images.append(wc_img)
else:
_logger.warning(
"Image upload failed (HTTP %s): %s",
resp.status_code, resp.text[:200],
)
except Exception as e:
_logger.error("Image upload error: %s", str(e))
if wc_images:
wc_data['images'] = wc_images
# --- Create WC product ---
try:
wc_product = client.create_product(wc_data)
wc_product_id = wc_product['id']
wc_permalink = wc_product.get('permalink', '')
except Exception as e:
raise UserError("Failed to create WooCommerce product: %s" % str(e))
# --- Create product mapping ---
self.env['woo.product.map'].create({
'instance_id': inst.id,
'product_id': odoo_product.id,
'woo_product_id': wc_product_id,
'woo_product_name': wc_data['name'],
'woo_sku': self.sku or '',
'woo_regular_price': self.sale_price,
'woo_sale_price': 0.0,
'woo_permalink': wc_permalink,
'woo_product_type': 'simple',
'state': 'mapped',
'company_id': inst.company_id.id,
})
inst._log_sync(
'product', 'odoo_to_woo', odoo_product.name, 'success',
'Created WC product #%s' % wc_product_id,
)
# Close wizard and show success
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Product Created',
'message': 'Product "%s" created in WooCommerce successfully!' % wc_data['name'],
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
},
}

View File

@@ -0,0 +1,206 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="woo_product_create_wizard_form" model="ir.ui.view">
<field name="name">woo.product.create.wizard.form</field>
<field name="model">woo.product.create.wizard</field>
<field name="arch" type="xml">
<form string="Create Product in WooCommerce">
<sheet>
<!-- Step indicator -->
<div class="o_statusbar_status mb-3">
<span class="badge bg-primary me-1" invisible="step != 'basic'">
1. Basic Info
</span>
<span class="badge bg-secondary me-1" invisible="step == 'basic'">
1. Basic Info &#10003;
</span>
<span class="badge bg-primary me-1" invisible="step != 'images'">
2. Images
</span>
<span class="badge bg-secondary me-1" invisible="step in ('basic', 'images')">
2. Images &#10003;
</span>
<span class="badge bg-light text-muted me-1" invisible="step not in ('basic',)">
2. Images
</span>
<span class="badge bg-primary me-1" invisible="step != 'content'">
3. Content &amp; SEO
</span>
<span class="badge bg-secondary me-1" invisible="step == 'review'">
</span>
<span class="badge bg-light text-muted me-1" invisible="step not in ('basic', 'images')">
3. Content &amp; SEO
</span>
<span class="badge bg-primary me-1" invisible="step != 'review'">
4. Review &amp; Create
</span>
<span class="badge bg-light text-muted me-1" invisible="step == 'review'">
4. Review &amp; Create
</span>
</div>
<field name="instance_id" invisible="1"/>
<!-- ========== STEP 1: Basic Info ========== -->
<group invisible="step != 'basic'">
<group string="Product Selection">
<field name="odoo_product_id"/>
</group>
<group string="Product Names">
<field name="product_name" placeholder="PRODUCT NAME IN CAPS"/>
<field name="wc_product_name" placeholder="Product Name In Title Case"/>
</group>
<group string="Category">
<field name="odoo_category_id"/>
<field name="wc_category_name"/>
</group>
<group string="Pricing">
<field name="sale_price"/>
<field name="cost_price"/>
</group>
<group string="Identification">
<field name="sku"/>
</group>
<group string="Taxes">
<field name="sales_tax_id"/>
<field name="purchase_tax_id"/>
<field name="wc_tax_class" readonly="1"/>
</group>
<field name="wc_category_id" invisible="1"/>
</group>
<!-- ========== STEP 2: Images ========== -->
<group invisible="step != 'images'">
<group string="Product Images">
<field name="image_1" filename="image_1_filename" widget="image"/>
<field name="image_1_filename" invisible="1"/>
<field name="image_2" filename="image_2_filename" widget="image"/>
<field name="image_2_filename" invisible="1"/>
<field name="image_3" filename="image_3_filename" widget="image"/>
<field name="image_3_filename" invisible="1"/>
<field name="image_4" filename="image_4_filename" widget="image"/>
<field name="image_4_filename" invisible="1"/>
<field name="image_5" filename="image_5_filename" widget="image"/>
<field name="image_5_filename" invisible="1"/>
</group>
<group string="Image Processing">
<div>
<button name="action_ai_tag_images" type="object"
string="AI Tag Images" class="btn btn-primary me-2"
icon="fa-magic"/>
<button name="action_geo_tag_images" type="object"
string="Geo-tag Images" class="btn btn-secondary"
icon="fa-map-marker"/>
</div>
<field name="images_geotagged" readonly="1"/>
<field name="image_metadata" readonly="1" widget="text"
placeholder="AI metadata will appear here after tagging..."/>
</group>
</group>
<!-- ========== STEP 3: Content & SEO ========== -->
<group invisible="step != 'content'">
<group string="Product Information for AI" colspan="2">
<field name="raw_product_info" colspan="2"
placeholder="Type everything you know about this product — features, materials, use cases, target audience, dimensions, etc. The AI will use this to generate all descriptions."/>
<field name="ai_keywords"
placeholder="mobility, wheelchair, medical equipment, ..."/>
</group>
<div colspan="2" class="mb-3">
<button name="action_ai_generate_all" type="object"
string="Generate All with AI" class="btn btn-primary"
icon="fa-magic"/>
</div>
<group string="Product Title" colspan="2">
<div class="d-flex align-items-start">
<field name="wc_title" class="flex-grow-1"/>
<button name="action_ai_generate_title" type="object"
string="AI" class="btn btn-outline-primary btn-sm ms-2"
icon="fa-magic"/>
</div>
</group>
<group string="Short Description" colspan="2">
<div>
<button name="action_ai_generate_short_desc" type="object"
string="AI Generate" class="btn btn-outline-primary btn-sm mb-1"
icon="fa-magic"/>
</div>
<field name="short_description" widget="html" nolabel="1" colspan="2"/>
</group>
<group string="Long Description" colspan="2">
<div>
<button name="action_ai_generate_long_desc" type="object"
string="AI Generate" class="btn btn-outline-primary btn-sm mb-1"
icon="fa-magic"/>
</div>
<field name="long_description" widget="html" nolabel="1" colspan="2"/>
</group>
<group string="SEO Meta Data" colspan="2">
<div class="mb-2">
<button name="action_ai_generate_meta" type="object"
string="AI Generate Meta" class="btn btn-outline-primary btn-sm"
icon="fa-magic"/>
<button name="action_ai_generate_keywords" type="object"
string="AI Generate Keywords" class="btn btn-outline-primary btn-sm ms-2"
icon="fa-magic"/>
</div>
<field name="meta_title" placeholder="SEO title (under 60 characters)"/>
<field name="meta_description" placeholder="SEO description (under 160 characters)"/>
<field name="seo_keywords" placeholder="keyword1, keyword2, keyword3"/>
</group>
</group>
<!-- ========== STEP 4: Review & Create ========== -->
<group invisible="step != 'review'">
<group string="Product Names">
<field name="product_name" readonly="1"/>
<field name="wc_product_name" readonly="1"/>
</group>
<group string="Category &amp; Tax">
<field name="odoo_category_id" readonly="1"/>
<field name="wc_category_name" readonly="1"/>
<field name="wc_tax_class" readonly="1"/>
</group>
<group string="Pricing">
<field name="sale_price" readonly="1"/>
<field name="cost_price" readonly="1"/>
<field name="sku" readonly="1"/>
</group>
<group string="Taxes">
<field name="sales_tax_id" readonly="1"/>
<field name="purchase_tax_id" readonly="1"/>
</group>
<group string="WooCommerce Content" colspan="2">
<field name="wc_title" readonly="1"/>
<field name="meta_title" readonly="1"/>
<field name="meta_description" readonly="1"/>
<field name="seo_keywords" readonly="1"/>
</group>
<group string="Images" colspan="2">
<field name="images_geotagged" readonly="1"/>
</group>
</group>
</sheet>
<footer>
<button string="&#8592; Back" type="object" name="action_back"
class="btn btn-secondary"
invisible="step == 'basic'"/>
<button string="Next &#8594;" type="object" name="action_next"
class="btn btn-primary"
invisible="step == 'review'"/>
<button string="Create in WooCommerce" type="object" name="action_create_product"
class="btn btn-success"
invisible="step != 'review'"
icon="fa-cloud-upload"/>
<button string="Cancel" class="btn btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>