sligro: Return substitute products when available

This commit is contained in:
polyfloyd 2025-04-21 19:37:03 +02:00
parent d9fc9ac084
commit 7a8997835d

View file

@ -8,7 +8,7 @@ import os
import requests import requests
import logging import logging
from supermarktconnector.ah import AHConnector from supermarktconnector.ah import AHConnector
from typing import List from typing import List, Optional
vat = Decimal('1.09') vat = Decimal('1.09')
@ -26,6 +26,7 @@ class Product:
gtin: str gtin: str
units: int units: int
aliases: List[str] aliases: List[str]
replacement: Optional["Product"] = None
def __str__(self): def __str__(self):
return self.name return self.name
@ -84,25 +85,46 @@ def sligro_client():
return _sess return _sess
def sligro_get_by_gtin(gtin13): def sligro_get_by_sku(sku, _recurse=0):
assert re.match(r'^\d{4,12}$', sku)
return _sligro_get(sku, _recurse=_recurse)
def sligro_get_by_gtin(gtin13, _recurse=0):
assert re.match(r'^\d{13}$', gtin13) assert re.match(r'^\d{13}$', gtin13)
gtin14 = f'{gtin13:0>14}' gtin14 = f'{gtin13:0>14}'
# The search feature of the website returns results in JSON and handles GTIN formats. Neat! # The search feature of the website returns results in JSON and handles GTIN formats. Neat!
# However, it can be a bit picky about leading zeros, so we try to query with GTIN14 as that is # However, it can be a bit picky about leading zeros, so we try to query with GTIN14 as that is
# what works in the most cases. Sometimes GTIN13 is still required though # what works in the most cases. Sometimes GTIN13 is still required though
for gtin_whatever in [gtin14, gtin13]: for gtin_whatever in [gtin14, gtin13]:
response = requests.get(f'https://www.sligro.nl/api/product-overview/sligro-nl/nl/query/3?term={gtin_whatever}') try:
response.raise_for_status() return _sligro_get(gtin_whatever, _recurse=_recurse)
body = response.json() except ProductNotFoundError:
if 'products' in body: continue
break raise ProductNotFoundError()
else:
def _sligro_get(query, *, _recurse=0):
# A runaway recursion could DoS the sligro API, which is impolite :)
assert _recurse <= 1
response = requests.get(f'https://www.sligro.nl/api/product-overview/sligro-nl/nl/query/3?term={query}')
response.raise_for_status()
body = response.json()
if 'products' not in body:
raise ProductNotFoundError() raise ProductNotFoundError()
product = body['products'][0] if len(body['products']) > 1:
product = next(filter(lambda p: 'productReferenceReplace' not in p, body['products']))
else:
product = body['products'][0]
sku = product["code"] sku = product["code"]
replacement = None
if 'productReferenceReplace' in product:
replacement = sligro_get_by_sku(product['productReferenceReplace'][0], _recurse=_recurse+1)
# Query the product page itself, there is more info that we need on there. In the website, the # Query the product page itself, there is more info that we need on there. In the website, the
# final path element is a derivation of the contentDescription field. It must be present, but # final path element is a derivation of the contentDescription field. It must be present, but
# matches anything. # matches anything.
@ -132,12 +154,16 @@ def sligro_get_by_gtin(gtin13):
else: else:
price_obj = pricing['price'] price_obj = pricing['price']
name = product["name"]
name = re.sub(' - Wordt binnenkort vervangen door.+$', '', name)
return Product( return Product(
name=f'{product["brandName"]} {product["name"]} ({volume})', name=f'{product["brandName"]} {name} ({volume})',
price=Decimal(price_obj['value']) * vat, price=Decimal(price_obj['value']) * vat,
gtin=gtin13, gtin=product['gtin'].lstrip('0'),
units=units, units=units,
aliases=[sub_gtin] if sub_gtin else [], aliases=[sub_gtin] if sub_gtin else [],
replacement=replacement,
) )