Files
enterprise-addons/product_barcodelookup/models/product_template.py
T

235 lines
11 KiB
Python

import base64
import re
import requests
from collections import OrderedDict
from odoo import api, models, Command
from markupsafe import Markup
from odoo.addons.product_barcodelookup.tools import barcode_lookup_service
from odoo.tools.barcode import check_barcode_encoding
BARCODE_UOM_REGEX = r'^((?P<uom_val>(\d*\.?\d+))([\s?]*)(?P<unit>(([a-zA-Z]*))))$'
class ProductTemplate(models.Model):
_inherit = 'product.template'
@api.onchange('barcode')
def _onchange_barcode(self):
for product in self:
if product.barcode and len(product.barcode) > 7:
barcode_lookup_data = self.barcode_lookup(product.barcode)
product._update_product_by_barcodelookup(product, barcode_lookup_data)
def _to_float(self, value):
try:
return float(value or 0)
except ValueError:
return 0
@api.model
def _update_product_by_barcodelookup(self, product, barcode_lookup_data):
product.ensure_one()
imperial_len = self._get_volume_uom_id_from_ir_config_parameter() == self.env.ref('uom.product_uom_cubic_foot')
imperial_weight = self._get_weight_uom_id_from_ir_config_parameter() == self.env.ref('uom.product_uom_lb')
if not barcode_lookup_data:
return
products = barcode_lookup_data.get('products')
# if no result or multi result ignore it
if not products or len(products) > 1:
return False
product_data = products[0]
if not product.name:
product.name = product_data.get('title')
# Image
if not product.image_1920 and (images := product_data.get('images')):
for image in images:
img_response = barcode_lookup_service.barcode_lookup_request(image)
if (
img_response.status_code == requests.codes.ok
and 'image/' in img_response.headers.get('Content-Type', '')
):
self._set_lookup_image(product, img_response)
# Weight
if not product.weight and (barcode_lookup_weight := product_data.get('weight', '')):
if imperial_weight:
convert_weight_uom = self.env.ref('uom.product_uom_kgm')
else:
convert_weight_uom = self.env.ref('uom.product_uom_lb')
weight_re_match = re.match(BARCODE_UOM_REGEX, barcode_lookup_weight)
if weight_re_match:
weight_dict = weight_re_match.groupdict()
weight = self._to_float(weight_dict.get('uom_val'))
if weight_dict.get('unit') \
and self._get_weight_uom_id_from_ir_config_parameter().name != weight_dict['unit']:
weight = convert_weight_uom._compute_quantity(
weight,
to_unit=self._get_weight_uom_id_from_ir_config_parameter(),
)
product.weight = weight
# Price
if (not self.list_price or self.list_price == 1.00) and (stores := product_data.get('stores')):
for store in stores:
price = self._to_float(store.get('price') or store.get('sale_price'))
if store.get('currency') == self.currency_id.name:
product.list_price = self._get_list_price(price)
break
else:
product_currency_id = self.env['res.currency'].with_context(active_test=False).search([
('name', '=', stores[0].get('currency')),
], limit=1)
price = self._to_float(stores[0].get('price') or stores[0].get('sale_price'))
if product_currency_id:
price = product_currency_id._convert(
price,
self.currency_id,
)
product.list_price = self._get_list_price(price)
# Attributes and values
extra_attributes = {}
if product._name == 'product.template' and self.env.user.has_group('product.group_product_variant') and not product.attribute_line_ids:
attribute_lines = []
for attr_name in ['color', 'gender', 'material', 'pattern', 'manufacturer', 'brand', 'size', 'age group']:
attr_values = product_data.get(attr_name)
if not attr_values:
continue
attribute = self.env['product.attribute'].search([
('name', 'ilike', attr_name),
], limit=1)
if attribute:
for attr_value in attr_values.split(','):
attribute_value = self.env['product.attribute.value'].search([
('name', 'ilike', attr_value.strip()),
('attribute_id', '=', attribute.id)
], limit=1)
if not (attribute_value or attr_name == 'color'):
attribute_value = self.env['product.attribute.value'].create({
'name': attr_value.strip().capitalize(),
'attribute_id': attribute.id,
})
if attribute_value:
attribute_lines.append(Command.create({
'attribute_id': attribute.id,
'value_ids': [Command.link(attribute_value.id)],
}))
else:
extra_attributes.update({attr_name.capitalize(): attr_values})
product.attribute_line_ids = attribute_lines
# Product Categories
if not product.id and (category := product_data.get('category')):
sub_category = product_data.get('category', "").split('>')[-1].strip()
if category := self.env['product.category'].search([('name', 'ilike', sub_category)], limit=1):
product.categ_id = category
# Dimensions and volume
dimensions = {dim: (product_data.get(dim)) for dim in ['length', 'width', 'height']}
uom_cm = self.env.ref('uom.product_uom_cm')
uom_m = self.env.ref('uom.product_uom_meter')
uom_in = self.env.ref('uom.product_uom_inch')
uom_foot = self.env.ref('uom.product_uom_foot')
convert_volume_uom = False
for dim_name, dimension in dimensions.items():
re_match = re.match(BARCODE_UOM_REGEX, dimension)
dim_value = 1
if re_match:
dim_dict = re_match.groupdict()
dim_value = self._to_float(dim_dict.get('uom_val'))
# convert value to correct uom
if dim_dict.get('unit'):
if dim_dict['unit'] == 'cm' and imperial_len:
dimensions[dim_name] = uom_cm._compute_quantity(dim_value, to_unit=uom_in)
convert_volume_uom = self.env.ref('uom.product_uom_cubic_meter')
if dim_dict['unit'] == 'in' and not imperial_len:
dimensions[dim_name] = uom_in._compute_quantity(dim_value, to_unit=uom_cm)
convert_volume_uom = self.env.ref('uom.product_uom_cubic_foot')
dimensions[dim_name] = dim_value
volume = 1 if re_match else 0
for dim_value in dimensions.values():
if imperial_len:
dim_value = uom_in._compute_quantity(dim_value, uom_foot)
else:
dim_value = uom_cm._compute_quantity(dim_value, uom_m)
volume *= dim_value
if convert_volume_uom:
product.volume = convert_volume_uom._compute_quantity(
volume,
to_unit=self._get_volume_uom_id_from_ir_config_parameter()
)
else:
product.volume = volume
# Description
product_description = OrderedDict()
if category := product_data.get("category"):
product_description.update({"Category": Markup("<li>%s</li>") % category})
if features_data := product_data.get('features'):
product_description.update({"Features": Markup('').join([Markup("<li>%s</li>") % (feature) for feature in features_data])})
for parameter in ['mpn', 'model', 'asin', 'weight']:
if para_val := product_data.get(parameter):
extra_attributes.update({parameter.capitalize(): para_val})
if product_data.get("length") or product_data.get("width") or product_data.get("height"):
dimension_data = ""
if product_data.get("length"):
dimension_data += f"{product_data['length']} (L) "
if product_data.get("width"):
dimension_data += f"{product_data['width']} (W) "
if product_data.get("height"):
dimension_data += f"{product_data['height']} (H)"
extra_attributes.update({"Dimension": dimension_data})
if extra_attributes.items():
product_description.update({"Attributes": Markup("").join([Markup("<li>%s: %s</li>") % (attr_name, attr_value) for attr_name, attr_value in extra_attributes.items()])})
if description := product_data.get('description'):
product.description = Markup("<p>%s</p>") % description
elif not product.description:
product.description = ""
product.description += Markup("<br>").join([Markup("<b>%s</b><br><ul>%s</ul>") % (attr_name, attr_value)
for attr_name, attr_value in product_description.items() if attr_value])
return description
@api.model
def barcode_lookup(self, barcode=False):
api_key = barcode_lookup_service.get_barcode_lookup_key(self)
if not api_key:
return False
if barcode and not self.env.context.get("skip_barcode_check", False) \
and not any(check_barcode_encoding(barcode, enc) for enc in ("upca", "ean8", "ean13")):
return False
params = {'barcode': barcode, 'key': api_key}
response = barcode_lookup_service.barcode_lookup_request('https://api.barcodelookup.com/v3/products', params)
return response.json() if response else {}
@api.model_create_multi
def create(self, vals_list):
products = super().create(vals_list)
for product, vals in zip(products, vals_list):
if 'is_published' in product and 'is_published' not in vals and not self.env.context.get('website_published') and product.public_categ_ids:
product.is_published = True
if 'available_in_pos' in product and not self.env.context.get('can_be_sold') and product.sudo().pos_categ_ids:
product.available_in_pos = True
return products
def copy_data(self, default=None):
vals_list = super().copy_data(default)
if 'is_published' in self:
for vals in vals_list:
vals['is_published'] = False
return vals_list
def _set_lookup_image(self, product, img):
image = base64.b64encode(img.content)
if not product.image_1920:
product.image_1920 = image
return True
return image