SamiKoen Claude commited on
Commit
dd6adb2
·
1 Parent(s): a7c5d42

Implement advanced variant/SKU system with new Trek XML fields

Browse files

- Add rootProductStockCode, isOptionOfAProduct, isOptionedProduct to Trek XML parsing
- Expand product tuple structure (12 fields: +root_stock, +is_option, +is_optioned)
- Implement proper XML parsing for SKU lookup (vs regex patterns)
- Add 3-level SKU matching: variants → main products → root lookup
- Fix field names: price→priceTaxWithCur, producturl→productLink
- Add fallback regex method for XML parsing errors
- Successfully tested: 501 variants, 226 products with variants

Enables precise warehouse ProductCode to Trek XML stockCode matching
and better variant/main product relationship handling.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>

Files changed (2) hide show
  1. app.py +13 -3
  2. smart_warehouse_with_price.py +67 -7
app.py CHANGED
@@ -400,6 +400,16 @@ try:
400
  stock_code_element = item.find('stockCode')
401
  stock_code = stock_code_element.text if stock_code_element is not None and stock_code_element.text else ""
402
 
 
 
 
 
 
 
 
 
 
 
403
  # Fiyat formatting (kampanya fiyatı veya normal fiyat)
404
  try:
405
  price_float = float(final_price_str)
@@ -435,9 +445,9 @@ try:
435
  except:
436
  price_eft = ""
437
 
438
- # Ürün bilgilerini tuple olarak oluştur (resim URL'si, kategoriler ve stockCode eklendi)
439
- # Index mapping: 0=stock_amount, 1=price, 2=product_link, 3=price_eft, 4=stock_number, 5=picture_url, 6=category_tree, 7=category_label, 8=stock_code
440
- item_info = (stock_amount, price, product_link, price_eft, str(stock_number), picture_url, category_tree, category_label, stock_code)
441
  products.append((name, item_info, full_name))
442
 
443
  # Summary disabled for production
 
400
  stock_code_element = item.find('stockCode')
401
  stock_code = stock_code_element.text if stock_code_element is not None and stock_code_element.text else ""
402
 
403
+ # Yeni variant/main product ilişki alanları
404
+ root_product_stock_code_element = item.find('rootProductStockCode')
405
+ root_product_stock_code = root_product_stock_code_element.text if root_product_stock_code_element is not None and root_product_stock_code_element.text else ""
406
+
407
+ is_option_of_product_element = item.find('isOptionOfAProduct')
408
+ is_option_of_product = is_option_of_product_element.text if is_option_of_product_element is not None and is_option_of_product_element.text else "0"
409
+
410
+ is_optioned_product_element = item.find('isOptionedProduct')
411
+ is_optioned_product = is_optioned_product_element.text if is_optioned_product_element is not None and is_optioned_product_element.text else "0"
412
+
413
  # Fiyat formatting (kampanya fiyatı veya normal fiyat)
414
  try:
415
  price_float = float(final_price_str)
 
445
  except:
446
  price_eft = ""
447
 
448
+ # Ürün bilgilerini tuple olarak oluştur (resim URL'si, kategoriler, stockCode ve variant bilgileri eklendi)
449
+ # Index mapping: 0=stock_amount, 1=price, 2=product_link, 3=price_eft, 4=stock_number, 5=picture_url, 6=category_tree, 7=category_label, 8=stock_code, 9=root_product_stock_code, 10=is_option_of_product, 11=is_optioned_product
450
+ item_info = (stock_amount, price, product_link, price_eft, str(stock_number), picture_url, category_tree, category_label, stock_code, root_product_stock_code, is_option_of_product, is_optioned_product)
451
  products.append((name, item_info, full_name))
452
 
453
  # Summary disabled for production
smart_warehouse_with_price.py CHANGED
@@ -38,11 +38,16 @@ def get_cached_trek_xml():
38
  return None
39
 
40
  def get_product_price_and_link_by_sku(product_code):
41
- """Get price and link from Trek XML using 2-level SKU matching (NEW SAFE METHOD)
42
- Level 1: Search in variants (real factory SKU)
43
- Level 2: Search in main products (for non-variant products)
 
 
44
  """
45
  try:
 
 
 
46
  # Get cached Trek XML
47
  xml_content = get_cached_trek_xml()
48
  if not xml_content:
@@ -52,13 +57,70 @@ def get_product_price_and_link_by_sku(product_code):
52
  if isinstance(xml_content, bytes):
53
  xml_content = xml_content.decode('utf-8')
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  # Level 1: Search in variants first (isOptionOfAProduct=1)
56
- # Look for: <isOptionOfAProduct>1</isOptionOfAProduct> followed by our stockCode
57
  variant_pattern = rf'<isOptionOfAProduct>1</isOptionOfAProduct>.*?<stockCode><!\[CDATA\[{re.escape(product_code)}\]\]></stockCode>.*?(?=<item>|$)'
58
  variant_match = re.search(variant_pattern, xml_content, re.DOTALL)
59
 
60
  if variant_match:
61
- # Found in variants, extract price and link from this section
62
  section = variant_match.group(0)
63
  price_match = re.search(r'<price><!\[CDATA\[(.*?)\]\]></price>', section)
64
  link_match = re.search(r'<producturl><!\[CDATA\[(.*?)\]\]></producturl>', section)
