178 lines
6.2 KiB
Python
178 lines
6.2 KiB
Python
import difflib
|
|
import logging
|
|
|
|
from odoo import fields, models
|
|
from odoo.exceptions import UserError
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class WooProductFetch(models.TransientModel):
|
|
_name = 'woo.product.fetch'
|
|
_description = 'Fetch WooCommerce Products'
|
|
|
|
instance_id = fields.Many2one('woo.instance', required=True, string='WooCommerce Instance')
|
|
state = fields.Selection([
|
|
('draft', 'Ready'),
|
|
('fetching', 'Fetching...'),
|
|
('matching', 'Matching...'),
|
|
('done', 'Complete'),
|
|
], default='draft')
|
|
total_fetched = fields.Integer(readonly=True)
|
|
auto_matched = fields.Integer(string='Auto-matched (SKU)', readonly=True)
|
|
suggested = fields.Integer(string='Name Suggestions', readonly=True)
|
|
unmatched = fields.Integer(readonly=True)
|
|
|
|
def action_fetch(self):
|
|
self.ensure_one()
|
|
instance = self.instance_id
|
|
if not instance:
|
|
raise UserError("Please select a WooCommerce instance.")
|
|
|
|
client = instance._get_client()
|
|
|
|
# --- Fetch all products (paginated) ---
|
|
self.state = 'fetching'
|
|
all_products = []
|
|
page = 1
|
|
while True:
|
|
batch = client.get_products(page=page, per_page=100)
|
|
if not batch:
|
|
break
|
|
all_products.extend(batch)
|
|
if len(batch) < 100:
|
|
break
|
|
page += 1
|
|
|
|
# Expand variable products with their variations
|
|
expanded = []
|
|
for product in all_products:
|
|
expanded.append(product)
|
|
if product.get('type') == 'variable':
|
|
var_page = 1
|
|
while True:
|
|
variations = client.get_product_variations(
|
|
product['id'], page=var_page, per_page=100
|
|
)
|
|
if not variations:
|
|
break
|
|
for var in variations:
|
|
var['_parent_id'] = product['id']
|
|
var['_is_variation'] = True
|
|
expanded.extend(variations)
|
|
if len(variations) < 100:
|
|
break
|
|
var_page += 1
|
|
|
|
total_fetched = len(expanded)
|
|
|
|
# --- Match products ---
|
|
self.state = 'matching'
|
|
|
|
ProductMap = self.env['woo.product.map']
|
|
ProductProduct = self.env['product.product']
|
|
|
|
# Pre-load existing maps for this instance to avoid repeated queries
|
|
existing_woo_ids = set(
|
|
ProductMap.search([('instance_id', '=', instance.id)]).mapped('woo_product_id')
|
|
)
|
|
|
|
# Pre-load all odoo products with a SKU for fast lookup
|
|
odoo_products_with_sku = ProductProduct.search([('default_code', '!=', False)])
|
|
sku_index = {p.default_code: p for p in odoo_products_with_sku}
|
|
|
|
# For name matching, load all product names
|
|
all_odoo_products = ProductProduct.search([])
|
|
odoo_name_list = [(p.name or '', p) for p in all_odoo_products]
|
|
|
|
auto_matched = 0
|
|
suggested_count = 0
|
|
unmatched = 0
|
|
|
|
for wc_product in expanded:
|
|
woo_id = wc_product.get('id')
|
|
if not woo_id:
|
|
continue
|
|
|
|
# Skip already-mapped
|
|
if woo_id in existing_woo_ids:
|
|
continue
|
|
|
|
woo_sku = wc_product.get('sku') or ''
|
|
woo_name = wc_product.get('name') or ''
|
|
woo_type = wc_product.get('type', 'simple')
|
|
is_variation = wc_product.get('_is_variation', False)
|
|
parent_id = wc_product.get('_parent_id') or wc_product.get('parent_id') or 0
|
|
|
|
wc_categories = wc_product.get('categories', [])
|
|
wc_cat_id = wc_categories[0].get('id', 0) if wc_categories else 0
|
|
wc_cat_name = wc_categories[0].get('name', '') if wc_categories else ''
|
|
|
|
map_vals = {
|
|
'instance_id': instance.id,
|
|
'woo_product_id': woo_id,
|
|
'woo_product_name': woo_name,
|
|
'woo_sku': woo_sku,
|
|
'woo_product_type': woo_type if not is_variation else 'variable',
|
|
'is_variation': is_variation,
|
|
'woo_parent_id': parent_id,
|
|
'woo_category_id': wc_cat_id,
|
|
'woo_category_name': wc_cat_name,
|
|
'company_id': instance.company_id.id,
|
|
}
|
|
|
|
# a) SKU match
|
|
matched_product = None
|
|
if woo_sku and woo_sku in sku_index:
|
|
matched_product = sku_index[woo_sku]
|
|
map_vals['product_id'] = matched_product.id
|
|
map_vals['state'] = 'mapped'
|
|
auto_matched += 1
|
|
else:
|
|
# b) Name similarity
|
|
if woo_name and odoo_name_list:
|
|
ratios = [
|
|
(difflib.SequenceMatcher(None, woo_name.lower(), name.lower()).ratio(), p)
|
|
for name, p in odoo_name_list
|
|
]
|
|
best_ratio, best_product = max(ratios, key=lambda x: x[0])
|
|
if best_ratio > 0.8:
|
|
map_vals['product_id'] = best_product.id
|
|
map_vals['state'] = 'unmapped'
|
|
suggested_count += 1
|
|
else:
|
|
map_vals['state'] = 'unmapped'
|
|
unmatched += 1
|
|
else:
|
|
map_vals['state'] = 'unmapped'
|
|
unmatched += 1
|
|
|
|
ProductMap.create(map_vals)
|
|
|
|
self.write({
|
|
'total_fetched': total_fetched,
|
|
'auto_matched': auto_matched,
|
|
'suggested': suggested_count,
|
|
'unmatched': unmatched,
|
|
'state': 'done',
|
|
})
|
|
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': self._name,
|
|
'res_id': self.id,
|
|
'view_mode': 'form',
|
|
'target': 'new',
|
|
}
|
|
|
|
def action_open_mapping(self):
|
|
self.ensure_one()
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': 'Product Mapping',
|
|
'res_model': 'woo.product.map',
|
|
'view_mode': 'list,form',
|
|
'domain': [('instance_id', '=', self.instance_id.id)],
|
|
'target': 'current',
|
|
}
|