|
@@ -1,6 +1,9 @@
|
|
import hashlib
|
|
import hashlib
|
|
import struct
|
|
import struct
|
|
from collections import defaultdict
|
|
from collections import defaultdict
|
|
|
|
+from datetime import datetime
|
|
|
|
+
|
|
|
|
+from dicttoxml import dicttoxml
|
|
|
|
|
|
from telecaster.Clients.PrestaShopClient import PrestaShopClient
|
|
from telecaster.Clients.PrestaShopClient import PrestaShopClient
|
|
|
|
|
|
@@ -38,7 +41,7 @@ class CosmohomeClient(PrestaShopClient):
|
|
cache_dict = self.caches[cache_key]
|
|
cache_dict = self.caches[cache_key]
|
|
for item in values:
|
|
for item in values:
|
|
key = get_nested(item, id_key_path)
|
|
key = get_nested(item, id_key_path)
|
|
- cache_dict[key] = item
|
|
|
|
|
|
+ cache_dict[str(key)] = item
|
|
|
|
|
|
def load_cache(self):
|
|
def load_cache(self):
|
|
categories = self.get_categories() # {'display': '[id,name, link_rewrite, id_parent]'})
|
|
categories = self.get_categories() # {'display': '[id,name, link_rewrite, id_parent]'})
|
|
@@ -46,20 +49,14 @@ class CosmohomeClient(PrestaShopClient):
|
|
feature_values = self.get_feature_values()
|
|
feature_values = self.get_feature_values()
|
|
product_options = self.get_product_options()
|
|
product_options = self.get_product_options()
|
|
product_option_values = self.get_product_option_values()
|
|
product_option_values = self.get_product_option_values()
|
|
|
|
+ specific_prices = self.get_specific_prices()
|
|
|
|
+
|
|
|
|
+ self.update_cache('specific_prices', specific_prices, ['id_product_attribute'])
|
|
|
|
+ self.update_cache('specific_prices', specific_prices, ['id_product'])
|
|
# tax_rule_groups = self.get_tax_rule_groups()
|
|
# tax_rule_groups = self.get_tax_rule_groups()
|
|
# tax_rules = self.get_tax_rules()
|
|
# tax_rules = self.get_tax_rules()
|
|
# taxes = self.get_taxes()
|
|
# taxes = self.get_taxes()
|
|
|
|
|
|
- categories = map(lambda x: x['category'], categories)
|
|
|
|
- features = map(lambda x: x['product_feature'], features)
|
|
|
|
- feature_values = map(lambda x: x['product_feature_value'], feature_values)
|
|
|
|
- product_options = map(lambda x: x['product_option'], product_options)
|
|
|
|
- product_option_values = map(lambda x: x['product_option_value'],
|
|
|
|
- product_option_values)
|
|
|
|
- # tax_rule_groups = map(lambda x: x['tax_rule_group'], tax_rule_groups)
|
|
|
|
- # tax_rules = map(lambda x: x['tax_rule'], tax_rules)
|
|
|
|
- # taxes = map(lambda x: x['tax'], taxes)
|
|
|
|
-
|
|
|
|
self.update_cache('categories', categories, ['id'])
|
|
self.update_cache('categories', categories, ['id'])
|
|
self.update_cache('features', features, ['id'])
|
|
self.update_cache('features', features, ['id'])
|
|
self.update_cache('feature_values', feature_values, ['id'])
|
|
self.update_cache('feature_values', feature_values, ['id'])
|
|
@@ -69,22 +66,13 @@ class CosmohomeClient(PrestaShopClient):
|
|
# self.update_cache('tax_rules', tax_rules, ['id'])
|
|
# self.update_cache('tax_rules', tax_rules, ['id'])
|
|
# self.update_cache('taxes', taxes, ['id'])
|
|
# self.update_cache('taxes', taxes, ['id'])
|
|
|
|
|
|
- def parse_product(self, product, combinations):
|
|
|
|
- product = product.get('product')
|
|
|
|
|
|
+ def parse_product(self, product, combinations=None):
|
|
product_combinations = self.get_product_combinations(product, combinations)
|
|
product_combinations = self.get_product_combinations(product, combinations)
|
|
|
|
|
|
- # Move this to pre-computed cache when perform batches
|
|
|
|
- id_product_attributes = [c.get('id') for c in product_combinations]
|
|
|
|
- sp = self.get_specific_prices(
|
|
|
|
- {'filter[id_product_attribute]': f"{'|'.join(id_product_attributes)}"})
|
|
|
|
- sp = list(map(lambda x: x['specific_price'], sp))
|
|
|
|
- self.update_cache('specific_prices', sp, ['id_product_attribute'])
|
|
|
|
- self.update_cache('specific_prices', sp, ['id_product'])
|
|
|
|
- ## END OF Specific prices calculations
|
|
|
|
-
|
|
|
|
serialized_product = {
|
|
serialized_product = {
|
|
"productId": product.get('id'),
|
|
"productId": product.get('id'),
|
|
"title": self.sanitize_language(product.get('name')),
|
|
"title": self.sanitize_language(product.get('name')),
|
|
|
|
+ # TODO FIX PRODUCT_URL - it has no domain + path!!
|
|
"productURL": product.get('link_rewrite'),
|
|
"productURL": product.get('link_rewrite'),
|
|
# add to the url the attributes like #/attr_id/attr_id/...
|
|
# add to the url the attributes like #/attr_id/attr_id/...
|
|
"Category": self.get_category_tree(product.get('id_category_default'),
|
|
"Category": self.get_category_tree(product.get('id_category_default'),
|
|
@@ -108,10 +96,13 @@ class CosmohomeClient(PrestaShopClient):
|
|
for combination in product_combinations:
|
|
for combination in product_combinations:
|
|
serialized_combinations.append({
|
|
serialized_combinations.append({
|
|
**serialized_product,
|
|
**serialized_product,
|
|
- "productId": product.get('id') + numHash(combination.get('reference'), 10),
|
|
|
|
|
|
+ "productId": str(product.get('id')) + numHash(
|
|
|
|
+ combination.get('reference'), 10),
|
|
"Barcode": combination.get('ean13') or serialized_product.get(
|
|
"Barcode": combination.get('ean13') or serialized_product.get(
|
|
'Barcode'),
|
|
'Barcode'),
|
|
- "Name": combination.get('name'),
|
|
|
|
|
|
+ # TODO: Product-URL should be overriden for combination
|
|
|
|
+ # TODO: Name is not a good idea it does not exist
|
|
|
|
+ # "Name": combination.get('name'),
|
|
"weight": product.get('weight'),
|
|
"weight": product.get('weight'),
|
|
"MPN": combination.get('reference') or combination.get(
|
|
"MPN": combination.get('reference') or combination.get(
|
|
'mpn') or serialized_product.get('MPN'),
|
|
'mpn') or serialized_product.get('MPN'),
|
|
@@ -127,7 +118,8 @@ class CosmohomeClient(PrestaShopClient):
|
|
"Size": self.get_size(product) or serialized_product.get('Size'),
|
|
"Size": self.get_size(product) or serialized_product.get('Size'),
|
|
})
|
|
})
|
|
|
|
|
|
- return [serialized_product] if not combinations else serialized_combinations
|
|
|
|
|
|
+ return [
|
|
|
|
+ serialized_product] if not serialized_combinations else serialized_combinations
|
|
|
|
|
|
def sanitize_language(self, value):
|
|
def sanitize_language(self, value):
|
|
if isinstance(value, str):
|
|
if isinstance(value, str):
|
|
@@ -139,7 +131,7 @@ class CosmohomeClient(PrestaShopClient):
|
|
|
|
|
|
def get_category_tree(self, category_id, min_depth=0):
|
|
def get_category_tree(self, category_id, min_depth=0):
|
|
categories = self.caches['categories']
|
|
categories = self.caches['categories']
|
|
- category = categories[category_id]
|
|
|
|
|
|
+ category = categories.get(category_id)
|
|
category_tree = []
|
|
category_tree = []
|
|
while category and int(category.get('level_depth')) > min_depth:
|
|
while category and int(category.get('level_depth')) > min_depth:
|
|
if category:
|
|
if category:
|
|
@@ -148,7 +140,7 @@ class CosmohomeClient(PrestaShopClient):
|
|
else:
|
|
else:
|
|
break
|
|
break
|
|
|
|
|
|
- return [self.sanitize_language(c) for c in category_tree]
|
|
|
|
|
|
+ return ' > '.join([self.sanitize_language(c) for c in category_tree])
|
|
|
|
|
|
def generate_image_url(self, product):
|
|
def generate_image_url(self, product):
|
|
image_id = product.get('id_default_image')
|
|
image_id = product.get('id_default_image')
|
|
@@ -156,9 +148,8 @@ class CosmohomeClient(PrestaShopClient):
|
|
return f"{CANONICAL_DOMAIN}/{image_id}-thickbox_default/{link_rewrite}.jpg"
|
|
return f"{CANONICAL_DOMAIN}/{image_id}-thickbox_default/{link_rewrite}.jpg"
|
|
|
|
|
|
def get_color(self, product):
|
|
def get_color(self, product):
|
|
- options = product.get('associations').get('product_option_values')
|
|
|
|
|
|
+ options = product.get('associations').get('product_option_values') or []
|
|
for option in options:
|
|
for option in options:
|
|
- option = option.get('product_option_value')
|
|
|
|
option_value = self.caches['product_option_values'].get(option.get('id'))
|
|
option_value = self.caches['product_option_values'].get(option.get('id'))
|
|
if option_value and option_value.get(
|
|
if option_value and option_value.get(
|
|
'id_attribute_group') in COLOR_PRODUCT_OPTION_IDS:
|
|
'id_attribute_group') in COLOR_PRODUCT_OPTION_IDS:
|
|
@@ -166,9 +157,8 @@ class CosmohomeClient(PrestaShopClient):
|
|
return None
|
|
return None
|
|
|
|
|
|
def get_size(self, product):
|
|
def get_size(self, product):
|
|
- options = product.get('associations').get('product_option_values')
|
|
|
|
|
|
+ options = product.get('associations').get('product_option_values') or []
|
|
for option in options:
|
|
for option in options:
|
|
- option = option.get('product_option_value')
|
|
|
|
option_value = self.caches['product_option_values'].get(option.get('id'))
|
|
option_value = self.caches['product_option_values'].get(option.get('id'))
|
|
if option_value and option_value.get(
|
|
if option_value and option_value.get(
|
|
'id_attribute_group') in SIZE_PRODUCT_OPTION_IDS:
|
|
'id_attribute_group') in SIZE_PRODUCT_OPTION_IDS:
|
|
@@ -177,15 +167,15 @@ class CosmohomeClient(PrestaShopClient):
|
|
|
|
|
|
def get_product_combinations(self, product, combinations):
|
|
def get_product_combinations(self, product, combinations):
|
|
if combinations:
|
|
if combinations:
|
|
- combinations = map(lambda x: x['combination'], combinations)
|
|
|
|
self.update_cache('combinations', combinations, ['id'])
|
|
self.update_cache('combinations', combinations, ['id'])
|
|
|
|
|
|
combinations = self.caches['combinations']
|
|
combinations = self.caches['combinations']
|
|
|
|
|
|
product_combinations = product.get('associations').get('combinations')
|
|
product_combinations = product.get('associations').get('combinations')
|
|
- product_combination_ids = map(lambda x: x['combination']['id'],
|
|
|
|
- product_combinations)
|
|
|
|
|
|
+ if not product_combinations:
|
|
|
|
+ return []
|
|
|
|
|
|
|
|
+ product_combination_ids = map(lambda x: x['id'], product_combinations)
|
|
return [combinations.get(cid) for cid in product_combination_ids]
|
|
return [combinations.get(cid) for cid in product_combination_ids]
|
|
|
|
|
|
def calculate_final_price(self, product, price=None):
|
|
def calculate_final_price(self, product, price=None):
|
|
@@ -195,10 +185,82 @@ class CosmohomeClient(PrestaShopClient):
|
|
price_with_tax = float(price) * (1 + TAX)
|
|
price_with_tax = float(price) * (1 + TAX)
|
|
|
|
|
|
sp = self.caches['specific_prices'].get(product.get('id'))
|
|
sp = self.caches['specific_prices'].get(product.get('id'))
|
|
- discount = sp.get('reduction')
|
|
|
|
- type = sp.get('reduction_type')
|
|
|
|
- if type == 'percentage':
|
|
|
|
- final_price_with_tax = price_with_tax * (1 - float(discount))
|
|
|
|
|
|
+ if sp:
|
|
|
|
+ discount = sp.get('reduction')
|
|
|
|
+ type = sp.get('reduction_type')
|
|
|
|
+ if type == 'percentage':
|
|
|
|
+ final_price_with_tax = price_with_tax * (1 - float(discount))
|
|
|
|
+ else:
|
|
|
|
+ final_price_with_tax = price_with_tax - float(discount)
|
|
else:
|
|
else:
|
|
- final_price_with_tax = price_with_tax - float(discount)
|
|
|
|
|
|
+ final_price_with_tax = price_with_tax
|
|
return round(final_price_with_tax, 2)
|
|
return round(final_price_with_tax, 2)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def batched_array(array, batch_size=50):
|
|
|
|
+ l = len(array)
|
|
|
|
+ offset = 0
|
|
|
|
+ while offset < l:
|
|
|
|
+ yield array[offset:offset + batch_size]
|
|
|
|
+ offset += batch_size
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def batch_products(client, params=None, batch_size=200, hard_limit=100000):
|
|
|
|
+ offset = 0
|
|
|
|
+ while True:
|
|
|
|
+ products = client.get_products({
|
|
|
|
+ 'display': 'full',
|
|
|
|
+ **(params if params else {}),
|
|
|
|
+ 'limit': f"{offset},{batch_size}",
|
|
|
|
+ 'sort': "[id_ASC]"
|
|
|
|
+ })
|
|
|
|
+ if not products:
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+ combinations = client.get_combinations({
|
|
|
|
+ 'filter[id_product]': f"[{'|'.join([str(p.get('id')) for p in products])}]"
|
|
|
|
+ })
|
|
|
|
+ client.update_cache("combinations", combinations, ['id'])
|
|
|
|
+
|
|
|
|
+ offset += len(products)
|
|
|
|
+ print(f"Fetched {offset} products")
|
|
|
|
+ yield from products
|
|
|
|
+ if offset >= hard_limit:
|
|
|
|
+ print(f"Hard limit reached {hard_limit}")
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def generate_xml():
|
|
|
|
+ client = CosmohomeClient()
|
|
|
|
+ print("Loading cache")
|
|
|
|
+ client.load_cache()
|
|
|
|
+ print("Cache loaded")
|
|
|
|
+
|
|
|
|
+ products = []
|
|
|
|
+ for product in batch_products(client, params={'filter[active]': '1'},
|
|
|
|
+ batch_size=200):
|
|
|
|
+ products += client.parse_product(product)
|
|
|
|
+
|
|
|
|
+ xml = dicttoxml(
|
|
|
|
+ products,
|
|
|
|
+ cdata=True,
|
|
|
|
+ xml_declaration=False,
|
|
|
|
+ custom_root="products",
|
|
|
|
+ attr_type=False,
|
|
|
|
+ item_func=lambda x: x[:-1]
|
|
|
|
+ )
|
|
|
|
+ xml_string = xml.decode('utf-8')
|
|
|
|
+ final_xml = (
|
|
|
|
+ ''
|
|
|
|
+ '<store name="cosmohome.gr" url="https://cosmohome.gr" encoding="utf8">'
|
|
|
|
+ f'<date>{datetime.now().strftime("%Y-%m-%d %H:%M")}</date>'
|
|
|
|
+ f"{xml_string}"
|
|
|
|
+ '</store>'
|
|
|
|
+ )
|
|
|
|
+ return final_xml
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def generate_xml_task():
|
|
|
|
+ xml = generate_xml()
|
|
|
|
+ with open('cosmohome_products.xml', 'w') as file:
|
|
|
|
+ file.write(xml)
|