mirror of
https://github.com/thekiwismarthome/shopping-list-manager.git
synced 2026-06-30 21:46:30 +00:00
Compare commits
11 Commits
d50bd39210
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f86b6f485 | |||
| 2d02a68fe3 | |||
| c971b89779 | |||
| 42746c86b8 | |||
| 6a344914a2 | |||
| 8d9dd5bf6a | |||
| c48c96e133 | |||
| 1dea960449 | |||
| fd5b17ced8 | |||
| 0468893919 | |||
| 55d2f2d31d |
@@ -1,8 +1,8 @@
|
|||||||
"""Shopping List Manager integration for Home Assistant."""
|
"""Shopping List Manager integration for Home Assistant."""
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
@@ -17,6 +17,14 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
DATA_STORAGE = f"{DOMAIN}_storage"
|
DATA_STORAGE = f"{DOMAIN}_storage"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_manifest_version(component_path: str) -> str:
|
||||||
|
"""Load the integration version from manifest.json."""
|
||||||
|
with open(os.path.join(component_path, "manifest.json"), encoding="utf-8") as manifest_file:
|
||||||
|
manifest = json.load(manifest_file)
|
||||||
|
|
||||||
|
return manifest.get("version", "unknown")
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the Shopping List Manager component from yaml (not used)."""
|
"""Set up the Shopping List Manager component from yaml (not used)."""
|
||||||
# This integration doesn't support YAML configuration
|
# This integration doesn't support YAML configuration
|
||||||
@@ -44,16 +52,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
image_handler = ImageHandler(hass, config_path)
|
image_handler = ImageHandler(hass, config_path)
|
||||||
|
|
||||||
# Read installed version from manifest
|
# Read installed version from manifest
|
||||||
import json as _json
|
version = await hass.async_add_executor_job(_load_manifest_version, component_path)
|
||||||
with open(os.path.join(component_path, "manifest.json")) as _f:
|
|
||||||
_manifest = _json.load(_f)
|
|
||||||
|
|
||||||
# Store instances in hass.data
|
# Store instances in hass.data
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][DATA_STORAGE] = storage
|
hass.data[DOMAIN][DATA_STORAGE] = storage
|
||||||
hass.data[DOMAIN]["image_handler"] = image_handler
|
hass.data[DOMAIN]["image_handler"] = image_handler
|
||||||
hass.data[DOMAIN]["country"] = country
|
hass.data[DOMAIN]["country"] = country
|
||||||
hass.data[DOMAIN]["version"] = _manifest.get("version", "unknown")
|
hass.data[DOMAIN]["version"] = version
|
||||||
|
|
||||||
# Register update listener for options changes
|
# Register update listener for options changes
|
||||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
|
|
||||||
def __init__(self, config_entry):
|
def __init__(self, config_entry):
|
||||||
"""Initialize options flow."""
|
"""Initialize options flow."""
|
||||||
self.config_entry = config_entry
|
self._config_entry = config_entry
|
||||||
|
|
||||||
async def async_step_init(self, user_input=None):
|
async def async_step_init(self, user_input=None):
|
||||||
"""Manage the options."""
|
"""Manage the options."""
|
||||||
@@ -59,9 +59,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
return self.async_create_entry(title="", data=user_input)
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
# Get current settings
|
# Get current settings
|
||||||
current_country = self.config_entry.options.get(
|
current_country = self._config_entry.options.get(
|
||||||
"country",
|
"country",
|
||||||
self.config_entry.data.get("country", "NZ")
|
self._config_entry.data.get("country", "NZ")
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
@@ -73,18 +73,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
"US": "United States",
|
"US": "United States",
|
||||||
"GB": "United Kingdom",
|
"GB": "United Kingdom",
|
||||||
"CA": "Canada",
|
"CA": "Canada",
|
||||||
|
"BE": "Belgium (Dutch)",
|
||||||
}),
|
}),
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
"enable_price_tracking",
|
"enable_price_tracking",
|
||||||
default=self.config_entry.options.get("enable_price_tracking", True)
|
default=self._config_entry.options.get("enable_price_tracking", True)
|
||||||
): bool,
|
): bool,
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
"enable_image_search",
|
"enable_image_search",
|
||||||
default=self.config_entry.options.get("enable_image_search", True)
|
default=self._config_entry.options.get("enable_image_search", True)
|
||||||
): bool,
|
): bool,
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
"metric_units_only",
|
"metric_units_only",
|
||||||
default=self.config_entry.options.get("metric_units_only", True)
|
default=self._config_entry.options.get("metric_units_only", True)
|
||||||
): bool,
|
): bool,
|
||||||
}),
|
}),
|
||||||
description_placeholders={
|
description_placeholders={
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"region": "BE",
|
||||||
|
"language": "nl-BE",
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "produce",
|
||||||
|
"name": "Groenten & Fruit",
|
||||||
|
"icon": "mdi:fruit-cherries",
|
||||||
|
"color": "#4CAF50",
|
||||||
|
"sort_order": 1,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dairy",
|
||||||
|
"name": "Zuivel & Eieren",
|
||||||
|
"icon": "mdi:cheese",
|
||||||
|
"color": "#FFC107",
|
||||||
|
"sort_order": 2,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "meat",
|
||||||
|
"name": "Vlees, Vis & Gevogelte",
|
||||||
|
"icon": "mdi:food-steak",
|
||||||
|
"color": "#F44336",
|
||||||
|
"sort_order": 3,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bakery",
|
||||||
|
"name": "Bakkerij",
|
||||||
|
"icon": "mdi:bread-slice",
|
||||||
|
"color": "#FF9800",
|
||||||
|
"sort_order": 4,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "frozen",
|
||||||
|
"name": "Diepvries",
|
||||||
|
"icon": "mdi:snowflake",
|
||||||
|
"color": "#2196F3",
|
||||||
|
"sort_order": 5,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pantry",
|
||||||
|
"name": "Voorraadkast",
|
||||||
|
"icon": "mdi:package-variant",
|
||||||
|
"color": "#795548",
|
||||||
|
"sort_order": 6,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "beverages",
|
||||||
|
"name": "Dranken",
|
||||||
|
"icon": "mdi:cup",
|
||||||
|
"color": "#00BCD4",
|
||||||
|
"sort_order": 7,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "snacks",
|
||||||
|
"name": "Snacks & Koekjes",
|
||||||
|
"icon": "mdi:food-apple",
|
||||||
|
"color": "#E91E63",
|
||||||
|
"sort_order": 8,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "household",
|
||||||
|
"name": "Huishouden",
|
||||||
|
"icon": "mdi:spray-bottle",
|
||||||
|
"color": "#9C27B0",
|
||||||
|
"sort_order": 9,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "health",
|
||||||
|
"name": "Verzorging",
|
||||||
|
"icon": "mdi:heart-pulse",
|
||||||
|
"color": "#009688",
|
||||||
|
"sort_order": 10,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pet",
|
||||||
|
"name": "Dieren",
|
||||||
|
"icon": "mdi:paw",
|
||||||
|
"color": "#FF5722",
|
||||||
|
"sort_order": 11,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "baby",
|
||||||
|
"name": "Baby",
|
||||||
|
"icon": "mdi:baby-face",
|
||||||
|
"color": "#F48FB1",
|
||||||
|
"sort_order": 12,
|
||||||
|
"system": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "other",
|
||||||
|
"name": "Overig",
|
||||||
|
"icon": "mdi:dots-horizontal",
|
||||||
|
"color": "#9E9E9E",
|
||||||
|
"sort_order": 99,
|
||||||
|
"system": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,941 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"region": "BE",
|
||||||
|
"language": "nl-BE",
|
||||||
|
"currency": "EUR",
|
||||||
|
"last_updated": "2026-06-14",
|
||||||
|
"description": "Belgium Dutch grocery catalog with common Belgian supermarket products and local brands",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": "prod_be_halfvolle_melk",
|
||||||
|
"name": "Halfvolle melk",
|
||||||
|
"category_id": "dairy",
|
||||||
|
"aliases": ["melk", "halfvolle melk", "halfvol"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 1.15,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "halfvolle_melk",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": ["milk"],
|
||||||
|
"substitution_group": "milk_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_volle_melk",
|
||||||
|
"name": "Volle melk",
|
||||||
|
"category_id": "dairy",
|
||||||
|
"aliases": ["melk", "volle melk", "vollemelk"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 1.25,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "volle_melk",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": ["milk"],
|
||||||
|
"substitution_group": "milk_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_havermelk",
|
||||||
|
"name": "Havermelk",
|
||||||
|
"category_id": "dairy",
|
||||||
|
"aliases": ["haverdrink", "havermelk", "plantaardige melk"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 2.29,
|
||||||
|
"brands": ["Alpro", "Boni", "Oatly"],
|
||||||
|
"image_hint": "havermelk",
|
||||||
|
"tags": ["plantaardig"],
|
||||||
|
"allergens": ["gluten"],
|
||||||
|
"substitution_group": "milk_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_boter",
|
||||||
|
"name": "Boter",
|
||||||
|
"category_id": "dairy",
|
||||||
|
"aliases": ["boter", "hoeveboter", "roomboter"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 250,
|
||||||
|
"price": 2.85,
|
||||||
|
"brands": ["Boni", "Balade", "Carlsbourg"],
|
||||||
|
"image_hint": "boter",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": ["milk"],
|
||||||
|
"substitution_group": "butter_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_eieren",
|
||||||
|
"name": "Eieren",
|
||||||
|
"category_id": "dairy",
|
||||||
|
"aliases": ["eieren", "eitjes", "vrije uitloop eieren"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 10,
|
||||||
|
"price": 3.25,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "eieren",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": ["egg"],
|
||||||
|
"substitution_group": "eggs_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_jonge_kaas",
|
||||||
|
"name": "Jonge kaas",
|
||||||
|
"category_id": "dairy",
|
||||||
|
"aliases": ["kaas", "jonge kaas", "sneetjes kaas"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 300,
|
||||||
|
"price": 3.79,
|
||||||
|
"brands": ["Boni", "Maredsous", "Passendale"],
|
||||||
|
"image_hint": "jonge_kaas",
|
||||||
|
"tags": ["broodbeleg"],
|
||||||
|
"allergens": ["milk"],
|
||||||
|
"substitution_group": "cheese_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_yoghurt_natuur",
|
||||||
|
"name": "Yoghurt natuur",
|
||||||
|
"category_id": "dairy",
|
||||||
|
"aliases": ["yoghurt", "natuuryoghurt", "yoghurt natuur"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 1.89,
|
||||||
|
"brands": ["Boni", "Danone", "Delhaize"],
|
||||||
|
"image_hint": "yoghurt_natuur",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": ["milk"],
|
||||||
|
"substitution_group": "yoghurt_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_griekse_yoghurt",
|
||||||
|
"name": "Griekse yoghurt",
|
||||||
|
"category_id": "dairy",
|
||||||
|
"aliases": ["griekse yoghurt", "yoghurt grieks"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 2.65,
|
||||||
|
"brands": ["Boni", "Oikos", "Delhaize"],
|
||||||
|
"image_hint": "griekse_yoghurt",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": ["milk"],
|
||||||
|
"substitution_group": "yoghurt_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_appels",
|
||||||
|
"name": "Appels",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["appels", "jonagold", "elstar"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 2.49,
|
||||||
|
"brands": ["BelOrta", "Boni"],
|
||||||
|
"image_hint": "appels",
|
||||||
|
"tags": ["fruit"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "apple_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_bananen",
|
||||||
|
"name": "Bananen",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["bananen", "banaan"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 1.89,
|
||||||
|
"brands": ["Chiquita", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "bananen",
|
||||||
|
"tags": ["fruit"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "banana_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_peren",
|
||||||
|
"name": "Peren",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["peren", "conference peer", "conference peren"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 2.59,
|
||||||
|
"brands": ["BelOrta", "Boni"],
|
||||||
|
"image_hint": "peren",
|
||||||
|
"tags": ["fruit"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "pear_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_aardbeien",
|
||||||
|
"name": "Aardbeien",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["aardbeien", "belgische aardbeien"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 4.49,
|
||||||
|
"brands": ["BelOrta", "Hoogstraten"],
|
||||||
|
"image_hint": "aardbeien",
|
||||||
|
"tags": ["fruit", "seizoen"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "berry_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_aardappelen",
|
||||||
|
"name": "Aardappelen",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["aardappelen", "patatten", "vastkokende aardappelen"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 2.5,
|
||||||
|
"price": 3.25,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "aardappelen",
|
||||||
|
"tags": ["groenten", "basis"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "potato_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_tomaten",
|
||||||
|
"name": "Tomaten",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["tomaten", "trostomaten", "tomaat"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 2.49,
|
||||||
|
"brands": ["BelOrta", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "tomaten",
|
||||||
|
"tags": ["groenten"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "tomato_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_witloof",
|
||||||
|
"name": "Witloof",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["witloof", "witlof", "grondwitloof"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 2.99,
|
||||||
|
"brands": ["BelOrta", "Boni"],
|
||||||
|
"image_hint": "witloof",
|
||||||
|
"tags": ["groenten", "belgisch"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "chicory_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_preien",
|
||||||
|
"name": "Prei",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["prei", "preien"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 3,
|
||||||
|
"price": 1.99,
|
||||||
|
"brands": ["Boni", "Delhaize"],
|
||||||
|
"image_hint": "prei",
|
||||||
|
"tags": ["groenten"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "leek_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_wortelen",
|
||||||
|
"name": "Wortelen",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["wortelen", "peen", "wortels"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 1.49,
|
||||||
|
"brands": ["Boni", "Carrefour"],
|
||||||
|
"image_hint": "wortelen",
|
||||||
|
"tags": ["groenten"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "carrot_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_uien",
|
||||||
|
"name": "Uien",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["uien", "ajuin", "gele uien"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 1.39,
|
||||||
|
"brands": ["Boni", "Delhaize"],
|
||||||
|
"image_hint": "uien",
|
||||||
|
"tags": ["groenten", "basis"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "onion_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_sla",
|
||||||
|
"name": "Sla",
|
||||||
|
"category_id": "produce",
|
||||||
|
"aliases": ["sla", "kropsla", "salade"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 1.59,
|
||||||
|
"brands": ["Boni", "Delhaize"],
|
||||||
|
"image_hint": "sla",
|
||||||
|
"tags": ["groenten"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "lettuce_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_brood_wit",
|
||||||
|
"name": "Wit brood",
|
||||||
|
"category_id": "bakery",
|
||||||
|
"aliases": ["wit brood", "brood", "gesneden wit brood"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 2.39,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "wit_brood",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": ["gluten"],
|
||||||
|
"substitution_group": "bread_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_brood_grijs",
|
||||||
|
"name": "Grijs brood",
|
||||||
|
"category_id": "bakery",
|
||||||
|
"aliases": ["grijs brood", "bruin brood", "tarwebrood"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 2.49,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "grijs_brood",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": ["gluten"],
|
||||||
|
"substitution_group": "bread_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_pistolets",
|
||||||
|
"name": "Pistolets",
|
||||||
|
"category_id": "bakery",
|
||||||
|
"aliases": ["pistolets", "broodjes", "witte pistolets"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 6,
|
||||||
|
"price": 2.19,
|
||||||
|
"brands": ["Delhaize", "Carrefour", "Boni"],
|
||||||
|
"image_hint": "pistolets",
|
||||||
|
"tags": ["belgisch"],
|
||||||
|
"allergens": ["gluten"],
|
||||||
|
"substitution_group": "rolls_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_sandwiches",
|
||||||
|
"name": "Sandwiches",
|
||||||
|
"category_id": "bakery",
|
||||||
|
"aliases": ["sandwiches", "melkbroodjes", "zachte broodjes"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 6,
|
||||||
|
"price": 2.69,
|
||||||
|
"brands": ["Boni", "Delhaize"],
|
||||||
|
"image_hint": "sandwiches",
|
||||||
|
"tags": ["belgisch"],
|
||||||
|
"allergens": ["gluten", "milk", "egg"],
|
||||||
|
"substitution_group": "rolls_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_luikse_wafels",
|
||||||
|
"name": "Luikse wafels",
|
||||||
|
"category_id": "bakery",
|
||||||
|
"aliases": ["luikse wafels", "suikerwafels", "wafels"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 6,
|
||||||
|
"price": 3.49,
|
||||||
|
"brands": ["Lotus", "Boni", "Milcamps"],
|
||||||
|
"image_hint": "luikse_wafels",
|
||||||
|
"tags": ["belgisch"],
|
||||||
|
"allergens": ["gluten", "milk", "egg"],
|
||||||
|
"substitution_group": "waffle_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_kipfilet",
|
||||||
|
"name": "Kipfilet",
|
||||||
|
"category_id": "meat",
|
||||||
|
"aliases": ["kipfilet", "kip", "kippenfilet"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 600,
|
||||||
|
"price": 7.49,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "kipfilet",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "chicken_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_gehakt",
|
||||||
|
"name": "Gemengd gehakt",
|
||||||
|
"category_id": "meat",
|
||||||
|
"aliases": ["gehakt", "gemengd gehakt", "varkens runds gehakt"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 4.99,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "gehakt",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "mince_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_spekblokjes",
|
||||||
|
"name": "Spekblokjes",
|
||||||
|
"category_id": "meat",
|
||||||
|
"aliases": ["spekblokjes", "spekreepjes", "spek"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 200,
|
||||||
|
"price": 2.99,
|
||||||
|
"brands": ["Herta", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "spekblokjes",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "bacon_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_hesp",
|
||||||
|
"name": "Hesp",
|
||||||
|
"category_id": "meat",
|
||||||
|
"aliases": ["hesp", "ham", "gekookte hesp"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 150,
|
||||||
|
"price": 2.89,
|
||||||
|
"brands": ["Boni", "Aoste", "Delhaize"],
|
||||||
|
"image_hint": "hesp",
|
||||||
|
"tags": ["broodbeleg"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "ham_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_zalmfilet",
|
||||||
|
"name": "Zalmfilet",
|
||||||
|
"category_id": "meat",
|
||||||
|
"aliases": ["zalm", "zalmfilet", "verse zalm"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 250,
|
||||||
|
"price": 6.99,
|
||||||
|
"brands": ["Delhaize", "Carrefour", "Boni"],
|
||||||
|
"image_hint": "zalmfilet",
|
||||||
|
"tags": ["vis"],
|
||||||
|
"allergens": ["fish"],
|
||||||
|
"substitution_group": "salmon_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_frietjes_dieprvries",
|
||||||
|
"name": "Diepvriesfrieten",
|
||||||
|
"category_id": "frozen",
|
||||||
|
"aliases": ["diepvriesfrieten", "frieten", "frietjes", "frites"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 2.39,
|
||||||
|
"brands": ["Boni", "Lutosa", "McCain"],
|
||||||
|
"image_hint": "diepvriesfrieten",
|
||||||
|
"tags": ["belgisch"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "fries_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_groentenmix_dieprvries",
|
||||||
|
"name": "Diepvriesgroenten",
|
||||||
|
"category_id": "frozen",
|
||||||
|
"aliases": ["diepvriesgroenten", "groentenmix", "diepvries groentemix"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 600,
|
||||||
|
"price": 2.49,
|
||||||
|
"brands": ["Boni", "Delhaize", "Carrefour"],
|
||||||
|
"image_hint": "diepvriesgroenten",
|
||||||
|
"tags": ["groenten"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "frozen_vegetables_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_vanille_ijs",
|
||||||
|
"name": "Vanille-ijs",
|
||||||
|
"category_id": "frozen",
|
||||||
|
"aliases": ["vanille ijs", "vanille-ijs", "roomijs"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 3.49,
|
||||||
|
"brands": ["Boni", "Ijsboerke", "Delhaize"],
|
||||||
|
"image_hint": "vanille_ijs",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": ["milk", "egg"],
|
||||||
|
"substitution_group": "ice_cream_group",
|
||||||
|
"priority_level": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_pasta",
|
||||||
|
"name": "Pasta",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["pasta", "spaghetti", "penne"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 1.39,
|
||||||
|
"brands": ["Boni", "Barilla", "Delhaize"],
|
||||||
|
"image_hint": "pasta",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": ["gluten"],
|
||||||
|
"substitution_group": "pasta_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_rijst",
|
||||||
|
"name": "Rijst",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["rijst", "basmatirijst", "lange korrel rijst"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 2.19,
|
||||||
|
"brands": ["Boni", "Uncle Ben's", "Delhaize"],
|
||||||
|
"image_hint": "rijst",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "rice_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_bloem",
|
||||||
|
"name": "Bloem",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["bloem", "tarwebloem", "zelfrijzende bloem"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 0.99,
|
||||||
|
"brands": ["Boni", "Anco", "Delhaize"],
|
||||||
|
"image_hint": "bloem",
|
||||||
|
"tags": ["bakken", "basis"],
|
||||||
|
"allergens": ["gluten"],
|
||||||
|
"substitution_group": "flour_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_suiker",
|
||||||
|
"name": "Suiker",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["suiker", "kristalsuiker", "witte suiker"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 1.29,
|
||||||
|
"brands": ["Tienen-Tirlemont", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "suiker",
|
||||||
|
"tags": ["bakken", "basis"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "sugar_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_koffie",
|
||||||
|
"name": "Koffie",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["koffie", "gemalen koffie", "koffiebonen"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 5.49,
|
||||||
|
"brands": ["Douwe Egberts", "Rombouts", "Boni"],
|
||||||
|
"image_hint": "koffie",
|
||||||
|
"tags": ["ontbijt"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "coffee_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_choco",
|
||||||
|
"name": "Choco",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["choco", "chocopasta", "hazelnootpasta"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 400,
|
||||||
|
"price": 2.99,
|
||||||
|
"brands": ["Nutella", "Boni", "Kwatta"],
|
||||||
|
"image_hint": "choco",
|
||||||
|
"tags": ["broodbeleg"],
|
||||||
|
"allergens": ["milk", "nuts"],
|
||||||
|
"substitution_group": "spread_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_confituur",
|
||||||
|
"name": "Confituur",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["confituur", "jam", "aardbeienconfituur"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 450,
|
||||||
|
"price": 2.39,
|
||||||
|
"brands": ["Materne", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "confituur",
|
||||||
|
"tags": ["broodbeleg"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "jam_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_mayonaise",
|
||||||
|
"name": "Mayonaise",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["mayonaise", "mayo", "fritessaus"],
|
||||||
|
"default_unit": "ml",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 2.89,
|
||||||
|
"brands": ["Devos Lemmens", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "mayonaise",
|
||||||
|
"tags": ["belgisch"],
|
||||||
|
"allergens": ["egg", "mustard"],
|
||||||
|
"substitution_group": "mayonnaise_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_pickles",
|
||||||
|
"name": "Pickles",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["pickles", "belgische pickles", "piccalilly"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 350,
|
||||||
|
"price": 2.49,
|
||||||
|
"brands": ["Devos Lemmens", "Bister", "Boni"],
|
||||||
|
"image_hint": "pickles",
|
||||||
|
"tags": ["belgisch"],
|
||||||
|
"allergens": ["mustard"],
|
||||||
|
"substitution_group": "sauce_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_olijfolie",
|
||||||
|
"name": "Olijfolie",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["olijfolie", "extra vierge olijfolie"],
|
||||||
|
"default_unit": "ml",
|
||||||
|
"default_quantity": 750,
|
||||||
|
"price": 6.49,
|
||||||
|
"brands": ["Boni", "Bertolli", "Delhaize"],
|
||||||
|
"image_hint": "olijfolie",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "oil_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_zout",
|
||||||
|
"name": "Zout",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["zout", "keukenzout", "zeezout"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 0.79,
|
||||||
|
"brands": ["Boni", "Jozo", "Delhaize"],
|
||||||
|
"image_hint": "zout",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "salt_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_peper",
|
||||||
|
"name": "Peper",
|
||||||
|
"category_id": "pantry",
|
||||||
|
"aliases": ["peper", "zwarte peper", "pepermolen"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 50,
|
||||||
|
"price": 2.29,
|
||||||
|
"brands": ["Boni", "Ducros", "Delhaize"],
|
||||||
|
"image_hint": "peper",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "spice_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_spa_bruis",
|
||||||
|
"name": "Bruiswater",
|
||||||
|
"category_id": "beverages",
|
||||||
|
"aliases": ["bruiswater", "spuitwater", "water bruis"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1.5,
|
||||||
|
"price": 0.89,
|
||||||
|
"brands": ["Spa", "Chaudfontaine", "Boni"],
|
||||||
|
"image_hint": "bruiswater",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "water_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_plat_water",
|
||||||
|
"name": "Plat water",
|
||||||
|
"category_id": "beverages",
|
||||||
|
"aliases": ["plat water", "mineraalwater", "water"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1.5,
|
||||||
|
"price": 0.75,
|
||||||
|
"brands": ["Spa", "Chaudfontaine", "Boni"],
|
||||||
|
"image_hint": "plat_water",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "water_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_sinaasappelsap",
|
||||||
|
"name": "Sinaasappelsap",
|
||||||
|
"category_id": "beverages",
|
||||||
|
"aliases": ["sinaasappelsap", "appelsiensap", "fruitsap"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1,
|
||||||
|
"price": 2.19,
|
||||||
|
"brands": ["Minute Maid", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "sinaasappelsap",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "juice_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_pils",
|
||||||
|
"name": "Pils",
|
||||||
|
"category_id": "beverages",
|
||||||
|
"aliases": ["pils", "bier", "pintjes"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 6,
|
||||||
|
"price": 5.99,
|
||||||
|
"brands": ["Jupiler", "Stella Artois", "Maes"],
|
||||||
|
"image_hint": "pils",
|
||||||
|
"tags": ["belgisch", "alcohol"],
|
||||||
|
"allergens": ["gluten"],
|
||||||
|
"substitution_group": "beer_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_coladrank",
|
||||||
|
"name": "Cola",
|
||||||
|
"category_id": "beverages",
|
||||||
|
"aliases": ["cola", "coca cola", "frisdrank"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1.5,
|
||||||
|
"price": 2.19,
|
||||||
|
"brands": ["Coca-Cola", "Pepsi", "Boni"],
|
||||||
|
"image_hint": "cola",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "soft_drink_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_speculoos",
|
||||||
|
"name": "Speculoos",
|
||||||
|
"category_id": "snacks",
|
||||||
|
"aliases": ["speculoos", "speculaas", "lotus koekjes"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 250,
|
||||||
|
"price": 2.29,
|
||||||
|
"brands": ["Lotus", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "speculoos",
|
||||||
|
"tags": ["belgisch"],
|
||||||
|
"allergens": ["gluten"],
|
||||||
|
"substitution_group": "biscuit_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_chocolade",
|
||||||
|
"name": "Chocolade",
|
||||||
|
"category_id": "snacks",
|
||||||
|
"aliases": ["chocolade", "tablet chocolade", "melkchocolade"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 200,
|
||||||
|
"price": 2.49,
|
||||||
|
"brands": ["Cote d'Or", "Jacques", "Boni"],
|
||||||
|
"image_hint": "chocolade",
|
||||||
|
"tags": ["belgisch"],
|
||||||
|
"allergens": ["milk", "soy", "nuts"],
|
||||||
|
"substitution_group": "chocolate_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_chips_paprika",
|
||||||
|
"name": "Paprikachips",
|
||||||
|
"category_id": "snacks",
|
||||||
|
"aliases": ["paprikachips", "chips paprika", "chips"],
|
||||||
|
"default_unit": "g",
|
||||||
|
"default_quantity": 200,
|
||||||
|
"price": 1.79,
|
||||||
|
"brands": ["Lay's", "Boni", "Croky"],
|
||||||
|
"image_hint": "paprikachips",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "chips_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_wasmiddel",
|
||||||
|
"name": "Wasmiddel",
|
||||||
|
"category_id": "household",
|
||||||
|
"aliases": ["wasmiddel", "vloeibaar wasmiddel", "waspoeder"],
|
||||||
|
"default_unit": "L",
|
||||||
|
"default_quantity": 1.5,
|
||||||
|
"price": 8.49,
|
||||||
|
"brands": ["Dash", "Ariel", "Boni"],
|
||||||
|
"image_hint": "wasmiddel",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "laundry_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_afwasmiddel",
|
||||||
|
"name": "Afwasmiddel",
|
||||||
|
"category_id": "household",
|
||||||
|
"aliases": ["afwasmiddel", "dreft", "vaatwasmiddel handwas"],
|
||||||
|
"default_unit": "ml",
|
||||||
|
"default_quantity": 500,
|
||||||
|
"price": 1.99,
|
||||||
|
"brands": ["Dreft", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "afwasmiddel",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "dish_soap_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_wc_papier",
|
||||||
|
"name": "Toiletpapier",
|
||||||
|
"category_id": "household",
|
||||||
|
"aliases": ["toiletpapier", "wc papier", "wc-papier"],
|
||||||
|
"default_unit": "rollen",
|
||||||
|
"default_quantity": 12,
|
||||||
|
"price": 4.99,
|
||||||
|
"brands": ["Page", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "toiletpapier",
|
||||||
|
"tags": ["basis"],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "toilet_paper_group",
|
||||||
|
"priority_level": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_keukenpapier",
|
||||||
|
"name": "Keukenpapier",
|
||||||
|
"category_id": "household",
|
||||||
|
"aliases": ["keukenpapier", "keukenrol", "huishoudpapier"],
|
||||||
|
"default_unit": "rollen",
|
||||||
|
"default_quantity": 2,
|
||||||
|
"price": 2.29,
|
||||||
|
"brands": ["Boni", "Page", "Delhaize"],
|
||||||
|
"image_hint": "keukenpapier",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "paper_towel_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_tandpasta",
|
||||||
|
"name": "Tandpasta",
|
||||||
|
"category_id": "health",
|
||||||
|
"aliases": ["tandpasta", "fluoride tandpasta"],
|
||||||
|
"default_unit": "ml",
|
||||||
|
"default_quantity": 75,
|
||||||
|
"price": 2.49,
|
||||||
|
"brands": ["Colgate", "Signal", "Oral-B"],
|
||||||
|
"image_hint": "tandpasta",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "toothpaste_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_shampoo",
|
||||||
|
"name": "Shampoo",
|
||||||
|
"category_id": "health",
|
||||||
|
"aliases": ["shampoo", "haarshampoo"],
|
||||||
|
"default_unit": "ml",
|
||||||
|
"default_quantity": 300,
|
||||||
|
"price": 3.99,
|
||||||
|
"brands": ["Nivea", "Dove", "Garnier"],
|
||||||
|
"image_hint": "shampoo",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "shampoo_group",
|
||||||
|
"priority_level": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_luiers",
|
||||||
|
"name": "Luiers",
|
||||||
|
"category_id": "baby",
|
||||||
|
"aliases": ["luiers", "pamper", "pampers"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 36,
|
||||||
|
"price": 9.99,
|
||||||
|
"brands": ["Pampers", "Boni", "Delhaize"],
|
||||||
|
"image_hint": "luiers",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "diaper_group",
|
||||||
|
"priority_level": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_babydoekjes",
|
||||||
|
"name": "Babydoekjes",
|
||||||
|
"category_id": "baby",
|
||||||
|
"aliases": ["babydoekjes", "vochtige doekjes", "billendoekjes"],
|
||||||
|
"default_unit": "stuks",
|
||||||
|
"default_quantity": 64,
|
||||||
|
"price": 1.99,
|
||||||
|
"brands": ["Pampers", "Boni", "Zwitsal"],
|
||||||
|
"image_hint": "babydoekjes",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "baby_wipes_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_kattenvoer",
|
||||||
|
"name": "Kattenvoer",
|
||||||
|
"category_id": "pet",
|
||||||
|
"aliases": ["kattenvoer", "kattenbrokken", "kat voeding"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 1.5,
|
||||||
|
"price": 5.99,
|
||||||
|
"brands": ["Whiskas", "Felix", "Boni"],
|
||||||
|
"image_hint": "kattenvoer",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "cat_food_group",
|
||||||
|
"priority_level": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod_be_hondenvoer",
|
||||||
|
"name": "Hondenvoer",
|
||||||
|
"category_id": "pet",
|
||||||
|
"aliases": ["hondenvoer", "hondenbrokken", "hond voeding"],
|
||||||
|
"default_unit": "kg",
|
||||||
|
"default_quantity": 3,
|
||||||
|
"price": 8.49,
|
||||||
|
"brands": ["Pedigree", "Cesar", "Boni"],
|
||||||
|
"image_hint": "hondenvoer",
|
||||||
|
"tags": [],
|
||||||
|
"allergens": [],
|
||||||
|
"substitution_group": "dog_food_group",
|
||||||
|
"priority_level": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"country": "Belgium"
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "shopping_list_manager",
|
"domain": "shopping_list_manager",
|
||||||
"name": "Shopping List Manager",
|
"name": "Shopping List Manager",
|
||||||
"version": "2.0.0",
|
"version": "2.2.2",
|
||||||
"documentation": "https://github.com/thekiwismarthome/shopping-list-manager",
|
"documentation": "https://github.com/thekiwismarthome/shopping-list-manager",
|
||||||
"issue_tracker": "https://github.com/thekiwismarthome/shopping-list-manager/issues",
|
"issue_tracker": "https://github.com/thekiwismarthome/shopping-list-manager/issues",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
|
|||||||
@@ -102,21 +102,8 @@ class ShoppingListStorage:
|
|||||||
self._images_dir.mkdir(parents=True, exist_ok=True)
|
self._images_dir.mkdir(parents=True, exist_ok=True)
|
||||||
await self._migrate_legacy_images_and_urls()
|
await self._migrate_legacy_images_and_urls()
|
||||||
|
|
||||||
# Load categories
|
# Load country-specific system categories so labels follow the selected country.
|
||||||
categories_data = await self._store_categories.async_load()
|
await self._load_categories_for_country(self._country)
|
||||||
if categories_data:
|
|
||||||
self._categories = [Category(**cat_data) for cat_data in categories_data]
|
|
||||||
_LOGGER.debug("Loaded %d categories", len(self._categories))
|
|
||||||
else:
|
|
||||||
# Initialize with default categories from JSON file
|
|
||||||
default_categories = await load_categories(self._component_path, self._country) # Use self._country
|
|
||||||
self._categories = [Category(**cat) for cat in default_categories]
|
|
||||||
await self._save_categories()
|
|
||||||
_LOGGER.info(
|
|
||||||
"Initialized %d default categories for country: %s",
|
|
||||||
len(self._categories),
|
|
||||||
self._country # Use self._country
|
|
||||||
)
|
|
||||||
|
|
||||||
# Load product catalog if products are empty
|
# Load product catalog if products are empty
|
||||||
if not self._products:
|
if not self._products:
|
||||||
@@ -581,6 +568,8 @@ class ShoppingListStorage:
|
|||||||
del self._products[pid]
|
del self._products[pid]
|
||||||
|
|
||||||
self._country = country_code
|
self._country = country_code
|
||||||
|
await self._load_categories_for_country(country_code)
|
||||||
|
|
||||||
catalog_products = await load_product_catalog(self._component_path, country_code)
|
catalog_products = await load_product_catalog(self._component_path, country_code)
|
||||||
count = 0
|
count = 0
|
||||||
for prod_data in catalog_products:
|
for prod_data in catalog_products:
|
||||||
@@ -738,6 +727,17 @@ class ShoppingListStorage:
|
|||||||
_LOGGER.warning("Failed to write config backup: %s", err)
|
_LOGGER.warning("Failed to write config backup: %s", err)
|
||||||
|
|
||||||
# Categories methods
|
# Categories methods
|
||||||
|
async def _load_categories_for_country(self, country_code: str) -> None:
|
||||||
|
"""Load and persist system categories for the selected country."""
|
||||||
|
categories = await load_categories(self._component_path, country_code)
|
||||||
|
self._categories = [Category(**cat_data) for cat_data in categories]
|
||||||
|
await self._save_categories()
|
||||||
|
_LOGGER.info(
|
||||||
|
"Loaded %d categories for country: %s",
|
||||||
|
len(self._categories),
|
||||||
|
country_code,
|
||||||
|
)
|
||||||
|
|
||||||
async def _save_categories(self) -> None:
|
async def _save_categories(self) -> None:
|
||||||
"""Save categories to storage."""
|
"""Save categories to storage."""
|
||||||
data = [cat.to_dict() for cat in self._categories]
|
data = [cat.to_dict() for cat in self._categories]
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Shopping List Manager",
|
||||||
|
"description": "Stel de Shopping List Manager-integratie in. Land en andere instellingen kunnen na de setup worden geconfigureerd via de knop Configureren."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Slechts één exemplaar van Shopping List Manager is toegestaan."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Shopping List Manager Opties",
|
||||||
|
"description": "Het wijzigen van land zal de productcatalogus bij de volgende herstart herladen.",
|
||||||
|
"data": {
|
||||||
|
"country": "Land",
|
||||||
|
"enable_price_tracking": "Prijstracking inschakelen",
|
||||||
|
"enable_image_search": "Afbeeldingen zoeken inschakelen",
|
||||||
|
"metric_units_only": "Alleen metrische eenheden"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"country": "Wordt gebruikt om de productcatalogus en prijzen te lokaliseren.",
|
||||||
|
"enable_price_tracking": "Productprijzen bijhouden en weergeven.",
|
||||||
|
"enable_image_search": "Automatisch naar productafbeeldingen zoeken.",
|
||||||
|
"metric_units_only": "Alleen metrische eenheden weergeven (g, kg, ml, L)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,11 +59,35 @@ from .. import get_storage
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
OPENFOODFACTS_DEFAULT_BASE_URL = "https://world.openfoodfacts.org"
|
||||||
|
OPENFOODFACTS_BASE_URL_BY_COUNTRY = {
|
||||||
|
"BE": "https://be.openfoodfacts.org",
|
||||||
|
}
|
||||||
|
OPENFOODFACTS_ACCEPT_LANGUAGE_BY_COUNTRY = {
|
||||||
|
"BE": "nl-BE,nl;q=0.9,en;q=0.7",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ACCESS-CHECK HELPERS
|
# ACCESS-CHECK HELPERS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
def _get_openfoodfacts_request_config(hass: HomeAssistant) -> tuple[str, Dict[str, str]]:
|
||||||
|
"""Return the OpenFoodFacts base URL and headers for the active catalog."""
|
||||||
|
country = hass.data.get(DOMAIN, {}).get("country", "NZ")
|
||||||
|
base_url = OPENFOODFACTS_BASE_URL_BY_COUNTRY.get(
|
||||||
|
country,
|
||||||
|
OPENFOODFACTS_DEFAULT_BASE_URL,
|
||||||
|
)
|
||||||
|
headers = {
|
||||||
|
"User-Agent": "HomeAssistant/ShoppingListManager (contact@homeassistant.io)",
|
||||||
|
}
|
||||||
|
if accept_language := OPENFOODFACTS_ACCEPT_LANGUAGE_BY_COUNTRY.get(country):
|
||||||
|
headers["Accept-Language"] = accept_language
|
||||||
|
|
||||||
|
return base_url, headers
|
||||||
|
|
||||||
|
|
||||||
def _user_can_access_list(lst, user) -> bool:
|
def _user_can_access_list(lst, user) -> bool:
|
||||||
"""Return True if the user may read or write to this list.
|
"""Return True if the user may read or write to this list.
|
||||||
|
|
||||||
@@ -1126,17 +1150,14 @@ async def websocket_off_fetch(
|
|||||||
msg: Dict[str, Any],
|
msg: Dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Proxy OpenFoodFacts requests through HA to avoid browser CORS restrictions."""
|
"""Proxy OpenFoodFacts requests through HA to avoid browser CORS restrictions."""
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
||||||
from aiohttp import ClientTimeout
|
|
||||||
|
|
||||||
session = async_get_clientsession(hass)
|
session = async_get_clientsession(hass)
|
||||||
headers = {"User-Agent": "HomeAssistant/ShoppingListManager (contact@homeassistant.io)"}
|
base_url, headers = _get_openfoodfacts_request_config(hass)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if msg.get("barcode"):
|
if msg.get("barcode"):
|
||||||
barcode = msg["barcode"]
|
barcode = msg["barcode"]
|
||||||
fields = "product_name,categories_tags,image_front_thumb_url,image_front_url,image_url,price"
|
fields = "product_name,categories_tags,image_front_thumb_url,image_front_url,image_url,price"
|
||||||
url = f"https://world.openfoodfacts.org/api/v2/product/{barcode}.json?fields={fields}"
|
url = f"{base_url}/api/v2/product/{barcode}.json?fields={fields}"
|
||||||
async with session.get(url, timeout=ClientTimeout(total=10), headers=headers) as resp:
|
async with session.get(url, timeout=ClientTimeout(total=10), headers=headers) as resp:
|
||||||
if not resp.ok:
|
if not resp.ok:
|
||||||
connection.send_result(msg["id"], {"status": 0})
|
connection.send_result(msg["id"], {"status": 0})
|
||||||
@@ -1151,7 +1172,7 @@ async def websocket_off_fetch(
|
|||||||
page_size = msg.get("page_size", 5)
|
page_size = msg.get("page_size", 5)
|
||||||
fields = "product_name,categories_tags,image_front_thumb_url,image_front_url,image_url,price"
|
fields = "product_name,categories_tags,image_front_thumb_url,image_front_url,image_url,price"
|
||||||
url = (
|
url = (
|
||||||
f"https://world.openfoodfacts.org/api/v2/search"
|
f"{base_url}/api/v2/search"
|
||||||
f"?search_terms={query}&fields={fields}&page_size={page_size}"
|
f"?search_terms={query}&fields={fields}&page_size={page_size}"
|
||||||
)
|
)
|
||||||
async with session.get(url, timeout=ClientTimeout(total=10), headers=headers) as resp:
|
async with session.get(url, timeout=ClientTimeout(total=10), headers=headers) as resp:
|
||||||
@@ -1194,12 +1215,13 @@ def websocket_get_integration_settings(
|
|||||||
"US": "United States",
|
"US": "United States",
|
||||||
"GB": "United Kingdom",
|
"GB": "United Kingdom",
|
||||||
"CA": "Canada",
|
"CA": "Canada",
|
||||||
|
"BE": "Belgium (Dutch)",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_VALID_COUNTRIES = ["NZ", "AU", "US", "GB", "CA"]
|
_VALID_COUNTRIES = ["NZ", "AU", "US", "GB", "CA", "BE"]
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user