From bbde08f89ab981c6426eb7f2e4de5e5db6ec34b7 Mon Sep 17 00:00:00 2001 From: Meik Date: Tue, 11 Nov 2025 09:51:42 +0100 Subject: [PATCH] disable expired desired booking slots --- services/notificationService.js | 20 +++++- services/pickupScheduler.js | 114 ++++++++++++++++++++++++++++++-- 2 files changed, 129 insertions(+), 5 deletions(-) diff --git a/services/notificationService.js b/services/notificationService.js index ebdaf85..068ea02 100644 --- a/services/notificationService.js +++ b/services/notificationService.js @@ -112,6 +112,23 @@ async function sendStoreWatchNotification({ profileId, storeName, storeId, regio }); } +async function sendDesiredWindowMissedNotification({ profileId, storeName, desiredWindowLabel }) { + if (!profileId) { + return; + } + const title = `Keine Slots gefunden: ${storeName}`; + const messageBase = desiredWindowLabel + ? `Für den gewünschten Zeitraum ${desiredWindowLabel} konnte kein Slot gefunden werden.` + : 'Für den gewünschten Zeitraum konnte kein Slot gefunden werden.'; + const message = `${messageBase} Der Eintrag wurde deaktiviert.`; + await notifyChannels(profileId, { + title, + message, + link: null, + priority: 'default' + }); +} + async function sendTestNotification(profileId, channel) { const title = 'Pickup Benachrichtigung (Test)'; const message = 'Das ist eine Testnachricht. Bei Fragen wende dich bitte an den Admin.'; @@ -148,5 +165,6 @@ async function sendTestNotification(profileId, channel) { module.exports = { sendSlotNotification, sendStoreWatchNotification, - sendTestNotification + sendTestNotification, + sendDesiredWindowMissedNotification }; diff --git a/services/pickupScheduler.js b/services/pickupScheduler.js index d76b43d..7d276f5 100644 --- a/services/pickupScheduler.js +++ b/services/pickupScheduler.js @@ -80,7 +80,7 @@ function deactivateEntryInMemory(entry) { } } -function persistEntryDeactivation(profileId, entryId) { +function persistEntryDeactivation(profileId, entryId, options = {}) { if (!profileId || !entryId) { return; } @@ -88,9 +88,27 @@ function persistEntryDeactivation(profileId, entryId) { const config = readConfig(profileId); let changed = false; const updated = config.map((item) => { - if (String(item?.id) === String(entryId) && item?.active !== false) { - changed = true; - return { ...item, active: false }; + if (String(item?.id) === String(entryId)) { + let mutated = false; + const updatedEntry = { ...item }; + if (item?.active !== false) { + updatedEntry.active = false; + mutated = true; + } + if (options.resetDesiredWindow) { + if (updatedEntry.desiredDate !== undefined) { + delete updatedEntry.desiredDate; + mutated = true; + } + if (updatedEntry.desiredDateRange !== undefined) { + delete updatedEntry.desiredDateRange; + mutated = true; + } + } + if (mutated) { + changed = true; + return updatedEntry; + } } return item; }); @@ -150,6 +168,89 @@ function toDateValue(input) { return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); } +function formatDesiredWindowLabel(entry) { + if (!entry) { + return null; + } + const formatValue = (value) => { + if (!value) { + return null; + } + const date = new Date(value); + if (Number.isNaN(date.getTime())) { + return null; + } + return date.toLocaleDateString('de-DE', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }); + }; + if (entry.desiredDateRange) { + const startLabel = formatValue(entry.desiredDateRange.start); + const endLabel = formatValue(entry.desiredDateRange.end); + if (startLabel && endLabel) { + return startLabel === endLabel ? startLabel : `${startLabel} – ${endLabel}`; + } + return startLabel || endLabel; + } + return formatValue(entry.desiredDate); +} + +function desiredWindowExpired(entry) { + if (!entry?.active) { + return false; + } + const todayValue = toDateValue(new Date()); + if (todayValue === null) { + return false; + } + if (entry.desiredDateRange?.start || entry.desiredDateRange?.end) { + const endValue = toDateValue(entry.desiredDateRange.end) ?? toDateValue(entry.desiredDateRange.start); + return endValue !== null && endValue < todayValue; + } + const desiredValue = toDateValue(entry.desiredDate); + return desiredValue !== null && desiredValue < todayValue; +} + +function resetDesiredWindow(entry) { + if (!entry) { + return; + } + if (entry.desiredDate !== undefined) { + delete entry.desiredDate; + } + if (entry.desiredDateRange !== undefined) { + delete entry.desiredDateRange; + } +} + +async function handleExpiredDesiredWindow(session, entry) { + const profileId = session?.profile?.id; + const storeName = entry.label || entry.id; + const desiredLabel = formatDesiredWindowLabel(entry); + console.log( + `[INFO] Wunschzeitraum abgelaufen für ${storeName}${desiredLabel ? ` (${desiredLabel})` : ''}. Eintrag wird deaktiviert.` + ); + deactivateEntryInMemory(entry); + resetDesiredWindow(entry); + persistEntryDeactivation(profileId, entry.id, { resetDesiredWindow: true }); + if (profileId) { + try { + await notificationService.sendDesiredWindowMissedNotification({ + profileId, + storeName, + desiredWindowLabel: desiredLabel + }); + } catch (error) { + console.error( + `[NOTIFY] Benachrichtigung zum verpassten Zeitraum für ${storeName} fehlgeschlagen:`, + error.message + ); + } + } +} + function matchesDesiredDate(pickupDate, desiredDate, desiredDateRange) { const pickupValue = toDateValue(pickupDate); if (pickupValue === null) { @@ -254,6 +355,11 @@ async function checkEntry(sessionId, entry, settings) { return; } + if (desiredWindowExpired(entry)) { + await handleExpiredDesiredWindow(session, entry); + return; + } + const ready = await ensureSession(session); if (!ready) { return;