fix: KoePi verwandte Ketten (Netto Marken-/Getraenke-Discount) werden jetzt gemeinsam durchsucht

This commit is contained in:
2026-07-20 13:15:56 +02:00
parent e36b39d9be
commit 8734067dcd

View File

@@ -62,10 +62,20 @@ function addressesMatch(a, b) {
// aktuellen und kürzlich abgelaufenen Prospekte GENAU dieser Kette, ganz ohne
// Umkreis-Raten. addressMatch bestimmt, welche der (ggf. mehreren
// bundesweiten) Filialen dieser Kette tatsächlich eure ist.
// Manche Ketten teilen sich bei marktguru offenbar denselben Datentopf — ein
// Prospekt kann unter der "falschen" uniqueName auftauchen (z.B. das
// Getränke-Discount-Prospekt bei "netto-marken-discount" statt bei
// "netto-getraenke-discount"). Für solche Familien wird beim Auflösen die
// gesamte Gruppe gemeinsam durchsucht, nicht nur die eigene Liste.
const RETAILER_FAMILIES = {
'netto-marken-discount': ['netto-marken-discount', 'netto-getraenke-discount'],
'netto-getraenke-discount':['netto-marken-discount', 'netto-getraenke-discount'],
};
const LOCAL_STORE_TARGETS = [
{ retailer: 'Netto Marken-Discount', uniqueName: 'netto-marken-discount', addressMatch: 'Im Bonnefeld 19' },
{ retailer: 'Netto Getränke-Discount', uniqueName: 'netto-getraenke-discount', addressMatch: 'Im Bonnefeld 21' },
{ retailer: 'trinkgut', uniqueName: 'trinkgut-discount', addressMatch: 'Keniastraße 39' },
{ retailer: 'trinkgut', uniqueName: 'trinkgut', addressMatch: 'Keniastraße 39' },
{ retailer: 'REWE', uniqueName: 'rewe', addressMatch: 'Fischerstr. 110-112' },
{ retailer: 'Kaufland', uniqueName: 'kaufland', addressMatch: 'Auf der Höhe 20' },
{ retailer: 'Penny', uniqueName: 'penny', addressMatch: 'Sittardsberger Allee 10' },
@@ -249,9 +259,20 @@ async function resolveLocalStores() {
const cache = new Map();
let resolved = 0;
// Alle benötigten uniqueNames einmal laden (Mehrfachabrufe vermeiden, falls
// mehrere Ziele/Familien sich eine uniqueName teilen)
const uniqueNames = [...new Set(LOCAL_STORE_TARGETS.map(t => t.uniqueName))];
const leafletsByUniqueName = new Map();
for (const uniqueName of uniqueNames) {
leafletsByUniqueName.set(uniqueName, await fetchLeafletsForRetailer(uniqueName));
}
for (const target of LOCAL_STORE_TARGETS) {
const leaflets = await fetchLeafletsForRetailer(target.uniqueName);
console.log(`🍺 KöPi: "${target.retailer}" (${target.uniqueName}) — ${leaflets.length} Prospekt(e) gefunden`);
// Bei verwandten Ketten (siehe RETAILER_FAMILIES) alle Prospekte der
// ganzen Familie gemeinsam durchsuchen, sonst nur die eigene Liste
const familyNames = RETAILER_FAMILIES[target.uniqueName] || [target.uniqueName];
const leaflets = familyNames.flatMap(name => leafletsByUniqueName.get(name) || []);
console.log(`🍺 KöPi: "${target.retailer}" (${target.uniqueName}) — ${leaflets.length} Prospekt(e) durchsucht${familyNames.length>1?` (Familie: ${familyNames.join(', ')})`:''}`);
let found = null;
for (const l of leaflets) {
if (!l.mainLeafletId) continue;
@@ -617,11 +638,21 @@ async function scrapeProspekte() {
}).map(({ rawName, ...rest }) => rest); // rawName war nur intern nötig
const targeted = withoutSondermagazin.filter(b => b.isTarget);
// Chronologisch in "aktuell laufend" und "kommend" trennen
const todayIso = new Date().toISOString().slice(0, 10);
// Chronologisch in "aktuell laufend" und "kommend" trennen — WICHTIG: in
// lokaler Zeit vergleichen, nicht per rohem UTC-Substring. "22:00 UTC" ist
// bei uns lokal schon der nächste Tag (z.B. "2026-07-19T22:00:00Z" = lokal
// 20.07., 00:00 Uhr) — ein reiner String-Schnitt hätte das um einen Tag
// verschoben.
const localDateStr = isoOrDate => {
const d = isoOrDate instanceof Date ? isoOrDate : new Date(isoOrDate);
if (Number.isNaN(d.getTime())) return '';
const pad = n => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
};
const todayIso = localDateStr(new Date());
const isCurrent = b => {
const from = (b.validFrom || '').slice(0, 10);
const to = (b.validTo || '').slice(0, 10);
const from = localDateStr(b.validFrom);
const to = localDateStr(b.validTo);
if (!from) return true; // ohne Datum lieber anzeigen als verstecken
if (from > todayIso) return false; // startet erst noch
if (to && to < todayIso) return false; // schon abgelaufen