@@ -71,7 +133,6 @@ def get_product_price_and_link_by_sku(product_code):
71
  main_match = re.search(main_pattern, xml_content, re.DOTALL)
72
 
73
  if main_match:
74
- # Found in main products, extract price and link
75
  section = main_match.group(0)
76
  price_match = re.search(r'<price><!\[CDATA\[(.*?)\]\]></price>', section)
77
  link_match = re.search(r'<producturl><!\[CDATA\[(.*?)\]\]></producturl>', section)
@@ -79,7 +140,6 @@ def get_product_price_and_link_by_sku(product_code):
79
  if price_match and link_match:
80
  return price_match.group(1), link_match.group(1)
81
 
82
- # Not found
83
  return None, None
84
 
85
  except Exception as e:
 
38
  return None
39
 
40
  def get_product_price_and_link_by_sku(product_code):
41
+ """Get price and link from Trek XML using improved SKU matching with new XML fields
42
+ Uses: stockCode, rootProductStockCode, isOptionOfAProduct, isOptionedProduct
43
+ Level 1: Search variants by stockCode where isOptionOfAProduct=1
44
+ Level 2: Search main products by stockCode where isOptionOfAProduct=0
45
+ Level 3: Search by rootProductStockCode for variant-to-main mapping
46
  """
47
  try:
48
+ # Import XML parsing for cleaner approach
49
+ import xml.etree.ElementTree as ET
50
+
51
  # Get cached Trek XML
52
  xml_content = get_cached_trek_xml()
53
  if not xml_content:
 
57
  if isinstance(xml_content, bytes):
58
  xml_content = xml_content.decode('utf-8')
59
 
60
+ # Parse XML properly instead of regex
61
+ try:
62
+ root = ET.fromstring(xml_content)
63
+ except:
64
+ # Fallback to regex if XML parsing fails
65
+ return get_product_price_and_link_by_sku_regex(product_code)
66
+
67
+ # Level 1: Search variants first (isOptionOfAProduct=1)
68
+ for item in root.findall('.//item'):
69
+ is_option_element = item.find('isOptionOfAProduct')
70
+ stock_code_element = item.find('stockCode')
71
+
72
+ if (is_option_element is not None and is_option_element.text == '1' and
73
+ stock_code_element is not None and stock_code_element.text and stock_code_element.text.strip() == product_code):
74
+
75
+ price_element = item.find('priceTaxWithCur')
76
+ link_element = item.find('productLink')
77
+
78
+ if price_element is not None and link_element is not None:
79
+ return price_element.text, link_element.text
80
+
81
+ # Level 2: Search main products (isOptionOfAProduct=0)
82
+ for item in root.findall('.//item'):
83
+ is_option_element = item.find('isOptionOfAProduct')
84
+ stock_code_element = item.find('stockCode')
85
+
86
+ if (is_option_element is not None and is_option_element.text == '0' and
87
+ stock_code_element is not None and stock_code_element.text and stock_code_element.text.strip() == product_code):
88
+
89
+ price_element = item.find('priceTaxWithCur')
90
+ link_element = item.find('productLink')
91
+
92
+ if price_element is not None and link_element is not None:
93
+ return price_element.text, link_element.text
94
+
95
+ # Level 3: Search by rootProductStockCode (variant parent lookup)
96
+ for item in root.findall('.//item'):
97
+ root_stock_element = item.find('rootProductStockCode')
98
+
99
+ if (root_stock_element is not None and root_stock_element.text and root_stock_element.text.strip() == product_code):
100
+ price_element = item.find('priceTaxWithCur')
101
+ link_element = item.find('productLink')
102
+
103
+ if price_element is not None and link_element is not None:
104
+ return price_element.text, link_element.text
105
+
106
+ # Not found
107
+ return None, None
108
+
109
+ except Exception as e:
110
+ return None, None
111
+
112
+ def get_product_price_and_link_by_sku_regex(product_code):
113
+ """Fallback regex method for SKU lookup if XML parsing fails"""
114
+ try:
115
+ xml_content = get_cached_trek_xml()
116
+ if isinstance(xml_content, bytes):
117
+ xml_content = xml_content.decode('utf-8')
118
+
119
  # Level 1: Search in variants first (isOptionOfAProduct=1)
 
120
  variant_pattern = rf'<isOptionOfAProduct>1</isOptionOfAProduct>.*?<stockCode><!\[CDATA\[{re.escape(product_code)}\]\]></stockCode>.*?(?=<item>|$)'
121
  variant_match = re.search(variant_pattern, xml_content, re.DOTALL)
122
 
123
  if variant_match:
 
124
  section = variant_match.group(0)
125
  price_match = re.search(r'<price><!\[CDATA\[(.*?)\]\]></price>', section)
126
  link_match = re.search(r'<producturl><!\[CDATA\[(.*?)\]\]></producturl>', section)
 
133
  main_match = re.search(main_pattern, xml_content, re.DOTALL)
134
 
135
  if main_match:
 
136
  section = main_match.group(0)
137
  price_match = re.search(r'<price><!\[CDATA\[(.*?)\]\]></price>', section)
138
  link_match = re.search(r'<producturl><!\[CDATA\[(.*?)\]\]></producturl>', section)
 
140
  if price_match and link_match:
141
  return price_match.group(1), link_match.group(1)
142
 
 
143
  return None, None
144
 
145
  except Exception as e: