Version 1.2.2
This commit is contained in:
parent
e59aa0241f
commit
ed9b1046b9
File diff suppressed because one or more lines are too long
37
CHANGELOG.md
37
CHANGELOG.md
|
|
@ -2,6 +2,43 @@
|
||||||
|
|
||||||
Alle wichtigen Änderungen werden hier dokumentiert.
|
Alle wichtigen Änderungen werden hier dokumentiert.
|
||||||
|
|
||||||
|
## [1.2.2] - 2026-01-25
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
- ⭐ **Vordefinierte Zeitraum-Optionen für "Alle Abrufen"**:
|
||||||
|
- "Diese Woche" - Montag bis Sonntag der aktuellen Woche
|
||||||
|
- "Nächste 2 Wochen" - Ab heute bis 14 Tage in die Zukunft
|
||||||
|
- "Dieser Monat" - 1. bis letzter Tag des aktuellen Monats
|
||||||
|
- "Nächste 2 Monate" - Ab heute bis 2 Monate in die Zukunft
|
||||||
|
- "Dieses Jahr" - 1. Januar bis 31. Dezember
|
||||||
|
- "Benutzerdefiniert" - Manuelle Start-/Enddatum-Eingabe
|
||||||
|
|
||||||
|
- ⭐ **Zeit-Filter für "Neue" und "Geänderte" Trigger**:
|
||||||
|
- "Alle (Kein Filter)" - Alle Reservierungen, unabhängig vom Datum
|
||||||
|
- "Nur Heute" - Nur Reservierungen, die heute stattfinden
|
||||||
|
- "Nächste 3 Tage" - Reservierungen in den nächsten 3 Tagen
|
||||||
|
- "Nächste 7 Tage" - Reservierungen in den nächsten 7 Tagen
|
||||||
|
- Use Case: Agent benachrichtigt nur bei Änderungen an heutigen Terminen
|
||||||
|
|
||||||
|
- ⭐ **Erweitertes Zeitfenster für Polling**:
|
||||||
|
- Neuer Option: "Nächste 180 Tage (6 Monate)" für längere Überwachungszeiträume
|
||||||
|
|
||||||
|
- 📋 **test-triggers.ts**: Umfassendes Test-Skript für alle Trigger-Funktionen:
|
||||||
|
- Date Range Berechnungen
|
||||||
|
- Time Filter Logik
|
||||||
|
- Create/Update/Delete Reservierung
|
||||||
|
- Änderungserkennung mit Hash-Vergleich
|
||||||
|
|
||||||
|
### Getestet
|
||||||
|
- ✅ 18 Tests erfolgreich bestanden
|
||||||
|
- ✅ Date Range Berechnungen: thisWeek, next2Weeks, thisMonth, next2Months, thisYear
|
||||||
|
- ✅ Time Filter: today, next3Days, next7Days
|
||||||
|
- ✅ Create, Update, Delete Reservierung mit echten API-Calls
|
||||||
|
- ✅ Änderungserkennung funktioniert korrekt
|
||||||
|
- **Test-URL**: https://librebooking.zell-cloud.de
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.2.1] - 2026-01-25
|
## [1.2.1] - 2026-01-25
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
# LibreBooking Trigger Node - Anleitung
|
||||||
|
|
||||||
|
Dieses Dokument beschreibt die drei Trigger-Modi und deren Konfiguration.
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Der LibreBooking Trigger Node bietet drei Modi:
|
||||||
|
|
||||||
|
| Modus | Beschreibung | Use Case |
|
||||||
|
|-------|-------------|----------|
|
||||||
|
| **Alle Abrufen** | Alle Reservierungen für einen Zeitraum | Täglicher Report, Dashboard |
|
||||||
|
| **Neue Reservierungen** | Triggert bei neuen Buchungen | Benachrichtigung, Bestätigung |
|
||||||
|
| **Geänderte Reservierungen** | Triggert bei Änderungen | Konfliktprüfung, Update-Mail |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Modus: Alle Abrufen (Einmalig)
|
||||||
|
|
||||||
|
### Beschreibung
|
||||||
|
Dieser Modus ruft bei jedem Poll **alle** Reservierungen im angegebenen Zeitraum ab.
|
||||||
|
|
||||||
|
### Zeitraum-Optionen
|
||||||
|
|
||||||
|
| Option | Beschreibung | Beispiel (25.01.2026) |
|
||||||
|
|--------|-------------|----------------------|
|
||||||
|
| **Benutzerdefiniert** | Manuelle Eingabe | Frei wählbar |
|
||||||
|
| **Diese Woche** | Mo-So der aktuellen Woche | 19.01. - 25.01.2026 |
|
||||||
|
| **Nächste 2 Wochen** | Ab heute + 14 Tage | 25.01. - 08.02.2026 |
|
||||||
|
| **Dieser Monat** | 1. bis letzter Tag | 01.01. - 31.01.2026 |
|
||||||
|
| **Nächste 2 Monate** | Ab heute + 2 Monate | 25.01. - 25.03.2026 |
|
||||||
|
| **Dieses Jahr** | 1. Jan bis 31. Dez | 01.01. - 31.12.2026 |
|
||||||
|
|
||||||
|
### Beispiel-Workflow
|
||||||
|
```
|
||||||
|
[LibreBooking Trigger] --> [Format] --> [E-Mail senden]
|
||||||
|
Alle Abrufen Tabellenformat Täglicher Report
|
||||||
|
Diese Woche
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Modus: Neue Reservierungen (Polling)
|
||||||
|
|
||||||
|
### Beschreibung
|
||||||
|
Triggert **nur** wenn eine neue Reservierung erstellt wird.
|
||||||
|
|
||||||
|
### Wichtig
|
||||||
|
- **Erster Poll**: Speichert existierende IDs, triggert NICHT
|
||||||
|
- **Folgende Polls**: Triggert nur bei wirklich neuen Reservierungen
|
||||||
|
|
||||||
|
### Zeit-Filter
|
||||||
|
Filtert getriggerte Reservierungen nach Startdatum:
|
||||||
|
|
||||||
|
| Filter | Beschreibung |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Alle (Kein Filter)** | Alle neuen Reservierungen, unabhängig vom Startdatum |
|
||||||
|
| **Nur Heute** | Nur wenn die Reservierung heute stattfindet |
|
||||||
|
| **Nächste 3 Tage** | Reservierung startet in den nächsten 3 Tagen |
|
||||||
|
| **Nächste 7 Tage** | Reservierung startet in den nächsten 7 Tagen |
|
||||||
|
|
||||||
|
### Beispiel-Workflow
|
||||||
|
```
|
||||||
|
[LibreBooking Trigger] --> [IF] --> [E-Mail]
|
||||||
|
Neue Reservierungen Prüfe Bestätigung senden
|
||||||
|
Nächste 3 Tage Ressource
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Case: Sofortige Buchungsbestätigung
|
||||||
|
- Trigger-Modus: **Neue Reservierungen**
|
||||||
|
- Zeit-Filter: **Alle (Kein Filter)**
|
||||||
|
- Aktion: E-Mail an Benutzer mit Buchungsdetails
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Modus: Geänderte Reservierungen (Polling)
|
||||||
|
|
||||||
|
### Beschreibung
|
||||||
|
Triggert **nur** wenn eine bestehende Reservierung geändert wird.
|
||||||
|
|
||||||
|
### Änderungserkennung
|
||||||
|
Folgende Felder werden überwacht:
|
||||||
|
- `title` (Titel)
|
||||||
|
- `description` (Beschreibung)
|
||||||
|
- `startDate` / `endDate` (Zeitraum)
|
||||||
|
- `resourceId` / `resourceName` (Ressource)
|
||||||
|
- `userId` (Benutzer)
|
||||||
|
- `statusId` (Status)
|
||||||
|
- `participants` / `invitees` (Teilnehmer)
|
||||||
|
|
||||||
|
### Zeit-Filter
|
||||||
|
Gleiche Optionen wie bei "Neue Reservierungen":
|
||||||
|
|
||||||
|
| Filter | Beschreibung | Use Case |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| **Nur Heute** | Änderungen an heutigen Terminen | Tagesaktueller Agent |
|
||||||
|
| **Nächste 3 Tage** | Kurzfristige Änderungen | Dringende Benachrichtigungen |
|
||||||
|
|
||||||
|
### Beispiel-Workflow
|
||||||
|
```
|
||||||
|
[LibreBooking Trigger] --> [Compare] --> [Slack]
|
||||||
|
Geänderte Vorher/ Benachrichtigung
|
||||||
|
Reservierungen Nachher "Termin wurde geändert"
|
||||||
|
Nur Heute
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Case: Agent für Tagesänderungen
|
||||||
|
- Trigger-Modus: **Geänderte Reservierungen**
|
||||||
|
- Zeit-Filter: **Nur Heute**
|
||||||
|
- Aktion: Slack/E-Mail wenn sich ein heutiger Termin ändert
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Allgemeine Einstellungen
|
||||||
|
|
||||||
|
### Filter (für alle Modi)
|
||||||
|
| Filter | Beschreibung |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Ressourcen-ID** | Nur Reservierungen für diese Ressource |
|
||||||
|
| **Zeitplan-ID** | Nur Reservierungen für diesen Zeitplan |
|
||||||
|
| **Benutzer-ID** | Nur Reservierungen für diesen Benutzer |
|
||||||
|
|
||||||
|
### Optionen (für alle Modi)
|
||||||
|
| Option | Beschreibung |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Detaillierte Daten Abrufen** | Holt vollständige Daten inkl. Custom Attributes |
|
||||||
|
| **Debug-Modus** | Gibt Debug-Informationen aus (für Entwicklung) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Polling-Intervall
|
||||||
|
|
||||||
|
Das Polling-Intervall wird in den n8n Workflow-Einstellungen konfiguriert:
|
||||||
|
|
||||||
|
- **Empfohlen für "Neue" und "Geänderte"**: 1-5 Minuten
|
||||||
|
- **Empfohlen für "Alle Abrufen"**: 15-60 Minuten (je nach Report-Bedarf)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
### Trigger triggert nicht bei neuen/geänderten Reservierungen
|
||||||
|
1. **Prüfen**: Ist es der erste Poll? → Triggert absichtlich nicht
|
||||||
|
2. **Prüfen**: Ist der Zeit-Filter zu restriktiv?
|
||||||
|
3. **Aktivieren**: Debug-Modus für detaillierte Logs
|
||||||
|
|
||||||
|
### Zu viele Events werden getriggert
|
||||||
|
1. **Verwenden**: Zeit-Filter ("Nur Heute", "Nächste 3 Tage")
|
||||||
|
2. **Filtern**: Nach Ressourcen-ID, Benutzer-ID
|
||||||
|
|
||||||
|
### Performance-Probleme
|
||||||
|
1. **Reduzieren**: Zeitfenster (z.B. 7 statt 90 Tage)
|
||||||
|
2. **Deaktivieren**: "Detaillierte Daten Abrufen" wenn nicht benötigt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Praxisbeispiele
|
||||||
|
|
||||||
|
### 1. Täglicher Reservierungsreport
|
||||||
|
```yaml
|
||||||
|
Modus: Alle Abrufen
|
||||||
|
Zeitraum: Diese Woche
|
||||||
|
Poll-Intervall: Täglich um 07:00
|
||||||
|
Aktion: E-Mail mit Wochenübersicht
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Sofortige Buchungsbestätigung
|
||||||
|
```yaml
|
||||||
|
Modus: Neue Reservierungen
|
||||||
|
Zeitfenster: Nächste 90 Tage
|
||||||
|
Zeit-Filter: Alle
|
||||||
|
Poll-Intervall: 1 Minute
|
||||||
|
Aktion: E-Mail an Buchenden
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Kurzfristige Änderungsbenachrichtigung
|
||||||
|
```yaml
|
||||||
|
Modus: Geänderte Reservierungen
|
||||||
|
Zeitfenster: Nächste 14 Tage
|
||||||
|
Zeit-Filter: Nächste 3 Tage
|
||||||
|
Poll-Intervall: 5 Minuten
|
||||||
|
Aktion: Slack-Nachricht an Team
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Tagesaktueller Terminagent
|
||||||
|
```yaml
|
||||||
|
Modus: Geänderte Reservierungen
|
||||||
|
Zeitfenster: Nächste 7 Tage
|
||||||
|
Zeit-Filter: Nur Heute
|
||||||
|
Poll-Intervall: 2 Minuten
|
||||||
|
Aktion: E-Mail an betroffene Teilnehmer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test-Ergebnisse
|
||||||
|
|
||||||
|
Stand: 25.01.2026
|
||||||
|
|
||||||
|
| Test | Status |
|
||||||
|
|------|--------|
|
||||||
|
| Date Range: thisWeek | ✅ |
|
||||||
|
| Date Range: next2Weeks | ✅ |
|
||||||
|
| Date Range: thisMonth | ✅ |
|
||||||
|
| Date Range: next2Months | ✅ |
|
||||||
|
| Date Range: thisYear | ✅ |
|
||||||
|
| Time Filter: today | ✅ |
|
||||||
|
| Time Filter: next3Days | ✅ |
|
||||||
|
| Time Filter: next7Days | ✅ |
|
||||||
|
| Create Reservation Detection | ✅ |
|
||||||
|
| Update Reservation Detection | ✅ |
|
||||||
|
| Delete Reservation | ✅ |
|
||||||
|
| **Gesamt: 18/18 Tests bestanden** | ✅ |
|
||||||
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"LibreBookingTrigger.node.d.ts","sourceRoot":"","sources":["../../../nodes/LibreBookingTrigger/LibreBookingTrigger.node.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,SAAS,EACT,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAGlB,MAAM,cAAc,CAAC;AAyOtB;;;;;;;GAOG;AACH,qBAAa,mBAAoB,YAAW,SAAS;IACpD,WAAW,EAAE,oBAAoB,CA4K/B;IAEI,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,GAAG,IAAI,CAAC;CAkUxE"}
|
{"version":3,"file":"LibreBookingTrigger.node.d.ts","sourceRoot":"","sources":["../../../nodes/LibreBookingTrigger/LibreBookingTrigger.node.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,SAAS,EACT,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAGlB,MAAM,cAAc,CAAC;AA6VtB;;;;;;;GAOG;AACH,qBAAa,mBAAoB,YAAW,SAAS;IACpD,WAAW,EAAE,oBAAoB,CAmQ/B;IAEI,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,GAAG,IAAI,CAAC;CA4WxE"}
|
||||||
|
|
@ -105,16 +105,77 @@ async function getReservationDetails(pollFunctions, baseUrl, session, referenceN
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Berechnet Zeitraum basierend auf der gewählten Option
|
||||||
|
*/
|
||||||
|
function getDateRange(dateRange) {
|
||||||
|
const now = new Date();
|
||||||
|
let startDate;
|
||||||
|
let endDate;
|
||||||
|
switch (dateRange) {
|
||||||
|
case 'thisWeek':
|
||||||
|
// Montag dieser Woche (ISO: Montag = 1, Sonntag = 0)
|
||||||
|
startDate = new Date(now);
|
||||||
|
const dayOfWeek = now.getDay();
|
||||||
|
const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
||||||
|
startDate.setDate(now.getDate() + diffToMonday);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
// Sonntag dieser Woche
|
||||||
|
endDate = new Date(startDate);
|
||||||
|
endDate.setDate(startDate.getDate() + 6);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
case 'next2Weeks':
|
||||||
|
startDate = new Date(now);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
endDate = new Date(now);
|
||||||
|
endDate.setDate(now.getDate() + 14);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
case 'thisMonth':
|
||||||
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
case 'next2Months':
|
||||||
|
startDate = new Date(now);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
endDate = new Date(now);
|
||||||
|
endDate.setMonth(now.getMonth() + 2);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
case 'thisYear':
|
||||||
|
startDate = new Date(now.getFullYear(), 0, 1);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
endDate = new Date(now.getFullYear(), 11, 31);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
default: // custom
|
||||||
|
return { startDate: '', endDate: '' };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
startDate: startDate.toISOString(),
|
||||||
|
endDate: endDate.toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Zeitfenster berechnen für "Get All" Mode
|
* Zeitfenster berechnen für "Get All" Mode
|
||||||
*/
|
*/
|
||||||
function getTimeWindowForGetAll(customStartDate, customEndDate, defaultDays = 14) {
|
function getTimeWindowForGetAll(dateRange, customStartDate, customEndDate, defaultDays = 14) {
|
||||||
|
// Wenn ein vordefinierter Zeitraum gewählt wurde
|
||||||
|
if (dateRange && dateRange !== 'custom') {
|
||||||
|
const { startDate, endDate } = getDateRange(dateRange);
|
||||||
|
return { start: startDate, end: endDate };
|
||||||
|
}
|
||||||
|
// Custom Modus mit manuellen Daten
|
||||||
if (customStartDate && customEndDate) {
|
if (customStartDate && customEndDate) {
|
||||||
return {
|
return {
|
||||||
start: new Date(customStartDate).toISOString(),
|
start: new Date(customStartDate).toISOString(),
|
||||||
end: new Date(customEndDate).toISOString(),
|
end: new Date(customEndDate).toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Fallback: Ab heute für defaultDays
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const endDate = new Date(now);
|
const endDate = new Date(now);
|
||||||
endDate.setDate(endDate.getDate() + defaultDays);
|
endDate.setDate(endDate.getDate() + defaultDays);
|
||||||
|
|
@ -143,6 +204,9 @@ function getTimeWindowForPolling(timeWindow) {
|
||||||
case '90days':
|
case '90days':
|
||||||
endDate.setDate(endDate.getDate() + 90);
|
endDate.setDate(endDate.getDate() + 90);
|
||||||
break;
|
break;
|
||||||
|
case '180days':
|
||||||
|
endDate.setDate(endDate.getDate() + 180);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
endDate.setDate(endDate.getDate() + 14);
|
endDate.setDate(endDate.getDate() + 14);
|
||||||
}
|
}
|
||||||
|
|
@ -151,14 +215,46 @@ function getTimeWindowForPolling(timeWindow) {
|
||||||
end: endDate.toISOString(),
|
end: endDate.toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Filter Reservierungen nach Zeitpunkt
|
||||||
|
*/
|
||||||
|
function filterByTime(reservations, timeFilter) {
|
||||||
|
if (timeFilter === 'all') {
|
||||||
|
return reservations;
|
||||||
|
}
|
||||||
|
const now = new Date();
|
||||||
|
now.setHours(0, 0, 0, 0);
|
||||||
|
return reservations.filter((reservation) => {
|
||||||
|
// Verwende startDate oder startDateTime
|
||||||
|
const dateStr = reservation.startDateTime || reservation.startDate;
|
||||||
|
if (!dateStr)
|
||||||
|
return true;
|
||||||
|
const startDate = new Date(dateStr);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
if (timeFilter === 'today') {
|
||||||
|
return startDate.getTime() === now.getTime();
|
||||||
|
}
|
||||||
|
if (timeFilter === 'next3Days') {
|
||||||
|
const threeDaysFromNow = new Date(now);
|
||||||
|
threeDaysFromNow.setDate(now.getDate() + 3);
|
||||||
|
return startDate >= now && startDate <= threeDaysFromNow;
|
||||||
|
}
|
||||||
|
if (timeFilter === 'next7Days') {
|
||||||
|
const sevenDaysFromNow = new Date(now);
|
||||||
|
sevenDaysFromNow.setDate(now.getDate() + 7);
|
||||||
|
return startDate >= now && startDate <= sevenDaysFromNow;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Hash für Reservierung generieren (für Änderungserkennung)
|
* Hash für Reservierung generieren (für Änderungserkennung)
|
||||||
*/
|
*/
|
||||||
function getReservationHash(reservation) {
|
function getReservationHash(reservation) {
|
||||||
const relevantData = {
|
const relevantData = {
|
||||||
referenceNumber: reservation.referenceNumber,
|
referenceNumber: reservation.referenceNumber,
|
||||||
startDate: reservation.startDate,
|
startDate: reservation.startDate || reservation.startDateTime,
|
||||||
endDate: reservation.endDate,
|
endDate: reservation.endDate || reservation.endDateTime,
|
||||||
title: reservation.title || '',
|
title: reservation.title || '',
|
||||||
description: reservation.description || '',
|
description: reservation.description || '',
|
||||||
resourceId: reservation.resourceId,
|
resourceId: reservation.resourceId,
|
||||||
|
|
@ -167,6 +263,7 @@ function getReservationHash(reservation) {
|
||||||
requiresApproval: reservation.requiresApproval,
|
requiresApproval: reservation.requiresApproval,
|
||||||
participants: reservation.participants || [],
|
participants: reservation.participants || [],
|
||||||
invitees: reservation.invitees || [],
|
invitees: reservation.invitees || [],
|
||||||
|
statusId: reservation.statusId,
|
||||||
};
|
};
|
||||||
return JSON.stringify(relevantData);
|
return JSON.stringify(relevantData);
|
||||||
}
|
}
|
||||||
|
|
@ -229,8 +326,52 @@ class LibreBookingTrigger {
|
||||||
description: 'Wählen Sie den Trigger-Modus',
|
description: 'Wählen Sie den Trigger-Modus',
|
||||||
},
|
},
|
||||||
// =====================================================
|
// =====================================================
|
||||||
// GET ALL MODE - DATE RANGE
|
// GET ALL MODE - DATE RANGE SELECTOR
|
||||||
// =====================================================
|
// =====================================================
|
||||||
|
{
|
||||||
|
displayName: 'Zeitraum',
|
||||||
|
name: 'dateRange',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
triggerMode: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Benutzerdefiniert',
|
||||||
|
value: 'custom',
|
||||||
|
description: 'Start- und Enddatum manuell angeben',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Diese Woche',
|
||||||
|
value: 'thisWeek',
|
||||||
|
description: 'Von Montag bis Sonntag der aktuellen Woche',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nächste 2 Wochen',
|
||||||
|
value: 'next2Weeks',
|
||||||
|
description: 'Ab heute bis 14 Tage in die Zukunft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dieser Monat',
|
||||||
|
value: 'thisMonth',
|
||||||
|
description: 'Vom 1. bis zum letzten Tag des aktuellen Monats',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nächste 2 Monate',
|
||||||
|
value: 'next2Months',
|
||||||
|
description: 'Ab heute bis 2 Monate in die Zukunft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dieses Jahr',
|
||||||
|
value: 'thisYear',
|
||||||
|
description: 'Vom 1. Januar bis 31. Dezember des aktuellen Jahres',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'custom',
|
||||||
|
description: 'Vordefinierter Zeitraum für den Abruf',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Startdatum',
|
displayName: 'Startdatum',
|
||||||
name: 'startDate',
|
name: 'startDate',
|
||||||
|
|
@ -238,6 +379,7 @@ class LibreBookingTrigger {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
triggerMode: ['getAll'],
|
triggerMode: ['getAll'],
|
||||||
|
dateRange: ['custom'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
|
|
@ -250,6 +392,7 @@ class LibreBookingTrigger {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
triggerMode: ['getAll'],
|
triggerMode: ['getAll'],
|
||||||
|
dateRange: ['custom'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
|
|
@ -272,10 +415,48 @@ class LibreBookingTrigger {
|
||||||
{ name: 'Nächste 14 Tage', value: '14days' },
|
{ name: 'Nächste 14 Tage', value: '14days' },
|
||||||
{ name: 'Nächste 30 Tage', value: '30days' },
|
{ name: 'Nächste 30 Tage', value: '30days' },
|
||||||
{ name: 'Nächste 90 Tage', value: '90days' },
|
{ name: 'Nächste 90 Tage', value: '90days' },
|
||||||
|
{ name: 'Nächste 180 Tage (6 Monate)', value: '180days' },
|
||||||
],
|
],
|
||||||
default: '14days',
|
default: '14days',
|
||||||
description: 'Zeitfenster für die Überwachung von Reservierungen',
|
description: 'Zeitfenster für die Überwachung von Reservierungen',
|
||||||
},
|
},
|
||||||
|
// =====================================================
|
||||||
|
// TIME FILTER FOR NEW/UPDATED MODES
|
||||||
|
// =====================================================
|
||||||
|
{
|
||||||
|
displayName: 'Zeit-Filter',
|
||||||
|
name: 'timeFilter',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
triggerMode: ['newReservations', 'updatedReservations'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Alle (Kein Filter)',
|
||||||
|
value: 'all',
|
||||||
|
description: 'Alle neuen/geänderten Reservierungen, unabhängig vom Datum',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nur Heute',
|
||||||
|
value: 'today',
|
||||||
|
description: 'Nur Reservierungen, die heute stattfinden',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nächste 3 Tage',
|
||||||
|
value: 'next3Days',
|
||||||
|
description: 'Nur Reservierungen, die in den nächsten 3 Tagen stattfinden',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nächste 7 Tage',
|
||||||
|
value: 'next7Days',
|
||||||
|
description: 'Nur Reservierungen, die in den nächsten 7 Tagen stattfinden',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'all',
|
||||||
|
description: 'Filtert Reservierungen nach ihrem Startdatum',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Hinweis',
|
displayName: 'Hinweis',
|
||||||
name: 'pollingNotice',
|
name: 'pollingNotice',
|
||||||
|
|
@ -376,25 +557,32 @@ class LibreBookingTrigger {
|
||||||
// MODE: Get All (One-Time / Every Poll)
|
// MODE: Get All (One-Time / Every Poll)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
if (triggerMode === 'getAll') {
|
if (triggerMode === 'getAll') {
|
||||||
|
const dateRange = this.getNodeParameter('dateRange', 'custom');
|
||||||
const startDate = this.getNodeParameter('startDate', '');
|
const startDate = this.getNodeParameter('startDate', '');
|
||||||
const endDate = this.getNodeParameter('endDate', '');
|
const endDate = this.getNodeParameter('endDate', '');
|
||||||
const { start, end } = getTimeWindowForGetAll(startDate || undefined, endDate || undefined, 14);
|
const { start, end } = getTimeWindowForGetAll(dateRange, startDate || undefined, endDate || undefined, 14);
|
||||||
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.log(`[LibreBooking Trigger] Get All Mode - Found ${reservations.length} reservations`);
|
console.log(`[LibreBooking Trigger] Get All Mode - Found ${reservations.length} reservations`);
|
||||||
console.log(`[LibreBooking Trigger] Date Range: ${start} to ${end}`);
|
console.log(`[LibreBooking Trigger] Date Range: ${dateRange}`);
|
||||||
|
console.log(`[LibreBooking Trigger] Period: ${start} to ${end}`);
|
||||||
}
|
}
|
||||||
if (reservations.length === 0) {
|
if (reservations.length === 0) {
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
return [[{
|
return [
|
||||||
|
[
|
||||||
|
{
|
||||||
json: {
|
json: {
|
||||||
_debug: true,
|
_debug: true,
|
||||||
_message: 'Keine Reservierungen im Zeitraum gefunden',
|
_message: 'Keine Reservierungen im Zeitraum gefunden',
|
||||||
|
_dateRange: dateRange,
|
||||||
_startDate: start,
|
_startDate: start,
|
||||||
_endDate: end,
|
_endDate: end,
|
||||||
_count: 0,
|
_count: 0,
|
||||||
},
|
},
|
||||||
}]];
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -416,6 +604,7 @@ class LibreBookingTrigger {
|
||||||
json: {
|
json: {
|
||||||
...reservationData,
|
...reservationData,
|
||||||
_eventType: 'getAll',
|
_eventType: 'getAll',
|
||||||
|
_dateRange: dateRange,
|
||||||
_triggeredAt: new Date().toISOString(),
|
_triggeredAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -426,6 +615,7 @@ class LibreBookingTrigger {
|
||||||
// ==========================================
|
// ==========================================
|
||||||
else if (triggerMode === 'newReservations') {
|
else if (triggerMode === 'newReservations') {
|
||||||
const timeWindow = this.getNodeParameter('timeWindow', '14days');
|
const timeWindow = this.getNodeParameter('timeWindow', '14days');
|
||||||
|
const timeFilter = this.getNodeParameter('timeFilter', 'all');
|
||||||
const { start, end } = getTimeWindowForPolling(timeWindow);
|
const { start, end } = getTimeWindowForPolling(timeWindow);
|
||||||
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
||||||
// Initialisiere seenIds beim ersten Poll
|
// Initialisiere seenIds beim ersten Poll
|
||||||
|
|
@ -437,6 +627,7 @@ class LibreBookingTrigger {
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.log(`[LibreBooking Trigger] New Reservations Mode`);
|
console.log(`[LibreBooking Trigger] New Reservations Mode`);
|
||||||
console.log(`[LibreBooking Trigger] First Poll: ${webhookData.isFirstPoll}`);
|
console.log(`[LibreBooking Trigger] First Poll: ${webhookData.isFirstPoll}`);
|
||||||
|
console.log(`[LibreBooking Trigger] Time Filter: ${timeFilter}`);
|
||||||
console.log(`[LibreBooking Trigger] Current IDs: ${currentIds.length}, Seen IDs: ${webhookData.seenIds.length}`);
|
console.log(`[LibreBooking Trigger] Current IDs: ${currentIds.length}, Seen IDs: ${webhookData.seenIds.length}`);
|
||||||
}
|
}
|
||||||
// Beim ersten Poll: Nur IDs speichern, NICHT triggern
|
// Beim ersten Poll: Nur IDs speichern, NICHT triggern
|
||||||
|
|
@ -445,7 +636,9 @@ class LibreBookingTrigger {
|
||||||
webhookData.isFirstPoll = false;
|
webhookData.isFirstPoll = false;
|
||||||
webhookData.lastPollTime = new Date().toISOString();
|
webhookData.lastPollTime = new Date().toISOString();
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
return [[{
|
return [
|
||||||
|
[
|
||||||
|
{
|
||||||
json: {
|
json: {
|
||||||
_debug: true,
|
_debug: true,
|
||||||
_message: 'Erster Poll - IDs wurden gespeichert, keine Events getriggert',
|
_message: 'Erster Poll - IDs wurden gespeichert, keine Events getriggert',
|
||||||
|
|
@ -453,19 +646,29 @@ class LibreBookingTrigger {
|
||||||
_ids: currentIds,
|
_ids: currentIds,
|
||||||
_timestamp: webhookData.lastPollTime,
|
_timestamp: webhookData.lastPollTime,
|
||||||
},
|
},
|
||||||
}]];
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return null; // Nichts triggern beim ersten Poll
|
return null; // Nichts triggern beim ersten Poll
|
||||||
}
|
}
|
||||||
// Nur NEUE Reservierungen (die wir noch nicht gesehen haben)
|
// Nur NEUE Reservierungen (die wir noch nicht gesehen haben)
|
||||||
const newReservations = reservations.filter((r) => !webhookData.seenIds.includes(r.referenceNumber));
|
let newReservations = reservations.filter((r) => !webhookData.seenIds.includes(r.referenceNumber));
|
||||||
// Update seenIds mit allen aktuellen IDs
|
// Update seenIds mit allen aktuellen IDs
|
||||||
webhookData.seenIds = currentIds;
|
webhookData.seenIds = currentIds;
|
||||||
webhookData.lastPollTime = new Date().toISOString();
|
webhookData.lastPollTime = new Date().toISOString();
|
||||||
|
if (debugMode) {
|
||||||
|
console.log(`[LibreBooking Trigger] Found ${newReservations.length} new reservations before filter`);
|
||||||
|
}
|
||||||
|
if (newReservations.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Zeit-Filter anwenden
|
||||||
|
newReservations = filterByTime(newReservations, timeFilter);
|
||||||
|
if (debugMode) {
|
||||||
|
console.log(`[LibreBooking Trigger] ${newReservations.length} reservations after time filter (${timeFilter})`);
|
||||||
|
}
|
||||||
if (newReservations.length === 0) {
|
if (newReservations.length === 0) {
|
||||||
if (debugMode) {
|
|
||||||
console.log(`[LibreBooking Trigger] No new reservations found`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Neue Reservierungen verarbeiten
|
// Neue Reservierungen verarbeiten
|
||||||
|
|
@ -486,6 +689,7 @@ class LibreBookingTrigger {
|
||||||
json: {
|
json: {
|
||||||
...reservationData,
|
...reservationData,
|
||||||
_eventType: 'new',
|
_eventType: 'new',
|
||||||
|
_timeFilter: timeFilter,
|
||||||
_triggeredAt: new Date().toISOString(),
|
_triggeredAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -499,6 +703,7 @@ class LibreBookingTrigger {
|
||||||
// ==========================================
|
// ==========================================
|
||||||
else if (triggerMode === 'updatedReservations') {
|
else if (triggerMode === 'updatedReservations') {
|
||||||
const timeWindow = this.getNodeParameter('timeWindow', '14days');
|
const timeWindow = this.getNodeParameter('timeWindow', '14days');
|
||||||
|
const timeFilter = this.getNodeParameter('timeFilter', 'all');
|
||||||
const { start, end } = getTimeWindowForPolling(timeWindow);
|
const { start, end } = getTimeWindowForPolling(timeWindow);
|
||||||
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
||||||
// Initialisiere reservationHashes beim ersten Poll
|
// Initialisiere reservationHashes beim ersten Poll
|
||||||
|
|
@ -509,29 +714,35 @@ class LibreBookingTrigger {
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.log(`[LibreBooking Trigger] Updated Reservations Mode`);
|
console.log(`[LibreBooking Trigger] Updated Reservations Mode`);
|
||||||
console.log(`[LibreBooking Trigger] First Poll: ${webhookData.isFirstPoll}`);
|
console.log(`[LibreBooking Trigger] First Poll: ${webhookData.isFirstPoll}`);
|
||||||
|
console.log(`[LibreBooking Trigger] Time Filter: ${timeFilter}`);
|
||||||
console.log(`[LibreBooking Trigger] Current: ${reservations.length}, Stored hashes: ${Object.keys(webhookData.reservationHashes).length}`);
|
console.log(`[LibreBooking Trigger] Current: ${reservations.length}, Stored hashes: ${Object.keys(webhookData.reservationHashes).length}`);
|
||||||
}
|
}
|
||||||
// Beim ersten Poll: Nur Hashes speichern, NICHT triggern
|
// Beim ersten Poll: Nur Hashes speichern, NICHT triggern
|
||||||
if (webhookData.isFirstPoll) {
|
if (webhookData.isFirstPoll) {
|
||||||
for (const reservation of reservations) {
|
for (const reservation of reservations) {
|
||||||
webhookData.reservationHashes[reservation.referenceNumber] = getReservationHash(reservation);
|
webhookData.reservationHashes[reservation.referenceNumber] =
|
||||||
|
getReservationHash(reservation);
|
||||||
}
|
}
|
||||||
webhookData.isFirstPoll = false;
|
webhookData.isFirstPoll = false;
|
||||||
webhookData.lastPollTime = new Date().toISOString();
|
webhookData.lastPollTime = new Date().toISOString();
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
return [[{
|
return [
|
||||||
|
[
|
||||||
|
{
|
||||||
json: {
|
json: {
|
||||||
_debug: true,
|
_debug: true,
|
||||||
_message: 'Erster Poll - Hashes wurden gespeichert, keine Events getriggert',
|
_message: 'Erster Poll - Hashes wurden gespeichert, keine Events getriggert',
|
||||||
_savedHashes: Object.keys(webhookData.reservationHashes).length,
|
_savedHashes: Object.keys(webhookData.reservationHashes).length,
|
||||||
_timestamp: webhookData.lastPollTime,
|
_timestamp: webhookData.lastPollTime,
|
||||||
},
|
},
|
||||||
}]];
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return null; // Nichts triggern beim ersten Poll
|
return null; // Nichts triggern beim ersten Poll
|
||||||
}
|
}
|
||||||
// Geänderte Reservierungen finden
|
// Geänderte Reservierungen finden
|
||||||
const updatedReservations = [];
|
let updatedReservations = [];
|
||||||
const newHashes = {};
|
const newHashes = {};
|
||||||
for (const reservation of reservations) {
|
for (const reservation of reservations) {
|
||||||
const currentHash = getReservationHash(reservation);
|
const currentHash = getReservationHash(reservation);
|
||||||
|
|
@ -547,10 +758,18 @@ class LibreBookingTrigger {
|
||||||
// Update Hashes mit allen aktuellen Reservierungen
|
// Update Hashes mit allen aktuellen Reservierungen
|
||||||
webhookData.reservationHashes = newHashes;
|
webhookData.reservationHashes = newHashes;
|
||||||
webhookData.lastPollTime = new Date().toISOString();
|
webhookData.lastPollTime = new Date().toISOString();
|
||||||
|
if (debugMode) {
|
||||||
|
console.log(`[LibreBooking Trigger] Found ${updatedReservations.length} updated reservations before filter`);
|
||||||
|
}
|
||||||
|
if (updatedReservations.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Zeit-Filter anwenden
|
||||||
|
updatedReservations = filterByTime(updatedReservations, timeFilter);
|
||||||
|
if (debugMode) {
|
||||||
|
console.log(`[LibreBooking Trigger] ${updatedReservations.length} reservations after time filter (${timeFilter})`);
|
||||||
|
}
|
||||||
if (updatedReservations.length === 0) {
|
if (updatedReservations.length === 0) {
|
||||||
if (debugMode) {
|
|
||||||
console.log(`[LibreBooking Trigger] No updated reservations found`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Geänderte Reservierungen verarbeiten
|
// Geänderte Reservierungen verarbeiten
|
||||||
|
|
@ -571,6 +790,7 @@ class LibreBookingTrigger {
|
||||||
json: {
|
json: {
|
||||||
...reservationData,
|
...reservationData,
|
||||||
_eventType: 'updated',
|
_eventType: 'updated',
|
||||||
|
_timeFilter: timeFilter,
|
||||||
_triggeredAt: new Date().toISOString(),
|
_triggeredAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -22,6 +22,8 @@ interface ReservationData {
|
||||||
userId: number;
|
userId: number;
|
||||||
description?: string;
|
description?: string;
|
||||||
resourceName?: string;
|
resourceName?: string;
|
||||||
|
startDateTime?: string;
|
||||||
|
endDateTime?: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,25 +164,96 @@ async function getReservationDetails(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Berechnet Zeitraum basierend auf der gewählten Option
|
||||||
|
*/
|
||||||
|
function getDateRange(dateRange: string): { startDate: string; endDate: string } {
|
||||||
|
const now = new Date();
|
||||||
|
let startDate: Date;
|
||||||
|
let endDate: Date;
|
||||||
|
|
||||||
|
switch (dateRange) {
|
||||||
|
case 'thisWeek':
|
||||||
|
// Montag dieser Woche (ISO: Montag = 1, Sonntag = 0)
|
||||||
|
startDate = new Date(now);
|
||||||
|
const dayOfWeek = now.getDay();
|
||||||
|
const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
||||||
|
startDate.setDate(now.getDate() + diffToMonday);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
// Sonntag dieser Woche
|
||||||
|
endDate = new Date(startDate);
|
||||||
|
endDate.setDate(startDate.getDate() + 6);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'next2Weeks':
|
||||||
|
startDate = new Date(now);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
endDate = new Date(now);
|
||||||
|
endDate.setDate(now.getDate() + 14);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'thisMonth':
|
||||||
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'next2Months':
|
||||||
|
startDate = new Date(now);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
endDate = new Date(now);
|
||||||
|
endDate.setMonth(now.getMonth() + 2);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'thisYear':
|
||||||
|
startDate = new Date(now.getFullYear(), 0, 1);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
endDate = new Date(now.getFullYear(), 11, 31);
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // custom
|
||||||
|
return { startDate: '', endDate: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startDate: startDate.toISOString(),
|
||||||
|
endDate: endDate.toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeitfenster berechnen für "Get All" Mode
|
* Zeitfenster berechnen für "Get All" Mode
|
||||||
*/
|
*/
|
||||||
function getTimeWindowForGetAll(
|
function getTimeWindowForGetAll(
|
||||||
|
dateRange: string,
|
||||||
customStartDate?: string,
|
customStartDate?: string,
|
||||||
customEndDate?: string,
|
customEndDate?: string,
|
||||||
defaultDays: number = 14
|
defaultDays: number = 14,
|
||||||
): { start: string; end: string } {
|
): { start: string; end: string } {
|
||||||
|
// Wenn ein vordefinierter Zeitraum gewählt wurde
|
||||||
|
if (dateRange && dateRange !== 'custom') {
|
||||||
|
const { startDate, endDate } = getDateRange(dateRange);
|
||||||
|
return { start: startDate, end: endDate };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom Modus mit manuellen Daten
|
||||||
if (customStartDate && customEndDate) {
|
if (customStartDate && customEndDate) {
|
||||||
return {
|
return {
|
||||||
start: new Date(customStartDate).toISOString(),
|
start: new Date(customStartDate).toISOString(),
|
||||||
end: new Date(customEndDate).toISOString(),
|
end: new Date(customEndDate).toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: Ab heute für defaultDays
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const endDate = new Date(now);
|
const endDate = new Date(now);
|
||||||
endDate.setDate(endDate.getDate() + defaultDays);
|
endDate.setDate(endDate.getDate() + defaultDays);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start: now.toISOString(),
|
start: now.toISOString(),
|
||||||
end: endDate.toISOString(),
|
end: endDate.toISOString(),
|
||||||
|
|
@ -193,7 +266,7 @@ function getTimeWindowForGetAll(
|
||||||
function getTimeWindowForPolling(timeWindow: string): { start: string; end: string } {
|
function getTimeWindowForPolling(timeWindow: string): { start: string; end: string } {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const start = now.toISOString();
|
const start = now.toISOString();
|
||||||
|
|
||||||
let endDate = new Date(now);
|
let endDate = new Date(now);
|
||||||
switch (timeWindow) {
|
switch (timeWindow) {
|
||||||
case '7days':
|
case '7days':
|
||||||
|
|
@ -208,6 +281,9 @@ function getTimeWindowForPolling(timeWindow: string): { start: string; end: stri
|
||||||
case '90days':
|
case '90days':
|
||||||
endDate.setDate(endDate.getDate() + 90);
|
endDate.setDate(endDate.getDate() + 90);
|
||||||
break;
|
break;
|
||||||
|
case '180days':
|
||||||
|
endDate.setDate(endDate.getDate() + 180);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
endDate.setDate(endDate.getDate() + 14);
|
endDate.setDate(endDate.getDate() + 14);
|
||||||
}
|
}
|
||||||
|
|
@ -218,14 +294,53 @@ function getTimeWindowForPolling(timeWindow: string): { start: string; end: stri
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter Reservierungen nach Zeitpunkt
|
||||||
|
*/
|
||||||
|
function filterByTime(reservations: ReservationData[], timeFilter: string): ReservationData[] {
|
||||||
|
if (timeFilter === 'all') {
|
||||||
|
return reservations;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
now.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return reservations.filter((reservation) => {
|
||||||
|
// Verwende startDate oder startDateTime
|
||||||
|
const dateStr = reservation.startDateTime || reservation.startDate;
|
||||||
|
if (!dateStr) return true;
|
||||||
|
|
||||||
|
const startDate = new Date(dateStr);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (timeFilter === 'today') {
|
||||||
|
return startDate.getTime() === now.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeFilter === 'next3Days') {
|
||||||
|
const threeDaysFromNow = new Date(now);
|
||||||
|
threeDaysFromNow.setDate(now.getDate() + 3);
|
||||||
|
return startDate >= now && startDate <= threeDaysFromNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeFilter === 'next7Days') {
|
||||||
|
const sevenDaysFromNow = new Date(now);
|
||||||
|
sevenDaysFromNow.setDate(now.getDate() + 7);
|
||||||
|
return startDate >= now && startDate <= sevenDaysFromNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash für Reservierung generieren (für Änderungserkennung)
|
* Hash für Reservierung generieren (für Änderungserkennung)
|
||||||
*/
|
*/
|
||||||
function getReservationHash(reservation: ReservationData): string {
|
function getReservationHash(reservation: ReservationData): string {
|
||||||
const relevantData = {
|
const relevantData = {
|
||||||
referenceNumber: reservation.referenceNumber,
|
referenceNumber: reservation.referenceNumber,
|
||||||
startDate: reservation.startDate,
|
startDate: reservation.startDate || reservation.startDateTime,
|
||||||
endDate: reservation.endDate,
|
endDate: reservation.endDate || reservation.endDateTime,
|
||||||
title: reservation.title || '',
|
title: reservation.title || '',
|
||||||
description: reservation.description || '',
|
description: reservation.description || '',
|
||||||
resourceId: reservation.resourceId,
|
resourceId: reservation.resourceId,
|
||||||
|
|
@ -234,13 +349,14 @@ function getReservationHash(reservation: ReservationData): string {
|
||||||
requiresApproval: reservation.requiresApproval,
|
requiresApproval: reservation.requiresApproval,
|
||||||
participants: reservation.participants || [],
|
participants: reservation.participants || [],
|
||||||
invitees: reservation.invitees || [],
|
invitees: reservation.invitees || [],
|
||||||
|
statusId: reservation.statusId,
|
||||||
};
|
};
|
||||||
return JSON.stringify(relevantData);
|
return JSON.stringify(relevantData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LibreBooking Trigger Node
|
* LibreBooking Trigger Node
|
||||||
*
|
*
|
||||||
* Drei Modi:
|
* Drei Modi:
|
||||||
* 1. Get All (One-Time): Alle Reservierungen für einen Zeitraum abrufen
|
* 1. Get All (One-Time): Alle Reservierungen für einen Zeitraum abrufen
|
||||||
* 2. New Reservations (Poll): Bei neuen Reservierungen triggern
|
* 2. New Reservations (Poll): Bei neuen Reservierungen triggern
|
||||||
|
|
@ -297,8 +413,52 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
},
|
},
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
// GET ALL MODE - DATE RANGE
|
// GET ALL MODE - DATE RANGE SELECTOR
|
||||||
// =====================================================
|
// =====================================================
|
||||||
|
{
|
||||||
|
displayName: 'Zeitraum',
|
||||||
|
name: 'dateRange',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
triggerMode: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Benutzerdefiniert',
|
||||||
|
value: 'custom',
|
||||||
|
description: 'Start- und Enddatum manuell angeben',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Diese Woche',
|
||||||
|
value: 'thisWeek',
|
||||||
|
description: 'Von Montag bis Sonntag der aktuellen Woche',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nächste 2 Wochen',
|
||||||
|
value: 'next2Weeks',
|
||||||
|
description: 'Ab heute bis 14 Tage in die Zukunft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dieser Monat',
|
||||||
|
value: 'thisMonth',
|
||||||
|
description: 'Vom 1. bis zum letzten Tag des aktuellen Monats',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nächste 2 Monate',
|
||||||
|
value: 'next2Months',
|
||||||
|
description: 'Ab heute bis 2 Monate in die Zukunft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dieses Jahr',
|
||||||
|
value: 'thisYear',
|
||||||
|
description: 'Vom 1. Januar bis 31. Dezember des aktuellen Jahres',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'custom',
|
||||||
|
description: 'Vordefinierter Zeitraum für den Abruf',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Startdatum',
|
displayName: 'Startdatum',
|
||||||
name: 'startDate',
|
name: 'startDate',
|
||||||
|
|
@ -306,6 +466,7 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
triggerMode: ['getAll'],
|
triggerMode: ['getAll'],
|
||||||
|
dateRange: ['custom'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
|
|
@ -318,6 +479,7 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
triggerMode: ['getAll'],
|
triggerMode: ['getAll'],
|
||||||
|
dateRange: ['custom'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
|
|
@ -341,10 +503,49 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
{ name: 'Nächste 14 Tage', value: '14days' },
|
{ name: 'Nächste 14 Tage', value: '14days' },
|
||||||
{ name: 'Nächste 30 Tage', value: '30days' },
|
{ name: 'Nächste 30 Tage', value: '30days' },
|
||||||
{ name: 'Nächste 90 Tage', value: '90days' },
|
{ name: 'Nächste 90 Tage', value: '90days' },
|
||||||
|
{ name: 'Nächste 180 Tage (6 Monate)', value: '180days' },
|
||||||
],
|
],
|
||||||
default: '14days',
|
default: '14days',
|
||||||
description: 'Zeitfenster für die Überwachung von Reservierungen',
|
description: 'Zeitfenster für die Überwachung von Reservierungen',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// TIME FILTER FOR NEW/UPDATED MODES
|
||||||
|
// =====================================================
|
||||||
|
{
|
||||||
|
displayName: 'Zeit-Filter',
|
||||||
|
name: 'timeFilter',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
triggerMode: ['newReservations', 'updatedReservations'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Alle (Kein Filter)',
|
||||||
|
value: 'all',
|
||||||
|
description: 'Alle neuen/geänderten Reservierungen, unabhängig vom Datum',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nur Heute',
|
||||||
|
value: 'today',
|
||||||
|
description: 'Nur Reservierungen, die heute stattfinden',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nächste 3 Tage',
|
||||||
|
value: 'next3Days',
|
||||||
|
description: 'Nur Reservierungen, die in den nächsten 3 Tagen stattfinden',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nächste 7 Tage',
|
||||||
|
value: 'next7Days',
|
||||||
|
description: 'Nur Reservierungen, die in den nächsten 7 Tagen stattfinden',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'all',
|
||||||
|
description: 'Filtert Reservierungen nach ihrem Startdatum',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Hinweis',
|
displayName: 'Hinweis',
|
||||||
name: 'pollingNotice',
|
name: 'pollingNotice',
|
||||||
|
|
@ -355,7 +556,8 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
triggerMode: ['newReservations', 'updatedReservations'],
|
triggerMode: ['newReservations', 'updatedReservations'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'Beim ersten Poll werden existierende Reservierungen gespeichert, aber nicht getriggert. Nur nachfolgende Änderungen lösen den Trigger aus.',
|
description:
|
||||||
|
'Beim ersten Poll werden existierende Reservierungen gespeichert, aber nicht getriggert. Nur nachfolgende Änderungen lösen den Trigger aus.',
|
||||||
},
|
},
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
|
|
@ -407,7 +609,8 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
name: 'fetchDetails',
|
name: 'fetchDetails',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
description: 'Ruft vollständige Reservierungsdaten inkl. Custom Attributes ab (zusätzliche API-Aufrufe)',
|
description:
|
||||||
|
'Ruft vollständige Reservierungsdaten inkl. Custom Attributes ab (zusätzliche API-Aufrufe)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Debug-Modus',
|
displayName: 'Debug-Modus',
|
||||||
|
|
@ -452,40 +655,43 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
// MODE: Get All (One-Time / Every Poll)
|
// MODE: Get All (One-Time / Every Poll)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
if (triggerMode === 'getAll') {
|
if (triggerMode === 'getAll') {
|
||||||
|
const dateRange = this.getNodeParameter('dateRange', 'custom') as string;
|
||||||
const startDate = this.getNodeParameter('startDate', '') as string;
|
const startDate = this.getNodeParameter('startDate', '') as string;
|
||||||
const endDate = this.getNodeParameter('endDate', '') as string;
|
const endDate = this.getNodeParameter('endDate', '') as string;
|
||||||
|
|
||||||
const { start, end } = getTimeWindowForGetAll(
|
const { start, end } = getTimeWindowForGetAll(
|
||||||
|
dateRange,
|
||||||
startDate || undefined,
|
startDate || undefined,
|
||||||
endDate || undefined,
|
endDate || undefined,
|
||||||
14
|
14,
|
||||||
);
|
);
|
||||||
|
|
||||||
const reservations = await getReservations(
|
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
||||||
this,
|
|
||||||
baseUrl,
|
|
||||||
session,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
filters,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.log(`[LibreBooking Trigger] Get All Mode - Found ${reservations.length} reservations`);
|
console.log(
|
||||||
console.log(`[LibreBooking Trigger] Date Range: ${start} to ${end}`);
|
`[LibreBooking Trigger] Get All Mode - Found ${reservations.length} reservations`,
|
||||||
|
);
|
||||||
|
console.log(`[LibreBooking Trigger] Date Range: ${dateRange}`);
|
||||||
|
console.log(`[LibreBooking Trigger] Period: ${start} to ${end}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reservations.length === 0) {
|
if (reservations.length === 0) {
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
return [[{
|
return [
|
||||||
json: {
|
[
|
||||||
_debug: true,
|
{
|
||||||
_message: 'Keine Reservierungen im Zeitraum gefunden',
|
json: {
|
||||||
_startDate: start,
|
_debug: true,
|
||||||
_endDate: end,
|
_message: 'Keine Reservierungen im Zeitraum gefunden',
|
||||||
_count: 0,
|
_dateRange: dateRange,
|
||||||
},
|
_startDate: start,
|
||||||
}]];
|
_endDate: end,
|
||||||
|
_count: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -514,6 +720,7 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
json: {
|
json: {
|
||||||
...reservationData,
|
...reservationData,
|
||||||
_eventType: 'getAll',
|
_eventType: 'getAll',
|
||||||
|
_dateRange: dateRange,
|
||||||
_triggeredAt: new Date().toISOString(),
|
_triggeredAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -525,16 +732,10 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
// ==========================================
|
// ==========================================
|
||||||
else if (triggerMode === 'newReservations') {
|
else if (triggerMode === 'newReservations') {
|
||||||
const timeWindow = this.getNodeParameter('timeWindow', '14days') as string;
|
const timeWindow = this.getNodeParameter('timeWindow', '14days') as string;
|
||||||
|
const timeFilter = this.getNodeParameter('timeFilter', 'all') as string;
|
||||||
const { start, end } = getTimeWindowForPolling(timeWindow);
|
const { start, end } = getTimeWindowForPolling(timeWindow);
|
||||||
|
|
||||||
const reservations = await getReservations(
|
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
||||||
this,
|
|
||||||
baseUrl,
|
|
||||||
session,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
filters,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialisiere seenIds beim ersten Poll
|
// Initialisiere seenIds beim ersten Poll
|
||||||
if (!webhookData.seenIds) {
|
if (!webhookData.seenIds) {
|
||||||
|
|
@ -547,7 +748,10 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.log(`[LibreBooking Trigger] New Reservations Mode`);
|
console.log(`[LibreBooking Trigger] New Reservations Mode`);
|
||||||
console.log(`[LibreBooking Trigger] First Poll: ${webhookData.isFirstPoll}`);
|
console.log(`[LibreBooking Trigger] First Poll: ${webhookData.isFirstPoll}`);
|
||||||
console.log(`[LibreBooking Trigger] Current IDs: ${currentIds.length}, Seen IDs: ${webhookData.seenIds.length}`);
|
console.log(`[LibreBooking Trigger] Time Filter: ${timeFilter}`);
|
||||||
|
console.log(
|
||||||
|
`[LibreBooking Trigger] Current IDs: ${currentIds.length}, Seen IDs: ${webhookData.seenIds.length}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beim ersten Poll: Nur IDs speichern, NICHT triggern
|
// Beim ersten Poll: Nur IDs speichern, NICHT triggern
|
||||||
|
|
@ -557,33 +761,53 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
webhookData.lastPollTime = new Date().toISOString();
|
webhookData.lastPollTime = new Date().toISOString();
|
||||||
|
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
return [[{
|
return [
|
||||||
json: {
|
[
|
||||||
_debug: true,
|
{
|
||||||
_message: 'Erster Poll - IDs wurden gespeichert, keine Events getriggert',
|
json: {
|
||||||
_savedIds: currentIds.length,
|
_debug: true,
|
||||||
_ids: currentIds,
|
_message: 'Erster Poll - IDs wurden gespeichert, keine Events getriggert',
|
||||||
_timestamp: webhookData.lastPollTime,
|
_savedIds: currentIds.length,
|
||||||
},
|
_ids: currentIds,
|
||||||
}]];
|
_timestamp: webhookData.lastPollTime,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // Nichts triggern beim ersten Poll
|
return null; // Nichts triggern beim ersten Poll
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nur NEUE Reservierungen (die wir noch nicht gesehen haben)
|
// Nur NEUE Reservierungen (die wir noch nicht gesehen haben)
|
||||||
const newReservations = reservations.filter((r: ReservationData) =>
|
let newReservations = reservations.filter(
|
||||||
!webhookData.seenIds!.includes(r.referenceNumber)
|
(r: ReservationData) => !webhookData.seenIds!.includes(r.referenceNumber),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update seenIds mit allen aktuellen IDs
|
// Update seenIds mit allen aktuellen IDs
|
||||||
webhookData.seenIds = currentIds;
|
webhookData.seenIds = currentIds;
|
||||||
webhookData.lastPollTime = new Date().toISOString();
|
webhookData.lastPollTime = new Date().toISOString();
|
||||||
|
|
||||||
|
if (debugMode) {
|
||||||
|
console.log(
|
||||||
|
`[LibreBooking Trigger] Found ${newReservations.length} new reservations before filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newReservations.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeit-Filter anwenden
|
||||||
|
newReservations = filterByTime(newReservations, timeFilter);
|
||||||
|
|
||||||
|
if (debugMode) {
|
||||||
|
console.log(
|
||||||
|
`[LibreBooking Trigger] ${newReservations.length} reservations after time filter (${timeFilter})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (newReservations.length === 0) {
|
if (newReservations.length === 0) {
|
||||||
if (debugMode) {
|
|
||||||
console.log(`[LibreBooking Trigger] No new reservations found`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -611,6 +835,7 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
json: {
|
json: {
|
||||||
...reservationData,
|
...reservationData,
|
||||||
_eventType: 'new',
|
_eventType: 'new',
|
||||||
|
_timeFilter: timeFilter,
|
||||||
_triggeredAt: new Date().toISOString(),
|
_triggeredAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -626,16 +851,10 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
// ==========================================
|
// ==========================================
|
||||||
else if (triggerMode === 'updatedReservations') {
|
else if (triggerMode === 'updatedReservations') {
|
||||||
const timeWindow = this.getNodeParameter('timeWindow', '14days') as string;
|
const timeWindow = this.getNodeParameter('timeWindow', '14days') as string;
|
||||||
|
const timeFilter = this.getNodeParameter('timeFilter', 'all') as string;
|
||||||
const { start, end } = getTimeWindowForPolling(timeWindow);
|
const { start, end } = getTimeWindowForPolling(timeWindow);
|
||||||
|
|
||||||
const reservations = await getReservations(
|
const reservations = await getReservations(this, baseUrl, session, start, end, filters);
|
||||||
this,
|
|
||||||
baseUrl,
|
|
||||||
session,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
filters,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialisiere reservationHashes beim ersten Poll
|
// Initialisiere reservationHashes beim ersten Poll
|
||||||
if (!webhookData.reservationHashes) {
|
if (!webhookData.reservationHashes) {
|
||||||
|
|
@ -646,33 +865,41 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.log(`[LibreBooking Trigger] Updated Reservations Mode`);
|
console.log(`[LibreBooking Trigger] Updated Reservations Mode`);
|
||||||
console.log(`[LibreBooking Trigger] First Poll: ${webhookData.isFirstPoll}`);
|
console.log(`[LibreBooking Trigger] First Poll: ${webhookData.isFirstPoll}`);
|
||||||
console.log(`[LibreBooking Trigger] Current: ${reservations.length}, Stored hashes: ${Object.keys(webhookData.reservationHashes).length}`);
|
console.log(`[LibreBooking Trigger] Time Filter: ${timeFilter}`);
|
||||||
|
console.log(
|
||||||
|
`[LibreBooking Trigger] Current: ${reservations.length}, Stored hashes: ${Object.keys(webhookData.reservationHashes).length}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beim ersten Poll: Nur Hashes speichern, NICHT triggern
|
// Beim ersten Poll: Nur Hashes speichern, NICHT triggern
|
||||||
if (webhookData.isFirstPoll) {
|
if (webhookData.isFirstPoll) {
|
||||||
for (const reservation of reservations) {
|
for (const reservation of reservations) {
|
||||||
webhookData.reservationHashes[reservation.referenceNumber] = getReservationHash(reservation);
|
webhookData.reservationHashes[reservation.referenceNumber] =
|
||||||
|
getReservationHash(reservation);
|
||||||
}
|
}
|
||||||
webhookData.isFirstPoll = false;
|
webhookData.isFirstPoll = false;
|
||||||
webhookData.lastPollTime = new Date().toISOString();
|
webhookData.lastPollTime = new Date().toISOString();
|
||||||
|
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
return [[{
|
return [
|
||||||
json: {
|
[
|
||||||
_debug: true,
|
{
|
||||||
_message: 'Erster Poll - Hashes wurden gespeichert, keine Events getriggert',
|
json: {
|
||||||
_savedHashes: Object.keys(webhookData.reservationHashes).length,
|
_debug: true,
|
||||||
_timestamp: webhookData.lastPollTime,
|
_message: 'Erster Poll - Hashes wurden gespeichert, keine Events getriggert',
|
||||||
},
|
_savedHashes: Object.keys(webhookData.reservationHashes).length,
|
||||||
}]];
|
_timestamp: webhookData.lastPollTime,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // Nichts triggern beim ersten Poll
|
return null; // Nichts triggern beim ersten Poll
|
||||||
}
|
}
|
||||||
|
|
||||||
// Geänderte Reservierungen finden
|
// Geänderte Reservierungen finden
|
||||||
const updatedReservations: ReservationData[] = [];
|
let updatedReservations: ReservationData[] = [];
|
||||||
const newHashes: Record<string, string> = {};
|
const newHashes: Record<string, string> = {};
|
||||||
|
|
||||||
for (const reservation of reservations) {
|
for (const reservation of reservations) {
|
||||||
|
|
@ -692,10 +919,26 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
webhookData.reservationHashes = newHashes;
|
webhookData.reservationHashes = newHashes;
|
||||||
webhookData.lastPollTime = new Date().toISOString();
|
webhookData.lastPollTime = new Date().toISOString();
|
||||||
|
|
||||||
|
if (debugMode) {
|
||||||
|
console.log(
|
||||||
|
`[LibreBooking Trigger] Found ${updatedReservations.length} updated reservations before filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedReservations.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeit-Filter anwenden
|
||||||
|
updatedReservations = filterByTime(updatedReservations, timeFilter);
|
||||||
|
|
||||||
|
if (debugMode) {
|
||||||
|
console.log(
|
||||||
|
`[LibreBooking Trigger] ${updatedReservations.length} reservations after time filter (${timeFilter})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (updatedReservations.length === 0) {
|
if (updatedReservations.length === 0) {
|
||||||
if (debugMode) {
|
|
||||||
console.log(`[LibreBooking Trigger] No updated reservations found`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -723,13 +966,16 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
json: {
|
json: {
|
||||||
...reservationData,
|
...reservationData,
|
||||||
_eventType: 'updated',
|
_eventType: 'updated',
|
||||||
|
_timeFilter: timeFilter,
|
||||||
_triggeredAt: new Date().toISOString(),
|
_triggeredAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debugMode && returnData.length > 0) {
|
if (debugMode && returnData.length > 0) {
|
||||||
console.log(`[LibreBooking Trigger] Triggering ${returnData.length} updated reservations`);
|
console.log(
|
||||||
|
`[LibreBooking Trigger] Triggering ${returnData.length} updated reservations`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -738,7 +984,6 @@ export class LibreBookingTrigger implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
return [returnData];
|
return [returnData];
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
await signOutTrigger(this, baseUrl, session);
|
await signOutTrigger(this, baseUrl, session);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-nodes-librebooking",
|
"name": "n8n-nodes-librebooking",
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"description": "n8n Node für LibreBooking - Ressourcen- und Reservierungsverwaltung",
|
"description": "n8n Node für LibreBooking - Ressourcen- und Reservierungsverwaltung",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"n8n-community-node-package",
|
"n8n-community-node-package",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,485 @@
|
||||||
|
/**
|
||||||
|
* Comprehensive Trigger Test Script for LibreBooking
|
||||||
|
*
|
||||||
|
* Tests:
|
||||||
|
* 1. Authentication
|
||||||
|
* 2. Get All Reservations
|
||||||
|
* 3. Create New Reservation (triggers "New" mode)
|
||||||
|
* 4. Update Reservation (triggers "Updated" mode)
|
||||||
|
* 5. Delete Reservation
|
||||||
|
* 6. Date Range calculations
|
||||||
|
* 7. Time Filter logic
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as https from 'https';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://librebooking.zell-cloud.de';
|
||||||
|
const USERNAME = 'sebastian.zell@zell-aufmass.de';
|
||||||
|
const PASSWORD = 'wanUQ4uVqU6lfP';
|
||||||
|
|
||||||
|
// Test date: 7.2.2026
|
||||||
|
const TEST_DATE = '2026-02-07';
|
||||||
|
|
||||||
|
interface TestResult {
|
||||||
|
name: string;
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Session {
|
||||||
|
sessionToken: string;
|
||||||
|
userId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: TestResult[] = [];
|
||||||
|
|
||||||
|
function log(message: string) {
|
||||||
|
console.log(`[${new Date().toISOString()}] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logSuccess(name: string, message: string, data?: any) {
|
||||||
|
log(`✅ ${name}: ${message}`);
|
||||||
|
results.push({ name, success: true, message, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
function logError(name: string, message: string, data?: any) {
|
||||||
|
log(`❌ ${name}: ${message}`);
|
||||||
|
results.push({ name, success: false, message, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeRequest(
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
body?: any,
|
||||||
|
headers?: Record<string, string>
|
||||||
|
): Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = new URL(`${BASE_URL}${path}`);
|
||||||
|
const options: https.RequestOptions = {
|
||||||
|
hostname: url.hostname,
|
||||||
|
port: 443,
|
||||||
|
path: url.pathname + url.search,
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...headers,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = https.request(options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => (data += chunk));
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
resolve(json);
|
||||||
|
} catch (e) {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', reject);
|
||||||
|
if (body) {
|
||||||
|
req.write(JSON.stringify(body));
|
||||||
|
}
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== AUTHENTICATION =====
|
||||||
|
async function authenticate(): Promise<Session> {
|
||||||
|
const response = await makeRequest(
|
||||||
|
'POST',
|
||||||
|
'/Web/Services/index.php/Authentication/Authenticate',
|
||||||
|
{ username: USERNAME, password: PASSWORD }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.isAuthenticated) {
|
||||||
|
throw new Error('Authentication failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionToken: response.sessionToken,
|
||||||
|
userId: response.userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function signOut(session: Session): Promise<void> {
|
||||||
|
await makeRequest(
|
||||||
|
'POST',
|
||||||
|
'/Web/Services/index.php/Authentication/SignOut',
|
||||||
|
{ userId: session.userId, sessionToken: session.sessionToken }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== API REQUESTS =====
|
||||||
|
async function getReservations(
|
||||||
|
session: Session,
|
||||||
|
startDate: string,
|
||||||
|
endDate: string
|
||||||
|
): Promise<any[]> {
|
||||||
|
const response = await makeRequest(
|
||||||
|
'GET',
|
||||||
|
`/Web/Services/index.php/Reservations/?startDateTime=${startDate}T00:00:00&endDateTime=${endDate}T23:59:59`,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
'X-Booked-SessionToken': session.sessionToken,
|
||||||
|
'X-Booked-UserId': session.userId.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response.reservations || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createReservation(
|
||||||
|
session: Session,
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
resourceId: number;
|
||||||
|
startDateTime: string;
|
||||||
|
endDateTime: string;
|
||||||
|
}
|
||||||
|
): Promise<any> {
|
||||||
|
const response = await makeRequest(
|
||||||
|
'POST',
|
||||||
|
'/Web/Services/index.php/Reservations/',
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
userId: session.userId,
|
||||||
|
termsAccepted: true,
|
||||||
|
allowParticipation: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'X-Booked-SessionToken': session.sessionToken,
|
||||||
|
'X-Booked-UserId': session.userId.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateReservation(
|
||||||
|
session: Session,
|
||||||
|
referenceNumber: string,
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
resourceId: number;
|
||||||
|
startDateTime: string;
|
||||||
|
endDateTime: string;
|
||||||
|
}
|
||||||
|
): Promise<any> {
|
||||||
|
const response = await makeRequest(
|
||||||
|
'POST',
|
||||||
|
`/Web/Services/index.php/Reservations/${referenceNumber}`,
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
userId: session.userId,
|
||||||
|
termsAccepted: true,
|
||||||
|
allowParticipation: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'X-Booked-SessionToken': session.sessionToken,
|
||||||
|
'X-Booked-UserId': session.userId.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteReservation(session: Session, referenceNumber: string): Promise<any> {
|
||||||
|
const response = await makeRequest(
|
||||||
|
'DELETE',
|
||||||
|
`/Web/Services/index.php/Reservations/${referenceNumber}`,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
'X-Booked-SessionToken': session.sessionToken,
|
||||||
|
'X-Booked-UserId': session.userId.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getResources(session: Session): Promise<any[]> {
|
||||||
|
const response = await makeRequest(
|
||||||
|
'GET',
|
||||||
|
'/Web/Services/index.php/Resources/',
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
'X-Booked-SessionToken': session.sessionToken,
|
||||||
|
'X-Booked-UserId': session.userId.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response.resources || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== DATE RANGE TESTS =====
|
||||||
|
function testDateRange() {
|
||||||
|
log('\n📅 Testing Date Range Calculations...\n');
|
||||||
|
|
||||||
|
const now = new Date('2026-01-25'); // Simulate current date
|
||||||
|
|
||||||
|
// Test thisWeek
|
||||||
|
const dayOfWeek = now.getDay();
|
||||||
|
const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
||||||
|
const monday = new Date(now);
|
||||||
|
monday.setDate(now.getDate() + diffToMonday);
|
||||||
|
monday.setHours(0, 0, 0, 0);
|
||||||
|
const sunday = new Date(monday);
|
||||||
|
sunday.setDate(monday.getDate() + 6);
|
||||||
|
|
||||||
|
logSuccess(
|
||||||
|
'thisWeek',
|
||||||
|
`Monday: ${monday.toISOString().split('T')[0]}, Sunday: ${sunday.toISOString().split('T')[0]}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test next2Weeks
|
||||||
|
const next2Weeks = new Date(now);
|
||||||
|
next2Weeks.setDate(now.getDate() + 14);
|
||||||
|
logSuccess(
|
||||||
|
'next2Weeks',
|
||||||
|
`From: ${now.toISOString().split('T')[0]}, To: ${next2Weeks.toISOString().split('T')[0]}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test thisMonth
|
||||||
|
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||||
|
logSuccess(
|
||||||
|
'thisMonth',
|
||||||
|
`From: ${monthStart.toISOString().split('T')[0]}, To: ${monthEnd.toISOString().split('T')[0]}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test next2Months
|
||||||
|
const next2Months = new Date(now);
|
||||||
|
next2Months.setMonth(now.getMonth() + 2);
|
||||||
|
logSuccess(
|
||||||
|
'next2Months',
|
||||||
|
`From: ${now.toISOString().split('T')[0]}, To: ${next2Months.toISOString().split('T')[0]}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test thisYear
|
||||||
|
const yearStart = new Date(now.getFullYear(), 0, 1);
|
||||||
|
const yearEnd = new Date(now.getFullYear(), 11, 31);
|
||||||
|
logSuccess(
|
||||||
|
'thisYear',
|
||||||
|
`From: ${yearStart.toISOString().split('T')[0]}, To: ${yearEnd.toISOString().split('T')[0]}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== TIME FILTER TESTS =====
|
||||||
|
function testTimeFilter() {
|
||||||
|
log('\n⏰ Testing Time Filter Logic...\n');
|
||||||
|
|
||||||
|
const today = new Date('2026-02-07');
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const reservations = [
|
||||||
|
{ title: 'Today', startDateTime: '2026-02-07T10:00:00' },
|
||||||
|
{ title: 'Tomorrow', startDateTime: '2026-02-08T10:00:00' },
|
||||||
|
{ title: 'In 3 days', startDateTime: '2026-02-10T10:00:00' },
|
||||||
|
{ title: 'In 7 days', startDateTime: '2026-02-14T10:00:00' },
|
||||||
|
{ title: 'In 10 days', startDateTime: '2026-02-17T10:00:00' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filter: today
|
||||||
|
const todayOnly = reservations.filter((r) => {
|
||||||
|
const startDate = new Date(r.startDateTime);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
return startDate.getTime() === today.getTime();
|
||||||
|
});
|
||||||
|
logSuccess('timeFilter=today', `Found ${todayOnly.length} reservation(s): ${todayOnly.map(r => r.title).join(', ')}`);
|
||||||
|
|
||||||
|
// Filter: next3Days
|
||||||
|
const threeDays = new Date(today);
|
||||||
|
threeDays.setDate(today.getDate() + 3);
|
||||||
|
const next3Days = reservations.filter((r) => {
|
||||||
|
const startDate = new Date(r.startDateTime);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
return startDate >= today && startDate <= threeDays;
|
||||||
|
});
|
||||||
|
logSuccess('timeFilter=next3Days', `Found ${next3Days.length} reservation(s): ${next3Days.map(r => r.title).join(', ')}`);
|
||||||
|
|
||||||
|
// Filter: next7Days
|
||||||
|
const sevenDays = new Date(today);
|
||||||
|
sevenDays.setDate(today.getDate() + 7);
|
||||||
|
const next7Days = reservations.filter((r) => {
|
||||||
|
const startDate = new Date(r.startDateTime);
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
return startDate >= today && startDate <= sevenDays;
|
||||||
|
});
|
||||||
|
logSuccess('timeFilter=next7Days', `Found ${next7Days.length} reservation(s): ${next7Days.map(r => r.title).join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== MAIN TEST =====
|
||||||
|
async function runTests() {
|
||||||
|
console.log('🧪 LibreBooking Trigger Test Suite\n');
|
||||||
|
console.log('═'.repeat(60) + '\n');
|
||||||
|
|
||||||
|
// Test date calculations first (no API needed)
|
||||||
|
testDateRange();
|
||||||
|
testTimeFilter();
|
||||||
|
|
||||||
|
log('\n🔌 Testing API Operations...\n');
|
||||||
|
|
||||||
|
let session: Session | null = null;
|
||||||
|
let createdRefNumber: string | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Authentication
|
||||||
|
log('1️⃣ Authenticating...');
|
||||||
|
session = await authenticate();
|
||||||
|
logSuccess('Authentication', `Session: ${session.sessionToken.substring(0, 20)}...`);
|
||||||
|
|
||||||
|
// 2. Get Resources (to find available resource)
|
||||||
|
log('\n2️⃣ Getting Resources...');
|
||||||
|
const resources = await getResources(session);
|
||||||
|
if (resources.length === 0) {
|
||||||
|
logError('GetResources', 'No resources found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logSuccess('GetResources', `Found ${resources.length} resources`);
|
||||||
|
const testResourceId = resources[0].resourceId;
|
||||||
|
log(` Using resource: ${resources[0].name} (ID: ${testResourceId})`);
|
||||||
|
|
||||||
|
// 3. Get Initial Reservations
|
||||||
|
log('\n3️⃣ Getting initial reservations...');
|
||||||
|
const initialReservations = await getReservations(session, TEST_DATE, '2026-02-14');
|
||||||
|
logSuccess('GetReservations', `Found ${initialReservations.length} reservations`);
|
||||||
|
|
||||||
|
// Store initial IDs (simulating first poll)
|
||||||
|
const seenIds = initialReservations.map((r: any) => r.referenceNumber);
|
||||||
|
log(` Stored ${seenIds.length} IDs (simulating first poll)`);
|
||||||
|
|
||||||
|
// 4. Create New Reservation
|
||||||
|
log('\n4️⃣ Creating new reservation...');
|
||||||
|
const createResult = await createReservation(session, {
|
||||||
|
title: 'TEST: Neue Reservierung für Trigger-Test',
|
||||||
|
description: 'Diese Reservierung wird erstellt, um den "Neue Objekte" Trigger zu testen',
|
||||||
|
resourceId: testResourceId,
|
||||||
|
startDateTime: `${TEST_DATE}T14:00:00`,
|
||||||
|
endDateTime: `${TEST_DATE}T15:00:00`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (createResult.referenceNumber) {
|
||||||
|
createdRefNumber = createResult.referenceNumber;
|
||||||
|
logSuccess('CreateReservation', `Created: ${createdRefNumber}`);
|
||||||
|
} else {
|
||||||
|
logError('CreateReservation', `Failed: ${JSON.stringify(createResult)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Get Reservations Again (simulate second poll)
|
||||||
|
log('\n5️⃣ Simulating second poll...');
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2 seconds
|
||||||
|
|
||||||
|
const afterCreateReservations = await getReservations(session, TEST_DATE, '2026-02-14');
|
||||||
|
const currentIds = afterCreateReservations.map((r: any) => r.referenceNumber);
|
||||||
|
const newIds = currentIds.filter((id: string) => !seenIds.includes(id));
|
||||||
|
|
||||||
|
logSuccess(
|
||||||
|
'NewReservationDetection',
|
||||||
|
`Found ${newIds.length} new reservation(s): ${newIds.join(', ')}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Update Reservation
|
||||||
|
if (createdRefNumber) {
|
||||||
|
log('\n6️⃣ Updating reservation...');
|
||||||
|
|
||||||
|
// Store hash before update
|
||||||
|
const reservationBefore = afterCreateReservations.find(
|
||||||
|
(r: any) => r.referenceNumber === createdRefNumber
|
||||||
|
);
|
||||||
|
const hashBefore = JSON.stringify({
|
||||||
|
title: reservationBefore?.title,
|
||||||
|
description: reservationBefore?.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update - must include all required fields
|
||||||
|
const updateResult = await updateReservation(session, createdRefNumber, {
|
||||||
|
title: 'TEST: GEÄNDERTE Reservierung',
|
||||||
|
description: 'Diese Reservierung wurde geändert - Update-Trigger sollte feuern',
|
||||||
|
resourceId: testResourceId,
|
||||||
|
startDateTime: `${TEST_DATE}T14:00:00`,
|
||||||
|
endDateTime: `${TEST_DATE}T15:00:00`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updateResult.referenceNumber) {
|
||||||
|
logSuccess('UpdateReservation', `Updated: ${createdRefNumber}`);
|
||||||
|
} else {
|
||||||
|
logError('UpdateReservation', `Failed: ${JSON.stringify(updateResult)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Get Reservations Again (check for changes)
|
||||||
|
log('\n7️⃣ Checking for changes...');
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
|
const afterUpdateReservations = await getReservations(session, TEST_DATE, '2026-02-14');
|
||||||
|
const reservationAfter = afterUpdateReservations.find(
|
||||||
|
(r: any) => r.referenceNumber === createdRefNumber
|
||||||
|
);
|
||||||
|
const hashAfter = JSON.stringify({
|
||||||
|
title: reservationAfter?.title,
|
||||||
|
description: reservationAfter?.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hashBefore !== hashAfter) {
|
||||||
|
logSuccess(
|
||||||
|
'ChangeDetection',
|
||||||
|
`Change detected! Title: "${reservationAfter?.title}"`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logError('ChangeDetection', 'No change detected (hash unchanged)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Delete Reservation
|
||||||
|
log('\n8️⃣ Deleting reservation...');
|
||||||
|
await deleteReservation(session, createdRefNumber);
|
||||||
|
logSuccess('DeleteReservation', `Deleted: ${createdRefNumber}`);
|
||||||
|
|
||||||
|
// Verify deletion
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
const afterDeleteReservations = await getReservations(session, TEST_DATE, '2026-02-14');
|
||||||
|
const stillExists = afterDeleteReservations.some(
|
||||||
|
(r: any) => r.referenceNumber === createdRefNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!stillExists) {
|
||||||
|
logSuccess('DeletionVerified', 'Reservation successfully deleted');
|
||||||
|
} else {
|
||||||
|
logError('DeletionVerified', 'Reservation still exists!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. Sign Out
|
||||||
|
log('\n9️⃣ Signing out...');
|
||||||
|
await signOut(session);
|
||||||
|
logSuccess('SignOut', 'Successfully signed out');
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
logError('TestSuite', `Error: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
console.log('\n' + '═'.repeat(60));
|
||||||
|
console.log('📊 TEST SUMMARY\n');
|
||||||
|
|
||||||
|
const passed = results.filter((r) => r.success).length;
|
||||||
|
const failed = results.filter((r) => !r.success).length;
|
||||||
|
|
||||||
|
console.log(` ✅ Passed: ${passed}`);
|
||||||
|
console.log(` ❌ Failed: ${failed}`);
|
||||||
|
console.log(` 📝 Total: ${results.length}`);
|
||||||
|
|
||||||
|
if (failed > 0) {
|
||||||
|
console.log('\n Failed Tests:');
|
||||||
|
results
|
||||||
|
.filter((r) => !r.success)
|
||||||
|
.forEach((r) => console.log(` - ${r.name}: ${r.message}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n' + '═'.repeat(60));
|
||||||
|
|
||||||
|
// Exit with appropriate code
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests();
|
||||||
Loading…
Reference in New Issue