commit bc98a19fc624393f464a576cc92859883d307e75 Author: Michael Kaufmann Date: Thu Feb 12 08:36:05 2026 +0100 feat: server-based version with menu flagging, distributed polling, and SSE Complete implementation including: - Express server with Bessa API proxy - Puppeteer scraper for menu data - Flag storage (file-based persistence) - SSE manager for real-time updates - Polling orchestrator for distributed polling - Frontend with weekly view, ordering, and flagging UI - Yellow/green glow indicators for flagged items diff --git a/.agent/rules/rules.md b/.agent/rules/rules.md new file mode 100644 index 0000000..c99cddf --- /dev/null +++ b/.agent/rules/rules.md @@ -0,0 +1,50 @@ +--- +trigger: always_on +--- + +# Antigravity Project Rules & Guidelines + +## 1. Project Context +- **Goal**: Create a user-friendly wrapper for `https://web.bessa.app/knapp-kantine`. +- **Core Mission**: Improve UX with a Weekly View, simplified booking flow, and cost transparency. +- **Environment**: **LIVE PRODUCTION SYSTEM**. Usage incurs real financial costs. + +## 2. Security & Operational Protocols 🛡️ +**CRITICAL: You must obtain explicit user approval before:** +1. **Financial Actions**: Any action that triggers a cost (Ordering/Booking). +2. **Credentials**: Submitting forms with usernames/passwords. +3. **High-Risk Commands**: System-level deletion or potentially destructive terminal commands. + +**Live System Protocol:** +- Treat the target website as a fragile production environment. +- Minimize automated browsing traffic. + +## 3. Web Software Expert Persona +- **Role**: Senior Developer Advocate and Solutions Architect. +- **Language**: Interaction in **German**; Code Comments in **English**. +- **Thinking Process**: Use `` blocks for complex architectural decisions (Simulate "Deep Think"). +- **Interaction**: Be proactive, concise, and helpful. Focus on code value. + +## 4. Development Standards +**Tech Stack:** +- **Container**: Docker-based application. +- **Config**: Configurable port. + +**Coding Style:** +- **Typing**: Strict typing where applicable. +- **Comments**: Concise, English. +- **Frontend/UX**: + - Priority on Usability. + - **MANDATORY**: Tooltips/Help texts for all interactions. + +## 5. Agentic Workflow & Artifacts +**Core Philosophy**: Plan first, act second. +1. **Planning Mode**: For complex tasks, create an implementation plan and **wait for user review**. +2. **Artifacts**: + - **Visuals**: Generate screenshots/mockups for UI changes. + - **Evidence**: Log outputs for verification. +3. **Design**: Optimize code for AI readability (context efficiency). + +## 6. Workspace Scopes +- **Browser**: Allowed for documentation and safe browsing. No automated logins without permission. +- **Terminal**: No `rm -rf`. Run tests (`pytest` etc.) after logic changes. diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..eff8180 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Bessa Credentials (NEVER commit real credentials!) +BESSA_EMPLOYEE_NUMBER=YOUR_EMPLOYEE_NUMBER +BESSA_PASSWORD=YOUR_PASSWORD + +# Optional: Headless mode (set to false for debugging) +PUPPETEER_HEADLESS=true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c11155 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.env +*.log +data/*.json +!data/.gitkeep diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b237e80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +# BASIS: Wir nutzen das Webtop Image (Ubuntu + XFCE Desktop) +FROM lscr.io/linuxserver/webtop:ubuntu-xfce + +# METADATEN +LABEL maintainer="DeinName" +LABEL description="Google Antigravity IDE (Official Repo)" + +# VORBEREITUNG: Notwendige Tools installieren (curl, gpg für den Schlüssel) +RUN apt-get update && apt-get install -y \ + curl \ + gnupg \ + && rm -rf /var/lib/apt/lists/* + +# INSTALLATION: Google Repository hinzufügen +# 1. Keyring Verzeichnis erstellen +# 2. GPG Key herunterladen und speichern +# 3. Repository zur sources.list hinzufügen +RUN mkdir -p /etc/apt/keyrings && \ + curl -fsSL https://us-central1-apt.pkg.dev/doc/repo-signing-key.gpg | gpg --dearmor -o /etc/apt/keyrings/antigravity-repo-key.gpg && \ + echo "deb [signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" | tee /etc/apt/sources.list.d/antigravity.list + +# INSTALLATION: Antigravity installieren +RUN apt-get update && \ + apt-get install -y antigravity && \ + rm -rf /var/lib/apt/lists/* + +# INSTALLATION: Node.js und npm für Scraper-Entwicklung +# NodeSource Repository für aktuelle Node.js Version +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ + apt-get install -y nodejs && \ + rm -rf /var/lib/apt/lists/* + +# UMGEBUNGSVARIABLEN für Chromium und Playwright +# Diese helfen, Browser-Probleme in Docker zu vermeiden +# HINWEIS: Kein --headless damit Browser sichtbar für Monitoring/manuelle Eingriffe +ENV CHROME_BIN=/usr/bin/chromium-browser \ + CHROMIUM_FLAGS="--disable-gpu --remote-debugging-port=9222 --no-sandbox --disable-dev-shm-usage" \ + PLAYWRIGHT_BROWSERS_PATH=/usr/bin \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \ + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true + +# MODIFIZIERUNG DER .desktop-Datei +# Die Original-Datei befindet sich typischerweise unter /usr/share/applications/antigravity.desktop +# Wir suchen die Zeile, die mit 'Exec=' beginnt, und fügen die benötigten Flags hinzu. +RUN DESKTOP_FILE="/usr/share/applications/antigravity.desktop" && \ + if [ -f "$DESKTOP_FILE" ]; then \ + # Suchen und Ersetzen der 'Exec' Zeile mit den Flags --no-sandbox und --disable-gpu + sed -i 's|Exec=/usr/share/antigravity/antigravity %F|Exec=/usr/share/antigravity/antigravity --no-sandbox --disable-gpu %F|g' "$DESKTOP_FILE" && \ + echo "INFO: antigravity.desktop wurde erfolgreich gepatcht." || \ + echo "FEHLER: sed-Befehl konnte die Datei $DESKTOP_FILE nicht patchen." ; \ + else \ + echo "WARNUNG: $DESKTOP_FILE nicht gefunden. Überspringe Patch." ; \ + fi + +# OPTIONAL: Autostart einrichten +# Wir versuchen, die .desktop Datei in den Autostart-Ordner zu kopieren. +# Der Name ist meistens antigravity.desktop, zur Sicherheit prüfen wir beide Varianten. +#RUN mkdir -p /defaults/autostart 2> /dev/null && \ +# cp /usr/share/applications/antigravity.desktop ~/.config/autostart/ 2>/dev/null + +# VOLUME für Persistenz +VOLUME /config \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..85d2137 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# Bessa Knapp-Kantine Menu Scraper + +Automatischer Menü-Scraper für die Knapp-Kantine basierend auf https://web.bessa.app/knapp-kantine. + +## Setup + +1. **Dependencies installieren**: + ```bash + npm install + ``` + +2. **Credentials konfigurieren**: + ```bash + cp .env.example .env + # Dann .env bearbeiten und echte Zugangsdaten eintragen + ``` + +3. **TypeScript kompilieren**: + ```bash + npm run build + ``` + +## Usage + +### Menüs scrapen + +```bash +# Development mode (mit TypeScript direkt) +npm run dev + +# Production mode (kompiliertes JavaScript) +npm run build +npm run scrape +``` + +### Scraper-Ablauf + +1. Öffnet Browser (Puppeteer) +2. Akzeptiert Cookies +3. Klickt "Pre-order menu" +4. Loggt sich ein (mit Credentials aus `.env`) +5. Navigiert durch Kalenderwochenansicht +6. Extrahiert Menüdaten für jeden Tag +7. Speichert alles in `data/menus.json` + +## Output + +Die gescrapten Daten werden in `data/menus.json` gespeichert: + +```json +{ + "lastUpdated": "2026-02-02T10:00:00.000Z", + "weeks": [ + { + "year": 2026, + "weekNumber": 6, + "days": [ + { + "date": "2026-02-03", + "weekday": "Monday", + "items": [ + { + "id": "2026-02-03_M1_Herzhaftes", + "name": "M1 Herzhaftes", + "description": "Rindsuppe mit Backerbsen / Puten Tikka Masala...", + "price": 5.5, + "available": true + } + ] + } + ] + } + ] +} +``` + +## Development + +- `npm run dev` - Run scraper in development mode with tsx +- `npm run build` - Compile TypeScript to JavaScript +- `npm run type-check` - Check TypeScript types without building + +## Debugging + +Setze `PUPPETEER_HEADLESS=false` in `.env` um den Browser sichtbar zu machen. + +## Sicherheit + +⚠️ **WICHTIG**: Die `.env`-Datei enthält Zugangsdaten und darf niemals ins Git committed werden! diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md new file mode 100644 index 0000000..23faf8d --- /dev/null +++ b/REQUIREMENTS.md @@ -0,0 +1,56 @@ +# System Requirements Specification (SRS) + +## 1. Einleitung +### 1.1 Zweck des Systems +Das System dient als Wrapper und Erweiterung für das Bessa Web-Shop-Portal der Knapp-Kantine (https://web.bessa.app/knapp-kantine). Es ermöglicht den Benutzern, Menüpläne einzusehen, Buchungen automatisiert vorzunehmen und Menüdaten persistent für den öffentlichen Zugriff bereitzustellen, ohne dass jeder Betrachter eigene Zugangsdaten benötigt. + +### 1.2 High-Level-Scope +Das System umfasst einen automatisierten Scraper, eine API zur Datenbereitstellung, einen persistenten Dateispeicher für erfasste Menüs und ein Frontend-Dashboard zur Visualisierung der Kantinen-Wochenpläne. + +## 2. Funktionale Anforderungen + +| ID | Anforderung (Satzschablone nach Chris Rupp) | Priorität | +|:---|:---|:---| +| **Auth & Sessions** | | | +| FR-001 | Das System muss dem Benutzer die Möglichkeit bieten, sich mit Mitarbeiternummer und Passwort am Bessa-Backend anzumelden. | Hoch | +| FR-002 | Sobald ein Benutzer erfolgreich angemeldet ist, muss das System eine Session-ID erzeugen und die resultierenden Cookies verschlüsselt für 2 Stunden vorhalten. | Hoch | +| FR-003 | Das System muss die Zugangsdaten (Mitarbeiternummer/Passwort) unmittelbar nach der Verwendung durch den Scraper verwerfen und darf diese nicht dauerhaft speichern. | Hoch | +| **Scraper & Datenextraktion** | | | +| FR-004 | Das System muss in der Lage sein, den wöchentlichen Menüplan (Montag bis Freitag) automatisiert von der Bessa-Webseite zu extrahieren. | Hoch | +| FR-005 | Wenn ein Tag bereits eine Buchung enthält, muss das System den Navigationspfad so anpassen, dass dennoch alle verfügbaren Menüoptionen (M1F, M2F, etc.) extrahiert werden können. | Mittel | +| FR-006 | Das System muss für jedes extrahierte Gericht den Namen, die Beschreibung, den Preis und den Status (verfügbar/nicht verfügbar/ bestellt) erfassen. | Hoch | +| **Datenhaltung & Zugriff** | | | +| FR-007 | Das System muss erfolgreich gescrapte Menüpläne in einer persistenten JSON-Datei (`data/menus.json`) speichern. | Hoch | +| FR-008 | Das System muss unauthentifizierten Benutzern den Zugriff auf bereits im Speicher befindliche Menüdaten ermöglichen (Public Access). | Mittel | +| FR-009 | Falls keine Daten im persistenten Speicher vorhanden sind, muss das System einen nicht authentifizierten Benutzer die Möglichkeit bieten sich anzumelden, um den Speicher zu initialisieren. | Hoch | +| **Dashboard & UI** | | | +| FR-010 | Das System muss dem Benutzer eine intuitive Wochenansicht des Menüplans im Browser darstellen. | Mittel | +| FR-011 | Wenn ein Scraper-Vorgang aktiv ist, muss das System den Status (Fortschritt, aktuelle Aktion) in Echtzeit visualisieren. | Niedrig | +| **Buchungsfunktion** | | | +| FR-012 | Wenn der Benutzer authentifiziert ist, soll das System eine Bestellung für ein Menü ermöglichen. | Mittel | +| FR-013 | Wenn der Benutzer authentifiziert ist, soll das System ein bereits bestelltes Menü zu stornieren. | Mittel | +| **Menu Flagging & Notifications** | | | +| FR-014 | Das System muss authentifizierten Benutzern ermöglichen, nicht bestellbare Menüs (deren Cutoff noch nicht erreicht ist) zu markieren ("flaggen"). | Mittel | +| FR-015 | Das System soll die Statusprüfung für geflaggte Menüs auf verbundene Clients verteilen (Distributed Polling), um die Last zu minimieren. Der Server orchestriert, welcher Client wann welche Menüs prüft. | Mittel | +| FR-016 | Bei Statusänderung auf "verfügbar" muss das System den Benutzer benachrichtigen (Systembenachrichtigung). | Mittel | +| FR-017 | Geflaggte und ausverkaufte Menüs müssen im UI mit einem gelben Glow hervorgehoben werden. | Mittel | +| FR-018 | Geflaggte und verfügbare Menüs müssen im UI mit einem grünen Glow hervorgehoben werden. | Mittel | +| FR-019 | Wenn die Bestell-Cutoff-Zeit erreicht ist, muss das System das Flag automatisch entfernen. | Mittel | +## 3. Nicht-funktionale Anforderungen + +| Kategorie (ISO 25010) | ID | Anforderung | Zielwert/Metrik | +|:---|:---|:---|:---| +| **Performance** | NFR-001 | Antwortzeit der API für gecachte Daten | < 200 ms (95. Perzentil) | +| **Performance** | NFR-007 | Polling-Effizienz & Token-Nutzung | Kein System-Token verfügbar. Polling muss über authentifizierte User-Clients erfolgen. Der Server muss die Anfragen so verteilen, dass redundante Abfragen vermieden werden. | +| **Performance** | NFR-002 | Dauer eines vollständigen Scrape-Vorgangs (exkl. Navigation) | < 30 Sekunden pro Woche | +| **Sicherheit** | NFR-003 | Speicherung von Zugangsdaten | 0 (keine dauerhafte Speicherung von Passwörtern) | +| **Sicherheit** | NFR-004 | Session-Sicherheit | HttpOnly Cookies für die Kommunikation zwischen Frontend und Backend | +| **Wartbarkeit** | NFR-005 | Testabdeckung der Scraper-Logik | Alle Kern-Selektoren müssen durch Debug-HTML-Dumps verifizierbar sein | +| **Benutzbarkeit** | NFR-006 | Mobile Responsiveness | Dashboard muss auf Viewports ab 320px Breite fehlerfrei nutzbar sein | + +## 4. Technische Randbedingungen +* **Architektur**: Node.js Backend mit Express (API + Static Serving) und Vanilla JS Frontend. +* **Engine**: Direkte API-Integration (Reverse Engineering der Bessa API) für maximale Performance und Zuverlässigkeit. +* **Datenspeicher**: Dateibasierter JSON-Store für persistente Daten + In-Memory Caching. +* **Schnittstellen**: REST API (`/api/bookings`, `/api/status`, `/api/order`). +* **Runtime**: Node.js Umgebung, Docker-ready. diff --git a/RESEARCH.md b/RESEARCH.md new file mode 100644 index 0000000..99ad5f6 --- /dev/null +++ b/RESEARCH.md @@ -0,0 +1,55 @@ +# Bessa API Authentication Research + +This document describes the authentication flow for the Bessa Web App (`web.bessa.app/knapp-kantine`). + +## Overview + +The authentication process follows a multi-step flow involving a guest token and user credentials. + +### 1. Initial Guest Session +When the page first loads, it initializes a guest session. This session is associated with a guest token. + +* **Identified Guest Token:** `c3418725e95a9f90e3645cbc846b4d67c7c66131` +* **Usage:** Mandatory for the login request itself. + +### 2. User Login +The login request is sent to the `/auth/login/` endpoint. + +* **Endpoint:** `POST https://api.bessa.app/v1/auth/login/` +* **Headers:** + * `Authorization`: `Token ` + * `Content-Type`: `application/json` + * `Accept`: `application/json` + * `X-Client-Version`: `1.7.0_prod/2026-01-26` (Example) +* **Request Body:** + ```json + { + "email": "knapp-@bessa.app", + "password": "" + } + ``` + > [!NOTE] + > The employee number entered in the UI is automatically transformed into an email format: `knapp-@bessa.app`. + +### 3. Authentication Result +A successful login returns a session key. + +* **Response (200 OK):** + ```json + { + "key": "dba7d86e83c7f462fd8af96521dea41c4facd8a5" + } + ``` +* **Usage:** This `key` MUST be used in the `Authorization` header for all subsequent API requests. +* **Header Format:** `Authorization: Token dba7d86e83c7f462fd8af96521dea41c4facd8a5` + +### 4. Token Persistence +* The token is stored in the browser's `localStorage` under the key `AkitaStores`. +* Path: `AkitaStores.auth.token` + +## Implementation Considerations + +For the wrapper implementation: +1. **In-Memory Storage**: The token should be handled purely in-memory (e.g., in the user session) to ensure security and follow privacy guidelines. +2. **No Persistence**: Credentials or tokens should never be written to disk in a production environment. +3. **Automatic Email Transformation**: The login handler should automatically prepend `knapp-` and append `@bessa.app` to the provided employee number to mimic the official app's behavior. diff --git a/analysis_results/cookies.json b/analysis_results/cookies.json new file mode 100644 index 0000000..8c20424 --- /dev/null +++ b/analysis_results/cookies.json @@ -0,0 +1,113 @@ +{ + "cookies": [ + { + "name": "__cmpcc", + "value": "1", + "domain": "web.bessa.app", + "path": "/", + "expires": 1804426749, + "size": 8, + "httpOnly": false, + "secure": true, + "session": false, + "sameSite": "Lax", + "priority": "Medium", + "sameParty": false, + "sourceScheme": "Secure", + "sourcePort": 443 + }, + { + "name": "g_state", + "value": "{\"i_l\":0,\"i_ll\":1770298749698,\"i_b\":\"OwmjB/xOTtJtApPjLsCx6Vw3vtuduXvkmTEMjNJSXuE\",\"i_e\":{\"enable_itp_optimization\":15}}", + "domain": "web.bessa.app", + "path": "/", + "expires": 1785850749, + "size": 126, + "httpOnly": false, + "secure": false, + "session": false, + "priority": "Medium", + "sameParty": false, + "sourceScheme": "Secure", + "sourcePort": 443 + }, + { + "name": "_fbp", + "value": "fb.1.1770298749735.57921644138573676", + "domain": ".bessa.app", + "path": "/", + "expires": 1778074808, + "size": 40, + "httpOnly": false, + "secure": false, + "session": false, + "sameSite": "Lax", + "priority": "Medium", + "sameParty": false, + "sourceScheme": "Secure", + "sourcePort": 443 + }, + { + "name": "_ga", + "value": "GA1.1.531786136.1770298750", + "domain": ".bessa.app", + "path": "/", + "expires": 1804858750.164727, + "size": 29, + "httpOnly": false, + "secure": false, + "session": false, + "priority": "Medium", + "sameParty": false, + "sourceScheme": "Secure", + "sourcePort": 443 + }, + { + "name": "__cmpconsent16021", + "value": "CQfJhpgQfJhpgAfS8BENCQFgAAAAAAAAAAigF5wAQF5gXnABAXmAAA", + "domain": ".bessa.app", + "path": "/", + "expires": 1801834763, + "size": 71, + "httpOnly": false, + "secure": true, + "session": false, + "sameSite": "None", + "priority": "Medium", + "sameParty": false, + "sourceScheme": "Secure", + "sourcePort": 443 + }, + { + "name": "__cmpcccu16021", + "value": "aCQfLid1gA6XgGsY5PMqNQExowBZVC0A0QwAgQIRoBSm", + "domain": ".bessa.app", + "path": "/", + "expires": 1801834763, + "size": 58, + "httpOnly": false, + "secure": true, + "session": false, + "sameSite": "None", + "priority": "Medium", + "sameParty": false, + "sourceScheme": "Secure", + "sourcePort": 443 + }, + { + "name": "_ga_NT5W7DSRT4", + "value": "GS2.1.s1770298750$o1$g1$t1770298809$j1$l0$h0", + "domain": ".bessa.app", + "path": "/", + "expires": 1804858809.787043, + "size": 58, + "httpOnly": false, + "secure": false, + "session": false, + "priority": "Medium", + "sameParty": false, + "sourceScheme": "Secure", + "sourcePort": 443 + } + ] +} \ No newline at end of file diff --git a/analysis_results/final_page_state.html b/analysis_results/final_page_state.html new file mode 100644 index 0000000..f3aa066 --- /dev/null +++ b/analysis_results/final_page_state.html @@ -0,0 +1,459 @@ + + + + + + Bessa Web Shop + + + + + + + + + + + + + + + + + + + + + +
close
CW 7
mode_edit
February 10, 2026
Bessa Web Shop
MKaccount_circle
ORDER. +
Shopping cart
shopping_cart
Start your order
Choose your favorite dishes from our menu and add them.
+ + + + + +
\ No newline at end of file diff --git a/analysis_results/local_storage.json b/analysis_results/local_storage.json new file mode 100644 index 0000000..d6647e9 --- /dev/null +++ b/analysis_results/local_storage.json @@ -0,0 +1 @@ +{"lastExternalReferrerTime":"1770298749727","__cmpconsent16021_.bessa.app":"CQfJhpgQfJhpgAfS8BENCQFgAAAAAAAAAAigF5wAQF5gXnABAXmAAA","topicsLastReferenceTime":"1770298808783","__cmpconsent16021_expire_.bessa.app":"1801834763719","lastExternalReferrer":"empty","__cmpcccu16021_.bessa.app":"aCQfLid1gA6XgGsY5PMqNQExowBZVC0A0QwAgQIRoBSm","__cmpcccu16021_expire_.bessa.app":"1801834763719","AkitaStores":"{\"$cache\":{\"auth\":false},\"auth\":{\"askedForNotifications\":false,\"token\":\"dba7d86e83c7f462fd8af96521dea41c4facd8a5\",\"user\":{\"id\":85567,\"lastLogin\":\"2026-02-05T13:40:04.251Z\",\"created\":\"2025-11-26T22:22:46.535Z\",\"updated\":\"2025-12-09T07:26:02.309Z\",\"email\":\"knapp-2041@bessa.app\",\"firstName\":\"Michael\",\"lastName\":\"Kaufmann\",\"locale\":\"de_de\",\"country\":\"\",\"language\":\"de\",\"profile\":null,\"uuid\":\"de0e6518-f917-4679-afd1-47a6f5e22a55\",\"groups\":[\"Managed\"],\"dateOfBirth\":null,\"passwordChanged\":\"2025-12-09T07:26:02.304Z\",\"gender\":1}}}"} \ No newline at end of file diff --git a/analysis_results/network_traffic.json b/analysis_results/network_traffic.json new file mode 100644 index 0000000..ea10367 --- /dev/null +++ b/analysis_results/network_traffic.json @@ -0,0 +1,8139 @@ +[ + { + "timestamp": "2026-02-05T13:39:08.776Z", + "method": "GET", + "url": "https://web.bessa.app/knapp-kantine", + "status": 404, + "type": "document", + "requestHeaders": { + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "\"Linux\"", + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" + }, + "responseHeaders": { + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9c92cfeb5955c2fc-VIE", + "content-encoding": "zstd", + "content-type": "text/html", + "date": "Thu, 05 Feb 2026 13:39:08 GMT", + "last-modified": "Mon, 26 Jan 2026 10:52:11 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=A7PA0CqBf4jy4DftHXX5dwtPiE5OnDolAPbuoCcTOmFsKZ9%2FaoUUSMSt1Lw06G1fXDfh8IY0cZnS59FXBw8b9KrUd7ueAnETTEji\"}]}", + "server": "cloudflare", + "x-amz-error-code": "NoSuchKey", + "x-amz-error-detail-key": "knapp-kantine", + "x-amz-error-message": "The specified key does not exist.", + "x-amz-id-2": "QFTchO3r7GkzIHz23T8M0/oEN7Ye5QuEja8zCXRCTsUz6F1iDhwMsgcTCMVANzfqDMlSRFr9m0I=", + "x-amz-request-id": "TST4DZABAGJA66XZ", + "x-amz-version-id": "NaP6WlOw.zQxpMfEvcQeCgnQcTYK8RCe" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:08.818Z", + "method": "GET", + "url": "https://web.bessa.app/polyfills.5addf6de08934c01.js", + "status": 200, + "type": "script", + "requestHeaders": { + "origin": "https://web.bessa.app", + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/knapp-kantine", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*" + }, + "responseHeaders": { + "age": "444", + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "max-age=14400", + "cf-cache-status": "HIT", + "cf-ray": "9c92cfec09f6c2fc-VIE", + "content-encoding": "zstd", + "content-type": "text/javascript", + "date": "Thu, 05 Feb 2026 13:39:08 GMT", + "etag": "W/\"4963856c4ff8c5334a881f1756ba4245\"", + "last-modified": "Mon, 02 Jun 2025 04:17:57 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=gBONavXhEAxbVFcy2o1j2sgZhZXjHJNk7jfftiW1CV19DBb1RMBl5wMo4QrlXSmqwS0afpj9U6KmTri0oCjue5xPpV%2BSwArN4MR3\"}]}", + "server": "cloudflare", + "x-amz-id-2": "aZaYPlH0U2jdge3Ry2rbB/DvSo3lZ4SQvr0lFcfnJpy7DgmcfJuxUwQBQaPikjXLD66r2aXrWC0d6/jWMLUDeJVGy3W5mEwP", + "x-amz-request-id": "3N9X6P19PQWZTW4Y", + "x-amz-version-id": "T0u6KotVfRlYvKsh4gdSGCEuGAd6c8KO" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:08.819Z", + "method": "GET", + "url": "https://web.bessa.app/runtime.759001ef2bad74a9.js", + "status": 200, + "type": "script", + "requestHeaders": { + "origin": "https://web.bessa.app", + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/knapp-kantine", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*" + }, + "responseHeaders": { + "age": "444", + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "max-age=14400", + "cf-cache-status": "HIT", + "cf-ray": "9c92cfec09f3c2fc-VIE", + "content-encoding": "zstd", + "content-type": "text/javascript", + "date": "Thu, 05 Feb 2026 13:39:08 GMT", + "etag": "W/\"14e6a1a6429dbec8e9937f52838fe459\"", + "last-modified": "Fri, 12 Dec 2025 14:33:24 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=7jvHSP%2Fem7SnPkhwP3gkikxqZ9oS5UueEm4d1wJn%2B3AnnnbW0crc6eElhP648lgWD24v5uIcGsEK2%2Bfc2YiXwJn5fd91L91Q5a8I\"}]}", + "server": "cloudflare", + "x-amz-id-2": "Q3zfDM35UX3UOGlQcBBjnW9O/iha4sgMoCyP2+Q0pER59n9NoiZTGjYj2Yr40UECqJdlxW7QIfmh04kAUMerAbCaOSJXfBI2U7gThBKbShQ=", + "x-amz-request-id": "4JHCBW9X9AZD00SY", + "x-amz-version-id": "bIdGrB6b5w81AVxGiy3jBY9Rbw.Qe89e" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:08.929Z", + "method": "GET", + "url": "https://web.bessa.app/main.603f033ca5d94196.js", + "status": 200, + "type": "script", + "requestHeaders": { + "origin": "https://web.bessa.app", + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/knapp-kantine", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*" + }, + "responseHeaders": { + "age": "5310", + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "max-age=14400", + "cf-cache-status": "HIT", + "cf-ray": "9c92cfec2e975b81-VIE", + "content-encoding": "zstd", + "content-type": "text/javascript", + "date": "Thu, 05 Feb 2026 13:39:08 GMT", + "etag": "W/\"5771d9e0f8a56e1e49c194f2ea5c2a79\"", + "last-modified": "Mon, 26 Jan 2026 10:52:11 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "priority": "u=1,i=?0", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=%2F0fc6DEMtQnaAMRF85nj%2FLvHVnhvWewFqbdqGtWKaSUKedNfGM0xvSE5iCIwJE4rg1Hc2FqIHhPneiypZU6i6IWHcN0c%2BkRYoodev4Q%3D\"}]}", + "server": "cloudflare", + "server-timing": "cfExtPri", + "x-amz-id-2": "fokbP4vQvA/sckej1oCPSklXGcRAHL/Mg8fKAJ2LY1teXGni2joIUrMVKil5gbtLJdIhx4zpMsAma9UynjU7OkLpbUTIf7IDZ75T9frPT/s=", + "x-amz-request-id": "BP3NTFS5CBAY55ZJ", + "x-amz-version-id": "73oeW96ItVmrd4OGA8KgnpQs6LjP0q6g" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:09.126Z", + "method": "GET", + "url": "https://a.delivery.consentmanager.net/delivery/cmp.php?&cdid=c91380084548&h=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&&l=en&o=1770298748791", + "status": 200, + "type": "script", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*" + }, + "responseHeaders": { + "access-control-allow-origin": "*", + "cache-control": "no-store, no-cache, must-revalidate", + "content-encoding": "gzip", + "content-type": "text/javascript; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:39:08 GMT", + "edge-control": "no-store, no-cache, must-revalidate", + "expires": "Thu, 01 Dec 1994 16:00:00 GMT", + "last-modified": "Thu, 05 Feb 2026 13:39:08 GMT", + "pragma": "no-cache", + "set-cookie": "__cmpcc=1; Expires=Sun, 07-Mar-2027 13:39:08 GMT; Path=/; SameSite=None; Secure", + "vary": "Accept-Encoding" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:09.213Z", + "method": "GET", + "url": "https://web.bessa.app/assets/i18n/en.json", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/knapp-kantine", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json, text/plain, */*", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0" + }, + "responseHeaders": { + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9c92cfee0ea75b81-VIE", + "content-encoding": "zstd", + "content-type": "application/json", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "etag": "W/\"77581970270796c6943b5ee9d6f36e5a\"", + "last-modified": "Mon, 26 Jan 2026 10:52:11 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "priority": "u=1,i", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=JoldeMpTh59KKM2%2FdX%2BL4k2WYRzB8Z9RNEwYzsEb9LEznq0kuXgBH%2FGnajsyseZ7JLN5eEEK663Q2M8w6rh%2Fe9n1N6vamu119WuVI6s%3D\"}]}", + "server": "cloudflare", + "server-timing": "cfExtPri", + "x-amz-id-2": "YJ4iI/VoSAhMFe+7d0XBnMpI/jQdelasHVmHEQkYmtgRvWjDCswO5KvegOrMiLTp2Vvc0/SaCJY=", + "x-amz-request-id": "4ANSP3T7NWJR7NX0", + "x-amz-version-id": "T1eYk_kXkmWN16SXEMZb_OVMmqH086QR" + }, + "body": { + "WARNING_MESSAGE": "Your order is currently processed, please wait until your order has been processed. Please don't close the window.", + "DONE_MESSAGE": "To start a new order, please click on the \"Done\" button.", + "START": { + "EDIT": "Bessa - Edit your menu", + "IMPRINT": "Imprint and Terms of Service", + "COUPON": "Voucher Shop", + "NEW_MENU": "New Menu", + "HERO_ONE": "Create your own digital menu", + "HERO_TWO": "For smartphones and tablets. Generate individual QR code. Change and update at any time.", + "HERO_BUTTON": "Create now for free", + "HERO_USER": "Hello {{name}}, you don't have a menu yet, create one!", + "CONTENT_ONE_TITLE": "Your digital menu - Many advantages, so easy!", + "CONTENT_ONE_DESCRIPTION": "Create categories, dishes and drinks with just a few clicks and customize the look of your restaurant. Use our strong advantages for your restaurant.", + "CONTENT_POINT_ONE": "Easy access for your guests via QR code", + "CONTENT_POINT_TWO": "Change and update at any time", + "CONTENT_POINT_THREE": "Relieves your staff", + "CONTENT_POINT_FOUR": "Accelerates your order processing", + "CONTENT_TWO_TITLE": "QR code for your guests — perfect fit!", + "CONTENT_TWO_DESCRIPTION": "Export the QR code of your digital menu in the usual graphic formats or ready to print as a table cover or table display.", + "CONTENT_TWO_JPG": "JPG/PNG", + "CONTENT_TWO_DINA4": "DIN A4", + "CONTENT_TWO_DINA4LONG": "DIN LANG", + "CONTENT_THREE_TITLE": "Effortlessly expand the digital offer", + "CONTENT_THREE_DESCRIPTION": "Hundreds of restaurants enjoy the benefits of their digital menu with QR code.", + "FOOTER_AGB": "Terms and Conditions", + "FOOTER_PRIVACY": "Privacy", + "FOOTER_IMPRINT": "Imprint", + "LONG_POWERED_BY": "This web shop is powered by ", + "MENUS": "Your Menu", + "TITLE": "Bessa Web Shop", + "CREATE_ACCOUNT_DESCRIPTION": "In the next step, start immediately and free of charge with your digital menu.", + "ACCOUNT_EXISTS": "Restaurant already created? ➞ To login", + "EDIT_MENU": "Edit menu", + "CREATE_VENUE": "Create restaurant", + "CREATE_ACCOUNT_HELP": "We create an account for your restaurant so that you can change and update your digital menu at any time.", + "EXISTING_ACCOUNT_HELP": "Great, you already have a Bessa account. Create a digital menu, which you can then change and update at any time.", + "ACCOUNT_HELP": "If you want a new Bessa account with a different email, just log out and restart the dialog." + }, + "SELECT": { + "TITLE": "Order your way!", + "NO_ORDERING": "Sorry, no ordering today!", + "DESCRIPTION": "The restaurant is currently not taking orders. Please try another time.", + "OR": "or buy a", + "COUPON": "Treat your loved ones!" + }, + "DEFAULT": { + "WELCOME": "Welcome", + "LOGIN_TEXT": "Register now", + "REGISTERING": "Register ...", + "DELIVERY": "Delivery", + "PICKUP": "Restaurant pick up", + "PICKUP2": "Pick up", + "DINEIN": "At the table", + "DINEIN2": "In the local", + "SELF_SERVICE": "Self-service", + "COUPON": "Voucher", + "CANTEEN": "Canteen", + "CANTEEN2": "Pick up", + "CLOSE": "Close", + "TIP": "Tip", + "TO_GO": "Order to go", + "SUM": "Sum", + "MAP": "Where do I have to go?", + "DELIVERYFEE": "Deliveryfee", + "DOWNLOAD_INVOICE": "Download Invoice", + "TODAY": "Today", + "TOMORROW": "Tomorrow", + "QR_SCAN": "Scan QR code", + "QR_SCAN_INFO": "To order, please scan the QR code on your table.", + "TABLE": "Order on {{ name }}", + "NO_SCAN": "No QR code scanned yet", + "REMOVED_AT": "dropped at {{value}}", + "HELP_TEXT": "depending on the delivery area.", + "NAME": "Restaurant name", + "NAME_ERROR": "Please enter a name.", + "OR": "or", + "OK": "Ok", + "BACK": "Back", + "OPENING_HOURS": "Opening hours", + "NO_OPENING_HOURS": "The restaurant is currently not open.", + "MY_ACCOUNT": "My Account", + "LOGOUT": "Log Out", + "LOGOUT_QUESTION": "Do you really want to log out?", + "LOGOUT_DELETE": "Log out and delete account", + "LOGIN": "Log In", + "FACEBOOK": "With Facebook", + "GOOGLE": "With Google", + "GOOGLE.ERROR": "Google log in was cancelled.", + "EMAIL_ERROR": "Please use a valid email.", + "PASSWORD_ERROR": "Please enter your password. (minimum 6 characters)", + "PASSWORD_LONG_ERROR": "The password is a mandatory field, a password must have at least 6 characters.", + "PASSWORD_LONG2_ERROR": "The password is a mandatory field, the passwords must match.", + "RECOVERY": "Forgot your password?", + "NO_ACCOUNT": "Don't have an account yet? Click here!", + "EMAIL": "E-Mail", + "PASSWORD": "Password", + "RESET": "Reset password", + "RESET_ERROR": "The password reset link is no longer valid. Please request a new one.", + "SEND": "Send email", + "RESET_DESCRIPTION": "Please enter your email address. We will then send you an email with instructions on how to reset your password.", + "RESET_DONE": "The instructions for resetting the password were sent to the e-mail address after successful assignment of a Bessa account!.", + "CREATE_ACCOUNT": "Create account", + "ACCOUNT_CREATED": "After registration you will receive an e-mail from us with a confirmation link. Please also note further information in our Privacy Policy. By registering, I agree to the Terms of Service.", + "ENTER_PASSWORD": "Please enter your new password here.", + "CONFIRM_PASSWORD": "Password confirmation", + "PASSWORD_RESET": "Your password has been reset. You can now close this browser tab.", + "DISABLED": "The online ordering tool for the URL you are using is not active.", + "NO_SHOP": "This restaurant currently has no web shop.", + "MORE_INFO": "Learn more at bessa.app", + "PAGE_VIEWS": "Page views", + "VISITORS": "Visitors", + "SECONDS_ON_PAGE": "Time on Page (seconds)", + "VISITS": "Visits", + "HISTORY": "Last 30 Days", + "IMPRINT_TOS": "Imprint and ToS", + "CANCEL": "Cancel?", + "CANCEL_MESSAGE": "Do you want to cancel the order process?", + "DISCOUNT": "Voucher", + "CLOSING_RULES": "Vacation / Holidays", + "CLOSING": "Closed from {{start}} to {{end}}", + "CLOSING_ORDER": "No {{types}} Orders from {{start}} to {{end}}", + "S_DELIVERY": "Delivery", + "S_PICKUP": "Pickup", + "S_DINEIN": "Table", + "NO_ITEMS": "nothing", + "ACCESS_CODE": "Access Code", + "ACCESS_CODE_LOGIN": "Access Code Login", + "ACCESS_CODE_ERROR": "Access code must be 4-12 alphanumeric characters", + "EMPLOYEE_CODE": "Employee number", + "EMPLOYEE_CODE_LOGIN": "Employee number Login", + "EMPLOYEE_CODE_ERROR": "Employee number must be 4-6 numeric characters", + "LOGGING_IN": "Logging in...", + "FIRST_LOGIN_TITLE": "Set Your Password", + "FIRST_LOGIN_DESC": "This is your first login. Please set a new password to continue.", + "CHANGE_PASSWORD_DESC": " Please set a new password.", + "NEW_PASSWORD": "New Password", + "OLD_PASSWORD": "Old Password", + "PASSWORD_MISMATCH": "Passwords do not match", + "CHANGE_PASSWORD": "Change Password", + "RESET_PASSWORD": "Reset Password", + "SLOT_DESCRIPTION": "" + }, + "ORDER": { + "PREPARED": "Order is being prepared", + "PREPARED_LONG": "Your order has been accepted and is being prepared.", + "PREPARED_FAILED": "Order has been cancelled", + "PREPARED_DONE": "Order prepared", + "PAYED": "Order is paid...", + "PAYED_SOURCE": "Order is paid with Sofortüberweisung, which can take up to 5 minutes. ...", + "PAYED_DONE": "Order paid!", + "PAYED_LATER": "Order will be paid later!", + "PAYED_LATER_PICKUP": "Order will be paid upon collection!", + "PAYED_FAILED": "Payment error, please try again!", + "PAYED_NEXT_FAILED": "The order was rejected by the restaurant. The payment will be reversed!", + "PAYED_EXPIRED_FAILED": "Order was not processed within the specified time frame and has expired! In the case you used a payment service (like stripe, paypal, etc), the payment will automatically be reversed!", + "TRANSMITTED": "Order is being transferred...", + "TRANSMITTED_DONE": "Order transferred!", + "TRANSMITTED_FAILED": "Transmission error!", + "ACCEPTED": "Waiting for the order to be accepted...", + "ACCEPTED_PREORDER": "The order is in the restaurant and will be processed on the day of delivery.", + "ACCEPTED_REJECTED": "Order was rejected!", + "ACCEPTED_EXPIRED": "Order was not processed within the specified time frame and has expired!", + "ACCEPTED_DONE": "Order accepted", + "REASON": "Reason: {{message}}", + "CANCELLED": "The order was cancelled.", + "CANTEEN_CANCELLED": "The order was not claimed.", + "SERVICE_CANCELLED": "The order was cancelled by the third party service.", + "NOSTATUS": "noStatus", + "CANTACCEPTORDERS": "The restaurant cannot accept orders at the moment.", + "CANACCEPTPREORDERS0": "Pre-orders open for {{date}}", + "CANACCEPTPREORDERS1": "Pre-orders open for {{date}}", + "CANACCEPTPREORDERS2": "Delivery possible for {{date}}", + "NOORDERSLOTSTODAY": "Outside of the order times", + "ORDERSLOTSTARTSAT0": "Earliest order time {{time}}", + "ORDERSLOTSTARTSAT1": "Earliest pickup time {{time}}", + "ORDERSLOTSTARTSAT2": "Earliest order time {{time}}", + "MENUONLYBETWEEN": "Only between {{from}} - {{to}}", + "MENUONLYBETWEEN_1": "Only between {{from}} - {{to}} and the tavern is open", + "ACCEPTORDERCAPACITYWARNING": "The order capacity is over 90%, order now" + }, + "ORDERS": { + "TITLE": "My Orders", + "MY_ORDERS": "My Orders", + "ACTIVE": "Current", + "PAST": "Past", + "PAST_ORDERS": "Past Orders", + "ACTIVE_ORDERS": "Active Orders", + "ACTIVE_COUNT": "active order(s)", + "NO_ACTIVE_ORDERS": "No active orders available", + "NO_PAST_ORDERS": "No past orders available", + "ORDER_NUMBER": "Order", + "TYPE": "Order Type", + "TOTAL": "Total", + "TABLE": "Table", + "QR_CODE": "Show QR Code", + "DETAILS": "Show Details", + "STATE": { + "PAYMENT_PROCESSING": "Payment processing", + "TRANSMITTED": "Transmitted", + "TRANSMITTABLE": "Ready for transmission", + "ACCEPTED": "Accepted", + "COMPLETED": "Completed", + "CANCELLED": "Cancelled", + "REJECTED": "Rejected", + "EXPIRED": "Expired", + "FOOD_PREPARED": "Prepared", + "VENDOR_ACCEPTED": "Accepted", + "SERVICE_CANCELLED": "Unclaimed" + } + }, + "MENU": { + "ALLERGENS": "You can find a list and description of all allergens here.", + "AMOUNT": "Only {{amount}} available", + "NOT_AVAILABLE": "Not available", + "SEARCH": "My Searchinput", + "SEARCH_RESULT": "Search result", + "SEARCH_EMPTY": "No dishes found", + "SEARCH_CLOSE": "End search", + "CART": { + "TITLE": "Shopping cart", + "EMPTY": "Start your order", + "EMPTY_DESCRIPTION": "Choose your favorite dishes from our menu and add them.", + "SLOT_BY": "at {{date}}", + "SLOT_AT": "on {{date}}", + "NO_SLOT": "Please select a date.", + "ASAP": "As quickly as possible", + "TABLE": "on {{table}}", + "DELIVERY_DATE": "delivery date", + "PICKUP_DATE": "pickup date", + "DELIVERY_TIME": "delivery time", + "PICKUP_TIME": "pickup time", + "SELECT_DELIVERY_TIME": "Select delivery time", + "SELECT_PICKUP_TIME": "Select pickup time", + "MINIMUM_VALUE": "Minimum order value", + "ORDER_AMOUNT": "Order quantity", + "GO_TO": "Proceed to checkout", + "ORDER_SIZE": "Order size", + "MISSING": "still missing {{value}}", + "REMAINING": "{{value}} excluding delivery costs", + "REDUCE_SIZE": "Please reduce the order quantity.", + "INCLUDED": "included", + "ARTICLES": "{{amount}} Article", + "NO_ARTICLES": "{{amount}}", + "CONFIRM": "Confirm", + "TOO_MANY_DRINKS": "Too many drinks! Maximum {{max}} allowed.", + "TOO_MANY_ITEMS": "Too many dishes! Maximum {{max}} allowed.", + "REUSABLE": "Returnable container to go", + "REUSABLE_DESCRIPTION": "Would you like to use a returnable container?", + "ORDER_TO_GO": "Order to go", + "ORDER_TO_GO_DESCRIPTION": "Would you like your order as take away?", + "COURSE_HELP_TITLE": "Did you know?", + "COURSE_HELP_DESC": "Move your items between menu course with the pencil icon", + "COURSE_GROUP": "Select a course" + }, + "ADD": { + "SELECT": "Mandatory field. Choose an item", + "SELECTN": "Choose a minimum of {{min}} and a maximum of {{max}}", + "COMMENT": "My comment", + "ADD": "Add {{value}}" + }, + "WAITER_CALL": "call Waiter", + "WAITER_CALL_DONE": "The waiter was called. Please have a little patience. Many Thanks." + }, + "CUSTOMER": { + "HEADER": "Contact details", + "WELCOME": "Welcome to {{ name }}", + "DESCRIPTION": "In order for us to be able to guarantee that your order runs smoothly, we need your contact details.", + "REQUIRED": "Mandatory field", + "FIRSTNAME": "First name", + "LASTNAME": "Surname", + "PHONE": "Phone", + "EMAIL": "Email", + "FIRSTNAME_ERROR": "The first name is required.", + "LASTNAME_ERROR": "The surname is required.", + "PHONE_ERROR": "The phone number is required. Maximum 15 characters long, please enter only digits.", + "EMAIL_ERROR": "The email is required.", + "NEWSLETTER": "Subscribe to {{name}} newsletter", + "NEWSLETTER_DESCRIPTION": "I agree that I will receive information about products, services, discount campaigns, competitions from the {{name}} restaurant at regular intervals. (You can of course unsubscribe later.)", + "NEXT": "Next", + "ACCOUNT": "Account", + "COLLECT_POINTS": "Collect bonus points", + "COLLECT_POINTS_DESCRIPTION": "Receive bonus points on your order and redeem them.", + "NO_ACCOUNT": "Don't want to create an account? Just complete the order as a guest.", + "GUEST": "Order as a Guest", + "NEXT_GUEST": "Continue as a guest" + }, + "TABLE": { + "HEADER": "Table selection", + "DESCRIPTION": "Please select your table, then you can start ordering. The table number can be found on the card with the QR code.", + "CONFIRM": "Confirm Table", + "REQUIRED_ERROR": "The table number is required.", + "NOTFOUND_ERROR": "Table with number \"{{input}}\" does not exist.", + "NUMBER": "Enter table number", + "NO_CAMERA_ACCESS": "We don't have access to your camera. In order for us to be able to scan the table QR code, we need permission to do so. Alternatively, you can scan the table QR code with your camera app.", + "NO_CAMERA": "This device does not have a camera app.", + "SEARCH_QR_CODE": "Search table QR code" + }, + "CHECKOUT": { + "TIME_CHANGED": "The restaurant has adjusted your selected pickup time to:", + "TIME_CHANGED_DELIVERY": "The restaurant has adjusted your selected delivery time to:", + "DELIVERY": "Delivery on", + "DESCRIPTION": "Your order is now being processed.", + "DELIVERY_DESCRIPTION": "The delivery time will be displayed once the order has been processed.", + "PREORDER_DESCRIPTION": "The order will be prepared and shipped on that date.", + "DINE_IN_TITLE": "ORDER FOR TABLE", + "PICKUP_TITLE": "YOUR PICKUP CODE", + "DELIVERY_TITLE": "WILL BE DELIVERED IN", + "PREORDER_DELIVERY_TITLE": "WILL BE DELIVERED ON", + "SELF_SERVICE_TITLE": "SELF-SERVICE", + "SELF_SERVICE_DESCRIPTION": "Your order is now being processed. Please note the pickup screen.", + "MINUTES": "{{minutes}} minutes", + "DETAILS": "Order details", + "STATUS": "Order state", + "THANK": "Thank you very much!", + "ERROR": "Change payment method?", + "BACK": "Back", + "DONE": "Done", + "SUMMARY": "Summary", + "DINE_IN_TITLE2": "Order for table", + "VOUCHER": "Voucher code", + "ADD": "Add", + "REMOVE": "Remove", + "COUPON_TITLE": "Redeem voucher", + "COUPON_HELP": "Enter your voucher code here.", + "NO_COUPON_CODE": "This voucher code is invalid or has already been redeemed.", + "COUPON_VALUE": "{{value}} are available, maximum the order total will be deducted. If there is a remainder, it can be redeemed for the next order.", + "CANTEEN_TITLE": "YOUR ORDER", + "CANTEEN_DESCRIPTION": "Your order can be picked up on the day of the order via QR code. The QR code can be displayed at any time via the date menu." + }, + "PAYMENT": { + "TITLE": "Pay", + "PAY": "Pay {{value}}", + "DELIVERY": "Pay {{value}} on delivery", + "PICKUP": "Pay {{value}} on pickup", + "DINEIN": "Pay {{value}} at the table", + "DEMO": "Demo Payment", + "PICKUP_HINT": "Mandatory field, please enter your {{name}}.", + "PICKUP_ERROR": "The {{name}} is required.", + "CREDIT_CARD": "Credit card", + "SOFORT": "Sofortüberweisung", + "PAYROLL": "Pay later through your payroll", + "CASH": "Cash", + "CASH_DINEIN": "Pay in the Restaurant", + "CASH_SEND": "Submit Order", + "PAYPAL": "PayPal", + "TWINT_PAY": "Pay {{value}} with Twint", + "TWINT": "Twint", + "HOBEX": "Credit card", + "WORLD_LINE": "Credit card", + "VOUCHER": "Pay {{value}} with Voucher!", + "COUPON_VALUE": "There are {{value}} available, {{discount}} will be deducted. The rest can be redeemed on the next order.", + "TOS_TITLE": "I agree to the Terms and Conditions.", + "TOS_DESCRIPTION": "I have read the terms and conditions and agree to them.", + "TOS_LINK": "Go to terms and conditions.", + "AGE_TITLE": "I am older than 16 years.", + "AGE_DESCRIPTION": "Alcoholic drinks can also be purchased in our shop. Please confirm your age.", + "EXPIRE": "Your payment session will expire in 60 seconds.", + "PAYROLL_INFO": "Branches / Temporary workers / External workers must be paid on-site." + }, + "ADDRESS": { + "HEADER": "Your address", + "PARAGRAPH_1": "Before you start, please enter your delivery address.", + "PARAGRAPH_2": "We need your address to ensure that you are in the delivery area.", + "PARAGRAPH": "We need this to ensure that you are also in the delivery area.", + "CONFIRM": "Use address", + "STREET_LABEL": "Your street and house number", + "STREET": "The street and house number are required.", + "REQUIRED": "The address is required for delivery orders.", + "INVALID_ADDRESS": "The address provided is outside the delivery area.", + "REGION_LABEL": "Please select a district", + "REGION_REQUIRED": "The district is required.", + "COMMENT": "Comment / extra information", + "COMMENT_PLACEHOLDER": "You can enter your information here" + }, + "CANTEEN": { + "HEADER": "Select a Day", + "DESCRIPTION": "Start your order or view details of your order.", + "WEEK_ABBR": "CW {{week}}", + "EMPTY": "Unfortunately, there are no menu entries for this week yet.", + "QR_CODE": "Show QR Code", + "DONE": "Done", + "CANCEL": "Cancel", + "CANCEL_QUESTION": "Do you really want to cancel this order?", + "CURRENT_WEEK": "Current Week", + "ADD_ORDER": "Add Order?", + "ORDER_TITLE": "Order at {{date}}" + }, + "UPSELL": { + "TITLE": "Make your menu even better.", + "DESCRIPTION": "Expand your digital menu with a shopping cart and payment function and expand your online service.", + "SUB_TITLE": "Your own web shop", + "POINT_ONE": "For pickup and delivery service", + "POINT_TWO": "Integrate directly into website", + "POINT_THREE": "Book automatically in the cash register system", + "POINT_FOUR": "For table ordering in the restaurant", + "INFO": "Contact us without obligation and we will give you more information and a free demo.", + "DONE": "Thank you for your interest, we have sent you a confirmation email.", + "BUTTON": "Inquire now" + } + } + }, + { + "timestamp": "2026-02-05T13:39:09.214Z", + "method": "GET", + "url": "https://web.bessa.app/assets/i18n/de.json", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/knapp-kantine", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json, text/plain, */*", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0" + }, + "responseHeaders": { + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9c92cfee0ea65b81-VIE", + "content-encoding": "zstd", + "content-type": "application/json", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "etag": "W/\"675e6ea53d7d0c3fd583fcf15811c90b\"", + "last-modified": "Mon, 26 Jan 2026 10:52:11 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "priority": "u=1,i", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=2c3R2iEcVF8DnAwQetP6HYvfpAUQ8HircSOLhV5dNeFsxdFr3juRK%2FkEaGythG42VT02KFDuBKLTKmKKO5SKMe8VsCXsQeKEefgRZMY%3D\"}]}", + "server": "cloudflare", + "server-timing": "cfExtPri", + "x-amz-id-2": "S1pZdG7Oc99xYSLtZEKdCIONZ0Kk9T7F5zwoL74b79qp4IlC0NER0us88eb1CEuyAya0rj7CvBc=", + "x-amz-request-id": "4ANP3GSNM5ZVVFNT", + "x-amz-version-id": "bv3gPVgtoM171tVr5TP.30xG5Yudmlwe" + }, + "body": { + "WARNING_MESSAGE": "Ihre Bestellung und Zahlung wird gerade verarbeitet. Bitte schließen Sie das Fenster währenddessen nicht.", + "DONE_MESSAGE": "Um eine neue Bestellung zu starten, klicken Sie bitte auf den \"Fertig\" Button.", + "START": { + "EDIT": "Bessa - Editiere dein Menü", + "IMPRINT": "Impressum und AGB", + "COUPON": "Gutschein Shop", + "NEW_MENU": "Neue Speisekarte", + "HERO_ONE": "Erstelle deine digitale Speisekarte", + "HERO_TWO": "Für Smartphones und Tablets. Individuellen QR-Code generieren. Jederzeit ändern und aktualisieren.", + "HERO_BUTTON": "Jetzt kostenlos erstellen", + "HERO_USER": "Hallo {{name}}, du hast noch keine Speisekarte, erstelle eine!", + "CONTENT_ONE_TITLE": "Deine digitale Speisekarte — Viele Vorteile, so einfach!", + "CONTENT_ONE_DESCRIPTION": "Erzeuge mit wenigen Klicks Kategorien, Speisen und Getränke und passe das Aussehen individuell an dein Restaurant an. Nutze unsere starken Vorteile für dein Restaurant.", + "CONTENT_POINT_ONE": "Einfacher Zugriff deiner Gäste via QR-Code", + "CONTENT_POINT_TWO": "Jederzeit ändern und aktualisieren", + "CONTENT_POINT_THREE": "Entlastet dein Personal", + "CONTENT_POINT_FOUR": "Beschleunigt die Bestellabwicklung", + "CONTENT_TWO_TITLE": "QR-Code für deine Gäste — Passgenau!", + "CONTENT_TWO_DESCRIPTION": "Exportiere den QR-Code zu deiner digitalen Speisekarte in den gängigen Grafikformaten oder druckfertig als Tischauflage oder Tischaufsteller.", + "CONTENT_TWO_JPG": "JPG/PNG", + "CONTENT_TWO_DINA4": "DIN A4", + "CONTENT_TWO_DINA4LONG": "DIN LANG", + "CONTENT_THREE_TITLE": "Mühelos das digitale Angebot erweitern", + "CONTENT_THREE_DESCRIPTION": "Hunderte Restaurants genießen die Vorteile ihrer digitalen Speisekarte mit QR-Code.", + "FOOTER_AGB": "AGB", + "FOOTER_PRIVACY": "Datenschutz", + "FOOTER_IMPRINT": "Impressum", + "LONG_POWERED_BY": "Dieser Web Shop ist powered by ", + "MENUS": "Deine Karte", + "TITLE": "Bessa Web Shop", + "CREATE_ACCOUNT_DESCRIPTION": "Starte im nächsten Schritt sofort und kostenlos mit deiner digitalen Speisekarte.", + "ACCOUNT_EXISTS": "Restaurant bereits erstellt? ➞ Zum Login", + "EDIT_MENU": "Speisekarte bearbeiten", + "CREATE_VENUE": "Restaurant erstellen", + "CREATE_ACCOUNT_HELP": "Wir erzeugen für dein Restaurant einen Account, damit du deine digitale Speisekarte jederzeit ändern und aktualisieren kannst.", + "EXISTING_ACCOUNT_HELP": "Super, du hast schon einen Bessa Account. Erzeuge eine digitale Speisekarte, diese kannst du dann jederzeit ändern und aktualisieren.", + "ACCOUNT_HELP": "Falls du einen neuen Bessa Account mit einer anderen E-Mail haben willst, melde dich einfach ab und starte den Dialog neu." + }, + "SELECT": { + "TITLE": "Bestelle auf deine Art!", + "NO_ORDERING": "Sorry, heute keine Bestellungen!", + "DESCRIPTION": "Das Restaurant nimmt derzeit keine Bestellungen an. Bitte versuche es ein andermal.", + "OR": "oder kaufe einen", + "COUPON": "Gönn deinen Lieben etwas!" + }, + "DEFAULT": { + "WELCOME": "Willkommen", + "LOGIN_TEXT": "Jetzt anmelden", + "REGISTERING": "Anmeldung ...", + "DELIVERY": "Lieferung", + "PICKUP2": "Abholung", + "PICKUP": "Abholung im Lokal", + "DINEIN": "Direkt zum Tisch", + "DINEIN2": "im Lokal", + "SELF_SERVICE": "Selbstbedienung", + "COUPON": "Gutschein", + "CANTEEN": "Kantine", + "CANTEEN2": "Abholung in der Kantine", + "CLOSE": "Schließen", + "TIP": "Trinkgeld", + "TO_GO": "Bestellung zum Mitnehmen", + "SUM": "Summe", + "MAP": "Wo muss ich hin?", + "DELIVERYFEE": "Liefergebühr", + "DOWNLOAD_INVOICE": "Rechnung herunterladen", + "TODAY": "Heute", + "TOMORROW": "Morgen", + "QR_SCAN": "QR Code Scannen", + "QR_SCAN_INFO": "Damit Sie bestellen können, scannen Sie bitte den QR-Code auf ihrem Tisch.", + "TABLE": "Auf {{ name }} bestellen", + "NO_SCAN": "Noch kein QR-Code gescannt", + "REMOVED_AT": "Entfällt ab {{value}}", + "HELP_TEXT": "abhängig vom Liefergebiet.", + "NAME": "Name des Restaurants", + "NAME_ERROR": "Bitte geben sie einen Namen ein.", + "OR": "oder", + "OK": "Ok", + "BACK": "Zurück", + "OPENING_HOURS": "Öffnungszeiten", + "NO_OPENING_HOURS": "Das Restaurant ist derzeit nicht geöffnet.", + "MY_ACCOUNT": "Mein Account", + "LOGOUT": "Abmelden", + "LOGOUT_QUESTION": "Wollen Sie sich wirklich abmelden?", + "LOGOUT_DELETE": "Abmelden und Account Löschen", + "LOGIN": "Anmelden", + "FACEBOOK": "Mit Facebook", + "GOOGLE": "Mit Google", + "GOOGLE.ERROR": "Google Login wurde abgebrochen.", + "EMAIL_ERROR": "Bitte verwenden Sie eine gültig E-Mail.", + "PASSWORD_ERROR": "Bitte geben Sie ihr Passwort ein. (Minimum 6 Zeichen)", + "PASSWORD_LONG_ERROR": "Das Passwort ist ein Pflichtfeld, ein Passwort muss mindestens 6 Zeichen haben.", + "PASSWORD_LONG2_ERROR": "Das Passwort ist ein Pflichtfeld. Die Passwörter müssen übereinstimmen.", + "RECOVERY": "Passwort vergessen?", + "NO_ACCOUNT": "Sie haben noch kein Konto? Klicken Sie hier!", + "EMAIL": "E-Mail", + "PASSWORD": "Passwort", + "RESET": "Passwort zurücksetzen", + "RESET_ERROR": "Der Passwort Link ist nicht mehr gültig. Bitte fordern Sie einen neuen an.", + "SEND": "E-Mail senden", + "RESET_DESCRIPTION": "Bitte gib deine E-Mail-Adresse ein. Wir senden dir dann eine E-Mail mit der Anleitung zum Zurücksetzen deines Passworts.", + "RESET_DONE": "Die Anleitung zum Zurücksetzen des Passwortes wurde, bei erfolgreicher Zuordnung eines Bessa Accounts, an die E-Mail-Adresse versandt!.", + "CREATE_ACCOUNT": "Konto erstellen", + "ACCOUNT_CREATED": "Nach Registrierung erhalten Sie von uns eine E-Mail mit einem Bestätigungslink. Beachten Sie bitte auch weiterführende Informationen in unseren Datenschutzhinweisen. Mit der Registrierung stimme ich den AGB zu.", + "ENTER_PASSWORD": "Bitte geben Sie hier Ihr neues Passwort ein.", + "CONFIRM_PASSWORD": "Passwort bestätigen", + "PASSWORD_RESET": "Ihr Passwort wurde zurückgesetzt. Sie können diesen Browser Tab nun schließen.", + "DISABLED": "Das Online Bestelltool für die von Ihnen verwendete URL ist nicht aktiv.", + "NO_SHOP": "Dieses Restaurant hat derzeit keinen Web Shop.", + "MORE_INFO": "Erfahre mehr auf bessa.app", + "PAGE_VIEWS": "Seitenzugriffe", + "VISITORS": "Besucher", + "SECONDS_ON_PAGE": "Zeit auf Seite (Sekunden)", + "VISITS": "Zugriffe", + "HISTORY": "Letzten 30 Tage", + "IMPRINT_TOS": "Impressum und AGB", + "CANCEL": "Abbrechen?", + "CANCEL_MESSAGE": "Wollen sie den Bestellvorgang abbrechen?", + "DISCOUNT": "Gutschein", + "CLOSING_RULES": "Betriebsurlaub / Feiertag", + "CLOSING": "Geschlossen vom {{start}} bis {{end}}", + "CLOSING_ORDER": "Keine {{types}}bestellung von {{start}} bis {{end}}", + "S_DELIVERY": "Liefer", + "S_PICKUP": "Abhol", + "S_DINEIN": "Tisch", + "NO_ITEMS": "Keine", + "ACCESS_CODE": "Zutrittscode", + "ACCESS_CODE_LOGIN": "Zutrittscode Login", + "ACCESS_CODE_ERROR": "Zutrittscode muss 4-12 alphanumerische Zeichen enthalten", + "EMPLOYEE_CODE": "Personalnummer", + "EMPLOYEE_CODE_LOGIN": "Personalnummer Login", + "EMPLOYEE_CODE_ERROR": "Personalnummer muss mindestens 4-6 Zeichen enthalten", + "LOGGING_IN": "Anmeldung läuft...", + "FIRST_LOGIN_TITLE": "Passwort festlegen", + "FIRST_LOGIN_DESC": "Dies ist Ihre erste Anmeldung. Bitte legen Sie ein neues Passwort fest, um fortzufahren.", + "CHANGE_PASSWORD_DESC": "Bitte legen Sie ein neues Passwort fest.", + "NEW_PASSWORD": "Neues Passwort", + "OLD_PASSWORD": "Altes Password", + "PASSWORD_MISMATCH": "Passwörter stimmen nicht überein", + "CHANGE_PASSWORD": "Passwort ändern", + "RESET_PASSWORD": "Passwort zurücksetzen", + "SLOT_DESCRIPTION": "" + }, + "ORDER": { + "PREPARED": "Bestellung wird zubereitet", + "PREPARED_LONG": "Ihre Bestellung wurde angenommen und wird gerade zubereitet.", + "PREPARED_FAILED": "Bestellung wurde abgebrochen", + "PREPARED_DONE": "Bestellung zubereitet", + "PAYED": "Bestellung wird bezahlt...", + "PAYED_SOURCE": "Bestellung wird mit Sofortüberweisung bezahlt, das kann bis zu 5 Minuten dauern. ...", + "PAYED_DONE": "Bestellung bezahlt!", + "PAYED_LATER": "Bestellung wird später bezahlt!", + "PAYED_LATER_PICKUP": "Bestellung wird bei Abholung bezahlt!", + "PAYED_FAILED": "Fehler bei der Bezahlung, bitte versuche es noch einmal!", + "PAYED_NEXT_FAILED": "Die Bestellung wurde vom Restaurant abgelehnt. Die Bezahlung wird rückabgewickelt!", + "PAYED_EXPIRED_FAILED": "Bestellung wurde nicht im vorgegebenen Rahmen bearbeitet und ist abgelaufen! Sollten Sie einen Zahlungsdienstleister verwendet haben wird die Bezahlung automatisch rückabgewickelt!", + "TRANSMITTED": "Bestellung wird übertragen...", + "TRANSMITTED_DONE": "Bestellung übertragen!", + "TRANSMITTED_FAILED": "Fehler bei der Übertragung!", + "ACCEPTED": "Warte auf Bestellannahme...", + "ACCEPTED_PREORDER": "Die Bestellung liegt dem Restaurant vor und wird zur Auslieferungszeitpunkt frisch zubereitet.", + "ACCEPTED_REJECTED": "Bestellung wurde abgelehnt!", + "ACCEPTED_EXPIRED": "Bestellung wurde nicht im vorgegebenen Rahmen bearbeitet und ist abgelaufen!", + "ACCEPTED_DONE": "Bestellung angenommen", + "REASON": "Grund: {{message}}", + "CANCELLED": "Die Bestellung wurde storniert.", + "CANTEEN_CANCELLED": "Die Bestellung wurde nicht abgeholt.", + "SERVICE_CANCELLED": "Die Bestellung wurde per Drittanbieter storniert.", + "NOSTATUS": "noStatus", + "CANTACCEPTORDERS": "Das Lokal kann im Moment keine Bestellungen annehmen", + "CANACCEPTPREORDERS0": "Vorbestellungen möglich für {{date}}", + "CANACCEPTPREORDERS1": "Vorbestellungen möglich für {{date}}", + "CANACCEPTPREORDERS2": "Lieferung möglich für {{date}}", + "NOORDERSLOTSTODAY": "Außerhalb der Bestellzeiten", + "ORDERSLOTSTARTSAT0": "Früheste Bestellzeit {{time}}", + "ORDERSLOTSTARTSAT1": "Früheste Abholzeit {{time}}", + "ORDERSLOTSTARTSAT2": "Früheste Bestellzeit {{time}}", + "MENUONLYBETWEEN": "Nur zwischen {{from}} - {{to}} Uhr", + "MENUONLYBETWEEN_1": "Nur während der Buschschankzeiten zwischen {{from}} - {{to}} Uhr.", + "ACCEPTORDERCAPACITYWARNING": "Die Bestellkapazität ist über 90%, bestelle jetzt" + }, + "ORDERS": { + "TITLE": "Meine Bestellungen", + "MY_ORDERS": "Meine Bestellungen", + "ACTIVE": "Aktuelle", + "PAST": "Alte", + "PAST_ORDERS": "Alte Bestellungen", + "ACTIVE_ORDERS": "Aktuelle Bestellungen", + "ACTIVE_COUNT": "aktive Bestellung(en)", + "NO_ACTIVE_ORDERS": "Keine aktiven Bestellungen vorhanden", + "NO_PAST_ORDERS": "Keine alten Bestellungen vorhanden", + "ORDER_NUMBER": "Bestellung", + "TYPE": "Bestellart", + "TOTAL": "Gesamt", + "TABLE": "Tisch", + "QR_CODE": "QR Code anzeigen", + "DETAILS": "Details anzeigen", + "STATE": { + "PAYMENT_PROCESSING": "Zahlung wird verarbeitet", + "TRANSMITTED": "Übertragen", + "TRANSMITTABLE": "Bereit zur Übertragung", + "ACCEPTED": "Bestätigt", + "COMPLETED": "Abgeschlossen", + "CANCELLED": "Storniert", + "REJECTED": "Abgelehnt", + "EXPIRED": "Abgelaufen", + "FOOD_PREPARED": "Zubereitet", + "VENDOR_ACCEPTED": "Angenommen", + "SERVICE_CANCELLED": "Nicht abgeholt" + } + }, + "MENU": { + "ALLERGENS": "Eine Auflistung und Beschreibung aller Allergene findest du hier.", + "AMOUNT": "Nur noch {{amount}} verfügbar", + "NOT_AVAILABLE": "Momentan nicht verfügbar", + "SEARCH": "Meine Sucheingabe", + "SEARCH_RESULT": "Suchergebnis", + "SEARCH_EMPTY": "Keine Gerichte gefunden", + "SEARCH_CLOSE": "Suche beenden", + "CART": { + "TITLE": "Warenkorb", + "EMPTY": "Starte deine Bestellung", + "EMPTY_DESCRIPTION": "Such dir deine Lieblingsgerichte von unserer Speisekarte aus und füge sie hinzu.", + "SLOT_BY": "um {{date}}", + "SLOT_AT": "am {{date}}", + "NO_SLOT": "Bitte wählen sie ein Datum aus.", + "ASAP": "So schnell wie möglich", + "TABLE": "auf {{table}}", + "DELIVERY_DATE": "Lieferdatum", + "PICKUP_DATE": "Abholdatum", + "DELIVERY_TIME": "Lieferzeit", + "PICKUP_TIME": "Abholzeit", + "SELECT_DELIVERY_TIME": "Lieferzeit auswählen", + "SELECT_PICKUP_TIME": "Abholzeit auswählen", + "MINIMUM_VALUE": "Mindestbestellwert", + "ORDER_AMOUNT": "Bestellmenge", + "GO_TO": "Zur Kasse gehen", + "ORDER_SIZE": "Bestellungsgröße", + "MISSING": "noch {{value}}", + "REMAINING": "{{value}} exklusive Lieferkosten", + "REDUCE_SIZE": "bitte reduzieren Sie die Bestellmenge.", + "INCLUDED": "inkludiert", + "ARTICLES": "{{amount}} Artikel", + "NO_ARTICLES": "{{amount}}", + "CONFIRM": "Bestätigen", + "TOO_MANY_DRINKS": "Zu viele Getränke! Maximal {{max}} erlaubt.", + "TOO_MANY_ITEMS": "Zu viele Speisen! Maximal {{max}} erlaubt.", + "REUSABLE": "Mehrwegverpackung To Go", + "REUSABLE_DESCRIPTION": "Wollen sie eine Mehrwegverpackungen verwenden?", + "ORDER_TO_GO": "Bestellung zum Mitnehmen", + "ORDER_TO_GO_DESCRIPTION": "Wollen Sie Ihre Bestellung gleich mitnehmen?", + "COURSE_HELP_TITLE": "Wusstest du schon?", + "COURSE_HELP_DESC": "Verschiebe deine Artikel zwischen den Gängen mit dem Stift-Symbol", + "COURSE_GROUP": "Gang auswählen" + }, + "ADD": { + "SELECT": "Pflichtfeld. Wähle einen Artikel", + "SELECTN": "Wähle mindestens {{min}} und maximal {{max}}", + "COMMENT": "Mein Kommentar", + "ADD": "Hinzufügen {{value}}" + }, + "WAITER_CALL": "Kellner rufen", + "WAITER_CALL_DONE": "Der Kellner wurde gerufen. Bitte haben Sie etwas Geduld. Vielen Dank." + }, + "CUSTOMER": { + "HEADER": "Kontaktdaten", + "WELCOME": "Willkommen im {{ name }}", + "DESCRIPTION": "Damit wir einen reibungslosen Ablauf deiner Bestellung garantieren können benötigen wir bitte deine Kontaktdaten.", + "REQUIRED": "Pflichtfeld", + "FIRSTNAME": "Vorname", + "LASTNAME": "Nachname", + "PHONE": "Telefonnummer", + "EMAIL": "E-Mail", + "FIRSTNAME_ERROR": "Der Vorname ist ein Pflichtfeld.", + "LASTNAME_ERROR": "Der Nachname ist ein Pflichtfeld.", + "PHONE_INVALID": "Bitte geben sie eine gültige Telefonnummer mit Landesvorwahl ein. z.B.: +43 640 1234567", + "EMAIL_ERROR": "Die E-Mail ist ein Pflichtfeld.", + "NEWSLETTER": "{{name}} Newsletter abonnieren", + "NEWSLETTER_DESCRIPTION": "Ich stimme zu, dass ich in regelmäßigen Abständen Informationen über Produkte, Dienstleistungen, Rabattaktionen, Gewinnspiele vom Restaurant {{name}} bekomme. (Sie können sich davon natürlich später wieder abmelden.)", + "NEXT": "Weiter", + "ACCOUNT": "Konto", + "COLLECT_POINTS": "Sammle Bonuspunkte", + "COLLECT_POINTS_DESCRIPTION": "Erhalte Bonuspunkte auf deine Bestellung und löse diese ein.", + "NO_ACCOUNT": "Du möchstest kein Konto erstellen? Schließe die Bestellung einfach als Gast ab.", + "GUEST": "Als Gast bestellen", + "NEXT_GUEST": "Weiter als Gast" + }, + "TABLE": { + "HEADER": "Tisch Auswahl", + "DESCRIPTION": "Bitte wählen Sie ihren Tisch aus, dann können Sie mit der Bestellung starten. Die Tischnummer ist auf der Karte mit dem QR-Code zu finden.", + "CONFIRM": "Tisch bestätigen", + "REQUIRED_ERROR": "Die Tischnummer ist ein Pflichtfeld.", + "NOTFOUND_ERROR": "Tisch mit der Nummer \"{{input}}\" existiert nicht.", + "NUMBER": "Tischnummer eingeben", + "NO_CAMERA_ACCESS": "Wir haben keinen Zugriff auf Ihre Kamera. Damit wir den Tisch QR-Code scannen können brauchen wir die Berechtigung dazu. Alternativ können Sie die Tisch QR-Code mit Ihrer Kamera App scannen.", + "NO_CAMERA": "Dieses Gerät besitzt keine Kamera App.", + "SEARCH_QR_CODE": "Suche Tisch QR-CODE" + }, + "CHECKOUT": { + "TIME_CHANGED": "Das Restaurant hat deine ausgewählte Abholzeit angepasst auf:", + "TIME_CHANGED_DELIVERY": "Das Restaurant hat deine ausgewählte Lieferzeit angepasst auf:", + "DELIVERY": "Lieferung am", + "DESCRIPTION": "Ihre Bestellung wird nun bearbeitet.", + "DELIVERY_DESCRIPTION": "Die Lieferzeit wird angezeigt sobald die Bestellung verarbeitet wurde.", + "PREORDER_DESCRIPTION": "Die Bestellung wird an diesem Datum zubereitet und ausgeliefert.", + "DINE_IN_TITLE": "BESTELLUNG FÜR TISCH", + "PICKUP_TITLE": "DEIN ABHOLCODE", + "DELIVERY_TITLE": "WIRD GELIEFERT IN", + "PREORDER_DELIVERY_TITLE": "WIRD GELIEFERT AM", + "SELF_SERVICE_TITLE": "SELBSTBEDIENUNG", + "SELF_SERVICE_DESCRIPTION": "Ihre Bestellung wird nun bearbeitet. Bitte beachten Sie den Abholbildschirm.", + "MINUTES": "{{minutes}} Minuten", + "DETAILS": "Details deiner Bestellung", + "STATUS": "Bestellstatus", + "THANK": "Vielen Dank!", + "ERROR": "Zahlungsart ändern?", + "BACK": "Zurück", + "DONE": "Fertig", + "SUMMARY": "Zusammenfassung", + "DINE_IN_TITLE2": "Bestellung für Tisch", + "VOUCHER": "Gutscheincode", + "ADD": "Hinzufügen", + "REMOVE": "Entfernen", + "COUPON_TITLE": "Gutschein einlösen", + "COUPON_HELP": "Gib hier deinen Gutscheincode ein.", + "NO_COUPON_CODE": "Dieser Gutscheincode ist ungültig oder wurde schon eingelöst.", + "COUPON_VALUE": "Es sind {{value}} verfügbar, es wird maximal die Bestellsumme abgezogen. Sollte ein Rest bleiben kann dieser bei der nächsten Bestellung eingelöst werden.", + "CANTEEN_TITLE": "DEINE BESTELLUNG", + "CANTEEN_DESCRIPTION": "Deine Bestellung kann am Tag der Bestellung per QR-Code abgeholt werden. Der QR-Code kann über das Datumsmenü jederzeit anzeigt werden." + }, + "PAYMENT": { + "TITLE": "Bezahlen", + "PAY": "Zahle {{value}}", + "DELIVERY": "Zahle {{value}} bei Lieferung", + "PICKUP": "Zahle {{value}} bei Abholung", + "DINEIN": "Zahle {{value}} im Lokal", + "DEMO": "Demo Zahlung", + "PICKUP_HINT": "Pflichtfeld, bitte geben Sie Ihren {{name}} ein.", + "PICKUP_ERROR": "{{name}} ist ein Pflichtfeld.", + "CREDIT_CARD": "Kreditkarte", + "SOFORT": "Sofortüberweisung", + "PAYROLL": "Zahle via Lohnverrechnung", + "CASH": "Bar", + "CASH_DINEIN": "Im Lokal bezahlen", + "CASH_SEND": "Bestellung absenden", + "PAYPAL": "PayPal", + "TWINT_PAY": "Zahle {{value}} mit Twint", + "TWINT": "Twint", + "HOBEX": "Kreditkarte", + "WORLD_LINE": "Kreditkarte", + "VOUCHER": "Zahle {{value}} mit Gutschein!", + "COUPON_VALUE": "Es sind {{value}} verfügbar, es werden {{discount}} abgezogen. Der Rest kann bei der nächsten Bestellung eingelöst werden.", + "TOS_TITLE": "Ich stimme den AGB zu.", + "TOS_DESCRIPTION": "Ich habe die AGB gelesen und stimme diesen zu.", + "TOS_LINK": "Zu den AGB.", + "AGE_TITLE": "Ich bin älter als 16 Jahre.", + "AGE_DESCRIPTION": "Es können auch alkoholische Getränke in unserem Shop gekauft werden. Bitte bestätigen Sie Ihr Alter.", + "EXPIRE": "Ihre Bezahl Sitzung läuft in 60 Sekunden ab.", + "PAYROLL_INFO": "Niederlassungen / Leiharbeiter / Externe müssen vor Ort bezahlt werden." + }, + "ADDRESS": { + "HEADER": "Ihre Adresse", + "PARAGRAPH_1": "Bevor du startest gibt bitte noch deine Lieferadresse ein.", + "PARAGRAPH_2": "Diese benötigen wir um sicherzustellen das du auch im Liefergebiet bist.", + "PARAGRAPH": "Wir benötigen deine Adresse um sicherzustellen das du auch im Liefergebiet bist.", + "CONFIRM": "Adresse verwenden", + "STREET_LABEL": "Deine Strasse und Hausnummer", + "STREET": "Die Strasse und Hausnummer werden benötigt.", + "REQUIRED": "Die Adresse wird für Lieferbestellungen benötigt.", + "INVALID_ADDRESS": "Die angegebene Adresse befindet sich außerhalb des Liefergebiets.", + "REGION_LABEL": "Bitte wähle einen Stadtteil/Ortsteil", + "REGION_REQUIRED": "Stadtteil/Ortsteil ist ein Pflichtfeld.", + "COMMENT": "Kommentar / Extra Information", + "COMMENT_PLACEHOLDER": "Hier können Sie Ihre Hinweise angeben" + }, + "CANTEEN": { + "HEADER": "Wähle einen Tag", + "DESCRIPTION": "Starte deine Bestellung oder zeigt dir Details zu deiner Bestellung.", + "WEEK_ABBR": "KW {{week}}", + "EMPTY": "Es gibt leider noch keine Menü Einträge für diese Woche.", + "QR_CODE": "QR-Code anzeigen", + "DONE": "Fertig", + "CANCEL": "Stornieren", + "CANCEL_QUESTION": "Wollen Sie diese Bestellung wirklich stornieren?", + "CURRENT_WEEK": "Aktuelle Woche", + "ADD_ORDER": "Neue Bestellung?", + "ORDER_TITLE": "Bestellung für {{date}}" + }, + "UPSELL": { + "TITLE": "Mach deine Speisekarte noch bessa.", + "DESCRIPTION": "Erweitere deine digitale Speisekarte um Warenkorb und Zahlungsfunktion und baue deinen Online-Service aus.", + "SUB_TITLE": "Dein eigener Webshop", + "POINT_ONE": "Für Abholungen und Lieferservice", + "POINT_TWO": "Direkt in Webseite integrieren", + "POINT_THREE": "Verbuche automatisch im Kassensystem", + "POINT_FOUR": "Für Tischbestellung im Lokal", + "INFO": "Nimm ganz unverbindlich mit uns Kontakt auf und wir geben dir weitere Informationen und eine kostenlose Demo.", + "DONE": "Vielen Dank für dein Interesse, wir haben dir eine Bestätigungsmail gesendet.", + "BUTTON": "Jetzt anfragen" + } + } + }, + { + "timestamp": "2026-02-05T13:39:09.234Z", + "method": "GET", + "url": "https://a.delivery.consentmanager.net/delivery/cmp.php?__cmpcc=1&id=25212&o=1770298748&h=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&&l=en&odw=0&dlt=1&l=en", + "status": 200, + "type": "script", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*" + }, + "responseHeaders": { + "access-control-allow-origin": "*", + "cache-control": "no-store, no-cache, must-revalidate", + "content-encoding": "gzip", + "content-length": "593", + "content-type": "text/javascript; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "edge-control": "no-store, no-cache, must-revalidate", + "expires": "Thu, 01 Dec 1994 16:00:00 GMT", + "last-modified": "Thu, 05 Feb 2026 13:39:09 GMT", + "pragma": "no-cache", + "set-cookie": "__cmpcc=1; Expires=Sun, 07-Mar-2027 13:39:09 GMT; Path=/; SameSite=None; Secure", + "vary": "Accept-Encoding" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:09.339Z", + "method": "GET", + "url": "https://a.delivery.consentmanager.net/delivery/cmp.php?__cmpcc=2&__cmpfcc=1&id=25212&o=1770298749&h=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&&l=en&odw=0&dlt=1&l=en", + "status": 200, + "type": "script", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*" + }, + "responseHeaders": { + "access-control-allow-origin": "*", + "cache-control": "no-store, no-cache, must-revalidate", + "content-encoding": "gzip", + "content-type": "text/javascript; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "edge-control": "no-store, no-cache, must-revalidate", + "expires": "Thu, 01 Dec 1994 16:00:00 GMT", + "last-modified": "Thu, 05 Feb 2026 13:39:09 GMT", + "pragma": "no-cache", + "vary": "Accept-Encoding" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:09.526Z", + "method": "GET", + "url": "https://api.bessa.app/v1/venue/shop/knapp-kantine/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token c3418725e95a9f90e3645cbc846b4d67c7c66131", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "1280", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "id": 591, + "name": "Werksrestaurant", + "about": "-", + "slogan": "", + "image_thumbnail": "https://files.treuepass.app/cache/f9/98/f998b02b3257e38bbefaba629c9f02dd.jpg", + "location": { + "latitude": 47.0431554, + "longitude": 15.5110764 + }, + "shop": { + "id": 485, + "created": "2025-08-04T12:35:38.773765Z", + "updated": "2025-11-27T07:26:22.413220Z", + "deleted": null, + "uuid": "379a94c3-cd24-46a3-8c76-8e55febf69d4", + "stylesheet": "https://web.bessa.app/assets/knapp-kantine.1764841216.css", + "name": "knapp-kantine", + "anonymous_ordering": true, + "use_table_selection": false, + "formal_localization": false, + "show_close_dialog": false, + "newsletter": true, + "read_only": false, + "login_type": "ACCESS_CODE", + "venue": 591 + }, + "payment_providers": [], + "prefix": "knapp", + "created": "2025-08-04T12:35:37.086282Z", + "updated": "2025-12-04T09:59:03.867803Z", + "deleted": null, + "uuid": "78ba8c27-567e-4a44-ba0d-faae9e66a2ce", + "image": "https://files.treuepass.app/account/users/NTkx/venues/33f822ea80dc431f97625bcf9fa318de.png", + "email": "petra.heschl@knapp.com", + "address": "Günter-Knapp-Straße 5-7, 8075 Hart bei Graz, Austria", + "phone": "+4367689791001", + "website": "", + "instagram": "", + "twitter": "", + "facebook": "", + "menu": "", + "table_booking": "", + "use_pin": true, + "treuepass_enabled": false, + "scans_per_day": 2, + "timezone": "CET", + "currency": "EUR", + "country": "AT", + "company": 533, + "parent": null + } + }, + { + "timestamp": "2026-02-05T13:39:09.546Z", + "method": "GET", + "url": "https://web.bessa.app/assets/knapp-kantine.1764841216.css", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/knapp-kantine", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json, text/plain, */*", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "cookie": "__cmpcc=1" + }, + "responseHeaders": { + "age": "7102", + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "max-age=14400", + "cf-cache-status": "HIT", + "cf-ray": "9c92cff08eb85b81-VIE", + "content-encoding": "zstd", + "content-type": "text/plain", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "etag": "W/\"0ec88512c527db3adc96b973fe63e129\"", + "last-modified": "Thu, 04 Dec 2025 09:40:18 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "priority": "u=1,i", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=xQ7ykqimiiV%2B2T9QpypycVX%2BWBHUxZVqpSTFER1TiK%2FMZt9t7208gX5NDSEMJ4wx3NTkqj6AmaWPYUdPNlKLaz1J%2FhcKHJNTJmqyp4Q%3D\"}]}", + "server": "cloudflare", + "server-timing": "cfExtPri", + "x-amz-id-2": "zrdmWWsbdnbTOxA0EA9++BWHvJrSW1uHYOf5D7V4mxs0ypERbJPBskVaFBLRG5hIHx9mrnnAwXrR8xC85wTJ/6dsZEynTTdKpZEmIowL4w0=", + "x-amz-request-id": "QMB6PBV38240KZ4M", + "x-amz-version-id": "37ch8Ul1v6t6ylwaAI_CqQUhVHITeZCk" + }, + "body": ".primary-color {\n color: #37cfaa;\n}\n\n.primary-bg-color {\n background-color: #37cfaa;\n}\n\n.secondary-color {\n color: #2190d7;\n}\n\n.secondary-border-color {\n border-color: #2190d7;\n}\n\n.danger {\n color: #c80000;\n}\n\n.bg-danger {\n background-color: #c80000;\n}\n\n.on-danger {\n color: #ffffff;\n}\n\n.icon-variant {\n color: $color-base-on-surface-icon;\n}\n\n.bg {\n background-color: #f8f8f8;\n}\n\n.side-cart-bg {\n background-color: #f8f8f8;\n}\n\n.surface {\n color: #08273b;\n background-color: #ffffff;\n}\n\n.surface-variant {\n color: #08273b;\n}\n\n.surface-variant-font {\n font-family: FONT-VARIANT-REPLACEMENT, sans-serif;\n}\n\n.border-color {\n border-color: #d8d8d8;\n}\n\n.active-item:hover {\n background-color: #f7f7f7;\n}\n\n.separator-color {\n background-color: #d8d8d8;\n}\n\n.surface-only {\n color: #08273b;\n}\n\n.warning {\n background-color: #ffe584 !important;\n color: #000000;\n}\n\n.bg-header {\n background-color: #37cfaa;\n}\n\n.on-header {\n color: #ffffff;\n}\n\n.link-color {\n color: #37cfaa;\n}\n\n.button {\n background-color: #2190d7;\n color: #ffffff;\n}\n\n.button:hover {\n background-color: #2793da;\n color: #ffffff;\n}\n\n.button:disabled {\n background-color: #e6e6e6;\n color: #ffffff;\n}\n\n.button:active {\n background-color: #1a8dd4;\n color: #ffffff;\n}\n\n.button-secondary {\n background-color: #ffffff;\n color: #2190d7;\n border: 1px solid #d8d8d8;\n}\n\n.button-secondary:hover {\n background-color: #ffffff;\n color: #2190d7;\n border: 1px solid #2190d7;\n}\n\n.button-secondary:disabled {\n background-color: #e6e6e6;\n color: #ffffff;\n border: 1px solid #e6e6e6;\n}\n\n.button-secondary:active {\n background-color: #fcfcfc;\n color: #1a8dd4;\n border: 1px solid #1a8dd4;\n}\n\n.icon-filled {\n border: 1px solid #2190d7;\n background-color: #2190d7;;\n color: #ffffff;\n}\n\n.icon-filled:hover {\n border: 1px solid #2793da;\n background-color: #2793da;;\n color: #ffffff;\n}\n\n.icon-filled:disabled {\n border: 1px solid #e6e6e6;\n background-color: #e6e6e6;;\n color: #ffffff;\n}\n\n.icon-filled:active {\n border:...[TRUNCATED]" + }, + { + "timestamp": "2026-02-05T13:39:09.612Z", + "method": "GET", + "url": "https://api.bessa.app/v1/venues/591/imprint/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token c3418725e95a9f90e3645cbc846b4d67c7c66131", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "293", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "name": "Werksrestaurant", + "company_name": "KNAPP AG", + "company_address": "", + "venue_address": "Günter-Knapp-Straße 5-7, 8075 Hart bei Graz, Austria", + "phone": "", + "website": "", + "email": "petra.heschl@knapp.com", + "tax_id": "", + "tos": "", + "updated": "2026-01-29T08:26:36.459456Z", + "last_changed": "01/29/2026" + } + }, + { + "timestamp": "2026-02-05T13:39:09.631Z", + "method": "GET", + "url": "https://api.bessa.app/v1/venues/591/menu/order/slots/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token c3418725e95a9f90e3645cbc846b4d67c7c66131", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "4521", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "next": null, + "previous": null, + "results": [ + { + "id": 4395, + "created": "2025-11-05T12:09:23.611185Z", + "updated": "2025-12-09T08:05:33.317065Z", + "deleted": null, + "uuid": "433b1675-08cd-49c4-bf75-e84a4a2e4704", + "order_type": 3, + "weekday": 1, + "from_hour": "06:00:00", + "to_hour": "18:00:00" + }, + { + "id": 4444, + "created": "2025-12-04T21:49:45.264924Z", + "updated": "2025-12-09T15:16:29.386669Z", + "deleted": null, + "uuid": "d5f7496b-7334-4639-8f66-5862bb6f4125", + "order_type": 1, + "weekday": 1, + "from_hour": "09:00:00", + "to_hour": "09:30:00" + }, + { + "id": 4456, + "created": "2025-12-09T15:16:29.267157Z", + "updated": "2025-12-09T15:30:31.069728Z", + "deleted": null, + "uuid": "4c5d214a-9ff1-462a-8ba5-ab3ce38d7fe0", + "order_type": 1, + "weekday": 1, + "from_hour": "11:00:00", + "to_hour": "11:30:00" + }, + { + "id": 4443, + "created": "2025-12-04T21:49:25.043385Z", + "updated": "2025-12-04T21:49:25.043407Z", + "deleted": null, + "uuid": "48171fb1-3be5-488e-bcf3-20730acafe9b", + "order_type": 7, + "weekday": 1, + "from_hour": "11:00:00", + "to_hour": "13:30:00" + }, + { + "id": 4394, + "created": "2025-11-05T12:09:23.608083Z", + "updated": "2025-12-09T08:05:33.131167Z", + "deleted": null, + "uuid": "f8fd0d2f-f528-4872-8a8b-c252f95e41d6", + "order_type": 3, + "weekday": 2, + "from_hour": "06:00:00", + "to_hour": "18:00:00" + }, + { + "id": 4391, + "created": "2025-11-05T12:08:54.589869Z", + "updated": "2025-12-09T15:16:29.308699Z", + "deleted": null, + "uuid": "6e406ae7-9230-45d7-9ea6-7af05a9abe15", + "order_type": 1, + "weekday": 2, + "from_hour": "09:00:00", + "to_hour": "09:30:00" + }, + { + "id": 4455, + "created": "2025-12-09T15:16:29.264362Z", + "updated": "2025-12-09T15:30:31.074248Z", + "deleted": null, + "uuid": "385ad0a5-bffb-449a-a9e3-86e49680a9a3", + "order_type": 1, + "weekday": 2, + "from_hour": "11:00:00", + "to_hour": "11:30:00" + }, + { + "id": 4398, + "created": "2025-11-05T12:10:04.793993Z", + "updated": "2025-12-04T09:49:05.035110Z", + "deleted": null, + "uuid": "aacc48e4-6863-4352-976e-88970f3ff9bc", + "order_type": 7, + "weekday": 2, + "from_hour": "11:00:00", + "to_hour": "13:30:00" + }, + { + "id": 4397, + "created": "2025-11-05T12:09:23.613611Z", + "updated": "2025-12-09T08:05:33.215928Z", + "deleted": null, + "uuid": "77c7cabf-e7c0-4668-8092-8fa3c8f07a76", + "order_type": 3, + "weekday": 3, + "from_hour": "06:00:00", + "to_hour": "18:00:00" + }, + { + "id": 4388, + "created": "2025-11-05T12:08:54.586393Z", + "updated": "2025-12-09T15:16:29.298389Z", + "deleted": null, + "uuid": "35819aea-0edb-41b7-b020-b542b56867c0", + "order_type": 1, + "weekday": 3, + "from_hour": "09:00:00", + "to_hour": "09:30:00" + }, + { + "id": 4457, + "created": "2025-12-09T15:16:29.269654Z", + "updated": "2025-12-09T17:40:01.183594Z", + "deleted": null, + "uuid": "95174e54-672b-4813-b44f-ef567e87fc9c", + "order_type": 1, + "weekday": 3, + "from_hour": "11:00:00", + "to_hour": "11:30:00" + }, + { + "id": 4399, + "created": "2025-11-05T12:10:04.807470Z", + "updated": "2025-12-04T09:49:05.147289Z", + "deleted": null, + "uuid": "4eaddb05-924a-4c68-af93-706590d40a86", + "order_type": 7, + "weekday": 3, + "from_hour": "11:00:00", + "to_hour": "13:30:00" + }, + { + "id": 4396, + "created": "2025-11-05T12:09:23.611220Z", + "updated": "2025-12-09T08:05:33.210994Z", + "deleted": null, + "uuid": "b4e7353e-1864-4847-957e-9d5b73b12e96", + "order_type": 3, + "weekday": 4, + "from_hour": "06:00:00", + "to_hour": "18:00:00" + }, + { + "id": 4392, + "created": "2025-11-05T12:08:54.592285Z", + "updated": "2025-12-09T15:16:29.299051Z", + "deleted": null, + "uuid": "2b9724b0-85ff-4c74-ae34-782610b3efda", + "order_type": 1, + "weekday": 4, + "from_hour": "09:00:00", + "to_hour": "09:30:00" + }, + { + "id": 4458, + "created": "2025-12-09T15:16:29.269901Z", + "updated": "2025-12-09T15:30:31.073158Z", + "deleted": null, + "uuid": "af268774-f5a2-4070-acb9-a320da7981d5", + "order_type": 1, + "weekday": 4, + "from_hour": "11:00:00", + "to_hour": "11:30:00" + }, + { + "id": 4400, + "created": "2025-11-05T12:10:04.827762Z", + "updated": "2025-12-04T09:49:05.025265Z", + "deleted": null, + "uuid": "5a2f60db-d88b-42a7-baf3-5c633ba85fdc", + "order_type": 7, + "weekday": 4, + "from_hour": "11:00:00", + "to_hour": "13:30:00" + }, + { + "id": 4445, + "created": "2025-12-05T10:08:33.181983Z", + "updated": "2025-12-09T08:05:33.208960Z", + "deleted": null, + "uuid": "7731893f-2f36-4783-b80b-e2caebaa6f3c", + "order_type": 3, + "weekday": 5, + "from_hour": "06:00:00", + "to_hour": "18:00:00" + }, + { + "id": 4390, + "created": "2025-11-05T12:08:54.588997Z", + "updated": "2025-12-11T11:15:01.577886Z", + "deleted": null, + "uuid": "d4028d4d-d78e-4528-b070-692e75ba3c96", + "order_type": 1, + "weekday": 5, + "from_hour": "09:00:00", + "to_hour": "09:30:00" + }, + { + "id": 4401, + "created": "2025-11-05T12:10:04.828235Z", + "updated": "2025-12-04T09:49:05.009523Z", + "deleted": null, + "uuid": "cf08a7d7-72fa-4088-a23d-2787b45e0d9e", + "order_type": 7, + "weekday": 5, + "from_hour": "11:00:00", + "to_hour": "13:30:00" + }, + { + "id": 4459, + "created": "2025-12-11T11:15:01.594549Z", + "updated": "2025-12-11T11:15:01.594573Z", + "deleted": null, + "uuid": "758ed900-7e86-417f-a44f-5a939162aee5", + "order_type": 1, + "weekday": 5, + "from_hour": "11:30:00", + "to_hour": "12:45:00" + } + ] + } + }, + { + "timestamp": "2026-02-05T13:39:09.631Z", + "method": "GET", + "url": "https://api.bessa.app/v1/opening/hours/591/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token c3418725e95a9f90e3645cbc846b4d67c7c66131", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "2", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": [] + }, + { + "timestamp": "2026-02-05T13:39:09.725Z", + "method": "GET", + "url": "https://connect.facebook.net/signals/config/908074230639058?v=2.9.256&r=stable&domain=web.bessa.app&hme=18f1088ef3e8892a74f943ddaee05e3bbd28c980d3b65c4c57d9fe86969d4bce&ex_m=95%2C158%2C135%2C20%2C67%2C68%2C128%2C63%2C42%2C129%2C72%2C62%2C10%2C142%2C81%2C15%2C94%2C123%2C116%2C70%2C73%2C122%2C139%2C103%2C144%2C7%2C3%2C4%2C6%2C5%2C2%2C82%2C92%2C145%2C226%2C170%2C56%2C228%2C229%2C49%2C185%2C27%2C69%2C234%2C233%2C173%2C29%2C55%2C9%2C58%2C88%2C89%2C90%2C96%2C119%2C28%2C26%2C121%2C118%2C117%2C136%2C71%2C138%2C137%2C44%2C54%2C112%2C14%2C141%2C39%2C215%2C217%2C180%2C23%2C24%2C25%2C17%2C18%2C38%2C34%2C36%2C35%2C77%2C83%2C87%2C101%2C127%2C130%2C40%2C102%2C21%2C19%2C108%2C64%2C32%2C132%2C131%2C133%2C124%2C22%2C31%2C53%2C100%2C140%2C65%2C16%2C134%2C105%2C76%2C61%2C30%2C195%2C165%2C285%2C213%2C156%2C198%2C191%2C166%2C98%2C120%2C75%2C110%2C48%2C41%2C43%2C104%2C109%2C115%2C52%2C59%2C114%2C47%2C50%2C46%2C91%2C143%2C0%2C113%2C13%2C111%2C11%2C1%2C51%2C84%2C57%2C60%2C107%2C80%2C79%2C146%2C147%2C85%2C86%2C8%2C93%2C45%2C125%2C78%2C74%2C66%2C106%2C97%2C37%2C126%2C33%2C99%2C12%2C148", + "status": 200, + "type": "script", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*" + }, + "responseHeaders": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "public, max-age=1200", + "content-encoding": "gzip", + "content-length": "35865", + "content-security-policy": "default-src 'self' blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;script-src 'nonce-5VADrWyf' *.facebook.com *.fbcdn.net *.facebook.net 127.0.0.1:* blob: 'self';style-src 'self' data: blob: 'unsafe-inline';connect-src 'self' data: blob: https://edge-chat.facebook.net https://edge-chat-latest.facebook.net wss://edge-chat-latest.facebook.net wss://edge-chat.facebook.net wss://edge-chat.socialplugin.facebook.net wss://edge-chat-latest.socialplugin.facebook.net https://edge-chat.socialplugin.facebook.net https://edge-chat-latest.socialplugin.facebook.net *.facebook.com facebook.com *.fbcdn.net *.facebook.net wss://*.facebook.com:* wss://*.fbcdn.net attachment.fbsbx.com ws://localhost:* *.cdninstagram.com;font-src 'self' data: blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;img-src 'self' data: blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;media-src 'self' data: blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;child-src 'self' data: blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;frame-src 'self' data: blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;manifest-src 'self' data: blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;object-src 'self' data: blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;worker-src 'self' data: blob: facebook.net *.facebook.net facebook.com *.facebook.com fbcdn.net *.fbcdn.net fbsbx.com *.fbsbx.com cdninstagram.com *.cdninstagram.com;block-all-mixed-content;upgrade-insecure-requests;report-uri https://www.facebook.com/csp/reporting/?m=c&minimize=0;require-trusted-types-for 'script';", + "content-type": "application/x-javascript; charset=utf-8", + "cross-origin-embedder-policy-report-only": "require-corp;report-to=\"coep_report\"", + "cross-origin-opener-policy": "same-origin-allow-popups", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "document-policy": "force-load-at-top\ninclude-js-call-stacks-in-crash-reports", + "expires": "Sat, 01 Jan 2000 00:00:00 GMT", + "origin-agent-cluster": "?1", + "permissions-policy": "accelerometer=(), attribution-reporting=(), autoplay=(), bluetooth=(), camera=(), ch-device-memory=(), ch-downlink=(), ch-dpr=(), ch-ect=(), ch-rtt=(), ch-save-data=(), ch-ua-arch=(), ch-ua-bitness=(), ch-viewport-height=(), ch-viewport-width=(), ch-width=(), clipboard-read=(), clipboard-write=(), compute-pressure=(), display-capture=(), encrypted-media=(), fullscreen=(self), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), private-state-token-issuance=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), shared-storage=(), shared-storage-select-url=(), private-state-token-redemption=(), usb=(), unload=(self), window-management=(), xr-spatial-tracking=();report-to=\"permissions_policy\"", + "pragma": "public", + "priority": "u=3,i", + "report-to": "{\"max_age\":2592000,\"endpoints\":[{\"url\":\"https:\\/\\/www.facebook.com\\/browser_reporting\\/coop\\/?minimize=0\"}],\"group\":\"coop_report\",\"include_subdomains\":true}, {\"max_age\":86400,\"endpoints\":[{\"url\":\"https:\\/\\/www.facebook.com\\/browser_reporting\\/coep\\/?minimize=0\"}],\"group\":\"coep_report\"}, {\"max_age\":21600,\"endpoints\":[{\"url\":\"https:\\/\\/www.facebook.com\\/ajax\\/browser_error_reports\\/\"}],\"group\":\"permissions_policy\"}", + "reporting-endpoints": "coop_report=\"https://www.facebook.com/browser_reporting/coop/?minimize=0\", coep_report=\"https://www.facebook.com/browser_reporting/coep/?minimize=0\", permissions_policy=\"https://www.facebook.com/ajax/browser_error_reports/\"", + "strict-transport-security": "max-age=31536000; preload; includeSubDomains", + "timing-allow-origin": "*", + "vary": "Accept-Encoding", + "x-content-type-options": "nosniff", + "x-fb-connection-quality": "EXCELLENT; q=0.9, rtt=20, rtx=0, c=84, mss=1232, tbw=107279, tp=99, tpl=0, uplat=1, ullat=-1", + "x-fb-debug": "h4/QwHb/qZmTmqg4a7uUM4VPVQHB8IKRX2Gvv1PcrLQ7wmkqzywQK4CJ+pdAvD+esnwhJ8XaAcnYnYLh51lhKA==", + "x-frame-options": "DENY", + "x-xss-protection": "0" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:09.898Z", + "method": "GET", + "url": "https://www.facebook.com/x/oauth/status?client_id=1309426129223545&input_token&origin=1&redirect_uri=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&sdk=joey&wants_cookie_data=true", + "status": 200, + "type": "fetch", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "fb-s", + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "private, no-cache, no-store, must-revalidate", + "content-length": "0", + "content-type": "text/plain; charset=UTF-8", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:39:09 GMT", + "document-policy": "force-load-at-top\ninclude-js-call-stacks-in-crash-reports", + "expires": "Sat, 01 Jan 2000 00:00:00 GMT", + "fb-s": "unknown", + "nel": "{\"report_to\":\"network-errors\",\"max_age\":3600,\"failure_fraction\":0.01}", + "origin-agent-cluster": "?1", + "permissions-policy": "accelerometer=(), attribution-reporting=(self), autoplay=(), bluetooth=(), browsing-topics=(self), camera=(self \"https://www.fbsbx.com\"), ch-device-memory=(), ch-downlink=(), ch-dpr=(), ch-ect=(), ch-rtt=(), ch-save-data=(), ch-ua-arch=(), ch-ua-bitness=(), ch-viewport-height=(), ch-viewport-width=(), ch-width=(), clipboard-read=(self), clipboard-write=(self), compute-pressure=(), display-capture=(self), encrypted-media=(self), fullscreen=(self), gamepad=*, geolocation=(self), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(self), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(self), midi=(), otp-credentials=(), payment=(), picture-in-picture=(self), private-state-token-issuance=(), publickey-credentials-get=(self), screen-wake-lock=(), serial=(), shared-storage=(), shared-storage-select-url=(), private-state-token-redemption=(), usb=(), unload=(self), window-management=(), xr-spatial-tracking=(self);report-to=\"permissions_policy\"", + "pragma": "no-cache", + "priority": "u=1,i", + "report-to": "{\"max_age\":259200,\"endpoints\":[{\"url\":\"https:\\/\\/www.facebook.com\\/ajax\\/browser_error_reports\\/?device_level=unknown&brsid=7603375233217676002&cpp=C3&cv=1032975692&st=1770298749822\"}]}, {\"max_age\":3600,\"endpoints\":[{\"url\":\"https:\\/\\/www.facebook.com\\/ajax\\/browser_error_reports\\/?device_level=unknown&brsid=7603375233217676002&cpp=C3&cv=1032975692&st=1770298749822\"}],\"group\":\"network-errors\"}, {\"max_age\":21600,\"endpoints\":[{\"url\":\"https:\\/\\/www.facebook.com\\/ajax\\/browser_error_reports\\/\"}],\"group\":\"permissions_policy\"}", + "reporting-endpoints": "default=\"https://www.facebook.com/ajax/browser_error_reports/?device_level=unknown&brsid=7603375233217676002&cpp=C3&cv=1032975692&st=1770298749822\", permissions_policy=\"https://www.facebook.com/ajax/browser_error_reports/\"", + "strict-transport-security": "max-age=15552000; preload", + "x-content-type-options": "nosniff", + "x-fb-connection-quality": "EXCELLENT; q=0.9, rtt=10, rtx=0, c=24, mss=1232, tbw=5466, tp=15, tpl=0, uplat=124, ullat=0", + "x-fb-debug": "NNHBlpOWIaqjy8Nwhbq+tf5rtnJG6fIHe6MTMPm93lQHFe6slSq5SSxqz9La6ZjW/WMzH0xlvICyIpCTOx9Jqg==" + }, + "body": "[COULD NOT READ BODY]" + }, + { + "timestamp": "2026-02-05T13:39:10.194Z", + "method": "GET", + "url": "https://api.bessa.app/v1/venues/591/config/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token c3418725e95a9f90e3645cbc846b4d67c7c66131", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "47002", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:39:10 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "employee": null, + "discount_food": null, + "discount_drink": null, + "tip_article": null, + "test_article_meal": null, + "test_article_drink": null, + "configurations": [ + { + "id": 869, + "name": "self service", + "delivery_fees": [], + "articles": [ + { + "id": 119, + "name": "daily menu article", + "cash_box_article": "48e86ff1-7b6d-46f7-91a0-346de93fb855", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:30:04.576527Z", + "deleted": null, + "uuid": "35676f7b-1326-49c8-8c64-93855e3e5823", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 869, + "article": 178727 + }, + { + "id": 120, + "name": "daily menu article", + "cash_box_article": "695bb5f6-cbbb-486a-ad3b-9158dc055f98", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:30:09.620426Z", + "deleted": null, + "uuid": "a3fcc4f3-5cac-4945-a312-14f2f56d4e82", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 869, + "article": 178731 + }, + { + "id": 104, + "name": "daily menu article", + "cash_box_article": "6208ac78-6cde-46ff-b998-f1afdc7e34b7", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "e4f00c7b-88c2-4463-abf9-451603a15b90", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 869, + "article": 176511 + }, + { + "id": 105, + "name": "daily menu article", + "cash_box_article": "c7e4c59f-2566-40c3-9497-d0e65f5b2631", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "d6668599-b073-4bb9-823a-8b7d5aea16bd", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 869, + "article": 176512 + }, + { + "id": 106, + "name": "daily menu article", + "cash_box_article": "fb901cf3-d9f1-4df4-886d-998dca8371ef", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "f2286f4c-f7ec-478d-9aaa-62dd019ca062", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 869, + "article": 176513 + }, + { + "id": 107, + "name": "daily menu article", + "cash_box_article": "338a80de-35ec-40ef-a1e7-6fc1dafa2fb1", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "6556e5f9-1b45-442a-a8f9-629392101c9e", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 869, + "article": 176515 + }, + { + "id": 108, + "name": "daily menu article", + "cash_box_article": "3ce62d9d-c75b-4cce-97e5-fabe2db7f243", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "4380eec7-96d0-4fc8-a0d6-9b6680faba27", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 869, + "article": 176516 + }, + { + "id": 109, + "name": "daily menu article", + "cash_box_article": "45a64da3-8973-4f49-94d5-ce293afde7e3", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "b7bfbe38-0c45-49c9-af76-ce0d56a9abf4", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 869, + "article": 176517 + }, + { + "id": 110, + "name": "daily menu article", + "cash_box_article": "690b154a-95b1-4563-a73d-06735c62f203", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "2ef63a2a-68e6-485e-b8c0-1bf7052c2c95", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 869, + "article": 176514 + }, + { + "id": 112, + "name": "daily menu article", + "cash_box_article": "e6966a1c-ba45-450f-b007-f5f41cb79a49", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "ed8358f7-9f28-43be-8955-0d833329a88e", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 869, + "article": 178720 + }, + { + "id": 113, + "name": "daily menu article", + "cash_box_article": "04cb9490-ee5c-40f6-afa8-6837dae888a2", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "74e48d62-9343-4b00-b63e-6652ed02e084", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 869, + "article": 178724 + }, + { + "id": 114, + "name": "daily menu article", + "cash_box_article": "b65661dd-6c62-4a23-9384-c933c8ed424e", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "9d8885e1-f43a-4d4d-b6e7-66834252b989", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 869, + "article": 178722 + }, + { + "id": 115, + "name": "daily menu article", + "cash_box_article": "725d1aa6-f9f5-4e14-951c-90543d961b12", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "fd1e6ecb-71c3-493b-bc08-0073dd82cbbd", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 869, + "article": 178726 + }, + { + "id": 116, + "name": "daily menu article", + "cash_box_article": "868291bd-490d-47ef-b5be-e2002ce3bd10", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "bedf9d79-963b-4198-94c8-c686138d60bc", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 869, + "article": 178721 + }, + { + "id": 117, + "name": "daily menu article", + "cash_box_article": "cfdd8d4b-eb49-4b00-9db4-045e293dc0c0", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "5e2da10f-e986-4095-89c0-bb2bc9fc3f57", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 869, + "article": 178725 + }, + { + "id": 118, + "name": "daily menu article", + "cash_box_article": "f4b084f9-5fd4-494c-aabf-27199e1ecece", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "6844439a-7325-40fa-9a83-48df125376c0", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 869, + "article": 178723 + }, + { + "id": 121, + "name": "daily menu article", + "cash_box_article": "5207e0e3-06a3-4e15-9e8f-aca634005fad", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:30:13.330586Z", + "deleted": null, + "uuid": "7c9a382c-e93e-4a3e-aaa3-5988a624b5fa", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 869, + "article": 178729 + }, + { + "id": 122, + "name": "daily menu article", + "cash_box_article": "701c6989-194c-400c-b590-77734f9a5479", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:30:18.764950Z", + "deleted": null, + "uuid": "e9a55f58-bb49-4a06-9af9-d1e35feaba41", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 869, + "article": 178733 + }, + { + "id": 123, + "name": "daily menu article", + "cash_box_article": "a33ce321-1943-430e-a435-b83f87ff1080", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:30:23.100977Z", + "deleted": null, + "uuid": "9ec5cc0f-bd12-4fb8-9f09-753ed3bb2358", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 869, + "article": 178728 + }, + { + "id": 124, + "name": "daily menu article", + "cash_box_article": "7bf71611-3eb3-4784-bd29-b38ebd1821f9", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:30:26.998642Z", + "deleted": null, + "uuid": "e705d837-e00a-4f0c-8e97-4b758d991c3f", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 869, + "article": 178732 + }, + { + "id": 125, + "name": "daily menu article", + "cash_box_article": "8eb70af1-5cc6-4f30-be0a-3bf2f91321b3", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:30:31.104215Z", + "deleted": null, + "uuid": "10226f5c-d763-460d-9601-7965286e1143", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 869, + "article": 178730 + }, + { + "id": 126, + "name": "daily menu article", + "cash_box_article": "03672c2e-0ab3-4efd-80ad-564a910c69dc", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:31:29.377964Z", + "deleted": null, + "uuid": "83ab3db3-2ee0-4821-817a-0c1da2bc91c4", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 869, + "article": 178734 + }, + { + "id": 127, + "name": "daily menu article", + "cash_box_article": "ec147dfa-d0c5-446e-83ab-1e0f1c6a6f69", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:31:34.013038Z", + "deleted": null, + "uuid": "8efa01d7-e56e-41c7-95ff-3aa9d0b317dd", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 869, + "article": 178738 + }, + { + "id": 128, + "name": "daily menu article", + "cash_box_article": "4ceb0a3e-973a-4b82-ae4b-2592c5ef43eb", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:31:37.597358Z", + "deleted": null, + "uuid": "02d721c3-b86d-4639-a675-41d280057a5a", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 869, + "article": 178736 + }, + { + "id": 129, + "name": "daily menu article", + "cash_box_article": "5727d322-cae6-48e8-b1b1-0786d37935b1", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:31:40.157601Z", + "deleted": null, + "uuid": "ce142601-7b20-4626-a717-7f2fb13c078e", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 869, + "article": 178740 + }, + { + "id": 130, + "name": "daily menu article", + "cash_box_article": "88b59af4-f3fb-45a7-ba0f-9fce88d4710f", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:31:44.671190Z", + "deleted": null, + "uuid": "8eaa6055-359b-4c11-b87b-053c8233941f", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 869, + "article": 178735 + }, + { + "id": 131, + "name": "daily menu article", + "cash_box_article": "86a71457-dbfd-452d-a41c-d9cbbdf15cdf", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:31:48.742728Z", + "deleted": null, + "uuid": "543a3346-3a82-4dd6-910a-5b355b75bf88", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 869, + "article": 178739 + }, + { + "id": 133, + "name": "daily menu article", + "cash_box_article": "b266cf85-960b-4f13-b327-e11929151e00", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:34:57.521234Z", + "deleted": null, + "uuid": "7fa47fd3-2ec8-4b63-bf8b-65dc54e3e644", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 869, + "article": 178741 + }, + { + "id": 132, + "name": "daily menu article", + "cash_box_article": "1bd71fe6-b8fc-4c84-9780-96dedf6870f8", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:31:58.993671Z", + "deleted": null, + "uuid": "ccdb3045-c119-441c-95ab-0e4a5b20ec0e", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 869, + "article": 178737 + }, + { + "id": 134, + "name": "daily menu article", + "cash_box_article": "765cc903-2695-4bba-bed2-4f4078f294c2", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:04.445171Z", + "deleted": null, + "uuid": "2851b911-dc1d-402f-bffe-2b413879bd5f", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 869, + "article": 178745 + }, + { + "id": 135, + "name": "daily menu article", + "cash_box_article": "e4d4157c-4f4a-4701-b1f9-356326c1612d", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:07.985020Z", + "deleted": null, + "uuid": "5cf0dedd-dff8-4791-b108-6f701a9125e6", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 869, + "article": 178743 + }, + { + "id": 136, + "name": "daily menu article", + "cash_box_article": "21686838-0e22-4915-a4a0-e38b3bb43635", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:10.335110Z", + "deleted": null, + "uuid": "8782afad-7dcc-4940-ac88-8fd3a5cc03da", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 869, + "article": 178747 + }, + { + "id": 137, + "name": "daily menu article", + "cash_box_article": "ea309dc8-38bf-4e78-9b51-7c9a7efb2763", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:12.681191Z", + "deleted": null, + "uuid": "4bbc4f54-5ad9-4075-81da-c137f397d3d4", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 869, + "article": 178742 + }, + { + "id": 138, + "name": "daily menu article", + "cash_box_article": "1a7386e3-3d4a-4261-b235-565fdf268863", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:15.618971Z", + "deleted": null, + "uuid": "e2858724-1888-4dfa-bda2-ffdf30d904ab", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 869, + "article": 178746 + }, + { + "id": 139, + "name": "daily menu article", + "cash_box_article": "316e731d-dd8e-47b4-bb25-9d4f2e641151", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:18.765641Z", + "deleted": null, + "uuid": "dee0ea11-ca84-41bc-8a02-500817404c19", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 869, + "article": 178744 + }, + { + "id": 140, + "name": "daily menu article", + "cash_box_article": "42ec29ac-456c-4ca2-aea3-20dfbf3a28b5", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:39.894723Z", + "deleted": null, + "uuid": "9c1ef9eb-29a4-4df5-88a7-654754779184", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 869, + "article": 178748 + }, + { + "id": 141, + "name": "daily menu article", + "cash_box_article": "8e52c14c-6c2a-444c-adde-916107f42f1a", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:42.200690Z", + "deleted": null, + "uuid": "a28452d4-1b26-4174-b101-fb5789c76dc9", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 869, + "article": 178752 + }, + { + "id": 142, + "name": "daily menu article", + "cash_box_article": "3c7f4116-12be-4d10-9369-a61f8b81e387", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:44.439276Z", + "deleted": null, + "uuid": "6a975421-da44-481a-b342-318529b80281", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 869, + "article": 178750 + }, + { + "id": 143, + "name": "daily menu article", + "cash_box_article": "13203b86-7ce6-4e2d-b12a-a159100e6e0e", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:46.746960Z", + "deleted": null, + "uuid": "b46a25f0-2ed9-4ef6-982a-994d2ba60e39", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 869, + "article": 178754 + }, + { + "id": 144, + "name": "daily menu article", + "cash_box_article": "f6d5bd6e-2e14-4930-8f27-be027fa477b2", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:49.117097Z", + "deleted": null, + "uuid": "215264b7-9af3-4c44-a2e7-cac8fe8bf6a7", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 869, + "article": 178749 + }, + { + "id": 145, + "name": "daily menu article", + "cash_box_article": "dea4b5d4-7a2c-4565-94ae-9b5dc2cc5ed8", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:52.415287Z", + "deleted": null, + "uuid": "de586e3b-23c4-4358-959c-ae1731d95574", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 869, + "article": 178753 + }, + { + "id": 146, + "name": "daily menu article", + "cash_box_article": "95ecb237-e497-48f3-ab7e-0b0f8730f02d", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:35:54.851199Z", + "deleted": null, + "uuid": "cf56b9ab-23f2-42d6-ac07-05379df3f749", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 869, + "article": 178751 + }, + { + "id": 147, + "name": "daily menu article", + "cash_box_article": "5ec70cab-6b4c-410f-bb91-60a61fd8bbdd", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:36:33.204822Z", + "deleted": null, + "uuid": "958676a2-b631-40c1-8059-1f2212fb8425", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 869, + "article": 178755 + }, + { + "id": 148, + "name": "daily menu article", + "cash_box_article": "704e5191-4dd6-4fd9-b99c-233d44de5ff3", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:36:35.277296Z", + "deleted": null, + "uuid": "05367984-7df8-4720-893c-ae3a11afe9c0", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 869, + "article": 178759 + }, + { + "id": 149, + "name": "daily menu article", + "cash_box_article": "b270abf7-a35c-4eca-8d62-5fdb5ceaf0a8", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:36:38.787578Z", + "deleted": null, + "uuid": "f618563c-28aa-445c-9d24-65d8c4958583", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 869, + "article": 178757 + }, + { + "id": 150, + "name": "daily menu article", + "cash_box_article": "7810ea32-e693-4716-8f2d-552fa6bb0c45", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:36:40.891393Z", + "deleted": null, + "uuid": "16dc7d5f-fa7a-4a16-8ba0-d5bedd0dbe85", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 869, + "article": 178761 + }, + { + "id": 151, + "name": "daily menu article", + "cash_box_article": "59b371ea-ab48-4efc-9b63-04cf3df9ffc5", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:36:44.655362Z", + "deleted": null, + "uuid": "4e9d50dd-4cbd-4d49-a45b-ec6aa5e10c9c", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 869, + "article": 178756 + }, + { + "id": 152, + "name": "daily menu article", + "cash_box_article": "784431e8-2a34-4230-9d95-dbe9c6498fb4", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:36:46.885332Z", + "deleted": null, + "uuid": "d6bb053e-a8d1-467c-b48f-b9c68fe2ec05", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 869, + "article": 178760 + }, + { + "id": 153, + "name": "daily menu article", + "cash_box_article": "899690c3-8ef1-4b06-8f3c-f24a3afac9c1", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:36:49.686228Z", + "deleted": null, + "uuid": "65138d9d-5003-4666-a5ee-b53c8968ab5d", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 869, + "article": 178758 + }, + { + "id": 196, + "name": "daily menu article", + "cash_box_article": "8b9dad61-ca9c-4fbb-a7fe-a5cecaba50d5", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:14.446715Z", + "updated": "2025-12-04T15:28:34.638605Z", + "deleted": null, + "uuid": "2c00cc11-34b0-4c24-8588-8294f08406e7", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 869, + "article": 178713 + }, + { + "id": 197, + "name": "daily menu article", + "cash_box_article": "45476da6-84b7-4129-9b34-2251595ebbc5", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:14.446715Z", + "updated": "2025-12-04T15:28:41.885943Z", + "deleted": null, + "uuid": "c7adf9dc-8c07-4ce7-a6c4-617415490f60", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 869, + "article": 178717 + }, + { + "id": 202, + "name": "daily menu article", + "cash_box_article": "c64fa08d-b4a0-4510-b512-2c299d373d04", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:14.446715Z", + "updated": "2025-12-04T15:28:56.409464Z", + "deleted": null, + "uuid": "8f9db34e-5bcb-48fb-8e48-619b9057e48d", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 869, + "article": 178716 + }, + { + "id": 201, + "name": "daily menu article", + "cash_box_article": "34056ce2-ff61-4c94-9aba-b1f364fa9772", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:14.446715Z", + "updated": "2025-12-04T15:29:03.525064Z", + "deleted": null, + "uuid": "67aabc9f-67db-4dd1-a5ca-d693b9e40861", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 869, + "article": 178718 + }, + { + "id": 200, + "name": "daily menu article", + "cash_box_article": "dfaa35f6-4604-4e14-9f86-1862a05a5881", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:14.446715Z", + "updated": "2025-12-04T15:29:07.074691Z", + "deleted": null, + "uuid": "20a8dc47-4a6e-4ea7-829b-6596f25cc00f", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 869, + "article": 178714 + }, + { + "id": 199, + "name": "daily menu article", + "cash_box_article": "d9336280-1d15-440c-81a1-e47d6766c475", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:14.446715Z", + "updated": "2025-12-04T15:29:14.844529Z", + "deleted": null, + "uuid": "81e5faa7-3e11-4383-bc7d-bfb15c86bf37", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 869, + "article": 178719 + }, + { + "id": 198, + "name": "daily menu article", + "cash_box_article": "f9445453-fe41-42a3-b1ed-9aedafc6852c", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:14.446715Z", + "updated": "2025-12-04T15:29:25.307254Z", + "deleted": null, + "uuid": "38896fec-c358-4605-af47-a258d203f21c", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 869, + "article": 178715 + } + ], + "extras": [], + "created": "2025-11-05T10:47:38.217878Z", + "updated": "2025-12-09T09:31:02.431300Z", + "deleted": null, + "uuid": "644ade5f-6e46-4aec-a613-9fbd94389ae2", + "type": 3, + "enabled": true, + "visibility": 2, + "payment_types": [ + "hobex" + ], + "allow_preorder": false, + "preorder_delta": 0, + "order_lead_time": 0, + "preorder_threshold": 3600, + "preorder_future_limit": 86400, + "cancellation_cutoff": 0, + "slot_delta": 0, + "slot_interval": 900, + "slot_maximum_count": 1000, + "minimum_order_value": "0.00", + "take_away": false, + "comments_enabled": false, + "enable_courses": false, + "max_drink_items": 0, + "max_food_items": 0, + "tip_enabled": false, + "tip_mode": 1, + "default_tip": 0, + "price_level": -1, + "only_managed_users": false, + "needs_authentication": true, + "show_qr_code": false, + "allow_cancel": false, + "skip_customer_information": true + }, + { + "id": 870, + "name": "pickup", + "delivery_fees": [], + "articles": [ + { + "id": 97, + "name": "daily menu article", + "cash_box_article": "6208ac78-6cde-46ff-b998-f1afdc7e34b7", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "bad090e4-0890-40ef-9b3a-df3e73841fb7", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 870, + "article": 176511 + }, + { + "id": 98, + "name": "daily menu article", + "cash_box_article": "c7e4c59f-2566-40c3-9497-d0e65f5b2631", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "18770bdc-b373-4f05-b800-dc844bc89488", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 870, + "article": 176512 + }, + { + "id": 99, + "name": "daily menu article", + "cash_box_article": "fb901cf3-d9f1-4df4-886d-998dca8371ef", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "62f8fcb0-f90c-4222-be3c-bc4bcc7f65e1", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 870, + "article": 176513 + }, + { + "id": 100, + "name": "daily menu article", + "cash_box_article": "338a80de-35ec-40ef-a1e7-6fc1dafa2fb1", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "a3f17dfc-c87b-450c-954e-3033124cc3a1", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 870, + "article": 176515 + }, + { + "id": 101, + "name": "daily menu article", + "cash_box_article": "3ce62d9d-c75b-4cce-97e5-fabe2db7f243", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "c04a7e1c-f7c1-4581-b8e5-6971a1fbb1c7", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 870, + "article": 176516 + }, + { + "id": 102, + "name": "daily menu article", + "cash_box_article": "45a64da3-8973-4f49-94d5-ce293afde7e3", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "aa6afb17-19a4-4860-bbaa-2b3adc79e54e", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 870, + "article": 176517 + }, + { + "id": 103, + "name": "daily menu article", + "cash_box_article": "690b154a-95b1-4563-a73d-06735c62f203", + "cash_box_article_type": 1, + "created": "2025-11-27T07:48:55.453573Z", + "updated": "2025-11-27T07:48:55.453573Z", + "deleted": null, + "uuid": "0d7301f5-0e3a-44ab-ad17-04c88522b643", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 870, + "article": 176514 + } + ], + "extras": [], + "created": "2025-11-05T12:02:09.942714Z", + "updated": "2025-12-11T16:13:14.191490Z", + "deleted": null, + "uuid": "f40dded5-472d-4dd0-ac75-760e2f4714b5", + "type": 1, + "enabled": true, + "visibility": 3, + "payment_types": [ + "payroll" + ], + "allow_preorder": true, + "preorder_delta": 0, + "order_lead_time": 2700, + "preorder_threshold": 10800, + "preorder_future_limit": 518400, + "cancellation_cutoff": 2700, + "slot_delta": 0, + "slot_interval": 5400, + "slot_maximum_count": 1000, + "minimum_order_value": "0.00", + "take_away": false, + "comments_enabled": true, + "enable_courses": false, + "max_drink_items": 0, + "max_food_items": 0, + "tip_enabled": false, + "tip_mode": 1, + "default_tip": 0, + "price_level": -1, + "only_managed_users": true, + "needs_authentication": true, + "show_qr_code": true, + "allow_cancel": true, + "skip_customer_information": true + }, + { + "id": 877, + "name": "dine in", + "delivery_fees": [], + "articles": [], + "extras": [], + "created": "2025-11-26T15:50:23.745764Z", + "updated": "2025-11-26T20:46:49.512041Z", + "deleted": null, + "uuid": "6ddbb05f-40d6-4ee5-8fc6-85696db53c90", + "type": 0, + "enabled": false, + "visibility": 1, + "payment_types": [ + "cash" + ], + "allow_preorder": false, + "preorder_delta": 0, + "order_lead_time": 0, + "preorder_threshold": 3600, + "preorder_future_limit": 86400, + "cancellation_cutoff": 0, + "slot_delta": 0, + "slot_interval": 900, + "slot_maximum_count": 5, + "minimum_order_value": "0.00", + "take_away": false, + "comments_enabled": true, + "enable_courses": false, + "max_drink_items": 0, + "max_food_items": 0, + "tip_enabled": true, + "tip_mode": 1, + "default_tip": 0, + "price_level": -1, + "only_managed_users": false, + "needs_authentication": false, + "show_qr_code": false, + "allow_cancel": false, + "skip_customer_information": true + }, + { + "id": 857, + "name": "canteen", + "delivery_fees": [], + "articles": [ + { + "id": 212, + "name": "daily menu article", + "cash_box_article": "fa2e5d0f-f368-4bf3-b4cd-15fc02efb672", + "cash_box_article_type": 1, + "created": "2026-01-29T07:34:16.562320Z", + "updated": "2026-01-29T07:34:16.562343Z", + "deleted": null, + "uuid": "c464f85e-285b-4003-8d08-1678f9237b48", + "article_type": 12, + "fee": "7.00", + "limit": "0.00", + "configuration": 857, + "article": 182373 + }, + { + "id": 90, + "name": "daily menu article", + "cash_box_article": "6208ac78-6cde-46ff-b998-f1afdc7e34b7", + "cash_box_article_type": 1, + "created": "2025-11-27T07:40:38.594710Z", + "updated": "2025-11-27T07:40:38.594732Z", + "deleted": null, + "uuid": "88c628cb-3837-4d6d-8c63-a6e0b28f0341", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 176511 + }, + { + "id": 91, + "name": "daily menu article", + "cash_box_article": "c7e4c59f-2566-40c3-9497-d0e65f5b2631", + "cash_box_article_type": 1, + "created": "2025-11-27T07:40:59.156558Z", + "updated": "2025-11-27T07:40:59.156576Z", + "deleted": null, + "uuid": "07921176-b8d0-4686-8f7b-757ca2261d01", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 857, + "article": 176512 + }, + { + "id": 92, + "name": "daily menu article", + "cash_box_article": "fb901cf3-d9f1-4df4-886d-998dca8371ef", + "cash_box_article_type": 1, + "created": "2025-11-27T07:41:23.612442Z", + "updated": "2025-11-27T07:41:23.612459Z", + "deleted": null, + "uuid": "405371a9-fc20-418d-af81-91519d37039e", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 857, + "article": 176513 + }, + { + "id": 94, + "name": "daily menu article", + "cash_box_article": "338a80de-35ec-40ef-a1e7-6fc1dafa2fb1", + "cash_box_article_type": 1, + "created": "2025-11-27T07:42:07.469169Z", + "updated": "2025-11-27T07:42:13.560787Z", + "deleted": null, + "uuid": "81864d63-0ff4-41a6-9f42-4cbf72eb23d7", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 857, + "article": 176515 + }, + { + "id": 95, + "name": "daily menu article", + "cash_box_article": "3ce62d9d-c75b-4cce-97e5-fabe2db7f243", + "cash_box_article_type": 1, + "created": "2025-11-27T07:42:29.813091Z", + "updated": "2025-11-27T07:42:29.813108Z", + "deleted": null, + "uuid": "995fcf59-f8b6-4b84-9cba-8675ffaca9f2", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 857, + "article": 176516 + }, + { + "id": 96, + "name": "daily menu article", + "cash_box_article": "45a64da3-8973-4f49-94d5-ce293afde7e3", + "cash_box_article_type": 1, + "created": "2025-11-27T07:42:46.380403Z", + "updated": "2025-11-27T07:42:46.380425Z", + "deleted": null, + "uuid": "efdb1d47-f223-4c2e-bc14-c60c83b483a3", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 857, + "article": 176517 + }, + { + "id": 93, + "name": "daily menu article", + "cash_box_article": "690b154a-95b1-4563-a73d-06735c62f203", + "cash_box_article_type": 1, + "created": "2025-11-27T07:41:40.955821Z", + "updated": "2025-11-27T07:42:59.835122Z", + "deleted": null, + "uuid": "f0eaafd2-f497-4a4b-b998-0bfe50018143", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 857, + "article": 176514 + }, + { + "id": 215, + "name": "daily menu article", + "cash_box_article": "1dda4bed-3306-4263-bd3c-f1d8c87f340a", + "cash_box_article_type": 1, + "created": "2026-01-29T07:35:40.880679Z", + "updated": "2026-01-29T07:36:32.707801Z", + "deleted": null, + "uuid": "f74b47f0-5fbd-4dd7-b547-80dd2ed0bbc4", + "article_type": 12, + "fee": "7.00", + "limit": "0.00", + "configuration": 857, + "article": 182376 + }, + { + "id": 154, + "name": "daily menu article", + "cash_box_article": "e6966a1c-ba45-450f-b007-f5f41cb79a49", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "7c66ef66-9120-454f-b0a7-cb07b8ed4716", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 857, + "article": 178720 + }, + { + "id": 155, + "name": "daily menu article", + "cash_box_article": "04cb9490-ee5c-40f6-afa8-6837dae888a2", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "3cfc10e1-2853-4c0d-88a6-ca38ef085e92", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 857, + "article": 178724 + }, + { + "id": 156, + "name": "daily menu article", + "cash_box_article": "b65661dd-6c62-4a23-9384-c933c8ed424e", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "6f6ef837-746d-4a4a-88f5-c08d8f17aa6f", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 857, + "article": 178722 + }, + { + "id": 157, + "name": "daily menu article", + "cash_box_article": "725d1aa6-f9f5-4e14-951c-90543d961b12", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "59553dc0-2fb6-4147-92d5-b6a84d73882b", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 857, + "article": 178726 + }, + { + "id": 158, + "name": "daily menu article", + "cash_box_article": "868291bd-490d-47ef-b5be-e2002ce3bd10", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "95b19f9f-f0cf-46cd-8826-5a3bbd6de440", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 857, + "article": 178721 + }, + { + "id": 159, + "name": "daily menu article", + "cash_box_article": "cfdd8d4b-eb49-4b00-9db4-045e293dc0c0", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "cb54a774-795e-4e98-b8a3-5d8320c4a4ad", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 857, + "article": 178725 + }, + { + "id": 160, + "name": "daily menu article", + "cash_box_article": "f4b084f9-5fd4-494c-aabf-27199e1ecece", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:23:18.993140Z", + "deleted": null, + "uuid": "7ba2a414-2a2f-4d6e-80b1-7bf9d6813eff", + "article_type": 12, + "fee": "1.00", + "limit": "0.00", + "configuration": 857, + "article": 178723 + }, + { + "id": 213, + "name": "daily menu article", + "cash_box_article": "10e9d99d-d050-4565-8c02-1e9589fd2933", + "cash_box_article_type": 1, + "created": "2026-01-29T07:35:03.186688Z", + "updated": "2026-01-29T07:36:11.477030Z", + "deleted": null, + "uuid": "124aa4c9-e68e-45a2-ae9c-c7333bd35c3e", + "article_type": 12, + "fee": "7.00", + "limit": "0.00", + "configuration": 857, + "article": 182374 + }, + { + "id": 217, + "name": "daily menu article", + "cash_box_article": "d2814527-95bc-4ee8-8bda-e5fe2427bd47", + "cash_box_article_type": 1, + "created": "2026-01-29T08:21:51.364840Z", + "updated": "2026-01-29T08:21:51.364857Z", + "deleted": null, + "uuid": "e0ce3a5a-68d1-499f-a352-581eb9b4cfe3", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 182378 + }, + { + "id": 176, + "name": "daily menu article", + "cash_box_article": "765cc903-2695-4bba-bed2-4f4078f294c2", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:56.604340Z", + "deleted": null, + "uuid": "9e52e37f-f5df-40c5-9c6a-988a95b6bf45", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 857, + "article": 178745 + }, + { + "id": 177, + "name": "daily menu article", + "cash_box_article": "e4d4157c-4f4a-4701-b1f9-356326c1612d", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:58.444401Z", + "deleted": null, + "uuid": "dc93a7eb-68d9-4730-a240-b3d972d1fc14", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 857, + "article": 178743 + }, + { + "id": 178, + "name": "daily menu article", + "cash_box_article": "21686838-0e22-4915-a4a0-e38b3bb43635", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:00.602263Z", + "deleted": null, + "uuid": "3590c0eb-98a7-4c38-a204-2ed4e66242fb", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 857, + "article": 178747 + }, + { + "id": 161, + "name": "daily menu article", + "cash_box_article": "48e86ff1-7b6d-46f7-91a0-346de93fb855", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:39:39.892191Z", + "deleted": null, + "uuid": "30d94707-2c5d-4993-af73-a6b45b8644a4", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 857, + "article": 178727 + }, + { + "id": 162, + "name": "daily menu article", + "cash_box_article": "695bb5f6-cbbb-486a-ad3b-9158dc055f98", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:39:43.402728Z", + "deleted": null, + "uuid": "699c4743-6807-4dec-a980-b7a9ac22a85b", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 857, + "article": 178731 + }, + { + "id": 163, + "name": "daily menu article", + "cash_box_article": "5207e0e3-06a3-4e15-9e8f-aca634005fad", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:39:45.566061Z", + "deleted": null, + "uuid": "a4cbc519-f857-4890-bbcd-8ece056bdd79", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 857, + "article": 178729 + }, + { + "id": 164, + "name": "daily menu article", + "cash_box_article": "701c6989-194c-400c-b590-77734f9a5479", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:39:47.611356Z", + "deleted": null, + "uuid": "b70a3f59-8fe0-4a73-84ce-08ebf349f103", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 857, + "article": 178733 + }, + { + "id": 165, + "name": "daily menu article", + "cash_box_article": "a33ce321-1943-430e-a435-b83f87ff1080", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:39:49.571920Z", + "deleted": null, + "uuid": "ffe276a4-d297-42a3-af78-aee06aab2521", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 857, + "article": 178728 + }, + { + "id": 166, + "name": "daily menu article", + "cash_box_article": "7bf71611-3eb3-4784-bd29-b38ebd1821f9", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:39:51.532473Z", + "deleted": null, + "uuid": "497cdb95-5e8c-415d-8098-8ffba3cc83e2", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 857, + "article": 178732 + }, + { + "id": 167, + "name": "daily menu article", + "cash_box_article": "8eb70af1-5cc6-4f30-be0a-3bf2f91321b3", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:39:53.803963Z", + "deleted": null, + "uuid": "b05cb25d-cde4-43f5-a1dd-90e711c21e34", + "article_type": 12, + "fee": "2.00", + "limit": "0.00", + "configuration": 857, + "article": 178730 + }, + { + "id": 168, + "name": "daily menu article", + "cash_box_article": "03672c2e-0ab3-4efd-80ad-564a910c69dc", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:16.745761Z", + "deleted": null, + "uuid": "82fb659b-4cd1-412b-a55c-91860a362ee7", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 857, + "article": 178734 + }, + { + "id": 169, + "name": "daily menu article", + "cash_box_article": "ec147dfa-d0c5-446e-83ab-1e0f1c6a6f69", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:18.969566Z", + "deleted": null, + "uuid": "325e14dd-d242-47ad-980c-019f4d598a4f", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 857, + "article": 178738 + }, + { + "id": 170, + "name": "daily menu article", + "cash_box_article": "4ceb0a3e-973a-4b82-ae4b-2592c5ef43eb", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:21.038681Z", + "deleted": null, + "uuid": "91583c15-7708-41a4-b92b-f64c7dcc9d69", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 857, + "article": 178736 + }, + { + "id": 171, + "name": "daily menu article", + "cash_box_article": "5727d322-cae6-48e8-b1b1-0786d37935b1", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:22.949326Z", + "deleted": null, + "uuid": "0f2101f0-5228-4c2b-95d3-3d2997074567", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 857, + "article": 178740 + }, + { + "id": 172, + "name": "daily menu article", + "cash_box_article": "88b59af4-f3fb-45a7-ba0f-9fce88d4710f", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:25.052981Z", + "deleted": null, + "uuid": "28dbc585-1068-4e9b-9eda-f50edcd85b53", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 857, + "article": 178735 + }, + { + "id": 173, + "name": "daily menu article", + "cash_box_article": "86a71457-dbfd-452d-a41c-d9cbbdf15cdf", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:27.368859Z", + "deleted": null, + "uuid": "28da54aa-9df3-42e3-a053-216d7502c8a2", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 857, + "article": 178739 + }, + { + "id": 174, + "name": "daily menu article", + "cash_box_article": "1bd71fe6-b8fc-4c84-9780-96dedf6870f8", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:32.661812Z", + "deleted": null, + "uuid": "f573d3e0-230b-4b3e-a4d5-650e8537aba8", + "article_type": 12, + "fee": "3.00", + "limit": "0.00", + "configuration": 857, + "article": 178737 + }, + { + "id": 175, + "name": "daily menu article", + "cash_box_article": "b266cf85-960b-4f13-b327-e11929151e00", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:40:54.602903Z", + "deleted": null, + "uuid": "01c247d3-2320-496c-a282-78f9a40bd3a2", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 857, + "article": 178741 + }, + { + "id": 179, + "name": "daily menu article", + "cash_box_article": "ea309dc8-38bf-4e78-9b51-7c9a7efb2763", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:02.533437Z", + "deleted": null, + "uuid": "187afcb5-caba-45b6-95fb-a134a6eeeaca", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 857, + "article": 178742 + }, + { + "id": 180, + "name": "daily menu article", + "cash_box_article": "1a7386e3-3d4a-4261-b235-565fdf268863", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:04.495101Z", + "deleted": null, + "uuid": "fce8c9a5-8263-46a6-aa4c-416683e30cad", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 857, + "article": 178746 + }, + { + "id": 181, + "name": "daily menu article", + "cash_box_article": "316e731d-dd8e-47b4-bb25-9d4f2e641151", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:06.469152Z", + "deleted": null, + "uuid": "bd3373e0-e97f-460d-848a-932c875c0128", + "article_type": 12, + "fee": "4.00", + "limit": "0.00", + "configuration": 857, + "article": 178744 + }, + { + "id": 182, + "name": "daily menu article", + "cash_box_article": "42ec29ac-456c-4ca2-aea3-20dfbf3a28b5", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:26.819681Z", + "deleted": null, + "uuid": "1eab4039-75be-4eab-ba7f-686f0a8d577f", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 857, + "article": 178748 + }, + { + "id": 183, + "name": "daily menu article", + "cash_box_article": "8e52c14c-6c2a-444c-adde-916107f42f1a", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:29.984384Z", + "deleted": null, + "uuid": "6875dce5-deed-46b4-8b27-45408678bf63", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 857, + "article": 178752 + }, + { + "id": 184, + "name": "daily menu article", + "cash_box_article": "3c7f4116-12be-4d10-9369-a61f8b81e387", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:33.032586Z", + "deleted": null, + "uuid": "d0115b60-e5de-43ad-8197-7e16f5c909a4", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 857, + "article": 178750 + }, + { + "id": 185, + "name": "daily menu article", + "cash_box_article": "13203b86-7ce6-4e2d-b12a-a159100e6e0e", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:35.253020Z", + "deleted": null, + "uuid": "316dbee9-3bac-4c31-bc41-7e924bd6ccef", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 857, + "article": 178754 + }, + { + "id": 186, + "name": "daily menu article", + "cash_box_article": "f6d5bd6e-2e14-4930-8f27-be027fa477b2", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:37.173056Z", + "deleted": null, + "uuid": "21aff393-5ee1-4248-bd0b-a2431fe385f5", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 857, + "article": 178749 + }, + { + "id": 187, + "name": "daily menu article", + "cash_box_article": "dea4b5d4-7a2c-4565-94ae-9b5dc2cc5ed8", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:39.060392Z", + "deleted": null, + "uuid": "85d77712-172f-4b9c-b86d-78124752003e", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 857, + "article": 178753 + }, + { + "id": 188, + "name": "daily menu article", + "cash_box_article": "95ecb237-e497-48f3-ab7e-0b0f8730f02d", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:41:41.453354Z", + "deleted": null, + "uuid": "4bbf06be-9325-4556-9a26-402178d3717f", + "article_type": 12, + "fee": "5.00", + "limit": "0.00", + "configuration": 857, + "article": 178751 + }, + { + "id": 189, + "name": "daily menu article", + "cash_box_article": "5ec70cab-6b4c-410f-bb91-60a61fd8bbdd", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:42:02.072735Z", + "deleted": null, + "uuid": "982aea4b-fc8b-4196-a569-b72454fe336c", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 857, + "article": 178755 + }, + { + "id": 190, + "name": "daily menu article", + "cash_box_article": "704e5191-4dd6-4fd9-b99c-233d44de5ff3", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:42:04.204602Z", + "deleted": null, + "uuid": "c6e61b0b-0d77-48bf-852a-7d453943bb29", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 857, + "article": 178759 + }, + { + "id": 191, + "name": "daily menu article", + "cash_box_article": "b270abf7-a35c-4eca-8d62-5fdb5ceaf0a8", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:42:06.105292Z", + "deleted": null, + "uuid": "54c6ffbc-445a-454c-b29e-cd446949f022", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 857, + "article": 178757 + }, + { + "id": 192, + "name": "daily menu article", + "cash_box_article": "7810ea32-e693-4716-8f2d-552fa6bb0c45", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:42:08.040673Z", + "deleted": null, + "uuid": "bf625fdf-5cfd-4f1c-9606-84e158392f7d", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 857, + "article": 178761 + }, + { + "id": 193, + "name": "daily menu article", + "cash_box_article": "59b371ea-ab48-4efc-9b63-04cf3df9ffc5", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:42:10.373319Z", + "deleted": null, + "uuid": "5e3663c0-5b0c-4c79-86f4-11a92c549060", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 857, + "article": 178756 + }, + { + "id": 194, + "name": "daily menu article", + "cash_box_article": "784431e8-2a34-4230-9d95-dbe9c6498fb4", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:42:12.505487Z", + "deleted": null, + "uuid": "e8823a46-9175-49f3-b9eb-19c4b7dfa745", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 857, + "article": 178760 + }, + { + "id": 195, + "name": "daily menu article", + "cash_box_article": "899690c3-8ef1-4b06-8f3c-f24a3afac9c1", + "cash_box_article_type": 1, + "created": "2025-12-04T15:23:18.993140Z", + "updated": "2025-12-04T15:42:14.557305Z", + "deleted": null, + "uuid": "f408f867-2c35-4fb2-86d4-8150b7804492", + "article_type": 12, + "fee": "6.00", + "limit": "0.00", + "configuration": 857, + "article": 178758 + }, + { + "id": 203, + "name": "daily menu article", + "cash_box_article": "8b9dad61-ca9c-4fbb-a7fe-a5cecaba50d5", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:36.081247Z", + "updated": "2025-12-04T15:42:36.991214Z", + "deleted": null, + "uuid": "1dcf66fc-9ccf-4946-a764-058643b1d317", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 178713 + }, + { + "id": 204, + "name": "daily menu article", + "cash_box_article": "45476da6-84b7-4129-9b34-2251595ebbc5", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:36.081247Z", + "updated": "2025-12-04T15:42:39.140683Z", + "deleted": null, + "uuid": "958e0de1-13bf-41e7-aa45-4b6852b0a01b", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 178717 + }, + { + "id": 205, + "name": "daily menu article", + "cash_box_article": "f9445453-fe41-42a3-b1ed-9aedafc6852c", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:36.081247Z", + "updated": "2025-12-04T15:42:41.593534Z", + "deleted": null, + "uuid": "f96bb0c4-ac62-44e1-886f-075a1925bfb5", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 178715 + }, + { + "id": 206, + "name": "daily menu article", + "cash_box_article": "d9336280-1d15-440c-81a1-e47d6766c475", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:36.081247Z", + "updated": "2025-12-04T15:42:43.685959Z", + "deleted": null, + "uuid": "6b8023f5-e632-44de-8e82-36276681b861", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 178719 + }, + { + "id": 207, + "name": "daily menu article", + "cash_box_article": "dfaa35f6-4604-4e14-9f86-1862a05a5881", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:36.081247Z", + "updated": "2025-12-04T15:42:45.648699Z", + "deleted": null, + "uuid": "4e1413ea-28b0-43c3-8c4b-f7127cc24d56", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 178714 + }, + { + "id": 208, + "name": "daily menu article", + "cash_box_article": "34056ce2-ff61-4c94-9aba-b1f364fa9772", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:36.081247Z", + "updated": "2025-12-04T15:42:48.028674Z", + "deleted": null, + "uuid": "130cbb68-cb72-4340-815b-822d5fdfec71", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 178718 + }, + { + "id": 209, + "name": "daily menu article", + "cash_box_article": "c64fa08d-b4a0-4510-b512-2c299d373d04", + "cash_box_article_type": 1, + "created": "2025-12-04T15:27:36.081247Z", + "updated": "2025-12-04T15:42:50.033767Z", + "deleted": null, + "uuid": "ddadf870-a95a-4367-bbba-8a06190c0d72", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 178716 + }, + { + "id": 214, + "name": "daily menu article", + "cash_box_article": "262e5a5e-707b-462c-94b1-c8cbe639a8f9", + "cash_box_article_type": 1, + "created": "2026-01-29T07:35:21.770442Z", + "updated": "2026-01-29T07:36:22.914507Z", + "deleted": null, + "uuid": "e67d5993-fabb-403d-b799-5e1e6032cf5e", + "article_type": 12, + "fee": "7.00", + "limit": "0.00", + "configuration": 857, + "article": 182375 + }, + { + "id": 216, + "name": "daily menu article", + "cash_box_article": "ddf8192f-2666-4db9-8b92-4002a3b0d120", + "cash_box_article_type": 1, + "created": "2026-01-29T08:21:22.610095Z", + "updated": "2026-01-29T08:21:32.419099Z", + "deleted": null, + "uuid": "298378d5-349d-4559-be1f-708789355ab5", + "article_type": 12, + "fee": "0.00", + "limit": "0.00", + "configuration": 857, + "article": 182377 + } + ], + "extras": [ + { + "type": 2, + "text": "Ablauf / Process" + }, + { + "type": 3, + "text": "Deine Bestellung kann per Mitarbeiterkarte abgeholt werden. | Your order can be picked up using your employee card." + } + ], + "created": "2025-10-20T07:37:10.129582Z", + "updated": "2026-01-27T14:43:56.933001Z", + "deleted": null, + "uuid": "a1a52834-1e37-4e8a-be8c-a78b4700825a", + "type": 7, + "enabled": true, + "visibility": 1, + "payment_types": [ + "payroll" + ], + "allow_preorder": true, + "preorder_delta": 3600, + "order_lead_time": 0, + "preorder_threshold": 3600, + "preorder_future_limit": 1382400, + "cancellation_cutoff": 3600, + "slot_delta": 0, + "slot_interval": 10800, + "slot_maximum_count": 5000, + "minimum_order_value": "0.00", + "take_away": false, + "comments_enabled": true, + "enable_courses": false, + "max_drink_items": 0, + "max_food_items": 0, + "tip_enabled": false, + "tip_mode": 1, + "default_tip": 0, + "price_level": -1, + "only_managed_users": true, + "needs_authentication": true, + "show_qr_code": true, + "allow_cancel": true, + "skip_customer_information": true + } + ], + "needs_service_notification": false, + "enable_coupons": false + } + }, + { + "timestamp": "2026-02-05T13:39:10.208Z", + "method": "GET", + "url": "https://api.bessa.app/v1/closing/rules/591/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token c3418725e95a9f90e3645cbc846b4d67c7c66131", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "2", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:39:10 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": [] + }, + { + "timestamp": "2026-02-05T13:39:10.571Z", + "method": "POST", + "url": "https://region1.analytics.google.com/g/collect?v=2&tid=G-NT5W7DSRT4>m=45je6231v9102475426z89102461295za20gzb9102461295zd9102461295&_p=1770298748820&_gaz=1&gcd=13l3l3l2l1l1&npa=1&dma_cps=syphamo&dma=1&cid=531786136.1770298750&ul=en-us&sr=1920x944&uaa=x86&uab=64&uafvl=Not(A%253ABrand%3B8.0.0.0%7CChromium%3B144.0.7559.96&uamb=0&uam=&uap=Linux&uapv=&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AAAAAGA&_s=1&tag_exp=103116026~103200004~104527907~104528500~104573694~104684208~104684211~115938465~115938469~116185181~116185182~116988315&sid=1770298750&sct=1&seg=0&dl=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&dt=Bessa%20Web%20Shop&en=page_view&_fv=1&_nsi=1&_ss=1&ep.content_group=web.bessa.app&tfd=1826", + "status": 204, + "type": "fetch", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "cache-control": "no-cache, no-store, must-revalidate", + "content-length": "0", + "content-security-policy-report-only": "script-src 'none'; form-action 'none'; frame-src 'none'; report-uri https://csp.withgoogle.com/csp/scaffolding/ascnsrsggc:171:0", + "content-type": "text/plain", + "cross-origin-opener-policy-report-only": "same-origin; report-to=ascnsrsggc:171:0", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:39:10 GMT", + "expires": "Fri, 01 Jan 1990 00:00:00 GMT", + "pragma": "no-cache", + "report-to": "{\"group\":\"ascnsrsggc:171:0\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/scaffolding/ascnsrsggc:171:0\"}],}", + "server": "Golfe2" + }, + "body": "[COULD NOT READ BODY]" + }, + { + "timestamp": "2026-02-05T13:39:15.239Z", + "method": "POST", + "url": "https://region1.analytics.google.com/g/collect?v=2&tid=G-NT5W7DSRT4>m=45je6231v9102475426za20gzb9102461295zd9102461295&_p=1770298748820&gcd=13l3l3l2l1l1&npa=1&dma_cps=syphamo&dma=1&cid=531786136.1770298750&ul=en-us&sr=1920x944&uaa=x86&uab=64&uafvl=Not(A%253ABrand%3B8.0.0.0%7CChromium%3B144.0.7559.96&uamb=0&uam=&uap=Linux&uapv=&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AEAAAGQ&_s=2&tag_exp=103116026~103200004~104527907~104528500~104573694~104684208~104684211~115938465~115938469~116185181~116185182~116988315&sid=1770298750&sct=1&seg=0&dl=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&dt=Bessa%20Web%20Shop&en=scroll&ep.content_group=web.bessa.app&epn.percent_scrolled=90&tfd=6859", + "status": 204, + "type": "fetch", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "cache-control": "no-cache, no-store, must-revalidate", + "content-length": "0", + "content-security-policy-report-only": "script-src 'none'; form-action 'none'; frame-src 'none'; report-uri https://csp.withgoogle.com/csp/scaffolding/ascnsrsggc:171:0", + "content-type": "text/plain", + "cross-origin-opener-policy-report-only": "same-origin; report-to=ascnsrsggc:171:0", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:39:15 GMT", + "expires": "Fri, 01 Jan 1990 00:00:00 GMT", + "pragma": "no-cache", + "report-to": "{\"group\":\"ascnsrsggc:171:0\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/scaffolding/ascnsrsggc:171:0\"}],}", + "server": "Golfe2" + }, + "body": "[COULD NOT READ BODY]" + }, + { + "timestamp": "2026-02-05T13:39:52.674Z", + "method": "GET", + "url": "https://web.bessa.app/common.58c752d3eea01b54.js", + "status": 200, + "type": "script", + "requestHeaders": { + "origin": "https://web.bessa.app", + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/knapp-kantine", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*", + "cookie": "__cmpcc=1; g_state={\"i_l\":0,\"i_ll\":1770298749698,\"i_b\":\"OwmjB/xOTtJtApPjLsCx6Vw3vtuduXvkmTEMjNJSXuE\",\"i_e\":{\"enable_itp_optimization\":15}}; _fbp=fb.1.1770298749735.57921644138573676; _ga=GA1.1.531786136.1770298750; _ga_NT5W7DSRT4=GS2.1.s1770298750$o1$g0$t1770298750$j60$l0$h0; __cmpconsent16021=CQfJhpgQfJhpgAfS8BENCQFgAAAAAAAAAAigF5wAQF5gXnABAXmAAA; __cmpcccu16021=aCQfLid1gA6XgGsY5PMqNQExowBZVC0A0QwAgQIRoBSm" + }, + "responseHeaders": { + "age": "3257", + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "max-age=14400", + "cf-cache-status": "HIT", + "cf-ray": "9c92d0fe1d57c2fc-VIE", + "content-encoding": "zstd", + "content-type": "text/javascript", + "date": "Thu, 05 Feb 2026 13:39:52 GMT", + "etag": "W/\"bbdd7545f2b79da6a949c2017433ae29\"", + "last-modified": "Mon, 17 Nov 2025 15:40:54 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=PeZOuHdyfyl20Hd7Bq7RYdunII1X%2BWB45jzcXYuHtBX6m%2Fc6pbVlo%2BTFU8lren4UUBLGKaC0yE1Cf2LMOfLY61nizl%2Bp9FqAfO72\"}]}", + "server": "cloudflare", + "x-amz-id-2": "4nKORGcRhTiehILPwgEln/x+s9PLfAotOz+1gDYJQ0toCyJT3Bnem9VO0w9Wk888v8oRyhD99m36/gw/9ebYi8vR6/WLwuEkpuE61VQ0wQQ=", + "x-amz-request-id": "GEP2KBFY56QK90AS", + "x-amz-version-id": "VFD_VxQcuBZ69zuN_IMX.UxUxpNmteCX" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:52.676Z", + "method": "GET", + "url": "https://web.bessa.app/477.a03337408cbac0fc.js", + "status": 200, + "type": "script", + "requestHeaders": { + "origin": "https://web.bessa.app", + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/knapp-kantine", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*", + "cookie": "__cmpcc=1; g_state={\"i_l\":0,\"i_ll\":1770298749698,\"i_b\":\"OwmjB/xOTtJtApPjLsCx6Vw3vtuduXvkmTEMjNJSXuE\",\"i_e\":{\"enable_itp_optimization\":15}}; _fbp=fb.1.1770298749735.57921644138573676; _ga=GA1.1.531786136.1770298750; _ga_NT5W7DSRT4=GS2.1.s1770298750$o1$g0$t1770298750$j60$l0$h0; __cmpconsent16021=CQfJhpgQfJhpgAfS8BENCQFgAAAAAAAAAAigF5wAQF5gXnABAXmAAA; __cmpcccu16021=aCQfLid1gA6XgGsY5PMqNQExowBZVC0A0QwAgQIRoBSm" + }, + "responseHeaders": { + "age": "3257", + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "max-age=14400", + "cf-cache-status": "HIT", + "cf-ray": "9c92d0fe1d59c2fc-VIE", + "content-encoding": "zstd", + "content-type": "text/javascript", + "date": "Thu, 05 Feb 2026 13:39:52 GMT", + "etag": "W/\"6fe273f393f190f83d439e7c1ecb6083\"", + "last-modified": "Fri, 12 Dec 2025 14:33:22 GMT", + "nel": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}", + "report-to": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=6cOc7wwVY0oAApBzBhO%2B83%2FToUCvb6p%2BN2UmGxaZyQuXtP5c7lGOy4DswQbKRrLJ8%2BIN2Hephh1SQWGGRCGNn0kF8Wh1wblgizDf\"}]}", + "server": "cloudflare", + "x-amz-id-2": "zfHx7pBR39pQdPXSvRNPA1+ND19GLCOLdalZju+v8I0diNlt9YBPk1KVSRGUftmDTLPUhWZZYr5oEDjgj0tCLca6B9aV8FRp", + "x-amz-request-id": "YRXCA6YD1RTFFYSA", + "x-amz-version-id": "CYrFeT0j61l6HWRfnfkkOYa_4rNYseBN" + }, + "body": null + }, + { + "timestamp": "2026-02-05T13:39:52.866Z", + "method": "GET", + "url": "https://api.bessa.app/v1/venues/591/menu/categories/?type=7", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token c3418725e95a9f90e3645cbc846b4d67c7c66131", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "5320", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:39:52 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "next": null, + "previous": null, + "results": [ + { + "id": 11452, + "items": [ + { + "id": 178715, + "price": "5.50", + "name": "M1 Herzhaftes DO 1", + "description": "Minestrone / Minestrone(ACGLM) Grillhendl mit Semmel / Grilled chicken with bread roll(A) Dessert(ACG)", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_lK0z2", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.266923Z", + "updated": "2026-02-05T11:43:02.338812Z", + "deleted": null, + "uuid": "4ccc26c4-a918-4d2c-aab2-e58e380d35dc", + "image": null, + "number": "f9445453-fe41-42a3-b1ed-9aedafc6852c", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "0", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178722, + "price": "5.50", + "name": "M2 Gourmetteller DO 1", + "description": "Minestrone / Minestrone(ACLM) Grünes Fischcurry mit Reis/ Green fish curry with rice(ADGFL) Dessert(ACG)", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_xK8yq", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.267755Z", + "updated": "2026-02-05T12:07:42.650271Z", + "deleted": null, + "uuid": "25331814-f58f-4cab-bd3b-ee2a8ea26792", + "image": null, + "number": "b65661dd-6c62-4a23-9384-c933c8ed424e", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "3", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178729, + "price": "5.50", + "name": "M3 Basisch und Glutenarm DO 1", + "description": "Minestrone / Minestrone(ACLM) Spinat Käseknödel mit Butter / Spinach cheese dumplings with butter(ACG) Dessert(ACG)", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_0vJGN", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.268580Z", + "updated": "2026-02-05T12:43:43.460453Z", + "deleted": null, + "uuid": "a72efd00-40a6-4766-ba6a-4b8de4fc762b", + "image": null, + "number": "5207e0e3-06a3-4e15-9e8f-aca634005fad", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "30", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178736, + "price": "5.50", + "name": "M4 Ginko vegan DO 1", + "description": "Kartoffel Salbeicremesuppe, Gemüselasagne AFO) mit Tomatensauce, Hafer Nusskuchen (AFH) potato soup, vegetable lasagne with tomato soup, oat nut cake", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_mG0No", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.269479Z", + "updated": "2026-02-05T10:51:42.913683Z", + "deleted": null, + "uuid": "78a7de7a-d9ff-457c-8e05-9e891643861a", + "image": null, + "number": "4ceb0a3e-973a-4b82-ae4b-2592c5ef43eb", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "0", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178743, + "price": "5.50", + "name": "M5F Salat mit Gebäck DO 1", + "description": "Minestrone / Minestrone/ACLM) Salatteller mit Schinkenrolle / Salad plate with ham roll(AGLM) Dessert(ACG)", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_qalne", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.270337Z", + "updated": "2026-02-05T12:33:42.960616Z", + "deleted": null, + "uuid": "c84aa366-0f50-4fd4-ba76-2648a6c66875", + "image": null, + "number": "e4d4157c-4f4a-4701-b1f9-356326c1612d", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "2", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178750, + "price": "3.00", + "name": "M6 Suppe, kleiner Salat + Dessert DO 1", + "description": "Suppe / Soup Salat / Salad Dessert", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_zpzrZ", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.271249Z", + "updated": "2026-02-05T11:20:42.582130Z", + "deleted": null, + "uuid": "0a94e2e9-27de-4f70-974a-533ff1519dab", + "image": null, + "number": "3c7f4116-12be-4d10-9369-a61f8b81e387", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "7", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178757, + "price": "4.30", + "name": "M7F Kleines Hauptspeisenmenü DO 1", + "description": "Minestrone / Minestrone(ACLM) Grünes Fischcurry mit Reis/ Green fish curry with rice(ADGFL) Dessert(ACG)", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_Rra6N", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.272175Z", + "updated": "2026-02-05T11:56:42.849106Z", + "deleted": null, + "uuid": "c03d0480-1907-42e6-bdef-51bf2861f670", + "image": null, + "number": "b270abf7-a35c-4eca-8d62-5fdb5ceaf0a8", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "12", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 182376, + "price": "5.50", + "name": "M8 Mut-Menü DO 1", + "description": "Minestrone (ACLM) Eating together at a table without knowing what food is available (LMCOGAF)", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_MlEed", + "image_thumbnail": null, + "modifiers": [], + "created": "2026-01-28T14:50:17.801692Z", + "updated": "2026-02-05T11:48:43.266761Z", + "deleted": null, + "uuid": "b7c31020-ec32-42cd-94b6-d7cbbf554c71", + "image": null, + "number": "1dda4bed-3306-4263-bd3c-f1d8c87f340a", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "42", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + } + ], + "name": "Menü", + "description": "Menü", + "children": [], + "created": "2025-11-26T13:34:51.236986Z", + "updated": "2026-01-29T07:38:42.691415Z", + "deleted": null, + "uuid": "89c73f18-f3c8-45f3-8227-66a98db86396", + "sort": 0, + "image": null, + "from_hour": null, + "to_hour": null, + "collapse": false + } + ] + } + }, + { + "timestamp": "2026-02-05T13:40:02.316Z", + "method": "POST", + "url": "https://region1.analytics.google.com/g/collect?v=2&tid=G-NT5W7DSRT4>m=45je6231v9102475426za20gzb9102461295zd9102461295&_p=1770298748820&gcd=13l3l3l2l1l1&npa=1&dma_cps=syphamo&dma=1&cid=531786136.1770298750&ul=en-us&sr=1920x944&uaa=x86&uab=64&uafvl=Not(A%253ABrand%3B8.0.0.0%7CChromium%3B144.0.7559.96&uamb=0&uam=&uap=Linux&uapv=&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AEEAAGQ&_s=3&tag_exp=103116026~103200004~104527907~104528500~104573694~104684208~104684211~115938465~115938469~116185181~116185182~116988315&sid=1770298750&sct=1&seg=1&dl=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&dt=Bessa%20Web%20Shop&en=form_start&ep.content_group=web.bessa.app&ep.form_id=&ep.form_name=&ep.form_destination=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&epn.form_length=2&ep.first_field_id=mat-input-0&ep.first_field_name=&ep.first_field_type=text&epn.first_field_position=1&_et=12609&tfd=53912", + "status": 204, + "type": "fetch", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "cache-control": "no-cache, no-store, must-revalidate", + "content-length": "0", + "content-security-policy-report-only": "script-src 'none'; form-action 'none'; frame-src 'none'; report-uri https://csp.withgoogle.com/csp/scaffolding/ascnsrsggc:171:0", + "content-type": "text/plain", + "cross-origin-opener-policy-report-only": "same-origin; report-to=ascnsrsggc:171:0", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:40:02 GMT", + "expires": "Fri, 01 Jan 1990 00:00:00 GMT", + "pragma": "no-cache", + "report-to": "{\"group\":\"ascnsrsggc:171:0\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/scaffolding/ascnsrsggc:171:0\"}],}", + "server": "Golfe2" + }, + "body": "[COULD NOT READ BODY]" + }, + { + "timestamp": "2026-02-05T13:40:04.294Z", + "method": "POST", + "url": "https://api.bessa.app/v1/auth/login/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token c3418725e95a9f90e3645cbc846b4d67c7c66131", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "content-type": "application/json", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "POST, OPTIONS", + "content-language": "en-us", + "content-length": "50", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:40:04 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "set-cookie": "csrftoken=mSLKAKjkz5UzzNQGptO9Tn6UHEvhWi4y; expires=Thu, 04 Feb 2027 13:40:04 GMT; Max-Age=31449600; Path=/; SameSite=Lax; Secure\nsessionid=0oy7aa7624sis5mdb8p7fdo7ochvwi48; expires=Thu, 19 Feb 2026 13:40:04 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax; Secure", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, Cookie, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "key": "dba7d86e83c7f462fd8af96521dea41c4facd8a5" + } + }, + { + "timestamp": "2026-02-05T13:40:04.384Z", + "method": "GET", + "url": "https://api.bessa.app/v1/auth/user/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, PUT, PATCH, DELETE, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "421", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:40:04 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "id": 85567, + "last_login": "2026-02-05T13:40:04.251289Z", + "created": "2025-11-26T22:22:46.535197Z", + "updated": "2025-12-09T07:26:02.309323Z", + "email": "knapp-2041@bessa.app", + "first_name": "Michael", + "last_name": "Kaufmann", + "locale": "de_de", + "country": "", + "language": "de", + "profile": null, + "uuid": "de0e6518-f917-4679-afd1-47a6f5e22a55", + "groups": [ + "Managed" + ], + "date_of_birth": null, + "password_changed": "2025-12-09T07:26:02.304391Z", + "gender": 1 + } + }, + { + "timestamp": "2026-02-05T13:40:04.640Z", + "method": "GET", + "url": "https://api.bessa.app/v1/venues/591/menu/dates/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "8984", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:40:04 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "next": null, + "previous": null, + "results": [ + { + "id": 677, + "orders": [], + "created": "2026-01-27T11:03:31.218705Z", + "updated": "2026-01-29T10:25:31.220194Z", + "deleted": null, + "uuid": "7e226715-0997-4f35-9a2a-b35879f7327c", + "date": "2026-02-05", + "venue": 591 + }, + { + "id": 683, + "orders": [], + "created": "2026-01-29T08:39:18.518691Z", + "updated": "2026-02-03T08:16:05.183874Z", + "deleted": null, + "uuid": "a37cf93a-9de6-4ebe-a5c7-2a45d80c143f", + "date": "2026-02-06", + "venue": 591 + }, + { + "id": 689, + "orders": [ + { + "id": 1522667, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:27:14.291812Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:27:14.255483Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_eEzom", + "items": [ + { + "id": 3892875, + "article": 178723, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:27:14.294494Z", + "updated": "2026-02-05T09:27:14.294507Z", + "deleted": null, + "uuid": "79127077-242d-4237-9525-e92aa5c3efca", + "name": "M2 Gourmetteller", + "description": "Rindsuppe m. Kaspressknödel A,C,L,M,G, beef soup with cheese dumpling,Quiche Lorrain (Schinken) A,C,G,L,M, c quiche lorrain with ham, Donut A,C,G,H", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522667 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:27:14.281048Z", + "updated": "2026-02-05T09:27:14.281067Z", + "deleted": null, + "uuid": "975197dd-eb03-4370-bc4e-6fa984d374df", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-09T10:00:00Z", + "user_date": "2026-02-09T10:00:00Z", + "delivered": null, + "expires": "2026-02-09T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "404", + "number": 404, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ], + "created": "2026-02-03T13:02:54.802757Z", + "updated": "2026-02-04T06:42:12.854789Z", + "deleted": null, + "uuid": "258da875-7f5b-4b4a-a426-e1e35eb15a58", + "date": "2026-02-09", + "venue": 591 + }, + { + "id": 690, + "orders": [ + { + "id": 1522668, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:27:42.019668Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:27:41.972304Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_b5dpa", + "items": [ + { + "id": 3892876, + "article": 178714, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:27:42.025714Z", + "updated": "2026-02-05T09:27:42.025728Z", + "deleted": null, + "uuid": "373fe6ac-3fb5-4ccf-a6cd-3e3817fba69f", + "name": "M1 Herzhaftes", + "description": "Erdäpfelrahmsuppe L,M,C,G, potato soup, Acht Schätze (Rind) L,M,C,F,O, mit Eiernudeln A,C, Kuchen A,C,G,H,O, Silced beef asia with egg noodles", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522668 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:27:42.001341Z", + "updated": "2026-02-05T09:27:42.001364Z", + "deleted": null, + "uuid": "5f86b333-9546-4a90-a869-d6e59441ec9a", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-10T10:00:00Z", + "user_date": "2026-02-10T10:00:00Z", + "delivered": null, + "expires": "2026-02-10T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "377", + "number": 377, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ], + "created": "2026-02-03T13:25:54.577732Z", + "updated": "2026-02-04T06:17:20.035403Z", + "deleted": null, + "uuid": "d1eb84d3-69f0-4626-85ad-8cc19e2ddc26", + "date": "2026-02-10", + "venue": 591 + }, + { + "id": 691, + "orders": [ + { + "id": 1522671, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:28:59.631701Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:28:59.600005Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_vbeVw", + "items": [ + { + "id": 3892879, + "article": 178718, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:28:59.634388Z", + "updated": "2026-02-05T09:28:59.634403Z", + "deleted": null, + "uuid": "32fc3b50-f024-4bd6-ac98-a84b5d237326", + "name": "M1 Herzhaftes", + "description": "Knoblauchcremesuppe G,L,M, garlic cream soup, Champignonrahmschnitzel mit Kräuterspätzle L,A,M,O,C,G, mushroom cream schnitzel with spaetzle, Vanillepudding G, vanilla pudding", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522671 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:28:59.623919Z", + "updated": "2026-02-05T09:28:59.623939Z", + "deleted": null, + "uuid": "f6bb6f9d-8acc-4d06-9d52-f445919c9c51", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-11T10:00:00Z", + "user_date": "2026-02-11T10:00:00Z", + "delivered": null, + "expires": "2026-02-11T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "365", + "number": 365, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ], + "created": "2026-02-04T05:52:47.760453Z", + "updated": "2026-02-05T08:39:09.862471Z", + "deleted": null, + "uuid": "dee57b7d-6851-499f-8926-009c4af9a369", + "date": "2026-02-11", + "venue": 591 + }, + { + "id": 692, + "orders": [ + { + "id": 1522672, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:29:39.076987Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:29:39.038013Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_KOP4p", + "items": [ + { + "id": 3892880, + "article": 178744, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:29:39.079965Z", + "updated": "2026-02-05T09:29:39.079983Z", + "deleted": null, + "uuid": "b737bbc4-9c32-45a4-b6f2-684c5571ce33", + "name": "M5F Salat mit Gebäck DO 2", + "description": "Brokkoli Cheddasuppe, L,M,C,G, Chef Salat mit Schinken und Mozzarella, Mais, L,M,G, salad with ham and mozzarella", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522672 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:29:39.066841Z", + "updated": "2026-02-05T09:29:39.066907Z", + "deleted": null, + "uuid": "f42f9a86-7c81-412f-adb6-cb4b7f371cf8", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-12T10:00:00Z", + "user_date": "2026-02-12T10:00:00Z", + "delivered": null, + "expires": "2026-02-12T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "269", + "number": 269, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ], + "created": "2026-02-04T06:45:34.142340Z", + "updated": "2026-02-04T08:22:49.913380Z", + "deleted": null, + "uuid": "c4497dfc-00d7-408f-9e9e-f33e1639baba", + "date": "2026-02-12", + "venue": 591 + } + ] + } + }, + { + "timestamp": "2026-02-05T13:40:04.701Z", + "method": "GET", + "url": "https://api.bessa.app/v1/user/orders/?venue=591&ordering=-created", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, POST, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "54137", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:40:04 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "next": "https://api.bessa.app/v1/user/orders/?cursor=cD0yMDI1LTEyLTExKzExJTNBMDQlM0EyMC4yNjA3MzklMkIwMCUzQTAw&ordering=-created&venue=591", + "previous": null, + "results": [ + { + "id": 1522672, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:29:39.076987Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:29:39.038013Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_KOP4p", + "items": [ + { + "id": 3892880, + "article": 178744, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:29:39.079965Z", + "updated": "2026-02-05T09:29:39.079983Z", + "deleted": null, + "uuid": "b737bbc4-9c32-45a4-b6f2-684c5571ce33", + "name": "M5F Salat mit Gebäck DO 2", + "description": "Brokkoli Cheddasuppe, L,M,C,G, Chef Salat mit Schinken und Mozzarella, Mais, L,M,G, salad with ham and mozzarella", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522672 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:29:39.066841Z", + "updated": "2026-02-05T09:29:39.066907Z", + "deleted": null, + "uuid": "f42f9a86-7c81-412f-adb6-cb4b7f371cf8", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-12T10:00:00Z", + "user_date": "2026-02-12T10:00:00Z", + "delivered": null, + "expires": "2026-02-12T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "269", + "number": 269, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1522671, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:28:59.631701Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:28:59.600005Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_vbeVw", + "items": [ + { + "id": 3892879, + "article": 178718, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:28:59.634388Z", + "updated": "2026-02-05T09:28:59.634403Z", + "deleted": null, + "uuid": "32fc3b50-f024-4bd6-ac98-a84b5d237326", + "name": "M1 Herzhaftes", + "description": "Knoblauchcremesuppe G,L,M, garlic cream soup, Champignonrahmschnitzel mit Kräuterspätzle L,A,M,O,C,G, mushroom cream schnitzel with spaetzle, Vanillepudding G, vanilla pudding", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522671 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:28:59.623919Z", + "updated": "2026-02-05T09:28:59.623939Z", + "deleted": null, + "uuid": "f6bb6f9d-8acc-4d06-9d52-f445919c9c51", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-11T10:00:00Z", + "user_date": "2026-02-11T10:00:00Z", + "delivered": null, + "expires": "2026-02-11T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "365", + "number": 365, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1522668, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:27:42.019668Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:27:41.972304Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_b5dpa", + "items": [ + { + "id": 3892876, + "article": 178714, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:27:42.025714Z", + "updated": "2026-02-05T09:27:42.025728Z", + "deleted": null, + "uuid": "373fe6ac-3fb5-4ccf-a6cd-3e3817fba69f", + "name": "M1 Herzhaftes", + "description": "Erdäpfelrahmsuppe L,M,C,G, potato soup, Acht Schätze (Rind) L,M,C,F,O, mit Eiernudeln A,C, Kuchen A,C,G,H,O, Silced beef asia with egg noodles", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522668 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:27:42.001341Z", + "updated": "2026-02-05T09:27:42.001364Z", + "deleted": null, + "uuid": "5f86b333-9546-4a90-a869-d6e59441ec9a", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-10T10:00:00Z", + "user_date": "2026-02-10T10:00:00Z", + "delivered": null, + "expires": "2026-02-10T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "377", + "number": 377, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1522667, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:27:14.291812Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:27:14.255483Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_eEzom", + "items": [ + { + "id": 3892875, + "article": 178723, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:27:14.294494Z", + "updated": "2026-02-05T09:27:14.294507Z", + "deleted": null, + "uuid": "79127077-242d-4237-9525-e92aa5c3efca", + "name": "M2 Gourmetteller", + "description": "Rindsuppe m. Kaspressknödel A,C,L,M,G, beef soup with cheese dumpling,Quiche Lorrain (Schinken) A,C,G,L,M, c quiche lorrain with ham, Donut A,C,G,H", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522667 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:27:14.281048Z", + "updated": "2026-02-05T09:27:14.281067Z", + "deleted": null, + "uuid": "975197dd-eb03-4370-bc4e-6fa984d374df", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-09T10:00:00Z", + "user_date": "2026-02-09T10:00:00Z", + "delivered": null, + "expires": "2026-02-09T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "404", + "number": 404, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1513797, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-02-05T11:12:40.467225Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-02-05T09:10:45.731366Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-02-02T07:58:49.127268Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-02-02T07:58:49.098297Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_nbRmx", + "items": [ + { + "id": 3876839, + "article": 178715, + "course_group": null, + "modifiers": [], + "created": "2026-02-02T07:58:49.129999Z", + "updated": "2026-02-02T07:58:49.130018Z", + "deleted": null, + "uuid": "a45e6a67-c73c-452c-88ea-4a5286c16cdb", + "name": "M1 Herzhaftes DO 1", + "description": "Minestrone / Minestrone(ACGLM) Grillhendl mit Semmel / Grilled chicken with bread roll(A) Dessert(ACG)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1513797 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-02T07:58:49.120698Z", + "updated": "2026-02-05T11:12:40.458247Z", + "deleted": null, + "uuid": "55f0d46a-d1ab-43d1-b24e-114df39c850d", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-02-05T10:00:00Z", + "user_date": "2026-02-05T10:00:00Z", + "delivered": null, + "expires": "2026-02-05T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "500502b6-9715-40ed-843a-beaab1e95941", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/67b40e239fd3445396c9e432ee8eecc8", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "761", + "number": 761, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1513794, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-02-04T11:23:26.830750Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-02-04T09:22:20.305052Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-02-02T07:58:27.042159Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-02-02T07:58:27.009344Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_zbA5V", + "items": [ + { + "id": 3876836, + "article": 178717, + "course_group": null, + "modifiers": [], + "created": "2026-02-02T07:58:27.045440Z", + "updated": "2026-02-02T07:58:27.045455Z", + "deleted": null, + "uuid": "c7f1661c-471f-4f30-a120-4634a8ea507a", + "name": "M1 Herzhaftes", + "description": "Gemüsebouillon mit Grießnockerl / Vegetable broth with semolina dumplings(ACLM) Rindsgulasch mit Semmelknödel / Beef goulash with bread dumplings(ACGLM) Mohn-Orangencreme / Poppy seed orange cream(G)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1513794 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-02T07:58:27.032439Z", + "updated": "2026-02-04T11:23:26.824831Z", + "deleted": null, + "uuid": "1f8b0833-1a43-4ece-bcee-f00945f5ea51", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-02-04T10:00:00Z", + "user_date": "2026-02-04T10:00:00Z", + "delivered": null, + "expires": "2026-02-04T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "1e42af73-3d88-4d23-891a-c7ba2a3a2153", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/d52776ce8b5242a19e448ea971e3f0a1", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "795", + "number": 795, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1513780, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-02-03T10:12:02.887411Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-02-03T09:21:46.752779Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-02-02T07:56:59.197530Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-02-02T07:56:59.162108Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_mbBY0", + "items": [ + { + "id": 3876822, + "article": 178713, + "course_group": null, + "modifiers": [], + "created": "2026-02-02T07:56:59.201201Z", + "updated": "2026-02-02T07:56:59.201215Z", + "deleted": null, + "uuid": "9b51f22f-1087-47fc-96f3-ed8f534abc8b", + "name": "M1 Herzhaftes", + "description": "Pastinakencremesuppe / Parsnip cream soup(GLM) Schweinesteak mit Zitronenrahm und Polenta / Pork steak with lemon sauce and polenta(GLM) Kuchen / Cake(GLM)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1513780 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-02T07:56:59.189343Z", + "updated": "2026-02-03T10:12:02.878700Z", + "deleted": null, + "uuid": "8ef7921e-f09c-4fe6-95a6-ba4508e95e41", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-02-03T10:00:00Z", + "user_date": "2026-02-03T10:00:00Z", + "delivered": null, + "expires": "2026-02-03T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "05210ef0-68c2-4bd4-b464-abc72caaecd0", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/ff5cba575b9242c2ac9d6322184f4f28", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "856", + "number": 856, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1513375, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-02-02T10:48:58.366216Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-02-02T10:14:14.978478Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-02-02T07:15:02.560174Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-02-02T07:15:02.527963Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_Gl82j", + "items": [ + { + "id": 3876381, + "article": 176511, + "course_group": null, + "modifiers": [], + "created": "2026-02-02T07:15:02.562896Z", + "updated": "2026-02-02T07:15:02.562912Z", + "deleted": null, + "uuid": "6313a14a-6136-477f-978b-44b6fbbff995", + "name": "M1 Herzhaftes", + "description": "Rindsuppe mit Backerbsen / Beef soup with croutons(ALM) Puten Tikka Masala mit Basmatireis / Turkey tikka masala with basmati rice(AFLM) Corny(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1513375 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-02T07:15:02.550551Z", + "updated": "2026-02-02T10:48:58.356678Z", + "deleted": null, + "uuid": "d6c8232f-5f18-42e3-bf4b-a4eae6e01ea0", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-02-02T10:00:00Z", + "user_date": "2026-02-02T10:00:00Z", + "delivered": null, + "expires": "2026-02-02T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "31cc5c30-50fc-4603-962d-3513cc6f640a", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/cd3cd9b060d04980a875b316c46af03b", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "978", + "number": 978, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1493283, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-29T10:36:00.235301Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-29T09:05:46.638627Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-22T10:42:53.557389Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-22T10:42:53.530051Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_WO0pl", + "items": [ + { + "id": 3837002, + "article": 178716, + "course_group": null, + "modifiers": [], + "created": "2026-01-22T10:42:53.560085Z", + "updated": "2026-01-22T10:42:53.560105Z", + "deleted": null, + "uuid": "446460ef-50aa-4f0d-bd30-3662512ec7eb", + "name": "M1 Herzhaftes", + "description": "Millirahmsuppe / Cream soup(GLM) Chili con carne(ALM) Mini Plunder / Mini danishes(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1493283 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-22T10:42:53.551184Z", + "updated": "2026-01-29T10:36:00.222808Z", + "deleted": null, + "uuid": "4c13b290-ebe9-4f6e-926b-4e4135938004", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-29T10:00:00Z", + "user_date": "2026-01-29T10:00:00Z", + "delivered": null, + "expires": "2026-01-29T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "976bd717-fdff-4fb6-9178-5d1e6953df73", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/b01d28d52f1642538558248bf3cc6337", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "289", + "number": 289, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1493274, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-28T10:36:27.039951Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-28T09:11:50.766553Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-22T10:41:44.395860Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-22T10:41:44.369821Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_1aG0O", + "items": [ + { + "id": 3836981, + "article": 178718, + "course_group": null, + "modifiers": [], + "created": "2026-01-22T10:41:44.398256Z", + "updated": "2026-01-22T10:41:44.398269Z", + "deleted": null, + "uuid": "07bf07e4-840a-4ab7-bcac-10711d015e85", + "name": "M1 Herzhaftes", + "description": "Zwiebelsuppe / Onion soup(LM) Hühnerschnitzel mit Whisky Sauce und Paprikareis / Chicken schnitzel with wiskey sauce and paprika rice(GLMO) Schoko Bananencreme / Chocolate banana cream(G)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1493274 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-22T10:41:44.389779Z", + "updated": "2026-01-28T10:36:27.022763Z", + "deleted": null, + "uuid": "036b339d-974d-4b1a-b328-7ba16faf18ee", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-28T10:00:00Z", + "user_date": "2026-01-28T10:00:00Z", + "delivered": null, + "expires": "2026-01-28T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "2b744770-8c9e-4a0f-93d4-ad7bddf5c870", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/174d40654bd64ebd9f2648f625a20f7e", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "421", + "number": 421, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1493262, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-27T10:00:39.836145Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-27T09:10:50.917038Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-22T10:40:39.304813Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-22T10:40:39.275034Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_POQ7w", + "items": [ + { + "id": 3836963, + "article": 178721, + "course_group": null, + "modifiers": [], + "created": "2026-01-22T10:40:39.307500Z", + "updated": "2026-01-22T10:40:39.307517Z", + "deleted": null, + "uuid": "b0ee6f7d-dcd6-4c82-b7e6-a9df395eca01", + "name": "M2 Gourmetteller", + "description": "Brokkolicremesuppe / Broccoli cream soup(GLM) Berner Würstel mit Kartoffelschmarren / Berner Sausages with potatoes(G) Kuchen / Cake(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1493262 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-22T10:40:39.296450Z", + "updated": "2026-01-27T10:00:39.831505Z", + "deleted": null, + "uuid": "7b4ccd35-fb6c-4d0d-ac8b-03a1c88a933a", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-27T10:00:00Z", + "user_date": "2026-01-27T10:00:00Z", + "delivered": null, + "expires": "2026-01-27T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "cd160674-cede-48d4-ae71-9f6944c95cfa", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/4391d0f13e744c05aeaac67dca45c319", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "448", + "number": 448, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1493253, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-26T10:55:48.664421Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-26T09:07:43.539129Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-22T10:39:47.690372Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-22T10:39:47.661985Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_nba5M", + "items": [ + { + "id": 3836952, + "article": 178726, + "course_group": null, + "modifiers": [], + "created": "2026-01-22T10:39:47.692833Z", + "updated": "2026-01-22T10:39:47.692846Z", + "deleted": null, + "uuid": "f86137d4-7c4a-4720-9089-b2d9cb1fd747", + "name": "M2 Gourmetteller", + "description": "Rindsuppe mit Frittaten / Beef soup with pancake strips(ACGLM) Tagliatelle in Käsesauce mit Paprika / Tagliatelle with cheese sauce and peppers(ACGLM) Balisto(ACGH)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1493253 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-22T10:39:47.683167Z", + "updated": "2026-01-26T10:55:48.658817Z", + "deleted": null, + "uuid": "2d7e9fa1-5b74-4258-82c8-912c4b3e54a6", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-26T10:00:00Z", + "user_date": "2026-01-26T10:00:00Z", + "delivered": null, + "expires": "2026-01-26T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "f16663b0-55c4-41bd-b5e3-42345e2687d2", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/81de5348e28641988c06a2b4b323efe2", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "308", + "number": 308, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1479970, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-22T11:44:21.942887Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-22T09:09:05.884221Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-16T05:35:22.791486Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-16T05:35:22.762552Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_6lRZM", + "items": [ + { + "id": 3812756, + "article": 178743, + "course_group": null, + "modifiers": [], + "created": "2026-01-16T05:35:22.794990Z", + "updated": "2026-01-16T05:35:22.795012Z", + "deleted": null, + "uuid": "f11ac4bb-aac9-4edf-8d97-8e7e32985897", + "name": "M5F Salat mit Gebäck", + "description": "Tomatencremesuppe/Tomato cream soup(LM) Salatteller mit S-Steak / Salad plate with pork steak(ALM) Kuchen / Cake(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1479970 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-16T05:35:22.783313Z", + "updated": "2026-01-22T11:44:21.937295Z", + "deleted": null, + "uuid": "80cf2640-9b59-4eac-a0c5-189054308888", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-22T10:00:00Z", + "user_date": "2026-01-22T10:00:00Z", + "delivered": null, + "expires": "2026-01-22T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "5581c21b-9552-448c-987f-8431058fbac1", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/b7313bde10024ec4947008ef9faaca2a", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "348", + "number": 348, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1479968, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-21T11:03:37.201731Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-21T09:10:02.685935Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-16T05:34:46.202043Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-16T05:34:46.175284Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_b56OP", + "items": [ + { + "id": 3812753, + "article": 178745, + "course_group": null, + "modifiers": [], + "created": "2026-01-16T05:34:46.204758Z", + "updated": "2026-01-16T05:34:46.204776Z", + "deleted": null, + "uuid": "00606567-2d75-408f-873a-8da4ef35ed0c", + "name": "M5F Salat mit Gebäck", + "description": "Gemüsebouillon mit Schöberl/Vegetable bouillon with schöberl(ACLM) Salat Mexico mit Huhn / Salad Mexico with chicken(ALM) Stacciatellacreme(G)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1479968 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-16T05:34:46.194823Z", + "updated": "2026-01-21T11:03:37.189895Z", + "deleted": null, + "uuid": "e52f8b4c-a140-4e68-91e5-18533e16e3a5", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-21T10:00:00Z", + "user_date": "2026-01-21T10:00:00Z", + "delivered": null, + "expires": "2026-01-21T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "f9959a8b-0ce0-4b91-aa3e-f21e7f5d0576", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/248b8efa89604e59ad653ce0750c64b7", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "399", + "number": 399, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1479963, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-19T10:43:48.872961Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-19T09:12:44.365331Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-16T05:33:57.499291Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-16T05:33:57.471553Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_pbPAB", + "items": [ + { + "id": 3812746, + "article": 176511, + "course_group": null, + "modifiers": [], + "created": "2026-01-16T05:33:57.501888Z", + "updated": "2026-01-16T05:33:57.501903Z", + "deleted": null, + "uuid": "28321c9d-ea3d-41be-918d-34c6ae6d0e47", + "name": "M1F Herzhaftes", + "description": "Kräutercremesuppe/Herbal cream soup(GLM) Putenschnitzel mit Sesam und Spätzle/Turkey schnitzel with sesam and spätzle(ACGLMN) Muffin(ACGH)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1479963 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-16T05:33:57.492592Z", + "updated": "2026-01-19T10:43:48.866961Z", + "deleted": null, + "uuid": "6d7ad39f-25b2-4e09-8fa0-37af1f20ea79", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-19T10:00:00Z", + "user_date": "2026-01-19T10:00:00Z", + "delivered": null, + "expires": "2026-01-19T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "69782466-97f3-499b-9444-ce882d0572b1", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/88041b56cc1841dea7bee6f8e0cd64ac", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "423", + "number": 423, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1479962, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-20T10:09:18.525377Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-20T09:20:38.571778Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-16T05:33:16.824217Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-16T05:33:16.796351Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_PO6rL", + "items": [ + { + "id": 3812745, + "article": 178741, + "course_group": null, + "modifiers": [], + "created": "2026-01-16T05:33:16.826758Z", + "updated": "2026-01-16T05:33:16.826773Z", + "deleted": null, + "uuid": "87d7b205-3de1-4aa6-ae72-67ccd81cc695", + "name": "M5F Salat mit Gebäck", + "description": "Hühnereinmachsuppe/Chicken cream soup(GLM) Schweizer Wurstsalat / Swiss sausage salad(ACGLM) Kuchen / Cake(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1479962 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-16T05:33:16.816487Z", + "updated": "2026-01-20T10:09:18.513715Z", + "deleted": null, + "uuid": "fc81b319-71c6-4187-aec4-5e569249758e", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-20T10:00:00Z", + "user_date": "2026-01-20T10:00:00Z", + "delivered": null, + "expires": "2026-01-20T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "825e61a6-91a7-46d8-93c3-a7e49365971c", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/faffee9e48994a20a232a847ad4f2890", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "422", + "number": 422, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1467770, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-15T11:11:55.721987Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-15T09:09:53.003559Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-09T07:16:21.154005Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-09T07:16:21.129850Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_6lEWm", + "items": [ + { + "id": 3792826, + "article": 178716, + "course_group": null, + "modifiers": [], + "created": "2026-01-09T07:16:21.157327Z", + "updated": "2026-01-09T07:16:21.157343Z", + "deleted": null, + "uuid": "f6d3888c-93d6-4e7f-985f-6e0c6023d56f", + "name": "M1F Herzhaftes", + "description": "Linsen Karottensuppe / Lentil carrot soup(GLM) Mexikanische Feuerbowl mit Pute und Limettendip / Mexican fire bowl with turkey and lime dip(GLM) Obst / Fruit", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1467770 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-09T07:16:21.147021Z", + "updated": "2026-01-15T11:11:55.710280Z", + "deleted": null, + "uuid": "6c49f50c-a080-4ecb-828f-cef4c2a56ae7", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-15T10:00:00Z", + "user_date": "2026-01-15T10:00:00Z", + "delivered": null, + "expires": "2026-01-15T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "89c5e182-745b-44cd-a211-7a6cd2769afe", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/4e0f46565cb84bb5899e36a85a4edcea", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "361", + "number": 361, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1467769, + "venue": 591, + "states": [ + { + "state": 9, + "timestamp": "2026-01-13T09:40:08.881729Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-09T07:15:39.743003Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-09T07:15:39.716633Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_An98n", + "items": [ + { + "id": 3792825, + "article": 178718, + "course_group": null, + "modifiers": [], + "created": "2026-01-09T07:15:39.745647Z", + "updated": "2026-01-09T07:15:39.745663Z", + "deleted": null, + "uuid": "93e77a9c-77e1-4531-9349-b8eb62c82e36", + "name": "M1F Herzhaftes", + "description": "Paprikacremesuppe / Paprika cream soup(GLM) Indoesisches Rindfleisch mit Gemüsereis / Inonesian beef with vegetale rice(LFM) Schokocreme / Chocolate cream(ACGH)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1467769 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-09T07:15:39.734925Z", + "updated": "2026-01-13T09:40:08.875122Z", + "deleted": null, + "uuid": "a666a2ef-9eaa-4340-bb2f-e5022b40950f", + "payment_method": "payroll", + "order_state": 9, + "date": "2026-01-14T10:00:00Z", + "user_date": "2026-01-14T10:00:00Z", + "delivered": null, + "expires": "2026-01-14T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "Abgebrochen vom Benutzer", + "cancel_reason": "user_cancelled", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "380", + "number": 380, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1467766, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-13T10:12:16.214296Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-13T09:13:15.249071Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-09T07:13:32.408371Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-09T07:13:32.384274Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_xYGrJ", + "items": [ + { + "id": 3792822, + "article": 178714, + "course_group": null, + "modifiers": [], + "created": "2026-01-09T07:13:32.411053Z", + "updated": "2026-01-09T07:13:32.411070Z", + "deleted": null, + "uuid": "0f3ae063-25be-4793-b882-f646bc778b61", + "name": "M1F Herzhaftes", + "description": "Erbsencremesuppe / Cream of pea soup(GLM) Züricher Geschnetzeltes vom Schwein mit Röstinchen / Zuricht sliced pork with röstinchen(ACGLM) Kuchen / Cake(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1467766 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-09T07:13:32.401955Z", + "updated": "2026-01-13T10:12:16.205117Z", + "deleted": null, + "uuid": "a870d677-5835-4eed-8c57-033f5cdafabb", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-13T10:00:00Z", + "user_date": "2026-01-13T10:00:00Z", + "delivered": null, + "expires": "2026-01-13T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "3376010b-d9d5-428f-b7dd-6ed36bd7ac51", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/e1b9218e14b9494a8143b303b03e46b8", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "425", + "number": 425, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1467762, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-12T10:31:00.416947Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-12T09:16:21.297551Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-09T07:12:29.108629Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-09T07:12:29.084220Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_PO5b5", + "items": [ + { + "id": 3792817, + "article": 178733, + "course_group": null, + "modifiers": [], + "created": "2026-01-09T07:12:29.111851Z", + "updated": "2026-01-09T07:12:29.111868Z", + "deleted": null, + "uuid": "0ef2b959-3c73-4ea2-b5ea-d09aa2690814", + "name": "M3F Basisch und Glutenarm", + "description": "Rindsuppe mit Tropfteig / Beef soup with drip dough(ACGLM) Rotes Curry Laska / Red curry laska/LF) Dany&Sahne(G)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1467762 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-09T07:12:29.101221Z", + "updated": "2026-01-12T10:31:00.410770Z", + "deleted": null, + "uuid": "a7fed18f-0632-498f-8024-9c07c0e39c19", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-12T10:00:00Z", + "user_date": "2026-01-12T10:00:00Z", + "delivered": null, + "expires": "2026-01-12T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "492393ee-0a16-4df3-a6af-78458c9d43f8", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/43fcaa0355cc479387db7509a1c95434", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "439", + "number": 439, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1463986, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2026-01-08T10:35:59.839478Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2026-01-08T09:29:25.437954Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2026-01-07T09:36:13.163452Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2026-01-07T09:36:13.126393Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_QOeeB", + "items": [ + { + "id": 3786936, + "article": 178722, + "course_group": null, + "modifiers": [], + "created": "2026-01-07T09:36:13.166446Z", + "updated": "2026-01-07T09:36:13.166467Z", + "deleted": null, + "uuid": "ccd6c3c7-1b32-41da-b8c3-12eb4c71598a", + "name": "M2F Gourmetteller", + "description": "Karotten-Orangensuppe / Carrot-orange soup(GLM) Wildschweingulasch mit Knödel / Wildboar goulasch with dumplings(ACGLM) Kuchen / Cake(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1463986 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-01-07T09:36:13.152786Z", + "updated": "2026-01-08T10:35:59.828209Z", + "deleted": null, + "uuid": "105a72af-d236-4031-82e3-0b3d9b3a6d0e", + "payment_method": "payroll", + "order_state": 10, + "date": "2026-01-08T10:00:00Z", + "user_date": "2026-01-08T10:00:00Z", + "delivered": null, + "expires": "2026-01-08T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "7778e579-ad58-48d3-813e-07a838fbf088", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/976cb72407744719852ba68485408bb0", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "736", + "number": 736, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1440362, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2025-12-18T11:27:40.180299Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2025-12-18T09:06:59.674830Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2025-12-11T11:08:33.729195Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2025-12-11T11:08:33.712140Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_P6jGL", + "items": [ + { + "id": 3729766, + "article": 178716, + "course_group": null, + "modifiers": [], + "created": "2025-12-11T11:08:33.731863Z", + "updated": "2025-12-11T11:08:33.731897Z", + "deleted": null, + "uuid": "5df83891-4502-4566-a94c-73076f41e2bf", + "name": "M1F Herzhaftes", + "description": "Getrüffelte Kartoffelsuppe / Truffled potato soup (GLM) Gefüllte Hühnerbrust mit Maroni-Semmelfülle, Apfelconfit und Rotkraut / Sutffed chicken breast with chestnut-bread stuffing, apple confit, and red cabbage(ACGLM) Weihnachst-Tiramisu(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1440362 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2025-12-11T11:08:33.722141Z", + "updated": "2025-12-18T11:27:40.175224Z", + "deleted": null, + "uuid": "593d50cb-3ab0-46cd-b3d9-23e58c094ae3", + "payment_method": "payroll", + "order_state": 10, + "date": "2025-12-18T10:00:00Z", + "user_date": "2025-12-18T10:00:00Z", + "delivered": null, + "expires": "2025-12-18T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "038df183-62bc-4339-a07b-e38099f1cd76", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/221aff18eed54502914a0c5d59ce8e35", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "339", + "number": 339, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1440341, + "venue": 591, + "states": [ + { + "state": 9, + "timestamp": "2025-12-16T12:53:47.721966Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2025-12-11T11:05:48.277831Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2025-12-11T11:05:48.257009Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_jPj2d", + "items": [ + { + "id": 3729743, + "article": 178732, + "course_group": null, + "modifiers": [], + "created": "2025-12-11T11:05:48.280770Z", + "updated": "2025-12-11T11:05:48.280790Z", + "deleted": null, + "uuid": "bb777909-ddda-49f7-86be-e3933a4f92f8", + "name": "M3F Basisch und Glutenarm", + "description": "Rote Rüben Suppe / Beetroot soup (GLM) Käferbohnen-Gulasch mit Semmel / bean goulash with bread roll(ALM) Nußpalaschinke / Nut pancake(ACGH)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1440341 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2025-12-11T11:05:48.270714Z", + "updated": "2025-12-16T12:53:47.716021Z", + "deleted": null, + "uuid": "79d439ea-63c3-477d-8d95-110ed597943c", + "payment_method": "payroll", + "order_state": 9, + "date": "2025-12-17T10:00:00Z", + "user_date": "2025-12-17T10:00:00Z", + "delivered": null, + "expires": "2025-12-17T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "Abgebrochen vom Benutzer", + "cancel_reason": "user_cancelled", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "392", + "number": 392, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1440334, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2025-12-16T10:40:43.298400Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2025-12-16T09:10:53.354238Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2025-12-11T11:05:05.659136Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2025-12-11T11:05:05.609309Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_9OAzo", + "items": [ + { + "id": 3729736, + "article": 178721, + "course_group": null, + "modifiers": [], + "created": "2025-12-11T11:05:05.661870Z", + "updated": "2025-12-11T11:05:05.661883Z", + "deleted": null, + "uuid": "b1f68b75-5df5-455a-8082-ab1778b025a9", + "name": "M2F Gourmetteller", + "description": "Gemüsecremesuppe / Vegatable cream soup (GLM) Bratwurst mit Kürbisgemüse und Röstkartoffeln / Bratwurst with pumpkin vegetables and roasted poatatoes(GLM) Kuchen / Cake(ACGHO)", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1440334 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2025-12-11T11:05:05.651871Z", + "updated": "2025-12-16T10:40:43.293841Z", + "deleted": null, + "uuid": "eaa0bd3e-5daa-46f7-84a7-9c07ffc2e1db", + "payment_method": "payroll", + "order_state": 10, + "date": "2025-12-16T10:00:00Z", + "user_date": "2025-12-16T10:00:00Z", + "delivered": null, + "expires": "2025-12-16T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "044ab5d7-81e8-48ca-9ac1-6425ae056de7", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/9b4321924bbb4c5ebcd9bffa992e118f", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "436", + "number": 436, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + }, + { + "id": 1440324, + "venue": 591, + "states": [ + { + "state": 10, + "timestamp": "2025-12-16T03:00:01.396192Z", + "cancel_message": "" + }, + { + "state": 8, + "timestamp": "2025-12-15T09:11:55.889648Z", + "cancel_message": "" + }, + { + "state": 5, + "timestamp": "2025-12-11T11:04:20.268275Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": false, + "payment_meta_data": { + "timestamp": "2025-12-11T11:04:20.249266Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_b6jNP", + "items": [ + { + "id": 3729723, + "article": 178719, + "course_group": null, + "modifiers": [], + "created": "2025-12-11T11:04:20.271091Z", + "updated": "2025-12-11T11:04:20.271107Z", + "deleted": null, + "uuid": "e6383f3a-5cf7-4b81-b3f8-d364d75005f0", + "name": "M1F Herzhaftes", + "description": "Rindsuppe mit Speckknödel / Beef soup with bacon dumplings (ACGLM) S-Steak mit Kräutersauce und Spätzle / Pork steak with herbsauce and spaetzle (ACGLM) Obst /Fruits", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1440324 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2025-12-11T11:04:20.260739Z", + "updated": "2025-12-16T03:00:01.390448Z", + "deleted": null, + "uuid": "ca4ab272-d480-498a-a1da-2eae58aba2be", + "payment_method": "payroll", + "order_state": 10, + "date": "2025-12-15T10:00:00Z", + "user_date": "2025-12-15T10:00:00Z", + "delivered": null, + "expires": "2025-12-15T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": "697dfe63-539a-487a-a3cd-11c0f6128d94", + "receipt": "https://files.treuepass.app/account/venues/NTkx/company/bdb5fe97a8144903945b45d6dd3e6dff", + "cancel_message": "", + "cancel_reason": "", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "466", + "number": 466, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ] + } + }, + { + "timestamp": "2026-02-05T13:40:04.809Z", + "method": "GET", + "url": "https://api.bessa.app/v1/venues/591/menu/dates/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "8984", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:40:04 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "next": null, + "previous": null, + "results": [ + { + "id": 677, + "orders": [], + "created": "2026-01-27T11:03:31.218705Z", + "updated": "2026-01-29T10:25:31.220194Z", + "deleted": null, + "uuid": "7e226715-0997-4f35-9a2a-b35879f7327c", + "date": "2026-02-05", + "venue": 591 + }, + { + "id": 683, + "orders": [], + "created": "2026-01-29T08:39:18.518691Z", + "updated": "2026-02-03T08:16:05.183874Z", + "deleted": null, + "uuid": "a37cf93a-9de6-4ebe-a5c7-2a45d80c143f", + "date": "2026-02-06", + "venue": 591 + }, + { + "id": 689, + "orders": [ + { + "id": 1522667, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:27:14.291812Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:27:14.255483Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_eEzom", + "items": [ + { + "id": 3892875, + "article": 178723, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:27:14.294494Z", + "updated": "2026-02-05T09:27:14.294507Z", + "deleted": null, + "uuid": "79127077-242d-4237-9525-e92aa5c3efca", + "name": "M2 Gourmetteller", + "description": "Rindsuppe m. Kaspressknödel A,C,L,M,G, beef soup with cheese dumpling,Quiche Lorrain (Schinken) A,C,G,L,M, c quiche lorrain with ham, Donut A,C,G,H", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522667 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:27:14.281048Z", + "updated": "2026-02-05T09:27:14.281067Z", + "deleted": null, + "uuid": "975197dd-eb03-4370-bc4e-6fa984d374df", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-09T10:00:00Z", + "user_date": "2026-02-09T10:00:00Z", + "delivered": null, + "expires": "2026-02-09T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "404", + "number": 404, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ], + "created": "2026-02-03T13:02:54.802757Z", + "updated": "2026-02-04T06:42:12.854789Z", + "deleted": null, + "uuid": "258da875-7f5b-4b4a-a426-e1e35eb15a58", + "date": "2026-02-09", + "venue": 591 + }, + { + "id": 690, + "orders": [ + { + "id": 1522668, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:27:42.019668Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:27:41.972304Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_b5dpa", + "items": [ + { + "id": 3892876, + "article": 178714, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:27:42.025714Z", + "updated": "2026-02-05T09:27:42.025728Z", + "deleted": null, + "uuid": "373fe6ac-3fb5-4ccf-a6cd-3e3817fba69f", + "name": "M1 Herzhaftes", + "description": "Erdäpfelrahmsuppe L,M,C,G, potato soup, Acht Schätze (Rind) L,M,C,F,O, mit Eiernudeln A,C, Kuchen A,C,G,H,O, Silced beef asia with egg noodles", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522668 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:27:42.001341Z", + "updated": "2026-02-05T09:27:42.001364Z", + "deleted": null, + "uuid": "5f86b333-9546-4a90-a869-d6e59441ec9a", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-10T10:00:00Z", + "user_date": "2026-02-10T10:00:00Z", + "delivered": null, + "expires": "2026-02-10T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "377", + "number": 377, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ], + "created": "2026-02-03T13:25:54.577732Z", + "updated": "2026-02-04T06:17:20.035403Z", + "deleted": null, + "uuid": "d1eb84d3-69f0-4626-85ad-8cc19e2ddc26", + "date": "2026-02-10", + "venue": 591 + }, + { + "id": 691, + "orders": [ + { + "id": 1522671, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:28:59.631701Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:28:59.600005Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_vbeVw", + "items": [ + { + "id": 3892879, + "article": 178718, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:28:59.634388Z", + "updated": "2026-02-05T09:28:59.634403Z", + "deleted": null, + "uuid": "32fc3b50-f024-4bd6-ac98-a84b5d237326", + "name": "M1 Herzhaftes", + "description": "Knoblauchcremesuppe G,L,M, garlic cream soup, Champignonrahmschnitzel mit Kräuterspätzle L,A,M,O,C,G, mushroom cream schnitzel with spaetzle, Vanillepudding G, vanilla pudding", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522671 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:28:59.623919Z", + "updated": "2026-02-05T09:28:59.623939Z", + "deleted": null, + "uuid": "f6bb6f9d-8acc-4d06-9d52-f445919c9c51", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-11T10:00:00Z", + "user_date": "2026-02-11T10:00:00Z", + "delivered": null, + "expires": "2026-02-11T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "365", + "number": 365, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ], + "created": "2026-02-04T05:52:47.760453Z", + "updated": "2026-02-05T08:39:09.862471Z", + "deleted": null, + "uuid": "dee57b7d-6851-499f-8926-009c4af9a369", + "date": "2026-02-11", + "venue": 591 + }, + { + "id": 692, + "orders": [ + { + "id": 1522672, + "venue": 591, + "states": [ + { + "state": 5, + "timestamp": "2026-02-05T09:29:39.076987Z", + "cancel_message": "" + } + ], + "customer": { + "id": 169488, + "newsletter": false, + "created": "2025-12-09T08:16:54.918222Z", + "updated": "2025-12-09T08:16:54.918246Z", + "deleted": null, + "uuid": "3424958e-b0a3-43c5-ac07-f93abef07b54", + "first_name": "Michael", + "last_name": "Kaufmann", + "company_name": "", + "is_primary": false, + "phone": "", + "tax_id": "", + "type": 0, + "email": "knapp-2041@bessa.app" + }, + "address": null, + "cash_box_table_name": null, + "preorder": true, + "payment_meta_data": { + "timestamp": "2026-02-05T09:29:39.038013Z", + "transaction_id": "10227", + "merchant_receipt": "User: \"10227\" - \"Michael Kaufmann\" created a new order." + }, + "discount": null, + "notifications": { + "food": false, + "drink": false + }, + "hash_id": "o_KOP4p", + "items": [ + { + "id": 3892880, + "article": 178744, + "course_group": null, + "modifiers": [], + "created": "2026-02-05T09:29:39.079965Z", + "updated": "2026-02-05T09:29:39.079983Z", + "deleted": null, + "uuid": "b737bbc4-9c32-45a4-b6f2-684c5571ce33", + "name": "M5F Salat mit Gebäck DO 2", + "description": "Brokkoli Cheddasuppe, L,M,C,G, Chef Salat mit Schinken und Mozzarella, Mais, L,M,G, salad with ham and mozzarella", + "price": "5.50", + "amount": 1, + "vat": "10.00", + "comment": "", + "venue": 591, + "order": 1522672 + } + ], + "table": null, + "payment_method_id": null, + "order_type": 7, + "created": "2026-02-05T09:29:39.066841Z", + "updated": "2026-02-05T09:29:39.066907Z", + "deleted": null, + "uuid": "f42f9a86-7c81-412f-adb6-cb4b7f371cf8", + "payment_method": "payroll", + "order_state": 5, + "date": "2026-02-12T10:00:00Z", + "user_date": "2026-02-12T10:00:00Z", + "delivered": null, + "expires": "2026-02-12T11:15:00Z", + "total": "5.50", + "tip": "0.00", + "delivery_fee": "0.00", + "service_fee": "0.00", + "packaging_fee": "0.00", + "currency": "eur", + "client_secret": null, + "cash_box_order_id": null, + "receipt": null, + "cancel_message": "", + "cancel_reason": "none", + "redeemed_cards": null, + "is_testing": false, + "pickup_code": "269", + "number": 269, + "service": 0, + "comment": "", + "take_away": false, + "identity": null + } + ], + "created": "2026-02-04T06:45:34.142340Z", + "updated": "2026-02-04T08:22:49.913380Z", + "deleted": null, + "uuid": "c4497dfc-00d7-408f-9e9e-f33e1639baba", + "date": "2026-02-12", + "venue": 591 + } + ] + } + }, + { + "timestamp": "2026-02-05T13:40:09.056Z", + "method": "GET", + "url": "https://api.bessa.app/v1/venues/591/menu/7/2026-02-10/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "4685", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:40:09 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "next": null, + "previous": null, + "results": [ + { + "id": 11452, + "items": [ + { + "id": 178714, + "price": "5.50", + "name": "M1 Herzhaftes", + "description": "Erdäpfelrahmsuppe L,M,C,G, potato soup, Acht Schätze (Rind) L,M,C,F,O, mit Eiernudeln A,C, Kuchen A,C,G,H,O, Silced beef asia with egg noodles", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_7BzGM", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.266780Z", + "updated": "2026-02-03T13:15:20.845052Z", + "deleted": null, + "uuid": "4064152d-6e82-4f4d-9bea-0936ab307b1c", + "image": null, + "number": "dfaa35f6-4604-4e14-9f86-1862a05a5881", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "314", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178728, + "price": "5.50", + "name": "M3 Süß", + "description": "Erdäpfelrahmsuppe L,M,C,G, potato soup, Milchrahmstrudel mit Vanillesauce A,C,G, milk cream strudel with vanilla sauce", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_KalR9", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.268455Z", + "updated": "2026-02-03T13:14:37.728274Z", + "deleted": null, + "uuid": "12b75bc5-88ab-4df3-ad9b-00b864729ce3", + "image": null, + "number": "a33ce321-1943-430e-a435-b83f87ff1080", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "69", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178735, + "price": "5.50", + "name": "M4 Ginko vegan", + "description": "Tomaten Kokoscremesuppe, tomato coconut soup, Hirselaibchen A, mit Fisolen Karottengemüse u. Schnittlauchdip F,M, millet patties with fisole vegetable", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_YLxwb", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.269340Z", + "updated": "2026-02-02T06:03:28.072748Z", + "deleted": null, + "uuid": "6b3017a7-c950-4428-a474-8c61a819402d", + "image": null, + "number": "88b59af4-f3fb-45a7-ba0f-9fce88d4710f", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "0", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178742, + "price": "5.50", + "name": "M5 Salat mit Gebäck", + "description": "Erdäpflerahmsuppe G,L,M, Salatteller mit Prosciutto und Parmesan, salad with prosciutto and parmesan", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_Qydpp", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.270215Z", + "updated": "2026-02-03T13:16:43.665954Z", + "deleted": null, + "uuid": "1c1a14dd-cf4f-4355-a4ba-cb8543ae7e5a", + "image": null, + "number": "ea309dc8-38bf-4e78-9b51-7c9a7efb2763", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "0", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178749, + "price": "3.00", + "name": "M6 Suppe, kleiner Salat + Dessert D", + "description": "Suppe, kleiner Salat + Dessert", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_Nyoxb", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.271125Z", + "updated": "2026-02-03T13:17:39.906619Z", + "deleted": null, + "uuid": "c2956eb9-6ee0-4e65-96a7-b8b0615f314c", + "image": null, + "number": "f6d5bd6e-2e14-4930-8f27-be027fa477b2", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "37", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178756, + "price": "4.30", + "name": "M7F Kleines Hauptspeisenmenü DI 2", + "description": "Erdäpflerahmsuppe G,L,M, potato soup, kleine Hauptspeise von Menü 1", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_BzGr7", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.272048Z", + "updated": "2026-02-03T13:18:12.181876Z", + "deleted": null, + "uuid": "6ad46891-a26b-4e22-9a34-ae1cbb225788", + "image": null, + "number": "59b371ea-ab48-4efc-9b63-04cf3df9ffc5", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "20", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + }, + { + "id": 178721, + "price": "5.50", + "name": "M2 Gourmetteller", + "description": "Erdäpflerahmsuppe G,L,M, potato soup, Selschfleischknödel mit Sauerkraut L,M,C,O, smoked meat dumpling with carbbage", + "allergens": "", + "nutrition_facts": "", + "hash_id": "ar_ZGb2V", + "image_thumbnail": null, + "modifiers": [], + "created": "2025-12-04T13:02:07.267451Z", + "updated": "2026-02-03T13:13:23.315080Z", + "deleted": null, + "uuid": "32753644-de1b-4d68-82d1-001cbf5beeab", + "image": null, + "number": "868291bd-490d-47ef-b5be-e2002ce3bd10", + "type": 1, + "vat": "10.00", + "points": "0.00", + "minimum_amount": "0", + "available_amount": "256", + "amount_tracking": true, + "dispenser_id": "", + "course_group": null + } + ], + "name": "Menü", + "description": "Menü", + "children": [], + "created": "2025-11-26T13:34:51.236986Z", + "updated": "2026-01-29T07:38:42.691415Z", + "deleted": null, + "uuid": "89c73f18-f3c8-45f3-8227-66a98db86396", + "sort": 0, + "image": null, + "from_hour": null, + "to_hour": null, + "collapse": false + } + ] + } + }, + { + "timestamp": "2026-02-05T13:40:09.165Z", + "method": "GET", + "url": "https://api.bessa.app/v1/user/orders/591/venue/available/", + "status": 200, + "type": "xhr", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "referer": "https://web.bessa.app/", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "accept": "application/json", + "x-client-version": "1.7.0_prod/2026-01-26", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "access-control-expose-headers": "accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with, content-disposition", + "allow": "GET, HEAD, OPTIONS", + "content-language": "en-us", + "content-length": "37", + "content-type": "application/json", + "cross-origin-opener-policy": "same-origin", + "date": "Thu, 05 Feb 2026 13:40:09 GMT", + "referrer-policy": "same-origin", + "server": "nginx/1.27.3", + "strict-transport-security": "max-age=16000000\nmax-age=31536000", + "vary": "Accept, Accept-Language, origin", + "x-content-type-options": "nosniff", + "x-frame-options": "DENY" + }, + "body": { + "can_order": true, + "work_load": "0.00" + } + }, + { + "timestamp": "2026-02-05T13:40:14.831Z", + "method": "POST", + "url": "https://region1.analytics.google.com/g/collect?v=2&tid=G-NT5W7DSRT4>m=45je6231v9102475426za20gzb9102461295zd9102461295&_p=1770298748820&gcd=13l3l3l2l1l1&npa=1&dma_cps=syphamo&dma=1&cid=531786136.1770298750&ul=en-us&sr=1920x944&uaa=x86&uab=64&uafvl=Not(A%253ABrand%3B8.0.0.0%7CChromium%3B144.0.7559.96&uamb=0&uam=&uap=Linux&uapv=&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AEAAAGQ&_s=4&tag_exp=103116026~103200004~104527907~104528500~104573694~104684208~104684211~115938465~115938469~116185181~116185182~116988315&dl=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine%2F2026-02-10&dr=https%3A%2F%2Fweb.bessa.app%2Fknapp-kantine&sid=1770298750&sct=1&seg=1&dt=Bessa%20Web%20Shop&en=page_view&ep.content_group=web.bessa.app&_et=12534&tfd=66447", + "status": 204, + "type": "fetch", + "requestHeaders": { + "sec-ch-ua-platform": "\"Linux\"", + "referer": "https://web.bessa.app/", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "sec-ch-ua": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\"", + "sec-ch-ua-mobile": "?0", + "accept": "*/*", + "origin": "https://web.bessa.app" + }, + "responseHeaders": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "https://web.bessa.app", + "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "cache-control": "no-cache, no-store, must-revalidate", + "content-length": "0", + "content-security-policy-report-only": "script-src 'none'; form-action 'none'; frame-src 'none'; report-uri https://csp.withgoogle.com/csp/scaffolding/ascnsrsggc:171:0", + "content-type": "text/plain", + "cross-origin-opener-policy-report-only": "same-origin; report-to=ascnsrsggc:171:0", + "cross-origin-resource-policy": "cross-origin", + "date": "Thu, 05 Feb 2026 13:40:14 GMT", + "expires": "Fri, 01 Jan 1990 00:00:00 GMT", + "pragma": "no-cache", + "report-to": "{\"group\":\"ascnsrsggc:171:0\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/scaffolding/ascnsrsggc:171:0\"}],}", + "server": "Golfe2" + }, + "body": "[COULD NOT READ BODY]" + } +] \ No newline at end of file diff --git a/analysis_results/order_api_capture.json b/analysis_results/order_api_capture.json new file mode 100644 index 0000000..9cae756 --- /dev/null +++ b/analysis_results/order_api_capture.json @@ -0,0 +1,131 @@ +{ + "capturedAt": "2026-02-11T10:15:00Z", + "description": "Captured API traffic from Bessa web app during order placement and cancellation", + "apiCalls": [ + { + "step": "1. Fetch available dates (with existing orders)", + "method": "GET", + "url": "https://api.bessa.app/v1/venues/591/menu/dates/", + "requestHeaders": { + "Accept": "application/json", + "Authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "X-Client-Version": "1.7.0_prod/2026-01-26" + }, + "responseStatus": 200, + "responseBody": { + "results": [ + { + "date": "2026-02-11", + "id": 691, + "orders": [ + { + "id": 1522671, + "order_state": 8, + "total": "5.50", + "items": [ + { + "name": "M1 Herzhaftes" + } + ] + } + ] + }, + { + "date": "2026-02-12", + "id": 692, + "orders": [ + { + "id": 1522672, + "order_state": 5, + "total": "5.50", + "items": [ + { + "name": "M5F Salat mit Gebäck DO 2" + } + ] + } + ] + }, + { + "date": "2026-02-13", + "id": 698, + "orders": [] + } + ] + }, + "duration": 202 + }, + { + "step": "2. Place order (POST)", + "method": "POST", + "url": "https://api.bessa.app/v1/user/orders/", + "requestHeaders": { + "Accept": "application/json", + "Authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "Content-Type": "application/json" + }, + "requestBody": { + "customer": { + "email": "knapp-2041@bessa.app", + "first_name": "Michael", + "last_name": "Kaufmann" + }, + "date": "2026-02-13T10:00:00.000Z", + "items": [ + { + "amount": 1, + "article": 182378, + "name": "M1 W2", + "price": 4, + "vat": "10.00" + } + ], + "order_type": 7, + "payment_method": "payroll", + "total": 4, + "venue": 591 + }, + "responseStatus": 201, + "responseBody": { + "id": 1535066, + "hash_id": "o_xlOaq", + "order_state": 5, + "total": "4.00" + }, + "duration": 269 + }, + { + "step": "3. Cancel order (PATCH)", + "method": "PATCH", + "url": "https://api.bessa.app/v1/user/orders/1535066/cancel/", + "requestHeaders": { + "Accept": "application/json", + "Authorization": "Token dba7d86e83c7f462fd8af96521dea41c4facd8a5", + "Content-Type": "application/json" + }, + "requestBody": {}, + "responseStatus": 200, + "responseBody": { + "order_id": "o_xlOaq", + "state": "order cancelled." + }, + "duration": 133 + } + ], + "orderStates": { + "5": "Transmitted/Active (new order)", + "8": "Accepted/Processed (confirmed by kitchen)" + }, + "configurationDetails": { + "orderType": 7, + "configName": "canteen", + "paymentTypes": [ + "payroll" + ], + "cancellationCutoff": 3600, + "allowCancel": true, + "preorderDelta": 3600, + "venueId": 591, + "venueTimezone": "CET" + } +} \ No newline at end of file diff --git a/analysis_results/session_storage.json b/analysis_results/session_storage.json new file mode 100644 index 0000000..e1c18a1 --- /dev/null +++ b/analysis_results/session_storage.json @@ -0,0 +1 @@ +{"fbssls_1309426129223545":"{\"authResponse\":null,\"status\":\"unknown\",\"expiresAt\":1770385149890}","AkitaStores":"{\"$cache\":{\"cart\":true,\"menucategories\":true,\"course-group\":true},\"cart\":{\"entities\":{},\"ids\":[],\"loading\":false,\"error\":null,\"cartMetadata\":{\"orderType\":7,\"customer\":{\"firstName\":\"Michael\",\"lastName\":\"Kaufmann\",\"email\":\"knapp-2041@bessa.app\",\"newsletter\":false},\"tip\":0,\"tipTotal\":0,\"created\":1770298808772,\"dateRestriction\":\"2026-02-10T00:00:00.000Z\",\"date\":1770717600000},\"counts\":{},\"configuration\":{\"id\":857,\"name\":\"canteen\",\"deliveryFees\":[],\"articles\":[{\"id\":212,\"name\":\"daily menu article\",\"cashBoxArticle\":\"fa2e5d0f-f368-4bf3-b4cd-15fc02efb672\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T07:34:16.562Z\",\"updated\":\"2026-01-29T07:34:16.562Z\",\"deleted\":null,\"uuid\":\"c464f85e-285b-4003-8d08-1678f9237b48\",\"articleType\":12,\"fee\":7,\"limit\":0,\"configuration\":857,\"article\":182373},{\"id\":90,\"name\":\"daily menu article\",\"cashBoxArticle\":\"6208ac78-6cde-46ff-b998-f1afdc7e34b7\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:40:38.594Z\",\"updated\":\"2025-11-27T07:40:38.594Z\",\"deleted\":null,\"uuid\":\"88c628cb-3837-4d6d-8c63-a6e0b28f0341\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":176511},{\"id\":91,\"name\":\"daily menu article\",\"cashBoxArticle\":\"c7e4c59f-2566-40c3-9497-d0e65f5b2631\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:40:59.156Z\",\"updated\":\"2025-11-27T07:40:59.156Z\",\"deleted\":null,\"uuid\":\"07921176-b8d0-4686-8f7b-757ca2261d01\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":176512},{\"id\":92,\"name\":\"daily menu article\",\"cashBoxArticle\":\"fb901cf3-d9f1-4df4-886d-998dca8371ef\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:41:23.612Z\",\"updated\":\"2025-11-27T07:41:23.612Z\",\"deleted\":null,\"uuid\":\"405371a9-fc20-418d-af81-91519d37039e\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":176513},{\"id\":94,\"name\":\"daily menu article\",\"cashBoxArticle\":\"338a80de-35ec-40ef-a1e7-6fc1dafa2fb1\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:42:07.469Z\",\"updated\":\"2025-11-27T07:42:13.560Z\",\"deleted\":null,\"uuid\":\"81864d63-0ff4-41a6-9f42-4cbf72eb23d7\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":176515},{\"id\":95,\"name\":\"daily menu article\",\"cashBoxArticle\":\"3ce62d9d-c75b-4cce-97e5-fabe2db7f243\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:42:29.813Z\",\"updated\":\"2025-11-27T07:42:29.813Z\",\"deleted\":null,\"uuid\":\"995fcf59-f8b6-4b84-9cba-8675ffaca9f2\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":176516},{\"id\":96,\"name\":\"daily menu article\",\"cashBoxArticle\":\"45a64da3-8973-4f49-94d5-ce293afde7e3\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:42:46.380Z\",\"updated\":\"2025-11-27T07:42:46.380Z\",\"deleted\":null,\"uuid\":\"efdb1d47-f223-4c2e-bc14-c60c83b483a3\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":176517},{\"id\":93,\"name\":\"daily menu article\",\"cashBoxArticle\":\"690b154a-95b1-4563-a73d-06735c62f203\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:41:40.955Z\",\"updated\":\"2025-11-27T07:42:59.835Z\",\"deleted\":null,\"uuid\":\"f0eaafd2-f497-4a4b-b998-0bfe50018143\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":176514},{\"id\":215,\"name\":\"daily menu article\",\"cashBoxArticle\":\"1dda4bed-3306-4263-bd3c-f1d8c87f340a\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T07:35:40.880Z\",\"updated\":\"2026-01-29T07:36:32.707Z\",\"deleted\":null,\"uuid\":\"f74b47f0-5fbd-4dd7-b547-80dd2ed0bbc4\",\"articleType\":12,\"fee\":7,\"limit\":0,\"configuration\":857,\"article\":182376},{\"id\":154,\"name\":\"daily menu article\",\"cashBoxArticle\":\"e6966a1c-ba45-450f-b007-f5f41cb79a49\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"7c66ef66-9120-454f-b0a7-cb07b8ed4716\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178720},{\"id\":155,\"name\":\"daily menu article\",\"cashBoxArticle\":\"04cb9490-ee5c-40f6-afa8-6837dae888a2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"3cfc10e1-2853-4c0d-88a6-ca38ef085e92\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178724},{\"id\":156,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b65661dd-6c62-4a23-9384-c933c8ed424e\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"6f6ef837-746d-4a4a-88f5-c08d8f17aa6f\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178722},{\"id\":157,\"name\":\"daily menu article\",\"cashBoxArticle\":\"725d1aa6-f9f5-4e14-951c-90543d961b12\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"59553dc0-2fb6-4147-92d5-b6a84d73882b\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178726},{\"id\":158,\"name\":\"daily menu article\",\"cashBoxArticle\":\"868291bd-490d-47ef-b5be-e2002ce3bd10\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"95b19f9f-f0cf-46cd-8826-5a3bbd6de440\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178721},{\"id\":159,\"name\":\"daily menu article\",\"cashBoxArticle\":\"cfdd8d4b-eb49-4b00-9db4-045e293dc0c0\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"cb54a774-795e-4e98-b8a3-5d8320c4a4ad\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178725},{\"id\":160,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f4b084f9-5fd4-494c-aabf-27199e1ecece\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"7ba2a414-2a2f-4d6e-80b1-7bf9d6813eff\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178723},{\"id\":213,\"name\":\"daily menu article\",\"cashBoxArticle\":\"10e9d99d-d050-4565-8c02-1e9589fd2933\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T07:35:03.186Z\",\"updated\":\"2026-01-29T07:36:11.477Z\",\"deleted\":null,\"uuid\":\"124aa4c9-e68e-45a2-ae9c-c7333bd35c3e\",\"articleType\":12,\"fee\":7,\"limit\":0,\"configuration\":857,\"article\":182374},{\"id\":217,\"name\":\"daily menu article\",\"cashBoxArticle\":\"d2814527-95bc-4ee8-8bda-e5fe2427bd47\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T08:21:51.364Z\",\"updated\":\"2026-01-29T08:21:51.364Z\",\"deleted\":null,\"uuid\":\"e0ce3a5a-68d1-499f-a352-581eb9b4cfe3\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":182378},{\"id\":176,\"name\":\"daily menu article\",\"cashBoxArticle\":\"765cc903-2695-4bba-bed2-4f4078f294c2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:56.604Z\",\"deleted\":null,\"uuid\":\"9e52e37f-f5df-40c5-9c6a-988a95b6bf45\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178745},{\"id\":177,\"name\":\"daily menu article\",\"cashBoxArticle\":\"e4d4157c-4f4a-4701-b1f9-356326c1612d\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:58.444Z\",\"deleted\":null,\"uuid\":\"dc93a7eb-68d9-4730-a240-b3d972d1fc14\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178743},{\"id\":178,\"name\":\"daily menu article\",\"cashBoxArticle\":\"21686838-0e22-4915-a4a0-e38b3bb43635\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:00.602Z\",\"deleted\":null,\"uuid\":\"3590c0eb-98a7-4c38-a204-2ed4e66242fb\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178747},{\"id\":161,\"name\":\"daily menu article\",\"cashBoxArticle\":\"48e86ff1-7b6d-46f7-91a0-346de93fb855\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:39.892Z\",\"deleted\":null,\"uuid\":\"30d94707-2c5d-4993-af73-a6b45b8644a4\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178727},{\"id\":162,\"name\":\"daily menu article\",\"cashBoxArticle\":\"695bb5f6-cbbb-486a-ad3b-9158dc055f98\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:43.402Z\",\"deleted\":null,\"uuid\":\"699c4743-6807-4dec-a980-b7a9ac22a85b\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178731},{\"id\":163,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5207e0e3-06a3-4e15-9e8f-aca634005fad\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:45.566Z\",\"deleted\":null,\"uuid\":\"a4cbc519-f857-4890-bbcd-8ece056bdd79\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178729},{\"id\":164,\"name\":\"daily menu article\",\"cashBoxArticle\":\"701c6989-194c-400c-b590-77734f9a5479\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:47.611Z\",\"deleted\":null,\"uuid\":\"b70a3f59-8fe0-4a73-84ce-08ebf349f103\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178733},{\"id\":165,\"name\":\"daily menu article\",\"cashBoxArticle\":\"a33ce321-1943-430e-a435-b83f87ff1080\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:49.571Z\",\"deleted\":null,\"uuid\":\"ffe276a4-d297-42a3-af78-aee06aab2521\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178728},{\"id\":166,\"name\":\"daily menu article\",\"cashBoxArticle\":\"7bf71611-3eb3-4784-bd29-b38ebd1821f9\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:51.532Z\",\"deleted\":null,\"uuid\":\"497cdb95-5e8c-415d-8098-8ffba3cc83e2\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178732},{\"id\":167,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8eb70af1-5cc6-4f30-be0a-3bf2f91321b3\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:53.803Z\",\"deleted\":null,\"uuid\":\"b05cb25d-cde4-43f5-a1dd-90e711c21e34\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178730},{\"id\":168,\"name\":\"daily menu article\",\"cashBoxArticle\":\"03672c2e-0ab3-4efd-80ad-564a910c69dc\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:16.745Z\",\"deleted\":null,\"uuid\":\"82fb659b-4cd1-412b-a55c-91860a362ee7\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178734},{\"id\":169,\"name\":\"daily menu article\",\"cashBoxArticle\":\"ec147dfa-d0c5-446e-83ab-1e0f1c6a6f69\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:18.969Z\",\"deleted\":null,\"uuid\":\"325e14dd-d242-47ad-980c-019f4d598a4f\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178738},{\"id\":170,\"name\":\"daily menu article\",\"cashBoxArticle\":\"4ceb0a3e-973a-4b82-ae4b-2592c5ef43eb\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:21.038Z\",\"deleted\":null,\"uuid\":\"91583c15-7708-41a4-b92b-f64c7dcc9d69\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178736},{\"id\":171,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5727d322-cae6-48e8-b1b1-0786d37935b1\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:22.949Z\",\"deleted\":null,\"uuid\":\"0f2101f0-5228-4c2b-95d3-3d2997074567\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178740},{\"id\":172,\"name\":\"daily menu article\",\"cashBoxArticle\":\"88b59af4-f3fb-45a7-ba0f-9fce88d4710f\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:25.052Z\",\"deleted\":null,\"uuid\":\"28dbc585-1068-4e9b-9eda-f50edcd85b53\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178735},{\"id\":173,\"name\":\"daily menu article\",\"cashBoxArticle\":\"86a71457-dbfd-452d-a41c-d9cbbdf15cdf\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:27.368Z\",\"deleted\":null,\"uuid\":\"28da54aa-9df3-42e3-a053-216d7502c8a2\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178739},{\"id\":174,\"name\":\"daily menu article\",\"cashBoxArticle\":\"1bd71fe6-b8fc-4c84-9780-96dedf6870f8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:32.661Z\",\"deleted\":null,\"uuid\":\"f573d3e0-230b-4b3e-a4d5-650e8537aba8\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178737},{\"id\":175,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b266cf85-960b-4f13-b327-e11929151e00\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:54.602Z\",\"deleted\":null,\"uuid\":\"01c247d3-2320-496c-a282-78f9a40bd3a2\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178741},{\"id\":179,\"name\":\"daily menu article\",\"cashBoxArticle\":\"ea309dc8-38bf-4e78-9b51-7c9a7efb2763\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:02.533Z\",\"deleted\":null,\"uuid\":\"187afcb5-caba-45b6-95fb-a134a6eeeaca\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178742},{\"id\":180,\"name\":\"daily menu article\",\"cashBoxArticle\":\"1a7386e3-3d4a-4261-b235-565fdf268863\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:04.495Z\",\"deleted\":null,\"uuid\":\"fce8c9a5-8263-46a6-aa4c-416683e30cad\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178746},{\"id\":181,\"name\":\"daily menu article\",\"cashBoxArticle\":\"316e731d-dd8e-47b4-bb25-9d4f2e641151\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:06.469Z\",\"deleted\":null,\"uuid\":\"bd3373e0-e97f-460d-848a-932c875c0128\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178744},{\"id\":182,\"name\":\"daily menu article\",\"cashBoxArticle\":\"42ec29ac-456c-4ca2-aea3-20dfbf3a28b5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:26.819Z\",\"deleted\":null,\"uuid\":\"1eab4039-75be-4eab-ba7f-686f0a8d577f\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178748},{\"id\":183,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8e52c14c-6c2a-444c-adde-916107f42f1a\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:29.984Z\",\"deleted\":null,\"uuid\":\"6875dce5-deed-46b4-8b27-45408678bf63\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178752},{\"id\":184,\"name\":\"daily menu article\",\"cashBoxArticle\":\"3c7f4116-12be-4d10-9369-a61f8b81e387\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:33.032Z\",\"deleted\":null,\"uuid\":\"d0115b60-e5de-43ad-8197-7e16f5c909a4\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178750},{\"id\":185,\"name\":\"daily menu article\",\"cashBoxArticle\":\"13203b86-7ce6-4e2d-b12a-a159100e6e0e\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:35.253Z\",\"deleted\":null,\"uuid\":\"316dbee9-3bac-4c31-bc41-7e924bd6ccef\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178754},{\"id\":186,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f6d5bd6e-2e14-4930-8f27-be027fa477b2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:37.173Z\",\"deleted\":null,\"uuid\":\"21aff393-5ee1-4248-bd0b-a2431fe385f5\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178749},{\"id\":187,\"name\":\"daily menu article\",\"cashBoxArticle\":\"dea4b5d4-7a2c-4565-94ae-9b5dc2cc5ed8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:39.060Z\",\"deleted\":null,\"uuid\":\"85d77712-172f-4b9c-b86d-78124752003e\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178753},{\"id\":188,\"name\":\"daily menu article\",\"cashBoxArticle\":\"95ecb237-e497-48f3-ab7e-0b0f8730f02d\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:41.453Z\",\"deleted\":null,\"uuid\":\"4bbf06be-9325-4556-9a26-402178d3717f\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178751},{\"id\":189,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5ec70cab-6b4c-410f-bb91-60a61fd8bbdd\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:02.072Z\",\"deleted\":null,\"uuid\":\"982aea4b-fc8b-4196-a569-b72454fe336c\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178755},{\"id\":190,\"name\":\"daily menu article\",\"cashBoxArticle\":\"704e5191-4dd6-4fd9-b99c-233d44de5ff3\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:04.204Z\",\"deleted\":null,\"uuid\":\"c6e61b0b-0d77-48bf-852a-7d453943bb29\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178759},{\"id\":191,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b270abf7-a35c-4eca-8d62-5fdb5ceaf0a8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:06.105Z\",\"deleted\":null,\"uuid\":\"54c6ffbc-445a-454c-b29e-cd446949f022\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178757},{\"id\":192,\"name\":\"daily menu article\",\"cashBoxArticle\":\"7810ea32-e693-4716-8f2d-552fa6bb0c45\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:08.040Z\",\"deleted\":null,\"uuid\":\"bf625fdf-5cfd-4f1c-9606-84e158392f7d\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178761},{\"id\":193,\"name\":\"daily menu article\",\"cashBoxArticle\":\"59b371ea-ab48-4efc-9b63-04cf3df9ffc5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:10.373Z\",\"deleted\":null,\"uuid\":\"5e3663c0-5b0c-4c79-86f4-11a92c549060\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178756},{\"id\":194,\"name\":\"daily menu article\",\"cashBoxArticle\":\"784431e8-2a34-4230-9d95-dbe9c6498fb4\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:12.505Z\",\"deleted\":null,\"uuid\":\"e8823a46-9175-49f3-b9eb-19c4b7dfa745\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178760},{\"id\":195,\"name\":\"daily menu article\",\"cashBoxArticle\":\"899690c3-8ef1-4b06-8f3c-f24a3afac9c1\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:14.557Z\",\"deleted\":null,\"uuid\":\"f408f867-2c35-4fb2-86d4-8150b7804492\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178758},{\"id\":203,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8b9dad61-ca9c-4fbb-a7fe-a5cecaba50d5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:36.991Z\",\"deleted\":null,\"uuid\":\"1dcf66fc-9ccf-4946-a764-058643b1d317\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178713},{\"id\":204,\"name\":\"daily menu article\",\"cashBoxArticle\":\"45476da6-84b7-4129-9b34-2251595ebbc5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:39.140Z\",\"deleted\":null,\"uuid\":\"958e0de1-13bf-41e7-aa45-4b6852b0a01b\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178717},{\"id\":205,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f9445453-fe41-42a3-b1ed-9aedafc6852c\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:41.593Z\",\"deleted\":null,\"uuid\":\"f96bb0c4-ac62-44e1-886f-075a1925bfb5\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178715},{\"id\":206,\"name\":\"daily menu article\",\"cashBoxArticle\":\"d9336280-1d15-440c-81a1-e47d6766c475\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:43.685Z\",\"deleted\":null,\"uuid\":\"6b8023f5-e632-44de-8e82-36276681b861\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178719},{\"id\":207,\"name\":\"daily menu article\",\"cashBoxArticle\":\"dfaa35f6-4604-4e14-9f86-1862a05a5881\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:45.648Z\",\"deleted\":null,\"uuid\":\"4e1413ea-28b0-43c3-8c4b-f7127cc24d56\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178714},{\"id\":208,\"name\":\"daily menu article\",\"cashBoxArticle\":\"34056ce2-ff61-4c94-9aba-b1f364fa9772\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:48.028Z\",\"deleted\":null,\"uuid\":\"130cbb68-cb72-4340-815b-822d5fdfec71\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178718},{\"id\":209,\"name\":\"daily menu article\",\"cashBoxArticle\":\"c64fa08d-b4a0-4510-b512-2c299d373d04\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:50.033Z\",\"deleted\":null,\"uuid\":\"ddadf870-a95a-4367-bbba-8a06190c0d72\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178716},{\"id\":214,\"name\":\"daily menu article\",\"cashBoxArticle\":\"262e5a5e-707b-462c-94b1-c8cbe639a8f9\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T07:35:21.770Z\",\"updated\":\"2026-01-29T07:36:22.914Z\",\"deleted\":null,\"uuid\":\"e67d5993-fabb-403d-b799-5e1e6032cf5e\",\"articleType\":12,\"fee\":7,\"limit\":0,\"configuration\":857,\"article\":182375},{\"id\":216,\"name\":\"daily menu article\",\"cashBoxArticle\":\"ddf8192f-2666-4db9-8b92-4002a3b0d120\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T08:21:22.610Z\",\"updated\":\"2026-01-29T08:21:32.419Z\",\"deleted\":null,\"uuid\":\"298378d5-349d-4559-be1f-708789355ab5\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":182377}],\"extras\":[{\"type\":2,\"text\":\"Ablauf / Process\"},{\"type\":3,\"text\":\"Deine Bestellung kann per Mitarbeiterkarte abgeholt werden. | Your order can be picked up using your employee card.\"}],\"created\":\"2025-10-20T07:37:10.129Z\",\"updated\":\"2026-01-27T14:43:56.933Z\",\"deleted\":null,\"uuid\":\"a1a52834-1e37-4e8a-be8c-a78b4700825a\",\"type\":7,\"enabled\":true,\"visibility\":1,\"paymentTypes\":[\"payroll\"],\"allowPreorder\":true,\"preorderDelta\":3600,\"orderLeadTime\":0,\"preorderThreshold\":3600,\"preorderFutureLimit\":1382400,\"cancellationCutoff\":3600,\"slotDelta\":0,\"slotInterval\":10800,\"slotMaximumCount\":5000,\"minimumOrderValue\":\"0.00\",\"takeAway\":false,\"commentsEnabled\":true,\"enableCourses\":false,\"maxDrinkItems\":0,\"maxFoodItems\":0,\"tipEnabled\":false,\"tipMode\":1,\"defaultTip\":0,\"priceLevel\":-1,\"onlyManagedUsers\":true,\"needsAuthentication\":true,\"showQrCode\":true,\"allowCancel\":true,\"skipCustomerInformation\":true},\"venue\":{\"id\":591,\"name\":\"Werksrestaurant\",\"about\":\"-\",\"slogan\":\"\",\"imageThumbnail\":\"https://files.treuepass.app/cache/f9/98/f998b02b3257e38bbefaba629c9f02dd.jpg\",\"location\":{\"latitude\":47.0431554,\"longitude\":15.5110764},\"shop\":{\"id\":485,\"created\":\"2025-08-04T12:35:38.773Z\",\"updated\":\"2025-11-27T07:26:22.413Z\",\"deleted\":null,\"uuid\":\"379a94c3-cd24-46a3-8c76-8e55febf69d4\",\"stylesheet\":\"https://web.bessa.app/assets/knapp-kantine.1764841216.css\",\"name\":\"knapp-kantine\",\"anonymousOrdering\":true,\"useTableSelection\":false,\"formalLocalization\":false,\"showCloseDialog\":false,\"newsletter\":true,\"readOnly\":false,\"loginType\":\"ACCESS_CODE\",\"venue\":591},\"paymentProviders\":[],\"prefix\":\"knapp\",\"created\":\"2025-08-04T12:35:37.086Z\",\"updated\":\"2025-12-04T09:59:03.867Z\",\"deleted\":null,\"uuid\":\"78ba8c27-567e-4a44-ba0d-faae9e66a2ce\",\"image\":\"https://files.treuepass.app/account/users/NTkx/venues/33f822ea80dc431f97625bcf9fa318de.png\",\"email\":\"petra.heschl@knapp.com\",\"address\":\"Günter-Knapp-Straße 5-7, 8075 Hart bei Graz, Austria\",\"phone\":\"+4367689791001\",\"website\":\"\",\"instagram\":\"\",\"twitter\":\"\",\"facebook\":\"\",\"menu\":\"\",\"tableBooking\":\"\",\"usePin\":true,\"treuepassEnabled\":false,\"scansPerDay\":2,\"timezone\":\"CET\",\"currency\":\"EUR\",\"country\":\"AT\",\"company\":533,\"parent\":null},\"config\":{\"employee\":null,\"discountFood\":null,\"discountDrink\":null,\"tipArticle\":null,\"testArticleMeal\":null,\"testArticleDrink\":null,\"configurations\":[{\"id\":869,\"name\":\"self service\",\"deliveryFees\":[],\"articles\":[{\"id\":119,\"name\":\"daily menu article\",\"cashBoxArticle\":\"48e86ff1-7b6d-46f7-91a0-346de93fb855\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:30:04.576Z\",\"deleted\":null,\"uuid\":\"35676f7b-1326-49c8-8c64-93855e3e5823\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":869,\"article\":178727},{\"id\":120,\"name\":\"daily menu article\",\"cashBoxArticle\":\"695bb5f6-cbbb-486a-ad3b-9158dc055f98\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:30:09.620Z\",\"deleted\":null,\"uuid\":\"a3fcc4f3-5cac-4945-a312-14f2f56d4e82\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":869,\"article\":178731},{\"id\":104,\"name\":\"daily menu article\",\"cashBoxArticle\":\"6208ac78-6cde-46ff-b998-f1afdc7e34b7\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"e4f00c7b-88c2-4463-abf9-451603a15b90\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":869,\"article\":176511},{\"id\":105,\"name\":\"daily menu article\",\"cashBoxArticle\":\"c7e4c59f-2566-40c3-9497-d0e65f5b2631\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"d6668599-b073-4bb9-823a-8b7d5aea16bd\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":869,\"article\":176512},{\"id\":106,\"name\":\"daily menu article\",\"cashBoxArticle\":\"fb901cf3-d9f1-4df4-886d-998dca8371ef\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"f2286f4c-f7ec-478d-9aaa-62dd019ca062\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":869,\"article\":176513},{\"id\":107,\"name\":\"daily menu article\",\"cashBoxArticle\":\"338a80de-35ec-40ef-a1e7-6fc1dafa2fb1\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"6556e5f9-1b45-442a-a8f9-629392101c9e\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":869,\"article\":176515},{\"id\":108,\"name\":\"daily menu article\",\"cashBoxArticle\":\"3ce62d9d-c75b-4cce-97e5-fabe2db7f243\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"4380eec7-96d0-4fc8-a0d6-9b6680faba27\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":869,\"article\":176516},{\"id\":109,\"name\":\"daily menu article\",\"cashBoxArticle\":\"45a64da3-8973-4f49-94d5-ce293afde7e3\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"b7bfbe38-0c45-49c9-af76-ce0d56a9abf4\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":869,\"article\":176517},{\"id\":110,\"name\":\"daily menu article\",\"cashBoxArticle\":\"690b154a-95b1-4563-a73d-06735c62f203\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"2ef63a2a-68e6-485e-b8c0-1bf7052c2c95\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":869,\"article\":176514},{\"id\":112,\"name\":\"daily menu article\",\"cashBoxArticle\":\"e6966a1c-ba45-450f-b007-f5f41cb79a49\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"ed8358f7-9f28-43be-8955-0d833329a88e\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":869,\"article\":178720},{\"id\":113,\"name\":\"daily menu article\",\"cashBoxArticle\":\"04cb9490-ee5c-40f6-afa8-6837dae888a2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"74e48d62-9343-4b00-b63e-6652ed02e084\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":869,\"article\":178724},{\"id\":114,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b65661dd-6c62-4a23-9384-c933c8ed424e\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"9d8885e1-f43a-4d4d-b6e7-66834252b989\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":869,\"article\":178722},{\"id\":115,\"name\":\"daily menu article\",\"cashBoxArticle\":\"725d1aa6-f9f5-4e14-951c-90543d961b12\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"fd1e6ecb-71c3-493b-bc08-0073dd82cbbd\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":869,\"article\":178726},{\"id\":116,\"name\":\"daily menu article\",\"cashBoxArticle\":\"868291bd-490d-47ef-b5be-e2002ce3bd10\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"bedf9d79-963b-4198-94c8-c686138d60bc\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":869,\"article\":178721},{\"id\":117,\"name\":\"daily menu article\",\"cashBoxArticle\":\"cfdd8d4b-eb49-4b00-9db4-045e293dc0c0\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"5e2da10f-e986-4095-89c0-bb2bc9fc3f57\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":869,\"article\":178725},{\"id\":118,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f4b084f9-5fd4-494c-aabf-27199e1ecece\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"6844439a-7325-40fa-9a83-48df125376c0\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":869,\"article\":178723},{\"id\":121,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5207e0e3-06a3-4e15-9e8f-aca634005fad\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:30:13.330Z\",\"deleted\":null,\"uuid\":\"7c9a382c-e93e-4a3e-aaa3-5988a624b5fa\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":869,\"article\":178729},{\"id\":122,\"name\":\"daily menu article\",\"cashBoxArticle\":\"701c6989-194c-400c-b590-77734f9a5479\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:30:18.764Z\",\"deleted\":null,\"uuid\":\"e9a55f58-bb49-4a06-9af9-d1e35feaba41\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":869,\"article\":178733},{\"id\":123,\"name\":\"daily menu article\",\"cashBoxArticle\":\"a33ce321-1943-430e-a435-b83f87ff1080\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:30:23.100Z\",\"deleted\":null,\"uuid\":\"9ec5cc0f-bd12-4fb8-9f09-753ed3bb2358\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":869,\"article\":178728},{\"id\":124,\"name\":\"daily menu article\",\"cashBoxArticle\":\"7bf71611-3eb3-4784-bd29-b38ebd1821f9\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:30:26.998Z\",\"deleted\":null,\"uuid\":\"e705d837-e00a-4f0c-8e97-4b758d991c3f\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":869,\"article\":178732},{\"id\":125,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8eb70af1-5cc6-4f30-be0a-3bf2f91321b3\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:30:31.104Z\",\"deleted\":null,\"uuid\":\"10226f5c-d763-460d-9601-7965286e1143\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":869,\"article\":178730},{\"id\":126,\"name\":\"daily menu article\",\"cashBoxArticle\":\"03672c2e-0ab3-4efd-80ad-564a910c69dc\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:31:29.377Z\",\"deleted\":null,\"uuid\":\"83ab3db3-2ee0-4821-817a-0c1da2bc91c4\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":869,\"article\":178734},{\"id\":127,\"name\":\"daily menu article\",\"cashBoxArticle\":\"ec147dfa-d0c5-446e-83ab-1e0f1c6a6f69\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:31:34.013Z\",\"deleted\":null,\"uuid\":\"8efa01d7-e56e-41c7-95ff-3aa9d0b317dd\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":869,\"article\":178738},{\"id\":128,\"name\":\"daily menu article\",\"cashBoxArticle\":\"4ceb0a3e-973a-4b82-ae4b-2592c5ef43eb\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:31:37.597Z\",\"deleted\":null,\"uuid\":\"02d721c3-b86d-4639-a675-41d280057a5a\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":869,\"article\":178736},{\"id\":129,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5727d322-cae6-48e8-b1b1-0786d37935b1\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:31:40.157Z\",\"deleted\":null,\"uuid\":\"ce142601-7b20-4626-a717-7f2fb13c078e\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":869,\"article\":178740},{\"id\":130,\"name\":\"daily menu article\",\"cashBoxArticle\":\"88b59af4-f3fb-45a7-ba0f-9fce88d4710f\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:31:44.671Z\",\"deleted\":null,\"uuid\":\"8eaa6055-359b-4c11-b87b-053c8233941f\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":869,\"article\":178735},{\"id\":131,\"name\":\"daily menu article\",\"cashBoxArticle\":\"86a71457-dbfd-452d-a41c-d9cbbdf15cdf\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:31:48.742Z\",\"deleted\":null,\"uuid\":\"543a3346-3a82-4dd6-910a-5b355b75bf88\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":869,\"article\":178739},{\"id\":133,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b266cf85-960b-4f13-b327-e11929151e00\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:34:57.521Z\",\"deleted\":null,\"uuid\":\"7fa47fd3-2ec8-4b63-bf8b-65dc54e3e644\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":869,\"article\":178741},{\"id\":132,\"name\":\"daily menu article\",\"cashBoxArticle\":\"1bd71fe6-b8fc-4c84-9780-96dedf6870f8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:31:58.993Z\",\"deleted\":null,\"uuid\":\"ccdb3045-c119-441c-95ab-0e4a5b20ec0e\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":869,\"article\":178737},{\"id\":134,\"name\":\"daily menu article\",\"cashBoxArticle\":\"765cc903-2695-4bba-bed2-4f4078f294c2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:04.445Z\",\"deleted\":null,\"uuid\":\"2851b911-dc1d-402f-bffe-2b413879bd5f\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":869,\"article\":178745},{\"id\":135,\"name\":\"daily menu article\",\"cashBoxArticle\":\"e4d4157c-4f4a-4701-b1f9-356326c1612d\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:07.985Z\",\"deleted\":null,\"uuid\":\"5cf0dedd-dff8-4791-b108-6f701a9125e6\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":869,\"article\":178743},{\"id\":136,\"name\":\"daily menu article\",\"cashBoxArticle\":\"21686838-0e22-4915-a4a0-e38b3bb43635\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:10.335Z\",\"deleted\":null,\"uuid\":\"8782afad-7dcc-4940-ac88-8fd3a5cc03da\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":869,\"article\":178747},{\"id\":137,\"name\":\"daily menu article\",\"cashBoxArticle\":\"ea309dc8-38bf-4e78-9b51-7c9a7efb2763\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:12.681Z\",\"deleted\":null,\"uuid\":\"4bbc4f54-5ad9-4075-81da-c137f397d3d4\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":869,\"article\":178742},{\"id\":138,\"name\":\"daily menu article\",\"cashBoxArticle\":\"1a7386e3-3d4a-4261-b235-565fdf268863\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:15.618Z\",\"deleted\":null,\"uuid\":\"e2858724-1888-4dfa-bda2-ffdf30d904ab\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":869,\"article\":178746},{\"id\":139,\"name\":\"daily menu article\",\"cashBoxArticle\":\"316e731d-dd8e-47b4-bb25-9d4f2e641151\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:18.765Z\",\"deleted\":null,\"uuid\":\"dee0ea11-ca84-41bc-8a02-500817404c19\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":869,\"article\":178744},{\"id\":140,\"name\":\"daily menu article\",\"cashBoxArticle\":\"42ec29ac-456c-4ca2-aea3-20dfbf3a28b5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:39.894Z\",\"deleted\":null,\"uuid\":\"9c1ef9eb-29a4-4df5-88a7-654754779184\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":869,\"article\":178748},{\"id\":141,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8e52c14c-6c2a-444c-adde-916107f42f1a\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:42.200Z\",\"deleted\":null,\"uuid\":\"a28452d4-1b26-4174-b101-fb5789c76dc9\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":869,\"article\":178752},{\"id\":142,\"name\":\"daily menu article\",\"cashBoxArticle\":\"3c7f4116-12be-4d10-9369-a61f8b81e387\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:44.439Z\",\"deleted\":null,\"uuid\":\"6a975421-da44-481a-b342-318529b80281\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":869,\"article\":178750},{\"id\":143,\"name\":\"daily menu article\",\"cashBoxArticle\":\"13203b86-7ce6-4e2d-b12a-a159100e6e0e\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:46.746Z\",\"deleted\":null,\"uuid\":\"b46a25f0-2ed9-4ef6-982a-994d2ba60e39\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":869,\"article\":178754},{\"id\":144,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f6d5bd6e-2e14-4930-8f27-be027fa477b2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:49.117Z\",\"deleted\":null,\"uuid\":\"215264b7-9af3-4c44-a2e7-cac8fe8bf6a7\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":869,\"article\":178749},{\"id\":145,\"name\":\"daily menu article\",\"cashBoxArticle\":\"dea4b5d4-7a2c-4565-94ae-9b5dc2cc5ed8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:52.415Z\",\"deleted\":null,\"uuid\":\"de586e3b-23c4-4358-959c-ae1731d95574\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":869,\"article\":178753},{\"id\":146,\"name\":\"daily menu article\",\"cashBoxArticle\":\"95ecb237-e497-48f3-ab7e-0b0f8730f02d\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:35:54.851Z\",\"deleted\":null,\"uuid\":\"cf56b9ab-23f2-42d6-ac07-05379df3f749\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":869,\"article\":178751},{\"id\":147,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5ec70cab-6b4c-410f-bb91-60a61fd8bbdd\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:36:33.204Z\",\"deleted\":null,\"uuid\":\"958676a2-b631-40c1-8059-1f2212fb8425\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":869,\"article\":178755},{\"id\":148,\"name\":\"daily menu article\",\"cashBoxArticle\":\"704e5191-4dd6-4fd9-b99c-233d44de5ff3\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:36:35.277Z\",\"deleted\":null,\"uuid\":\"05367984-7df8-4720-893c-ae3a11afe9c0\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":869,\"article\":178759},{\"id\":149,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b270abf7-a35c-4eca-8d62-5fdb5ceaf0a8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:36:38.787Z\",\"deleted\":null,\"uuid\":\"f618563c-28aa-445c-9d24-65d8c4958583\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":869,\"article\":178757},{\"id\":150,\"name\":\"daily menu article\",\"cashBoxArticle\":\"7810ea32-e693-4716-8f2d-552fa6bb0c45\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:36:40.891Z\",\"deleted\":null,\"uuid\":\"16dc7d5f-fa7a-4a16-8ba0-d5bedd0dbe85\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":869,\"article\":178761},{\"id\":151,\"name\":\"daily menu article\",\"cashBoxArticle\":\"59b371ea-ab48-4efc-9b63-04cf3df9ffc5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:36:44.655Z\",\"deleted\":null,\"uuid\":\"4e9d50dd-4cbd-4d49-a45b-ec6aa5e10c9c\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":869,\"article\":178756},{\"id\":152,\"name\":\"daily menu article\",\"cashBoxArticle\":\"784431e8-2a34-4230-9d95-dbe9c6498fb4\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:36:46.885Z\",\"deleted\":null,\"uuid\":\"d6bb053e-a8d1-467c-b48f-b9c68fe2ec05\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":869,\"article\":178760},{\"id\":153,\"name\":\"daily menu article\",\"cashBoxArticle\":\"899690c3-8ef1-4b06-8f3c-f24a3afac9c1\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:36:49.686Z\",\"deleted\":null,\"uuid\":\"65138d9d-5003-4666-a5ee-b53c8968ab5d\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":869,\"article\":178758},{\"id\":196,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8b9dad61-ca9c-4fbb-a7fe-a5cecaba50d5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:14.446Z\",\"updated\":\"2025-12-04T15:28:34.638Z\",\"deleted\":null,\"uuid\":\"2c00cc11-34b0-4c24-8588-8294f08406e7\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":869,\"article\":178713},{\"id\":197,\"name\":\"daily menu article\",\"cashBoxArticle\":\"45476da6-84b7-4129-9b34-2251595ebbc5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:14.446Z\",\"updated\":\"2025-12-04T15:28:41.885Z\",\"deleted\":null,\"uuid\":\"c7adf9dc-8c07-4ce7-a6c4-617415490f60\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":869,\"article\":178717},{\"id\":202,\"name\":\"daily menu article\",\"cashBoxArticle\":\"c64fa08d-b4a0-4510-b512-2c299d373d04\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:14.446Z\",\"updated\":\"2025-12-04T15:28:56.409Z\",\"deleted\":null,\"uuid\":\"8f9db34e-5bcb-48fb-8e48-619b9057e48d\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":869,\"article\":178716},{\"id\":201,\"name\":\"daily menu article\",\"cashBoxArticle\":\"34056ce2-ff61-4c94-9aba-b1f364fa9772\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:14.446Z\",\"updated\":\"2025-12-04T15:29:03.525Z\",\"deleted\":null,\"uuid\":\"67aabc9f-67db-4dd1-a5ca-d693b9e40861\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":869,\"article\":178718},{\"id\":200,\"name\":\"daily menu article\",\"cashBoxArticle\":\"dfaa35f6-4604-4e14-9f86-1862a05a5881\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:14.446Z\",\"updated\":\"2025-12-04T15:29:07.074Z\",\"deleted\":null,\"uuid\":\"20a8dc47-4a6e-4ea7-829b-6596f25cc00f\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":869,\"article\":178714},{\"id\":199,\"name\":\"daily menu article\",\"cashBoxArticle\":\"d9336280-1d15-440c-81a1-e47d6766c475\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:14.446Z\",\"updated\":\"2025-12-04T15:29:14.844Z\",\"deleted\":null,\"uuid\":\"81e5faa7-3e11-4383-bc7d-bfb15c86bf37\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":869,\"article\":178719},{\"id\":198,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f9445453-fe41-42a3-b1ed-9aedafc6852c\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:14.446Z\",\"updated\":\"2025-12-04T15:29:25.307Z\",\"deleted\":null,\"uuid\":\"38896fec-c358-4605-af47-a258d203f21c\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":869,\"article\":178715}],\"extras\":[],\"created\":\"2025-11-05T10:47:38.217Z\",\"updated\":\"2025-12-09T09:31:02.431Z\",\"deleted\":null,\"uuid\":\"644ade5f-6e46-4aec-a613-9fbd94389ae2\",\"type\":3,\"enabled\":true,\"visibility\":2,\"paymentTypes\":[\"hobex\"],\"allowPreorder\":false,\"preorderDelta\":0,\"orderLeadTime\":0,\"preorderThreshold\":3600,\"preorderFutureLimit\":86400,\"cancellationCutoff\":0,\"slotDelta\":0,\"slotInterval\":900,\"slotMaximumCount\":1000,\"minimumOrderValue\":\"0.00\",\"takeAway\":false,\"commentsEnabled\":false,\"enableCourses\":false,\"maxDrinkItems\":0,\"maxFoodItems\":0,\"tipEnabled\":false,\"tipMode\":1,\"defaultTip\":0,\"priceLevel\":-1,\"onlyManagedUsers\":false,\"needsAuthentication\":true,\"showQrCode\":false,\"allowCancel\":false,\"skipCustomerInformation\":true},{\"id\":870,\"name\":\"pickup\",\"deliveryFees\":[],\"articles\":[{\"id\":97,\"name\":\"daily menu article\",\"cashBoxArticle\":\"6208ac78-6cde-46ff-b998-f1afdc7e34b7\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"bad090e4-0890-40ef-9b3a-df3e73841fb7\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":870,\"article\":176511},{\"id\":98,\"name\":\"daily menu article\",\"cashBoxArticle\":\"c7e4c59f-2566-40c3-9497-d0e65f5b2631\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"18770bdc-b373-4f05-b800-dc844bc89488\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":870,\"article\":176512},{\"id\":99,\"name\":\"daily menu article\",\"cashBoxArticle\":\"fb901cf3-d9f1-4df4-886d-998dca8371ef\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"62f8fcb0-f90c-4222-be3c-bc4bcc7f65e1\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":870,\"article\":176513},{\"id\":100,\"name\":\"daily menu article\",\"cashBoxArticle\":\"338a80de-35ec-40ef-a1e7-6fc1dafa2fb1\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"a3f17dfc-c87b-450c-954e-3033124cc3a1\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":870,\"article\":176515},{\"id\":101,\"name\":\"daily menu article\",\"cashBoxArticle\":\"3ce62d9d-c75b-4cce-97e5-fabe2db7f243\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"c04a7e1c-f7c1-4581-b8e5-6971a1fbb1c7\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":870,\"article\":176516},{\"id\":102,\"name\":\"daily menu article\",\"cashBoxArticle\":\"45a64da3-8973-4f49-94d5-ce293afde7e3\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"aa6afb17-19a4-4860-bbaa-2b3adc79e54e\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":870,\"article\":176517},{\"id\":103,\"name\":\"daily menu article\",\"cashBoxArticle\":\"690b154a-95b1-4563-a73d-06735c62f203\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:48:55.453Z\",\"updated\":\"2025-11-27T07:48:55.453Z\",\"deleted\":null,\"uuid\":\"0d7301f5-0e3a-44ab-ad17-04c88522b643\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":870,\"article\":176514}],\"extras\":[],\"created\":\"2025-11-05T12:02:09.942Z\",\"updated\":\"2025-12-11T16:13:14.191Z\",\"deleted\":null,\"uuid\":\"f40dded5-472d-4dd0-ac75-760e2f4714b5\",\"type\":1,\"enabled\":true,\"visibility\":3,\"paymentTypes\":[\"payroll\"],\"allowPreorder\":true,\"preorderDelta\":0,\"orderLeadTime\":2700,\"preorderThreshold\":10800,\"preorderFutureLimit\":518400,\"cancellationCutoff\":2700,\"slotDelta\":0,\"slotInterval\":5400,\"slotMaximumCount\":1000,\"minimumOrderValue\":\"0.00\",\"takeAway\":false,\"commentsEnabled\":true,\"enableCourses\":false,\"maxDrinkItems\":0,\"maxFoodItems\":0,\"tipEnabled\":false,\"tipMode\":1,\"defaultTip\":0,\"priceLevel\":-1,\"onlyManagedUsers\":true,\"needsAuthentication\":true,\"showQrCode\":true,\"allowCancel\":true,\"skipCustomerInformation\":true},{\"id\":877,\"name\":\"dine in\",\"deliveryFees\":[],\"articles\":[],\"extras\":[],\"created\":\"2025-11-26T15:50:23.745Z\",\"updated\":\"2025-11-26T20:46:49.512Z\",\"deleted\":null,\"uuid\":\"6ddbb05f-40d6-4ee5-8fc6-85696db53c90\",\"type\":0,\"enabled\":false,\"visibility\":1,\"paymentTypes\":[\"cash\"],\"allowPreorder\":false,\"preorderDelta\":0,\"orderLeadTime\":0,\"preorderThreshold\":3600,\"preorderFutureLimit\":86400,\"cancellationCutoff\":0,\"slotDelta\":0,\"slotInterval\":900,\"slotMaximumCount\":5,\"minimumOrderValue\":\"0.00\",\"takeAway\":false,\"commentsEnabled\":true,\"enableCourses\":false,\"maxDrinkItems\":0,\"maxFoodItems\":0,\"tipEnabled\":true,\"tipMode\":1,\"defaultTip\":0,\"priceLevel\":-1,\"onlyManagedUsers\":false,\"needsAuthentication\":false,\"showQrCode\":false,\"allowCancel\":false,\"skipCustomerInformation\":true},{\"id\":857,\"name\":\"canteen\",\"deliveryFees\":[],\"articles\":[{\"id\":212,\"name\":\"daily menu article\",\"cashBoxArticle\":\"fa2e5d0f-f368-4bf3-b4cd-15fc02efb672\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T07:34:16.562Z\",\"updated\":\"2026-01-29T07:34:16.562Z\",\"deleted\":null,\"uuid\":\"c464f85e-285b-4003-8d08-1678f9237b48\",\"articleType\":12,\"fee\":7,\"limit\":0,\"configuration\":857,\"article\":182373},{\"id\":90,\"name\":\"daily menu article\",\"cashBoxArticle\":\"6208ac78-6cde-46ff-b998-f1afdc7e34b7\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:40:38.594Z\",\"updated\":\"2025-11-27T07:40:38.594Z\",\"deleted\":null,\"uuid\":\"88c628cb-3837-4d6d-8c63-a6e0b28f0341\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":176511},{\"id\":91,\"name\":\"daily menu article\",\"cashBoxArticle\":\"c7e4c59f-2566-40c3-9497-d0e65f5b2631\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:40:59.156Z\",\"updated\":\"2025-11-27T07:40:59.156Z\",\"deleted\":null,\"uuid\":\"07921176-b8d0-4686-8f7b-757ca2261d01\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":176512},{\"id\":92,\"name\":\"daily menu article\",\"cashBoxArticle\":\"fb901cf3-d9f1-4df4-886d-998dca8371ef\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:41:23.612Z\",\"updated\":\"2025-11-27T07:41:23.612Z\",\"deleted\":null,\"uuid\":\"405371a9-fc20-418d-af81-91519d37039e\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":176513},{\"id\":94,\"name\":\"daily menu article\",\"cashBoxArticle\":\"338a80de-35ec-40ef-a1e7-6fc1dafa2fb1\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:42:07.469Z\",\"updated\":\"2025-11-27T07:42:13.560Z\",\"deleted\":null,\"uuid\":\"81864d63-0ff4-41a6-9f42-4cbf72eb23d7\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":176515},{\"id\":95,\"name\":\"daily menu article\",\"cashBoxArticle\":\"3ce62d9d-c75b-4cce-97e5-fabe2db7f243\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:42:29.813Z\",\"updated\":\"2025-11-27T07:42:29.813Z\",\"deleted\":null,\"uuid\":\"995fcf59-f8b6-4b84-9cba-8675ffaca9f2\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":176516},{\"id\":96,\"name\":\"daily menu article\",\"cashBoxArticle\":\"45a64da3-8973-4f49-94d5-ce293afde7e3\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:42:46.380Z\",\"updated\":\"2025-11-27T07:42:46.380Z\",\"deleted\":null,\"uuid\":\"efdb1d47-f223-4c2e-bc14-c60c83b483a3\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":176517},{\"id\":93,\"name\":\"daily menu article\",\"cashBoxArticle\":\"690b154a-95b1-4563-a73d-06735c62f203\",\"cashBoxArticleType\":1,\"created\":\"2025-11-27T07:41:40.955Z\",\"updated\":\"2025-11-27T07:42:59.835Z\",\"deleted\":null,\"uuid\":\"f0eaafd2-f497-4a4b-b998-0bfe50018143\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":176514},{\"id\":215,\"name\":\"daily menu article\",\"cashBoxArticle\":\"1dda4bed-3306-4263-bd3c-f1d8c87f340a\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T07:35:40.880Z\",\"updated\":\"2026-01-29T07:36:32.707Z\",\"deleted\":null,\"uuid\":\"f74b47f0-5fbd-4dd7-b547-80dd2ed0bbc4\",\"articleType\":12,\"fee\":7,\"limit\":0,\"configuration\":857,\"article\":182376},{\"id\":154,\"name\":\"daily menu article\",\"cashBoxArticle\":\"e6966a1c-ba45-450f-b007-f5f41cb79a49\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"7c66ef66-9120-454f-b0a7-cb07b8ed4716\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178720},{\"id\":155,\"name\":\"daily menu article\",\"cashBoxArticle\":\"04cb9490-ee5c-40f6-afa8-6837dae888a2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"3cfc10e1-2853-4c0d-88a6-ca38ef085e92\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178724},{\"id\":156,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b65661dd-6c62-4a23-9384-c933c8ed424e\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"6f6ef837-746d-4a4a-88f5-c08d8f17aa6f\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178722},{\"id\":157,\"name\":\"daily menu article\",\"cashBoxArticle\":\"725d1aa6-f9f5-4e14-951c-90543d961b12\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"59553dc0-2fb6-4147-92d5-b6a84d73882b\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178726},{\"id\":158,\"name\":\"daily menu article\",\"cashBoxArticle\":\"868291bd-490d-47ef-b5be-e2002ce3bd10\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"95b19f9f-f0cf-46cd-8826-5a3bbd6de440\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178721},{\"id\":159,\"name\":\"daily menu article\",\"cashBoxArticle\":\"cfdd8d4b-eb49-4b00-9db4-045e293dc0c0\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"cb54a774-795e-4e98-b8a3-5d8320c4a4ad\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178725},{\"id\":160,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f4b084f9-5fd4-494c-aabf-27199e1ecece\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:23:18.993Z\",\"deleted\":null,\"uuid\":\"7ba2a414-2a2f-4d6e-80b1-7bf9d6813eff\",\"articleType\":12,\"fee\":1,\"limit\":0,\"configuration\":857,\"article\":178723},{\"id\":213,\"name\":\"daily menu article\",\"cashBoxArticle\":\"10e9d99d-d050-4565-8c02-1e9589fd2933\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T07:35:03.186Z\",\"updated\":\"2026-01-29T07:36:11.477Z\",\"deleted\":null,\"uuid\":\"124aa4c9-e68e-45a2-ae9c-c7333bd35c3e\",\"articleType\":12,\"fee\":7,\"limit\":0,\"configuration\":857,\"article\":182374},{\"id\":217,\"name\":\"daily menu article\",\"cashBoxArticle\":\"d2814527-95bc-4ee8-8bda-e5fe2427bd47\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T08:21:51.364Z\",\"updated\":\"2026-01-29T08:21:51.364Z\",\"deleted\":null,\"uuid\":\"e0ce3a5a-68d1-499f-a352-581eb9b4cfe3\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":182378},{\"id\":176,\"name\":\"daily menu article\",\"cashBoxArticle\":\"765cc903-2695-4bba-bed2-4f4078f294c2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:56.604Z\",\"deleted\":null,\"uuid\":\"9e52e37f-f5df-40c5-9c6a-988a95b6bf45\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178745},{\"id\":177,\"name\":\"daily menu article\",\"cashBoxArticle\":\"e4d4157c-4f4a-4701-b1f9-356326c1612d\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:58.444Z\",\"deleted\":null,\"uuid\":\"dc93a7eb-68d9-4730-a240-b3d972d1fc14\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178743},{\"id\":178,\"name\":\"daily menu article\",\"cashBoxArticle\":\"21686838-0e22-4915-a4a0-e38b3bb43635\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:00.602Z\",\"deleted\":null,\"uuid\":\"3590c0eb-98a7-4c38-a204-2ed4e66242fb\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178747},{\"id\":161,\"name\":\"daily menu article\",\"cashBoxArticle\":\"48e86ff1-7b6d-46f7-91a0-346de93fb855\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:39.892Z\",\"deleted\":null,\"uuid\":\"30d94707-2c5d-4993-af73-a6b45b8644a4\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178727},{\"id\":162,\"name\":\"daily menu article\",\"cashBoxArticle\":\"695bb5f6-cbbb-486a-ad3b-9158dc055f98\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:43.402Z\",\"deleted\":null,\"uuid\":\"699c4743-6807-4dec-a980-b7a9ac22a85b\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178731},{\"id\":163,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5207e0e3-06a3-4e15-9e8f-aca634005fad\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:45.566Z\",\"deleted\":null,\"uuid\":\"a4cbc519-f857-4890-bbcd-8ece056bdd79\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178729},{\"id\":164,\"name\":\"daily menu article\",\"cashBoxArticle\":\"701c6989-194c-400c-b590-77734f9a5479\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:47.611Z\",\"deleted\":null,\"uuid\":\"b70a3f59-8fe0-4a73-84ce-08ebf349f103\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178733},{\"id\":165,\"name\":\"daily menu article\",\"cashBoxArticle\":\"a33ce321-1943-430e-a435-b83f87ff1080\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:49.571Z\",\"deleted\":null,\"uuid\":\"ffe276a4-d297-42a3-af78-aee06aab2521\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178728},{\"id\":166,\"name\":\"daily menu article\",\"cashBoxArticle\":\"7bf71611-3eb3-4784-bd29-b38ebd1821f9\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:51.532Z\",\"deleted\":null,\"uuid\":\"497cdb95-5e8c-415d-8098-8ffba3cc83e2\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178732},{\"id\":167,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8eb70af1-5cc6-4f30-be0a-3bf2f91321b3\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:39:53.803Z\",\"deleted\":null,\"uuid\":\"b05cb25d-cde4-43f5-a1dd-90e711c21e34\",\"articleType\":12,\"fee\":2,\"limit\":0,\"configuration\":857,\"article\":178730},{\"id\":168,\"name\":\"daily menu article\",\"cashBoxArticle\":\"03672c2e-0ab3-4efd-80ad-564a910c69dc\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:16.745Z\",\"deleted\":null,\"uuid\":\"82fb659b-4cd1-412b-a55c-91860a362ee7\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178734},{\"id\":169,\"name\":\"daily menu article\",\"cashBoxArticle\":\"ec147dfa-d0c5-446e-83ab-1e0f1c6a6f69\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:18.969Z\",\"deleted\":null,\"uuid\":\"325e14dd-d242-47ad-980c-019f4d598a4f\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178738},{\"id\":170,\"name\":\"daily menu article\",\"cashBoxArticle\":\"4ceb0a3e-973a-4b82-ae4b-2592c5ef43eb\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:21.038Z\",\"deleted\":null,\"uuid\":\"91583c15-7708-41a4-b92b-f64c7dcc9d69\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178736},{\"id\":171,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5727d322-cae6-48e8-b1b1-0786d37935b1\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:22.949Z\",\"deleted\":null,\"uuid\":\"0f2101f0-5228-4c2b-95d3-3d2997074567\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178740},{\"id\":172,\"name\":\"daily menu article\",\"cashBoxArticle\":\"88b59af4-f3fb-45a7-ba0f-9fce88d4710f\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:25.052Z\",\"deleted\":null,\"uuid\":\"28dbc585-1068-4e9b-9eda-f50edcd85b53\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178735},{\"id\":173,\"name\":\"daily menu article\",\"cashBoxArticle\":\"86a71457-dbfd-452d-a41c-d9cbbdf15cdf\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:27.368Z\",\"deleted\":null,\"uuid\":\"28da54aa-9df3-42e3-a053-216d7502c8a2\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178739},{\"id\":174,\"name\":\"daily menu article\",\"cashBoxArticle\":\"1bd71fe6-b8fc-4c84-9780-96dedf6870f8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:32.661Z\",\"deleted\":null,\"uuid\":\"f573d3e0-230b-4b3e-a4d5-650e8537aba8\",\"articleType\":12,\"fee\":3,\"limit\":0,\"configuration\":857,\"article\":178737},{\"id\":175,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b266cf85-960b-4f13-b327-e11929151e00\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:40:54.602Z\",\"deleted\":null,\"uuid\":\"01c247d3-2320-496c-a282-78f9a40bd3a2\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178741},{\"id\":179,\"name\":\"daily menu article\",\"cashBoxArticle\":\"ea309dc8-38bf-4e78-9b51-7c9a7efb2763\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:02.533Z\",\"deleted\":null,\"uuid\":\"187afcb5-caba-45b6-95fb-a134a6eeeaca\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178742},{\"id\":180,\"name\":\"daily menu article\",\"cashBoxArticle\":\"1a7386e3-3d4a-4261-b235-565fdf268863\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:04.495Z\",\"deleted\":null,\"uuid\":\"fce8c9a5-8263-46a6-aa4c-416683e30cad\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178746},{\"id\":181,\"name\":\"daily menu article\",\"cashBoxArticle\":\"316e731d-dd8e-47b4-bb25-9d4f2e641151\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:06.469Z\",\"deleted\":null,\"uuid\":\"bd3373e0-e97f-460d-848a-932c875c0128\",\"articleType\":12,\"fee\":4,\"limit\":0,\"configuration\":857,\"article\":178744},{\"id\":182,\"name\":\"daily menu article\",\"cashBoxArticle\":\"42ec29ac-456c-4ca2-aea3-20dfbf3a28b5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:26.819Z\",\"deleted\":null,\"uuid\":\"1eab4039-75be-4eab-ba7f-686f0a8d577f\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178748},{\"id\":183,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8e52c14c-6c2a-444c-adde-916107f42f1a\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:29.984Z\",\"deleted\":null,\"uuid\":\"6875dce5-deed-46b4-8b27-45408678bf63\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178752},{\"id\":184,\"name\":\"daily menu article\",\"cashBoxArticle\":\"3c7f4116-12be-4d10-9369-a61f8b81e387\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:33.032Z\",\"deleted\":null,\"uuid\":\"d0115b60-e5de-43ad-8197-7e16f5c909a4\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178750},{\"id\":185,\"name\":\"daily menu article\",\"cashBoxArticle\":\"13203b86-7ce6-4e2d-b12a-a159100e6e0e\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:35.253Z\",\"deleted\":null,\"uuid\":\"316dbee9-3bac-4c31-bc41-7e924bd6ccef\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178754},{\"id\":186,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f6d5bd6e-2e14-4930-8f27-be027fa477b2\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:37.173Z\",\"deleted\":null,\"uuid\":\"21aff393-5ee1-4248-bd0b-a2431fe385f5\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178749},{\"id\":187,\"name\":\"daily menu article\",\"cashBoxArticle\":\"dea4b5d4-7a2c-4565-94ae-9b5dc2cc5ed8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:39.060Z\",\"deleted\":null,\"uuid\":\"85d77712-172f-4b9c-b86d-78124752003e\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178753},{\"id\":188,\"name\":\"daily menu article\",\"cashBoxArticle\":\"95ecb237-e497-48f3-ab7e-0b0f8730f02d\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:41:41.453Z\",\"deleted\":null,\"uuid\":\"4bbf06be-9325-4556-9a26-402178d3717f\",\"articleType\":12,\"fee\":5,\"limit\":0,\"configuration\":857,\"article\":178751},{\"id\":189,\"name\":\"daily menu article\",\"cashBoxArticle\":\"5ec70cab-6b4c-410f-bb91-60a61fd8bbdd\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:02.072Z\",\"deleted\":null,\"uuid\":\"982aea4b-fc8b-4196-a569-b72454fe336c\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178755},{\"id\":190,\"name\":\"daily menu article\",\"cashBoxArticle\":\"704e5191-4dd6-4fd9-b99c-233d44de5ff3\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:04.204Z\",\"deleted\":null,\"uuid\":\"c6e61b0b-0d77-48bf-852a-7d453943bb29\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178759},{\"id\":191,\"name\":\"daily menu article\",\"cashBoxArticle\":\"b270abf7-a35c-4eca-8d62-5fdb5ceaf0a8\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:06.105Z\",\"deleted\":null,\"uuid\":\"54c6ffbc-445a-454c-b29e-cd446949f022\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178757},{\"id\":192,\"name\":\"daily menu article\",\"cashBoxArticle\":\"7810ea32-e693-4716-8f2d-552fa6bb0c45\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:08.040Z\",\"deleted\":null,\"uuid\":\"bf625fdf-5cfd-4f1c-9606-84e158392f7d\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178761},{\"id\":193,\"name\":\"daily menu article\",\"cashBoxArticle\":\"59b371ea-ab48-4efc-9b63-04cf3df9ffc5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:10.373Z\",\"deleted\":null,\"uuid\":\"5e3663c0-5b0c-4c79-86f4-11a92c549060\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178756},{\"id\":194,\"name\":\"daily menu article\",\"cashBoxArticle\":\"784431e8-2a34-4230-9d95-dbe9c6498fb4\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:12.505Z\",\"deleted\":null,\"uuid\":\"e8823a46-9175-49f3-b9eb-19c4b7dfa745\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178760},{\"id\":195,\"name\":\"daily menu article\",\"cashBoxArticle\":\"899690c3-8ef1-4b06-8f3c-f24a3afac9c1\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:23:18.993Z\",\"updated\":\"2025-12-04T15:42:14.557Z\",\"deleted\":null,\"uuid\":\"f408f867-2c35-4fb2-86d4-8150b7804492\",\"articleType\":12,\"fee\":6,\"limit\":0,\"configuration\":857,\"article\":178758},{\"id\":203,\"name\":\"daily menu article\",\"cashBoxArticle\":\"8b9dad61-ca9c-4fbb-a7fe-a5cecaba50d5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:36.991Z\",\"deleted\":null,\"uuid\":\"1dcf66fc-9ccf-4946-a764-058643b1d317\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178713},{\"id\":204,\"name\":\"daily menu article\",\"cashBoxArticle\":\"45476da6-84b7-4129-9b34-2251595ebbc5\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:39.140Z\",\"deleted\":null,\"uuid\":\"958e0de1-13bf-41e7-aa45-4b6852b0a01b\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178717},{\"id\":205,\"name\":\"daily menu article\",\"cashBoxArticle\":\"f9445453-fe41-42a3-b1ed-9aedafc6852c\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:41.593Z\",\"deleted\":null,\"uuid\":\"f96bb0c4-ac62-44e1-886f-075a1925bfb5\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178715},{\"id\":206,\"name\":\"daily menu article\",\"cashBoxArticle\":\"d9336280-1d15-440c-81a1-e47d6766c475\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:43.685Z\",\"deleted\":null,\"uuid\":\"6b8023f5-e632-44de-8e82-36276681b861\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178719},{\"id\":207,\"name\":\"daily menu article\",\"cashBoxArticle\":\"dfaa35f6-4604-4e14-9f86-1862a05a5881\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:45.648Z\",\"deleted\":null,\"uuid\":\"4e1413ea-28b0-43c3-8c4b-f7127cc24d56\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178714},{\"id\":208,\"name\":\"daily menu article\",\"cashBoxArticle\":\"34056ce2-ff61-4c94-9aba-b1f364fa9772\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:48.028Z\",\"deleted\":null,\"uuid\":\"130cbb68-cb72-4340-815b-822d5fdfec71\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178718},{\"id\":209,\"name\":\"daily menu article\",\"cashBoxArticle\":\"c64fa08d-b4a0-4510-b512-2c299d373d04\",\"cashBoxArticleType\":1,\"created\":\"2025-12-04T15:27:36.081Z\",\"updated\":\"2025-12-04T15:42:50.033Z\",\"deleted\":null,\"uuid\":\"ddadf870-a95a-4367-bbba-8a06190c0d72\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":178716},{\"id\":214,\"name\":\"daily menu article\",\"cashBoxArticle\":\"262e5a5e-707b-462c-94b1-c8cbe639a8f9\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T07:35:21.770Z\",\"updated\":\"2026-01-29T07:36:22.914Z\",\"deleted\":null,\"uuid\":\"e67d5993-fabb-403d-b799-5e1e6032cf5e\",\"articleType\":12,\"fee\":7,\"limit\":0,\"configuration\":857,\"article\":182375},{\"id\":216,\"name\":\"daily menu article\",\"cashBoxArticle\":\"ddf8192f-2666-4db9-8b92-4002a3b0d120\",\"cashBoxArticleType\":1,\"created\":\"2026-01-29T08:21:22.610Z\",\"updated\":\"2026-01-29T08:21:32.419Z\",\"deleted\":null,\"uuid\":\"298378d5-349d-4559-be1f-708789355ab5\",\"articleType\":12,\"fee\":0,\"limit\":0,\"configuration\":857,\"article\":182377}],\"extras\":[{\"type\":2,\"text\":\"Ablauf / Process\"},{\"type\":3,\"text\":\"Deine Bestellung kann per Mitarbeiterkarte abgeholt werden. | Your order can be picked up using your employee card.\"}],\"created\":\"2025-10-20T07:37:10.129Z\",\"updated\":\"2026-01-27T14:43:56.933Z\",\"deleted\":null,\"uuid\":\"a1a52834-1e37-4e8a-be8c-a78b4700825a\",\"type\":7,\"enabled\":true,\"visibility\":1,\"paymentTypes\":[\"payroll\"],\"allowPreorder\":true,\"preorderDelta\":3600,\"orderLeadTime\":0,\"preorderThreshold\":3600,\"preorderFutureLimit\":1382400,\"cancellationCutoff\":3600,\"slotDelta\":0,\"slotInterval\":10800,\"slotMaximumCount\":5000,\"minimumOrderValue\":\"0.00\",\"takeAway\":false,\"commentsEnabled\":true,\"enableCourses\":false,\"maxDrinkItems\":0,\"maxFoodItems\":0,\"tipEnabled\":false,\"tipMode\":1,\"defaultTip\":0,\"priceLevel\":-1,\"onlyManagedUsers\":true,\"needsAuthentication\":true,\"showQrCode\":true,\"allowCancel\":true,\"skipCustomerInformation\":true}],\"needsServiceNotification\":false,\"enableCoupons\":false}},\"menucategories\":{\"entities\":{\"89c73f18-f3c8-45f3-8227-66a98db86396\":{\"id\":11452,\"items\":[{\"id\":178714,\"price\":5.5,\"name\":\"M1 Herzhaftes\",\"description\":\"Erdäpfelrahmsuppe L,M,C,G, potato soup, Acht Schätze (Rind) L,M,C,F,O, mit Eiernudeln A,C, Kuchen A,C,G,H,O, Silced beef asia with egg noodles\",\"allergens\":\"\",\"nutritionFacts\":\"\",\"hashId\":\"ar_7BzGM\",\"imageThumbnail\":null,\"modifiers\":[],\"created\":\"2025-12-04T13:02:07.266Z\",\"updated\":\"2026-02-03T13:15:20.845Z\",\"deleted\":null,\"uuid\":\"4064152d-6e82-4f4d-9bea-0936ab307b1c\",\"image\":null,\"number\":\"dfaa35f6-4604-4e14-9f86-1862a05a5881\",\"type\":1,\"vat\":\"10.00\",\"points\":\"0.00\",\"minimumAmount\":\"0\",\"availableAmount\":\"314\",\"amountTracking\":true,\"dispenserId\":\"\",\"courseGroup\":null},{\"id\":178728,\"price\":5.5,\"name\":\"M3 Süß\",\"description\":\"Erdäpfelrahmsuppe L,M,C,G, potato soup, Milchrahmstrudel mit Vanillesauce A,C,G, milk cream strudel with vanilla sauce\",\"allergens\":\"\",\"nutritionFacts\":\"\",\"hashId\":\"ar_KalR9\",\"imageThumbnail\":null,\"modifiers\":[],\"created\":\"2025-12-04T13:02:07.268Z\",\"updated\":\"2026-02-03T13:14:37.728Z\",\"deleted\":null,\"uuid\":\"12b75bc5-88ab-4df3-ad9b-00b864729ce3\",\"image\":null,\"number\":\"a33ce321-1943-430e-a435-b83f87ff1080\",\"type\":1,\"vat\":\"10.00\",\"points\":\"0.00\",\"minimumAmount\":\"0\",\"availableAmount\":\"69\",\"amountTracking\":true,\"dispenserId\":\"\",\"courseGroup\":null},{\"id\":178735,\"price\":5.5,\"name\":\"M4 Ginko vegan\",\"description\":\"Tomaten Kokoscremesuppe, tomato coconut soup, Hirselaibchen A, mit Fisolen Karottengemüse u. Schnittlauchdip F,M, millet patties with fisole vegetable\",\"allergens\":\"\",\"nutritionFacts\":\"\",\"hashId\":\"ar_YLxwb\",\"imageThumbnail\":null,\"modifiers\":[],\"created\":\"2025-12-04T13:02:07.269Z\",\"updated\":\"2026-02-02T06:03:28.072Z\",\"deleted\":null,\"uuid\":\"6b3017a7-c950-4428-a474-8c61a819402d\",\"image\":null,\"number\":\"88b59af4-f3fb-45a7-ba0f-9fce88d4710f\",\"type\":1,\"vat\":\"10.00\",\"points\":\"0.00\",\"minimumAmount\":\"0\",\"availableAmount\":\"0\",\"amountTracking\":true,\"dispenserId\":\"\",\"courseGroup\":null},{\"id\":178742,\"price\":5.5,\"name\":\"M5 Salat mit Gebäck\",\"description\":\"Erdäpflerahmsuppe G,L,M, Salatteller mit Prosciutto und Parmesan, salad with prosciutto and parmesan\",\"allergens\":\"\",\"nutritionFacts\":\"\",\"hashId\":\"ar_Qydpp\",\"imageThumbnail\":null,\"modifiers\":[],\"created\":\"2025-12-04T13:02:07.270Z\",\"updated\":\"2026-02-03T13:16:43.665Z\",\"deleted\":null,\"uuid\":\"1c1a14dd-cf4f-4355-a4ba-cb8543ae7e5a\",\"image\":null,\"number\":\"ea309dc8-38bf-4e78-9b51-7c9a7efb2763\",\"type\":1,\"vat\":\"10.00\",\"points\":\"0.00\",\"minimumAmount\":\"0\",\"availableAmount\":\"0\",\"amountTracking\":true,\"dispenserId\":\"\",\"courseGroup\":null},{\"id\":178749,\"price\":3,\"name\":\"M6 Suppe, kleiner Salat + Dessert D\",\"description\":\"Suppe, kleiner Salat + Dessert\",\"allergens\":\"\",\"nutritionFacts\":\"\",\"hashId\":\"ar_Nyoxb\",\"imageThumbnail\":null,\"modifiers\":[],\"created\":\"2025-12-04T13:02:07.271Z\",\"updated\":\"2026-02-03T13:17:39.906Z\",\"deleted\":null,\"uuid\":\"c2956eb9-6ee0-4e65-96a7-b8b0615f314c\",\"image\":null,\"number\":\"f6d5bd6e-2e14-4930-8f27-be027fa477b2\",\"type\":1,\"vat\":\"10.00\",\"points\":\"0.00\",\"minimumAmount\":\"0\",\"availableAmount\":\"37\",\"amountTracking\":true,\"dispenserId\":\"\",\"courseGroup\":null},{\"id\":178756,\"price\":4.3,\"name\":\"M7F Kleines Hauptspeisenmenü DI 2\",\"description\":\"Erdäpflerahmsuppe G,L,M, potato soup, kleine Hauptspeise von Menü 1\",\"allergens\":\"\",\"nutritionFacts\":\"\",\"hashId\":\"ar_BzGr7\",\"imageThumbnail\":null,\"modifiers\":[],\"created\":\"2025-12-04T13:02:07.272Z\",\"updated\":\"2026-02-03T13:18:12.181Z\",\"deleted\":null,\"uuid\":\"6ad46891-a26b-4e22-9a34-ae1cbb225788\",\"image\":null,\"number\":\"59b371ea-ab48-4efc-9b63-04cf3df9ffc5\",\"type\":1,\"vat\":\"10.00\",\"points\":\"0.00\",\"minimumAmount\":\"0\",\"availableAmount\":\"20\",\"amountTracking\":true,\"dispenserId\":\"\",\"courseGroup\":null},{\"id\":178721,\"price\":5.5,\"name\":\"M2 Gourmetteller\",\"description\":\"Erdäpflerahmsuppe G,L,M, potato soup, Selschfleischknödel mit Sauerkraut L,M,C,O, smoked meat dumpling with carbbage\",\"allergens\":\"\",\"nutritionFacts\":\"\",\"hashId\":\"ar_ZGb2V\",\"imageThumbnail\":null,\"modifiers\":[],\"created\":\"2025-12-04T13:02:07.267Z\",\"updated\":\"2026-02-03T13:13:23.315Z\",\"deleted\":null,\"uuid\":\"32753644-de1b-4d68-82d1-001cbf5beeab\",\"image\":null,\"number\":\"868291bd-490d-47ef-b5be-e2002ce3bd10\",\"type\":1,\"vat\":\"10.00\",\"points\":\"0.00\",\"minimumAmount\":\"0\",\"availableAmount\":\"256\",\"amountTracking\":true,\"dispenserId\":\"\",\"courseGroup\":null}],\"name\":\"Menü\",\"description\":\"Menü\",\"children\":[],\"created\":\"2025-11-26T13:34:51.236Z\",\"updated\":\"2026-01-29T07:38:42.691Z\",\"deleted\":null,\"uuid\":\"89c73f18-f3c8-45f3-8227-66a98db86396\",\"sort\":0,\"image\":null,\"fromHour\":null,\"toHour\":null,\"collapse\":false}},\"ids\":[\"89c73f18-f3c8-45f3-8227-66a98db86396\"],\"loading\":false,\"error\":null,\"ui\":{\"loading\":false,\"updating\":false,\"scrollPosition\":120},\"active\":\"89c73f18-f3c8-45f3-8227-66a98db86396\"},\"course-group\":{\"entities\":{},\"ids\":[],\"loading\":false,\"error\":null}}"} \ No newline at end of file diff --git a/bessa-openapi.yaml b/bessa-openapi.yaml new file mode 100644 index 0000000..a772064 --- /dev/null +++ b/bessa-openapi.yaml @@ -0,0 +1,483 @@ +openapi: 3.0.0 +info: + title: Bessa API + version: 1.0.0 + description: | + Unofficial API documentation for Bessa (web.bessa.app) based on network traffic analysis. + Targeting the 'knapp-kantine' venue (ID 591). + +servers: + - url: https://api.bessa.app/v1 + description: Production Server + +components: + securitySchemes: + TokenAuth: + type: apiKey + in: header + name: Authorization + description: | + Token-based authentication. + Format: `Token ` + The token can be retrieved from LocalStorage key `AkitaStores` -> `auth.token`. + +security: + - TokenAuth: [] + +paths: + /auth/login/: + post: + summary: User Login + description: Authenticates a user with an employee-based email and password. Requires a Guest Token in the Authorization header. + operationId: login + security: + - TokenAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - email + - password + properties: + email: + type: string + description: User email (e.g., knapp-2041@bessa.app) + example: knapp-2041@bessa.app + password: + type: string + format: password + example: "#kaufi+2406@ESSEN#" + responses: + "200": + description: Successful login + content: + application/json: + schema: + type: object + properties: + key: + type: string + description: Authentication session key (Session Token) + example: dba7d86e83c7f462fd8af96521dea41c4facd8a5 + "400": + description: Invalid credentials + "401": + description: Unauthorized (missing or invalid Guest Token) + + /auth/user/: + get: + summary: Get Current User Details + description: Retrieves the details of the currently authenticated user. + operationId: getCurrentUser + security: + - TokenAuth: [] + responses: + "200": + description: Successful response with user details + content: + application/json: + schema: + type: object + properties: + id: + type: integer + description: User ID + example: 85567 + last_login: + type: string + format: date-time + example: "2026-02-05T13:40:04.251289Z" + created: + type: string + format: date-time + updated: + type: string + format: date-time + email: + type: string + example: "knapp-2041@bessa.app" + first_name: + type: string + description: User's first name + example: "Michael" + last_name: + type: string + description: User's last name + example: "Kaufmann" + locale: + type: string + example: "de_de" + country: + type: string + language: + type: string + example: "de" + profile: + type: string + nullable: true + uuid: + type: string + format: uuid + groups: + type: array + items: + type: string + example: ["Managed"] + date_of_birth: + type: string + format: date + nullable: true + password_changed: + type: string + format: date-time + gender: + type: integer + description: Gender identifier + "401": + description: Unauthorized (missing or invalid token) + + /venues/{venueId}/menu/{menuId}/{date}/: + get: + summary: Get Daily Menu + description: Retrieves the menu for a specific date. + operationId: getDailyMenu + parameters: + - name: venueId + in: path + required: true + description: ID of the venue (e.g., 591 for Knapp Kantine) + schema: + type: integer + example: 591 + - name: menuId + in: path + required: true + description: ID of the menu type (e.g., 7 for certain order types) + schema: + type: integer + example: 7 + - name: date + in: path + required: true + description: Date in YYYY-MM-DD format + schema: + type: string + format: date + example: "2026-02-10" + responses: + "200": + description: Successful response with menu data + content: + application/json: + schema: + type: object + properties: + next: + type: string + nullable: true + previous: + type: string + nullable: true + results: + type: array + items: + type: object + properties: + id: + type: integer + description: ID of the daily menu group + date: + type: string + format: date + venue: + type: integer + items: + type: array + description: List of dishes/items for this menu group + items: + type: object + properties: + id: + type: integer + name: + type: string + description: Name of the dish (e.g., "M1 Herzhaftes") + description: + type: string + description: Detailed description including allergens + price: + type: string + example: "5.50" + allergens: + type: string + uuid: + type: string + available_amount: + type: string + description: Number of items remaining (e.g. "136") + created: + type: string + format: date-time + updated: + type: string + + /venues/{venueId}/menu/dates/: + get: + summary: Get Menu Dates with Orders + description: | + Retrieves menu dates for a venue. When authenticated, includes the user's orders per date. + Used to build the order state map in the wrapper app. + operationId: getMenuDates + parameters: + - name: venueId + in: path + required: true + schema: + type: integer + example: 591 + responses: + "200": + description: Menu dates with orders + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + properties: + date: + type: string + format: date + orders: + type: array + items: + type: object + properties: + id: + type: integer + description: Order ID + order_state: + type: integer + description: "5=transmitted, 8=accepted, 9=cancelled" + total: + type: string + items: + type: array + items: + type: object + properties: + article: + type: integer + description: Article/menu item ID + name: + type: string + price: + type: string + + /user/orders/: + post: + summary: Place an Order + description: | + Creates a new pre-order for a future date. Requires full customer info, + item details, and order metadata. Returns the created order with pickup code. + operationId: placeOrder + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - uuid + - order_type + - items + - venue + - date + - payment_method + - customer + - total + properties: + uuid: + type: string + format: uuid + description: Client-generated unique order ID + created: + type: string + format: date-time + updated: + type: string + format: date-time + order_type: + type: integer + example: 7 + description: Menu/order type (7 = Kantine pre-order) + items: + type: array + items: + type: object + required: [article, name, price, amount] + properties: + article: + type: integer + description: Article ID from menu + example: 182378 + course_group: + type: integer + nullable: true + modifiers: + type: array + items: {} + uuid: + type: string + format: uuid + name: + type: string + example: "M1 W2" + description: + type: string + price: + type: string + example: "4" + amount: + type: integer + example: 1 + vat: + type: string + example: "10.00" + comment: + type: string + table: + type: integer + nullable: true + total: + type: number + example: 4 + tip: + type: number + example: 0 + currency: + type: string + example: "EUR" + venue: + type: integer + example: 591 + states: + type: array + items: {} + order_state: + type: integer + example: 1 + description: Initial state (1 = new) + date: + type: string + format: date-time + description: Order date with pickup time (T10:00:00.000Z) + example: "2026-02-13T10:00:00.000Z" + payment_method: + type: string + example: "payroll" + customer: + type: object + properties: + first_name: + type: string + last_name: + type: string + email: + type: string + newsletter: + type: boolean + preorder: + type: boolean + example: false + delivery_fee: + type: number + example: 0 + cash_box_table_name: + type: string + nullable: true + take_away: + type: boolean + example: false + responses: + "201": + description: Order created successfully + content: + application/json: + schema: + type: object + properties: + id: + type: integer + description: Bessa order ID + example: 1535097 + hash_id: + type: string + example: "o_nqymM" + order_state: + type: integer + description: "5 = transmitted/confirmed" + example: 5 + pickup_code: + type: string + example: "67" + total: + type: string + example: "4.00" + date: + type: string + format: date-time + expires: + type: string + format: date-time + description: Pickup expiry time + "400": + description: Invalid payload + "401": + description: Unauthorized + + /user/orders/{orderId}/cancel/: + patch: + summary: Cancel an Order + description: | + Cancels an existing order by its ID. Sends an empty body. + The order transitions to state 9 (cancelled). + operationId: cancelOrder + parameters: + - name: orderId + in: path + required: true + schema: + type: integer + example: 1535097 + requestBody: + content: + application/json: + schema: + type: object + description: Empty body + responses: + "200": + description: Order cancelled + content: + application/json: + schema: + type: object + properties: + order_id: + type: string + description: Hash ID of the cancelled order + example: "o_nqymM" + state: + type: string + example: "order cancelled." + "400": + description: Order cannot be cancelled + "401": + description: Unauthorized diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..6eec8af --- /dev/null +++ b/data/.gitkeep @@ -0,0 +1 @@ +// Placeholder for data directory diff --git a/debug_friday.js b/debug_friday.js new file mode 100644 index 0000000..57b18ed --- /dev/null +++ b/debug_friday.js @@ -0,0 +1,21 @@ + +const SESS_KEY = 'c3418725e95a9f90e3645cbc846b4d67c7c66131'; +const URL = 'https://web.bessa.app/api/v1/venues/591/menu/7/2026-02-13/'; + +async function run() { + try { + const response = await fetch(URL, { + headers: { + 'Authorization': `Token ${SESS_KEY}`, + 'Accept': 'application/json', + 'X-Client-Version': '3.10.2' + } + }); + const data = await response.json(); + console.log(JSON.stringify(data, null, 2)); + } catch (e) { + console.error(e); + } +} + +run(); diff --git a/debug_user_info.js b/debug_user_info.js new file mode 100644 index 0000000..714053a --- /dev/null +++ b/debug_user_info.js @@ -0,0 +1,58 @@ + +const GUEST_TOKEN = 'c3418725e95a9f90e3645cbc846b4d67c7c66131'; + +// User credentials (I need the user's credentials to test this... wait, I don't have them in plain text, only the hardcoded token in previous steps) +// Verify if I have a valid username/password to test with. +// The user provided a token 'c3418725e...' which is the guest token. +// The user has a session token in local storage from previous steps: 'c3418725e95a9f90e3645cbc846b4d67c7c66131' +// Actually that looks like the GUEST_TOKEN. +// The user logs in with Employee ID and Password. +// I can't test login without credentials. + +// However, I can check if there's a /users/me or /auth/me endpoint that returns user info given a token. +// Let's try fetching /api/v1/users/me with the token I have. + +// Node 18+ has global fetch +// const fetch = require('node-fetch'); + +async function checkMe() { + const token = 'c3418725e95a9f90e3645cbc846b4d67c7c66131'; // Using the token we have + const url = 'https://api.bessa.app/v1/users/me/'; // Guessing the endpoint + + // Or maybe /auth/user/ + + try { + console.log('Testing /users/me/ ...'); + let res = await fetch('https://api.bessa.app/v1/users/me/', { + headers: { + 'Authorization': `Token ${token}`, + 'Accept': 'application/json' + } + }); + if (res.ok) { + console.log(await res.json()); + return; + } else { + console.log(`Failed: ${res.status}`); + } + + console.log('Testing /auth/user/ ...'); + res = await fetch('https://api.bessa.app/v1/auth/user/', { + headers: { + 'Authorization': `Token ${token}`, + 'Accept': 'application/json' + } + }); + if (res.ok) { + console.log(await res.json()); + return; + } else { + console.log(`Failed: ${res.status}`); + } + + } catch (e) { + console.error(e); + } +} + +checkMe(); diff --git a/friday_debug.json b/friday_debug.json new file mode 100644 index 0000000..f836816 --- /dev/null +++ b/friday_debug.json @@ -0,0 +1,37 @@ + + + + + + Bessa Web Shop + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/friday_raw.json b/friday_raw.json new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1050984 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2637 @@ +{ + "name": "kantine-wrapper", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kantine-wrapper", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "dotenv": "^16.4.5", + "express": "^5.2.1", + "puppeteer": "^22.0.0" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^20.11.0", + "tsx": "^4.7.0", + "typescript": "^5.3.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz", + "integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", + "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chromium-bidi": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", + "license": "BSD-3-Clause" + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", + "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.15.0.tgz", + "integrity": "sha512-XjCY1SiSEi1T7iSYuxS82ft85kwDJUS7wj1Z0eGVXKdtr5g4xnVcbjwxhq5xBnpK/E7x1VZZoJDxpjAOasHT4Q==", + "deprecated": "< 24.15.0 is no longer supported", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.3.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1312386", + "puppeteer-core": "22.15.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a26134e --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "kantine-wrapper", + "version": "1.0.0", + "description": "Bessa Knapp-Kantine Menu Scraper", + "main": "dist/index.js", + "type": "module", + "scripts": { + "build": "tsc", + "scrape": "node dist/index.js", + "dev": "tsx src/index.ts", + "analyze": "tsx src/scraper/interactive-analyzer.ts", + "test:api": "tsx src/scraper/api-test.ts", + "server": "tsx src/server.ts", + "type-check": "tsc --noEmit" + }, + "keywords": [ + "scraper", + "puppeteer", + "bessa", + "kantine" + ], + "author": "", + "license": "MIT", + "dependencies": { + "dotenv": "^16.4.5", + "express": "^5.2.1", + "puppeteer": "^22.0.0" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^20.11.0", + "tsx": "^4.7.0", + "typescript": "^5.3.3" + } +} \ No newline at end of file diff --git a/public/app.js b/public/app.js new file mode 100644 index 0000000..0be5b79 --- /dev/null +++ b/public/app.js @@ -0,0 +1,1148 @@ +document.addEventListener('DOMContentLoaded', () => { + // State + let allWeeks = []; + let currentWeekNumber = getISOWeek(new Date()); + let currentYear = new Date().getFullYear(); + let displayMode = 'this-week'; // 'this-week' or 'next-week' + let authToken = sessionStorage.getItem('authToken'); + let currentUser = sessionStorage.getItem('currentUser'); + // orderMap: key = "date_articleId" -> value = [orderId, orderId, ...] + let orderMap = new Map(); + // userFlags: Set of "date_articleId" that are flagged + let userFlags = new Set(); + let eventSource = null; // Long-lived SSE connection + + // DOM Elements + const themeToggle = document.getElementById('theme-toggle'); + const themeIcon = themeToggle.querySelector('.theme-icon'); + const loading = document.getElementById('loading'); + const menuContainer = document.getElementById('menu-container'); + const lastUpdatedBanner = document.getElementById('last-updated-banner'); + const lastUpdatedText = document.getElementById('last-updated-text'); + const btnThisWeek = document.getElementById('btn-this-week'); + const btnNextWeek = document.getElementById('btn-next-week'); + + // Login Elements + const btnLoginOpen = document.getElementById('btn-login-open'); + const btnLoginClose = document.getElementById('btn-login-close'); + const loginModal = document.getElementById('login-modal'); + const loginForm = document.getElementById('login-form'); + const loginError = document.getElementById('login-error'); + const userInfo = document.getElementById('user-info'); + const userIdDisplay = document.getElementById('user-id-display'); + const btnLogout = document.getElementById('btn-logout'); + + // Refresh Elements + const btnRefresh = document.getElementById('btn-refresh'); + const progressModal = document.getElementById('progress-modal'); + const progressFill = document.getElementById('progress-fill'); + const progressPercent = document.getElementById('progress-percent'); + const progressMessage = document.getElementById('progress-message'); + + // === Theme Handling === + const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); + const savedTheme = localStorage.getItem('theme'); + + if (savedTheme === 'dark' || (!savedTheme && prefersDarkScheme.matches)) { + document.documentElement.setAttribute('data-theme', 'dark'); + themeIcon.textContent = 'dark_mode'; + } else { + document.documentElement.setAttribute('data-theme', 'light'); + themeIcon.textContent = 'light_mode'; + } + + themeToggle.addEventListener('click', () => { + const currentTheme = document.documentElement.getAttribute('data-theme'); + const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); + themeIcon.textContent = newTheme === 'dark' ? 'dark_mode' : 'light_mode'; + }); + + // === Navigation handling === + btnThisWeek.addEventListener('click', () => { + if (displayMode !== 'this-week') { + displayMode = 'this-week'; + updateNavState(); + renderVisibleWeeks(); + } + }); + + btnNextWeek.addEventListener('click', () => { + if (displayMode !== 'next-week') { + displayMode = 'next-week'; + updateNavState(); + renderVisibleWeeks(); + } + }); + + function updateNavState() { + if (displayMode === 'this-week') { + btnThisWeek.classList.add('active'); + btnNextWeek.classList.remove('active'); + } else { + btnThisWeek.classList.remove('active'); + btnNextWeek.classList.add('active'); + } + } + + // === Login Handling === + btnLoginOpen.addEventListener('click', () => { + loginModal.classList.remove('hidden'); + loginError.classList.add('hidden'); + loginForm.reset(); + }); + + btnLoginClose.addEventListener('click', () => { + loginModal.classList.add('hidden'); + }); + + window.addEventListener('click', (e) => { + if (e.target === loginModal) { + loginModal.classList.add('hidden'); + } + }); + + loginForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const employeeId = document.getElementById('employee-id').value; + const password = document.getElementById('password').value; + + loginError.classList.add('hidden'); + const submitBtn = loginForm.querySelector('button[type="submit"]'); + const originalText = submitBtn.textContent; + submitBtn.disabled = true; + submitBtn.textContent = 'Logging in...'; + + try { + const response = await fetch('/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ employeeId, password }) + }); + + const data = await response.json(); + + if (response.ok) { + authToken = data.key; + currentUser = employeeId; + + // Save to session storage + sessionStorage.setItem('authToken', data.key); + sessionStorage.setItem('currentUser', employeeId); + + if (data.firstName) { + sessionStorage.setItem('firstName', data.firstName); + } + if (data.lastName) { + sessionStorage.setItem('lastName', data.lastName); + } + + // Update UI + updateAuthUI(); + loginModal.classList.add('hidden'); + + // Load user specific data + fetchOrders(); + fetchFlags(); + + // Clear form + document.getElementById('employee-id').value = ''; + document.getElementById('password').value = ''; + + // Initialize Notification/Polling SSE + initSSE(); + } else { + loginError.textContent = data.error || 'Login failed'; + loginError.classList.remove('hidden'); + } + } catch (error) { + console.error('Login error:', error); + loginError.textContent = 'Ein Fehler ist aufgetreten'; + loginError.classList.remove('hidden'); + } finally { + submitBtn.disabled = false; + submitBtn.textContent = originalText; + } + }); + + btnLogout.addEventListener('click', () => { + sessionStorage.removeItem('authToken'); + sessionStorage.removeItem('currentUser'); + sessionStorage.removeItem('firstName'); + sessionStorage.removeItem('lastName'); + authToken = null; + currentUser = null; + orderMap = new Map(); + updateAuthUI(); + renderVisibleWeeks(); // Re-render to update badges + }); + + function updateAuthUI() { + authToken = sessionStorage.getItem('authToken'); + currentUser = sessionStorage.getItem('currentUser'); + let firstName = sessionStorage.getItem('firstName'); + + if (authToken && currentUser) { + // Self-healing: If name is missing, fetch it + if (!firstName) { + fetch('/api/me', { + headers: { 'Authorization': `Token ${authToken}` } + }) + .then(res => res.json()) + .then(data => { + if (data.firstName) { + sessionStorage.setItem('firstName', data.firstName); + if (data.lastName) sessionStorage.setItem('lastName', data.lastName); + // Re-run updateAuthUI to display the fetched name + updateAuthUI(); + } + }) + .catch(err => console.error('Failed to fetch user info:', err)); + } + + btnLoginOpen.classList.add('hidden'); + userInfo.classList.remove('hidden'); + // Display First Name if available, otherwise User ID + const displayName = firstName || `User ${currentUser}`; + userIdDisplay.textContent = displayName; + } else { + btnLoginOpen.classList.remove('hidden'); + userInfo.classList.add('hidden'); + userIdDisplay.textContent = ''; + } + + if (authToken) { + fetchOrders(); + fetchFlags(); + initSSE(); + } + + // Re-render to potentially show order badges + renderVisibleWeeks(); + } + + async function fetchOrders() { + if (!authToken) return; + + try { + const response = await fetch('/api/user/orders', { + headers: { 'Authorization': `Token ${authToken}` } + }); + const data = await response.json(); + if (response.ok) { + // Build orderMap from dateOrders: key="date_articleId" -> [orderId, ...] + orderMap = new Map(); + if (data.dateOrders) { + for (const dayData of data.dateOrders) { + for (const order of dayData.orders) { + for (const item of order.items) { + const key = `${dayData.date}_${item.articleId}`; + if (!orderMap.has(key)) { + orderMap.set(key, []); + } + orderMap.get(key).push(order.id); + } + } + } + } + renderVisibleWeeks(); + } + } catch (error) { + console.error('Error fetching orders:', error); + } + } + + async function fetchFlags() { + if (!authToken) return; + try { + const response = await fetch('/api/flags', { + headers: { 'Authorization': `Token ${authToken}` } + }); + const flags = await response.json(); + userFlags.clear(); + if (Array.isArray(flags)) { + flags.forEach(f => userFlags.add(f.id)); + } + renderVisibleWeeks(); + } catch (error) { + console.error('Error fetching flags:', error); + } + } + + async function toggleFlag(date, articleId, name, cutoff, description) { + if (!authToken) return; + const id = `${date}_${articleId}`; + const isFlagged = userFlags.has(id); + + try { + if (isFlagged) { + // Remove flag + const response = await fetch(`/api/flags/${id}`, { + method: 'DELETE', + headers: { 'Authorization': `Token ${authToken}` } + }); + if (response.ok) { + userFlags.delete(id); + showToast(`Flag entfernt für ${name}`, 'success'); + } + } else { + // Add flag + const response = await fetch('/api/flags', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Token ${authToken}` + }, + body: JSON.stringify({ + id, date, articleId, + userId: currentUser, // Sending ID for logging + cutoff, + name, + description + }) + }); + if (response.ok) { + userFlags.add(id); + showToast(`Benachrichtigung aktiviert für ${name}`, 'success'); + // Request notification permission if not granted + if (Notification.permission === 'default') { + Notification.requestPermission(); + } + } + } + renderVisibleWeeks(); // Re-render to update UI + } catch (error) { + console.error('Flag toggle error:', error); + showToast('Fehler beim Aktualisieren des Flags', 'error'); + } + } + + // Place an order for a menu item + async function placeOrder(date, articleId, name, price, description) { + if (!authToken) return; + + try { + const response = await fetch('/api/order', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Token ${authToken}` + }, + body: JSON.stringify({ date, articleId, name, price, description }) + }); + + const data = await response.json(); + + if (response.ok || response.status === 201) { + showToast(`Bestellt: ${name}`, 'success'); + await fetchOrders(); // Re-sync from Bessa + } else { + showToast(`Fehler: ${data.error || 'Bestellung fehlgeschlagen'}`, 'error'); + } + } catch (error) { + console.error('Order error:', error); + showToast('Netzwerkfehler bei Bestellung', 'error'); + } + } + + // Cancel an order (LIFO: cancels the most recent order) + async function cancelOrder(date, articleId, name) { + if (!authToken) return; + + const key = `${date}_${articleId}`; + const orderIds = orderMap.get(key); + if (!orderIds || orderIds.length === 0) return; + + // LIFO: cancel the last (most recent) order + const orderId = orderIds[orderIds.length - 1]; + + try { + const response = await fetch('/api/order/cancel', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Token ${authToken}` + }, + body: JSON.stringify({ orderId }) + }); + + const data = await response.json(); + + if (response.ok) { + showToast(`Storniert: ${name}`, 'success'); + await fetchOrders(); // Re-sync from Bessa + } else { + showToast(`Fehler: ${data.error || 'Stornierung fehlgeschlagen'}`, 'error'); + } + } catch (error) { + console.error('Cancel error:', error); + showToast('Netzwerkfehler bei Stornierung', 'error'); + } + } + + // Toast notification system + function showToast(message, type = 'info') { + let container = document.getElementById('toast-container'); + if (!container) { + container = document.createElement('div'); + container.id = 'toast-container'; + document.body.appendChild(container); + } + + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + const icon = type === 'success' ? 'check_circle' : type === 'error' ? 'error' : 'info'; + toast.innerHTML = `${icon}${message}`; + container.appendChild(toast); + + // Animate in + requestAnimationFrame(() => toast.classList.add('show')); + + // Auto-remove after 3 seconds + setTimeout(() => { + toast.classList.remove('show'); + setTimeout(() => toast.remove(), 300); + }, 3000); + } + + // === Data Fetching === + // === Data Fetching === + // Initial load + loadMenuData(); + + function loadMenuData() { + loading.classList.remove('hidden'); + return fetch('/api/menus') + .then(response => response.json()) + .then(data => { + // Update Last Updated Text + updateLastUpdatedTime(data.updated || data.scrapedAt); + + // Parse data + if (data.weeks && Array.isArray(data.weeks)) { + allWeeks = data.weeks; + } else if (data.days && Array.isArray(data.days)) { + allWeeks = [data]; + } else { + allWeeks = Object.values(data).filter(val => val && val.days); + } + + // Recalculate current week to be sure + currentWeekNumber = getISOWeek(new Date()); + + // If we have a session, fetch orders + if (authToken) { + // updateAuthUI(); // Already called in login check if strictly needed, but safe here + fetchOrders(); + } + + updateAuthUI(); // Ensure UI is consistent + + // Initial render + renderVisibleWeeks(); + updateNextWeekBadge(); + }) + .catch(error => { + console.error('Error fetching menu:', error); + loading.innerHTML = `

Failed to load menu data.

`; + }) + .finally(() => { + loading.classList.add('hidden'); + }); + } + + // Initialize Long-Lived SSE for Notifications & Distributed Polling + function initSSE() { + if (eventSource) return; // Already connected + if (!authToken) return; + + console.log('Connecting to SSE events...'); + eventSource = new EventSource('/api/events'); + + eventSource.addEventListener('connected', (e) => { + console.log('SSE Connected:', JSON.parse(e.data)); + }); + + // Handle Polling Request (Distributed Polling) + eventSource.addEventListener('poll_request', async (e) => { + const data = JSON.parse(e.data); + console.log('Received Poll Request:', data); + + // Fetch status check + await checkItemStatusAndReport(data); + }); + + // Handle Item Update (Notification) + eventSource.addEventListener('item_update', (e) => { + const data = JSON.parse(e.data); + console.log('Item Update:', data); + + if (data.status === 'available') { + // Show Notification + showToast(`${data.name} ist jetzt verfügbar!`, 'success'); + if (Notification.permission === 'granted') { + new Notification('Kantine Wrapper', { + body: `${data.name} ist jetzt verfügbar!`, + icon: '/favicon.ico' // Assuming favicon exists + }); + } + + // Update local data (hacky refresh or fetch?) + // Ideally we update just the item in memory? + // Let's reload everything for safety or trigger a targeted update? + // Reloading is safest to get correct stock numbers + loadMenuData(); + } + }); + + eventSource.onerror = (e) => { + console.error('SSE Error:', e); + eventSource.close(); + eventSource = null; + // Retry after delay? + setTimeout(() => initSSE(), 5000); + }; + } + + async function checkItemStatusAndReport(task) { + try { + // We need to fetch the menu for that specific date to check availability + // Using the existing Bessa proxy indirectly via the menu-scraper approach? + // No, we are the client. The client (browser) needs to fetch? + // Wait, frontend cannot fetch from Bessa API directly (CORS). + // The "Distributed Polling" implies the CLIENT (User's Browser) triggers a check using THEIR session. + // BUT the Client Browser cannot call `api.bessa.app` directly due to CORS if Bessa doesn't allow it. + // Bessa API CORS might block localhost:3000. + // + // CRITICAL CHECK: Does Bessa API allow CORS from anywhere? + // If not, "Distributed Polling" via Browser is impossible without a proxy. + // And if we use a proxy (our server), we defeat the purpose of "Distributed Polling" to avoid server rate limits/tokens, + // UNLESS the server just proxies the request but uses the CLIENT'S headers provided in the request? + // BUT logic FR-015 says "Distributed Polling... Server orchestrates... Client checks". + // If Browser cannot call Bessa, "Client" must mean "Server Agent acting on behalf of Client" OR we assume we can call Bessa. + // + // Assumption: Bessa API might not support CORS for 3rd party apps. + // However, the proxy is OUR server. + // Requirement says: "Polling muss über authentifizierte User-Clients erfolgen". + // Implementation: Browser calls `POST /api/check-item` (using user token) -> Server calls Bessa (using user token) -> Server returns result. + // This is valid distributed polling because: + // 1. It uses the USER'S token (not a system token). + // 2. The traffic originates from the Server IP technically, but authorized as User. + // Wait, if 100 users are online, and Server calls Bessa 100 times, it's still Server IP. + // Bessa might rate limit IP. + // + // IF Bessa allows CORS, Browser calls Bessa directly. + // Let's assume for now we use our PROXY to fetch data (standard way) but using the User's credentials. + // The orchestrator just triggers it. + // + // Refined Flow: + // 1. Server sends `poll_request` to Browser. + // 2. Browser calls local `/api/refresh-item` (new endpoint? or just reuse `/api/menus`? No, specific check). + // 3. Server proxies to Bessa using Browser's Token. + // 4. Server reports back to Orchestrator (internally? or Browser reports result?) + // + // Actually, if we use the Proxy map, the Browser calls `/api/order` etc. + // Let's add a lightweight `/api/menu-status` endpoint that proxies to Bessa. + // Then Browser calls that. + + // BUT: If the server is doing the call (Proxy), we are still spamming from Server IP. + // User Requirement: "Polling traffik wird reduziert". + // User Comment: "verteilt sich das polling auf unterschiedliche user ... traffic reduziert". + // This implies IP distribution? Or just Token distribution? + // If it implies IP distribution, it MUST come from Browser directly. + // If Bessa has CORS, we are screwed on IP distribution. + // Let's assume we try a fetch via our proxy for now (Token distribution). + + // Wait! We don't have a `checkItem` proxy endpoint yet. + // I should rely on the `fetchOrders` or a new endpoint? + // We have `/api/refresh-progress` which scrapes everything. Too heavy. + // I will use a simple fetch to `api.bessa.app` from Browser and see if it works? + // If CORS blocks, we fail. + // + // ALTERNATIVE: Use the `MenuScraper` on backend but configured with User Token? + // No, that's complex. + // + // Let's try to add a proxy endpoint in `server.ts` for checking status? + // I will stick to the plan: Browser reports result. + // I will try to fetch via proxy: `GET /api/proxy/menu/...`? + // I'll add `GET /api/menu-item-status` to `server.ts` later or now? + // I missed adding a specific "Check Status" endpoint in `server.ts`. + // `fetchMenuForDate` in `menu-scraper` exists but it's backend. + // + // Workaround: + // Browser calls `/api/menus`? No that returns cached data. + // + // Let's add `POST /api/check-availability` to `server.ts` that proxies to Bessa? + // Yes, I need to update `server.ts` one more time or just use `fetch` if CORS allows. + // I'll assume CORS BLOCKS. So I need a proxy. + // I will add `POST /api/check-availability` to `server.ts` quickly? + // Or I can use the existing order-fetching logic? `GET /api/user/orders` fetches data from Bessa. + // `orders` endpoint fetches `menu/dates/` then details? + // `GET /api/user/orders` calls `/venues/591/menu/dates/`. It returns specific Date details. + // I can use `GET /api/user/orders`? It returns `dateOrders` which contains `orders` but maybe not full menu availability? + // + // Let's look at `server.ts` again. `GET /api/user/orders` fetches orders, not menu items availability. + // + // I need a proxy. I will add `app.post('/api/proxy/check-item', ...)` to `server.ts` in the next step. + // For now, I'll implement the frontend to call this endpoint. + + const response = await fetch('/api/check-item', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Token ${authToken}` + }, + body: JSON.stringify({ date: task.date, articleId: task.articleId }) + }); + + if (response.ok) { + const result = await response.json(); + // Report back + await fetch('/api/poll-result', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + flagId: task.flagId, + isAvailable: result.available + }) + }); + } + } catch (e) { + console.error('Check Item Error:', e); + } + } + // Update the "Nächste Woche" button badge with available day count + function updateNextWeekBadge() { + let nextWeek = currentWeekNumber + 1; + let nextYear = currentYear; + + // Handle year boundary (week 52/53 -> week 1) + if (nextWeek > 52) { + nextWeek = 1; + nextYear++; + } + + const nextWeekData = allWeeks.find(w => + w.weekNumber === nextWeek && w.year === nextYear + ); + + let totalDataCount = 0; + let orderableCount = 0; + + if (nextWeekData && nextWeekData.days) { + nextWeekData.days.forEach(day => { + if (day.items && day.items.length > 0) { + totalDataCount++; + // Check if at least one item is orderable + const hasOrderableItem = day.items.some(item => item.available); + if (hasOrderableItem) { + orderableCount++; + } + } + }); + } + + // Update or create badge + let badge = btnNextWeek.querySelector('.nav-badge'); + + // Show badge if we have any data + if (totalDataCount > 0) { + if (!badge) { + badge = document.createElement('span'); + badge.className = 'nav-badge'; + btnNextWeek.appendChild(badge); + } + // Render split badge: Orderable / Total + badge.title = `${orderableCount} Tage bestellbar / ${totalDataCount} Tage mit Menüdaten`; + badge.innerHTML = ` + ${orderableCount} + / + ${totalDataCount} + `; + } else if (badge) { + badge.remove(); + } + } + + function updateWeeklyCost(days) { + let totalCost = 0; + if (days && days.length > 0) { + days.forEach(day => { + if (day.items) { + day.items.forEach(item => { + const articleId = item.id || item.articleId; + const key = `${day.date}_${articleId}`; + const orders = orderMap.get(key) || []; + if (orders.length > 0) { + totalCost += item.price * orders.length; + } + }); + } + }); + } + + const costDisplay = document.getElementById('weekly-cost-display'); + if (totalCost > 0) { + costDisplay.innerHTML = `shopping_bag Gesamt: ${totalCost.toFixed(2).replace('.', ',')} €`; + costDisplay.classList.remove('hidden'); + } else { + costDisplay.classList.add('hidden'); + } + } + + function renderVisibleWeeks() { + menuContainer.innerHTML = ''; + + let targetWeek = currentWeekNumber; + let targetYear = currentYear; + + if (displayMode === 'next-week') { + targetWeek++; + if (targetWeek > 52) { + targetWeek = 1; + targetYear++; + } + } + + // --- REGROUPING LOGIC --- + // Flatten all days from all weeks into a single array + const allDays = allWeeks.flatMap(w => w.days || []); + + // Filter days that belong to the target week + const daysInTargetWeek = allDays.filter(day => { + const d = new Date(day.date); + const w = getISOWeek(d); + // Simple year check (won't be perfect for week 1/52 boundary across years but suffices for now) + return w === targetWeek; + }); + + if (daysInTargetWeek.length === 0) { + menuContainer.innerHTML = ` +
+

Keine Menüdaten für KW ${targetWeek} (${targetYear}) verfügbar.

+ Versuchen Sie eine andere Woche oder schauen Sie später vorbei. +
+ `; + // Hide cost display if no data + document.getElementById('weekly-cost-display').classList.add('hidden'); + return; + } + + // Update cost display + updateWeeklyCost(daysInTargetWeek); + + // Display + // Update header week info + const headerWeekInfo = document.getElementById('header-week-info'); + const weekTitle = displayMode === 'this-week' ? 'Diese Woche' : 'Nächste Woche'; + headerWeekInfo.innerHTML = ` +
${weekTitle}
+
Week ${targetWeek} • ${targetYear}
+ `; + + // Grid + const grid = document.createElement('div'); + grid.className = 'days-grid'; + + // Sort days by date + daysInTargetWeek.sort((a, b) => a.date.localeCompare(b.date)); + + // Filter out weekends (Sat/Sun) for the clean view + const workingDays = daysInTargetWeek.filter(d => { + const date = new Date(d.date); + const day = date.getDay(); + return day !== 0 && day !== 6; + }); + + workingDays.forEach(day => { + const card = createDayCard(day); + if (card) { + grid.appendChild(card); + } + }); + + menuContainer.appendChild(grid); + + // Sync menu item heights for grid alignment + setTimeout(() => syncMenuItemHeights(grid), 0); + } + + // Synchronize menu item heights across all day cards + function syncMenuItemHeights(grid) { + const cards = grid.querySelectorAll('.menu-card'); + if (cards.length === 0) return; + + // Find maximum number of items across all cards + let maxItems = 0; + cards.forEach(card => { + const items = card.querySelectorAll('.menu-item'); + maxItems = Math.max(maxItems, items.length); + }); + + // For each position (0, 1, 2, ...), find the tallest item and apply that height to all + for (let i = 0; i < maxItems; i++) { + let maxHeight = 0; + const itemsAtPosition = []; + + // Collect all items at this position and find max height + cards.forEach(card => { + const items = card.querySelectorAll('.menu-item'); + if (items[i]) { + // Reset height first to get natural height + items[i].style.height = 'auto'; + const height = items[i].offsetHeight; + maxHeight = Math.max(maxHeight, height); + itemsAtPosition.push(items[i]); + } + }); + + // Apply max height to all items at this position + itemsAtPosition.forEach(item => { + item.style.height = `${maxHeight}px`; + }); + } + } + + function createDayCard(day) { + if (!day.items || day.items.length === 0) return null; + + const card = document.createElement('div'); + card.className = 'menu-card'; + + // Past Day Check - consider order cutoff time + const now = new Date(); + const cardDate = new Date(day.date); + + // Check if there's an order cutoff time + let isPastCutoff = false; + if (day.orderCutoff) { + const cutoffTime = new Date(day.orderCutoff); + isPastCutoff = now >= cutoffTime; + } else { + // Fallback: compare dates at midnight + const today = new Date(); + today.setHours(0, 0, 0, 0); + cardDate.setHours(0, 0, 0, 0); + isPastCutoff = cardDate < today; + } + + if (isPastCutoff) { + card.classList.add('past-day'); + } + + // Header + const header = document.createElement('div'); + header.className = 'card-header'; + + const dateStr = cardDate.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); + + header.innerHTML = ` + ${translateDay(day.weekday)} + ${dateStr} + `; + card.appendChild(header); + + // Body + const body = document.createElement('div'); + body.className = 'card-body'; + + if (day.items && day.items.length > 0) { + // Sort items by name + const sortedItems = [...day.items].sort((a, b) => a.name.localeCompare(b.name)); + + sortedItems.forEach(item => { + const itemEl = document.createElement('div'); + itemEl.className = 'menu-item'; + + // Extract article ID from composite id + const articleId = parseInt(item.id.split('_')[1]); + const orderKey = `${day.date}_${articleId}`; + const orderIds = orderMap.get(orderKey) || []; + const orderCount = orderIds.length; + + // Availability badge + let statusBadge = ''; + if (item.available) { + if (item.amountTracking) { + statusBadge = `Verfügbar (${item.availableAmount})`; + } else { + statusBadge = `Verfügbar`; + } + } else { + statusBadge = `Ausverkauft`; + } + + // Order badge + count + let orderedBadge = ''; + if (orderCount > 0) { + const countBadge = orderCount > 1 + ? `${orderCount}` + : ''; + orderedBadge = `check_circle Bestellt${countBadge}`; + } + + // Add classes for styling + if (orderCount > 0) { + itemEl.classList.add('ordered'); + + // Check if it's today's order + const now = new Date(); + const itemDate = new Date(day.date); + if (itemDate.toDateString() === now.toDateString()) { + itemEl.classList.add('today-ordered'); + } + } + + // Add Flagged Styles + const flagId = `${day.date}_${articleId}`; + const isFlagged = userFlags.has(flagId); + + if (isFlagged) { + if (item.available) { + itemEl.classList.add('flagged-available'); + } else { + itemEl.classList.add('flagged-sold-out'); + } + } + + // Action buttons built inline with badges + let orderButton = ''; + let cancelButton = ''; + let flagButton = ''; + + if (authToken && !isPastCutoff) { + // Flag Button + const flagIcon = isFlagged ? 'notifications_active' : 'notifications_none'; + const flagClass = isFlagged ? 'btn-flag active' : 'btn-flag'; + const flagTitle = isFlagged ? 'Benachrichtigung deaktivieren' : 'Benachrichtigen wenn verfügbar'; + + // Only show flag button if sold out OR already flagged (to unflag) + if (!item.available || isFlagged) { + flagButton = ``; + } + + // Order button: requires item.available + if (item.available) { + if (orderCount > 0) { + // Compact "+" when already ordered + orderButton = ``; + } else { + // Full "Bestellen" button + orderButton = ``; + } + } + + // Cancel button: always show if ordered (even if sold out) + if (orderCount > 0) { + const cancelIcon = orderCount === 1 ? 'close' : 'remove'; + const cancelTitle = orderCount === 1 ? 'Bestellung stornieren' : 'Eine Bestellung stornieren'; + cancelButton = ``; + } + } + + itemEl.innerHTML = ` +
+ ${escapeHtml(item.name)} + ${item.price.toFixed(2)} € +
+
+ ${orderedBadge} + ${cancelButton} + ${orderButton} + ${flagButton} +
+ ${statusBadge} +
+
+

${escapeHtml(item.description)}

+ `; + + // Attach event listeners for order/cancel buttons + const orderBtn = itemEl.querySelector('.btn-order'); + if (orderBtn) { + orderBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const btn = e.currentTarget; + btn.disabled = true; + btn.classList.add('loading'); + placeOrder( + btn.dataset.date, + parseInt(btn.dataset.article), + btn.dataset.name, + parseFloat(btn.dataset.price), + btn.dataset.desc || '' + ).finally(() => { + btn.disabled = false; + btn.classList.remove('loading'); + }); + }); + } + + const cancelBtn = itemEl.querySelector('.btn-cancel'); + if (cancelBtn) { + cancelBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const btn = e.currentTarget; + btn.disabled = true; + cancelOrder( + btn.dataset.date, + parseInt(btn.dataset.article), + btn.dataset.name + ).finally(() => { + btn.disabled = false; + }); + }); + } + + const flagBtn = itemEl.querySelector('.btn-flag'); + if (flagBtn) { + flagBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const btn = e.currentTarget; + toggleFlag( + btn.dataset.date, + parseInt(btn.dataset.article), + btn.dataset.name, + btn.dataset.cutoff, + '' // desc + ); + }); + } + + body.appendChild(itemEl); + }); + } else { + body.innerHTML = `
Kein Menü verfügbar
`; + } + + card.appendChild(body); + return card; + } + + // --- Helpers --- + + function getISOWeek(date) { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + return Math.ceil((((d - yearStart) / 86400000) + 1) / 7); + } + + function translateDay(englishDay) { + const map = { + 'Monday': 'Montag', + 'Tuesday': 'Dienstag', + 'Wednesday': 'Mittwoch', + 'Thursday': 'Donnerstag', + 'Friday': 'Freitag', + 'Saturday': 'Samstag', + 'Sunday': 'Sonntag' + }; + return map[englishDay] || englishDay; + } + + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + // === Menu Refresh Functionality === + btnRefresh.addEventListener('click', async () => { + // Check if user is authenticated + if (!authToken) { + // Prompt user to login first + loginModal.classList.remove('hidden'); + return; + } + + // Show progress modal + progressModal.classList.remove('hidden'); + btnRefresh.classList.add('refreshing'); + progressFill.style.width = '0%'; + progressPercent.textContent = '0%'; + progressMessage.textContent = 'Initialisierung...'; + + // Establish SSE connection (EventSource doesn't support custom headers) + const eventSource = new EventSource(`/api/refresh-progress?token=${encodeURIComponent(authToken)}`); + + eventSource.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + + // Update progress bar + const percent = Math.round((data.current / data.total) * 100); + progressFill.style.width = `${percent}%`; + progressPercent.textContent = `${percent}%`; + progressMessage.textContent = data.message; + + } catch (error) { + console.error('Error parsing SSE data:', error); + } + }; + + eventSource.addEventListener('done', () => { + eventSource.close(); + btnRefresh.classList.remove('refreshing'); + + // Reload menu data + setTimeout(async () => { + progressMessage.textContent = 'Menüdaten werden neu geladen...'; + await loadMenuData(); + progressModal.classList.add('hidden'); + }, 500); + }); + + eventSource.addEventListener('error', (event) => { + console.error('SSE error:', event); + eventSource.close(); + btnRefresh.classList.remove('refreshing'); + progressMessage.textContent = 'Fehler beim Aktualisieren. Bitte erneut versuchen.'; + + setTimeout(() => { + progressModal.classList.add('hidden'); + }, 2000); + }); + + + eventSource.onerror = () => { + eventSource.close(); + btnRefresh.classList.remove('refreshing'); + progressMessage.textContent = 'Verbindung verloren. Bitte erneut versuchen.'; + + setTimeout(() => { + progressModal.classList.add('hidden'); + }, 2000); + }; + }); + + function updateLastUpdatedTime(isoString) { + if (!isoString) return; + + const date = new Date(isoString); + const now = new Date(); + const diffMs = now - date; + const diffMinutes = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMinutes / 60); + + let timeString = ''; + if (diffMinutes < 1) { + timeString = 'Gerade aktualisiert'; + } else if (diffMinutes < 60) { + timeString = `vor ${diffMinutes} Minuten`; + } else { + const remainingMinutes = diffMinutes % 60; + timeString = `vor ${diffHours} Stunden`; + if (remainingMinutes > 0) { + timeString += ` ${remainingMinutes} Minuten`; + } + } + + const subtitle = document.getElementById('last-updated-subtitle'); + if (subtitle) { + subtitle.textContent = `Zuletzt aktualisiert ${timeString}`; + } + } +}); diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..79804f4 --- /dev/null +++ b/public/index.html @@ -0,0 +1,125 @@ + + + + + + + Kantine Weekly Menu + + + + + + + + +
+
+
+ restaurant_menu +
+

Kantinen Übersicht

+
+
+
+
+ +
+ +
+ + + + + +
+
+
+ + + + + +
+ + +
+
+

Lade Menüdaten...

+
+ + +
+ +
+

Bessa Knapp-Kantine Wrapper • 2026

+
+ + + + + \ No newline at end of file diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..a10e578 --- /dev/null +++ b/public/style.css @@ -0,0 +1,1075 @@ +:root { + /* Premium Slate/Gray-Blue Palette - Light Mode */ + --bg-body: #f1f5f9; + /* Slate 100 */ + --bg-card: #ffffff; + --text-primary: #334155; + /* Slate 700 */ + --text-secondary: #64748b; + --accent-color: #0f172a; + /* Slate 900 (High contrast) */ + --border-color: #cbd5e1; + /* Slate 300 */ + --banner-bg: #e2e8f0; + --banner-text: #1e293b; + --success-color: #059669; + --error-color: #dc2626; + --card-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05); + --header-bg: rgba(255, 255, 255, 0.9); + --header-border: 1px solid rgba(203, 213, 225, 0.6); +} + +[data-theme="dark"] { + /* Premium Slate/Gray-Blue Palette - Dark Mode */ + --bg-body: #1e293b; + /* Deep Slate Gray (Requested) */ + --bg-card: #334155; + /* Slate 700 */ + --text-primary: #f8fafc; + /* Slate 50 */ + --text-secondary: #cbd5e1; + /* Slate 300 */ + --accent-color: #60a5fa; + /* Blue 400 */ + --border-color: #475569; + /* Slate 600 */ + --banner-bg: #475569; + --banner-text: #e2e8f0; + --header-bg: rgba(30, 41, 59, 0.9); + --header-border: 1px solid rgba(71, 85, 105, 0.6); + --card-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.4); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: 'Inter', system-ui, -apple-system, sans-serif; + background-color: var(--bg-body); + color: var(--text-primary); + transition: background-color 0.3s ease, color 0.3s ease; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +/* Header */ +.app-header { + position: sticky; + top: 0; + z-index: 100; + backdrop-filter: blur(12px); + background-color: var(--header-bg); + border-bottom: var(--header-border); + padding: 1rem 0; +} + +.header-content { + width: 100%; + /* Full width */ + padding: 0 2rem; + /* Comfortable padding */ + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 1rem; +} + +.brand { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.brand-text { + display: flex; + flex-direction: column; +} + +.brand h1 { + font-size: 1.25rem; + font-weight: 700; + letter-spacing: -0.025em; + margin-bottom: 0; +} + +.subtitle { + font-size: 0.85rem; + color: var(--text-secondary); + font-weight: 400; + margin-left: 2px; +} + +.logo-icon { + font-size: 1.5rem; + color: var(--accent-color); +} + +/* Controls */ +.controls { + display: flex; + align-items: center; + gap: 1.5rem; + justify-self: end; +} + +/* Header Week Info (centered) */ +.header-week-info { + text-align: center; + line-height: 1.3; +} + +.header-week-title { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-primary); +} + +.header-week-subtitle { + font-size: 0.85rem; + color: var(--text-secondary); +} + +.nav-group { + display: flex; + background-color: var(--bg-card); + border: 1px solid var(--border-color); + padding: 0.25rem; + border-radius: 8px; +} + +.nav-btn { + background: none; + border: none; + padding: 0.5rem 1rem; + font-size: 0.875rem; + font-weight: 500; + color: var(--text-secondary); + cursor: pointer; + border-radius: 6px; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.nav-btn:hover { + color: var(--text-primary); + background-color: rgba(100, 116, 139, 0.1); +} + +.nav-btn.active { + background-color: var(--accent-color); + color: white; +} + +/* Badge for nav buttons (day count indicator) */ +.nav-badge { + background-color: var(--error-color); + color: white; + font-size: 0.75rem; + font-weight: 600; + padding: 0 6px; + border-radius: 10px; + min-width: 18px; + height: 18px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 8px; + gap: 3px; + line-height: 1; +} + +.nav-badge .orderable { + color: #fff; + font-weight: 800; +} + +.nav-badge .separator { + opacity: 0.6; + font-weight: 400; +} + +.nav-badge .total { + opacity: 0.8; + font-weight: 400; +} + +.nav-btn.active .nav-badge { + background: rgba(255, 255, 255, 0.3); +} + +/* Primary style for Login Button to match header */ +#btn-login-open { + background-color: var(--accent-color); + color: white; + padding: 0.5rem 1.25rem; + border-radius: 8px; + font-weight: 600; + letter-spacing: 0.025em; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +#btn-login-open:hover { + background-color: #334155; + /* Slightly lighter than slate-900 */ + transform: translateY(-1px); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +/* User Badge Button (Login) */ +.user-badge-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 20px; + font-size: 0.9rem; + font-weight: 500; + color: var(--text-primary); + cursor: pointer; + transition: all 0.2s; +} + +.user-badge-btn:hover { + background: rgba(100, 116, 139, 0.1); + border-color: var(--accent-color); +} + +.user-badge-btn .material-icons-round { + font-size: 1.25rem; + color: var(--accent-color); +} + +.icon-btn { + background: none; + border: none; + color: var(--text-primary); + cursor: pointer; + padding: 0.5rem; + border-radius: 50%; + transition: background-color 0.2s; + display: flex; + align-items: center; + justify-content: center; +} + +.icon-btn:hover { + background-color: rgba(100, 116, 139, 0.1); +} + +/* Refresh button animation */ +#btn-refresh.refreshing .material-icons-round { + animation: rotate 1s linear infinite; +} + +@keyframes rotate { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +/* Progress Modal */ +.progress-container { + margin-bottom: 1.5rem; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: var(--border-color); + border-radius: 4px; + overflow: hidden; + margin-bottom: 0.75rem; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--accent-color) 0%, #60a5fa 100%); + width: 0%; + transition: width 0.3s ease; + border-radius: 4px; +} + +.progress-percent { + text-align: center; + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 0.5rem; +} + +.progress-message { + text-align: center; + color: var(--text-secondary); + font-size: 0.9rem; + font-weight: 500; +} + +.weekly-cost { + background-color: rgba(59, 130, 246, 0.1); + /* Blue tint */ + color: var(--accent-color); + padding: 0.4rem 0.8rem; + border-radius: 8px; + font-weight: 600; + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 0.5rem; + border: 1px solid rgba(59, 130, 246, 0.2); +} + +.weekly-cost .material-icons-round { + font-size: 18px; +} + +/* Container */ +.container { + width: 100%; + /* Full width */ + margin: 2rem auto; + padding: 0 2rem; + min-height: 80vh; +} + +/* Banner */ +.banner { + background-color: var(--banner-bg); + color: var(--banner-text); + padding: 0.75rem 1rem; + border-radius: 8px; + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 2rem; + font-size: 0.875rem; + font-weight: 500; + border: 1px solid var(--border-color); + max-width: fit-content; +} + +/* User Badge */ +.user-badge { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: var(--bg-card); + /* Changed from --surface */ + border: 1px solid var(--border-color); + /* Changed from --border */ + border-radius: 20px; + font-size: 0.9rem; + font-weight: 500; +} + +.icon-btn-small { + background: none; + border: none; + padding: 4px; + cursor: pointer; + color: var(--text-secondary); + /* Changed from --text-muted */ + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: all 0.2s; +} + +.icon-btn-small:hover { + color: var(--error-color); + /* Changed from --danger */ + background: rgba(239, 68, 68, 0.1); +} + +/* Modal */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + transition: all 0.3s; +} + +.modal.hidden { + opacity: 0; + pointer-events: none; +} + +.modal-content { + background: var(--bg-card); + /* Changed from --surface */ + width: 90%; + max-width: 400px; + border-radius: 16px; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + overflow: hidden; + animation: modalSlide 0.3s ease-out; +} + +@keyframes modalSlide { + from { + transform: translateY(20px); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px; + border-bottom: 1px solid var(--border-color); + /* Changed from --border */ +} + +.modal-header h2 { + margin: 0; + font-size: 1.25rem; +} + +#login-form { + padding: 20px; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 6px; + font-weight: 500; + font-size: 0.9rem; +} + +.form-group input { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--border-color); + /* Changed from --border */ + border-radius: 8px; + background: var(--bg-body); + /* Changed from --bg */ + color: var(--text-primary); + /* Changed from --text */ + font-family: inherit; + transition: border-color 0.2s; +} + +.form-group input:focus { + outline: none; + border-color: var(--accent-color); + /* Changed from --primary */ +} + +.help-text { + display: block; + margin-top: 4px; + color: var(--text-secondary); + /* Changed from --text-muted */ + font-size: 0.75rem; +} + +.error-msg { + margin-bottom: 16px; + padding: 10px; + background: rgba(239, 68, 68, 0.1); + color: var(--error-color); + /* Changed from --danger */ + border-radius: 8px; + font-size: 0.85rem; + text-align: center; +} + +.modal-actions { + margin-top: 24px; +} + +.btn-primary.wide { + width: 100%; + justify-content: center; +} + +.hidden { + display: none !important; +} + +/* Menu Grid */ +.menu-grid { + display: grid; + gap: 2rem; +} + +.week-section { + margin-bottom: 3rem; +} + +.week-header { + margin-bottom: 1.5rem; + border-bottom: 1px solid var(--border-color); + padding-bottom: 1rem; + text-align: center; +} + +.week-title { + font-size: 1.75rem; + font-weight: 700; + color: var(--text-primary); +} + +.week-range { + color: var(--text-secondary); + font-size: 0.9rem; + margin-top: 0.25rem; +} + +.days-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 0.75rem; +} + +/* Card */ +.menu-card { + background-color: var(--bg-card); + border-radius: 12px; + border: 1px solid var(--border-color); + box-shadow: var(--card-shadow); + overflow: hidden; + transition: transform 0.2s ease, box-shadow 0.2s ease; + display: flex; + flex-direction: column; +} + +/* Past Day Styling - Target specific elements so ordered items can remain visible */ +.menu-card.past-day .card-header, +.menu-card.past-day .menu-item:not(.ordered) { + opacity: 0.6; + filter: grayscale(0.8); + transition: opacity 0.3s, filter 0.3s; +} + +.menu-card.past-day:hover .card-header, +.menu-card.past-day:hover .menu-item:not(.ordered) { + opacity: 0.8; + filter: grayscale(0.4); +} + +/* Enhancements for ordered items */ +.menu-card.past-day .menu-item.ordered { + /* No opacity/filter here - fully visible */ + background: var(--bg-card); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border: 1px solid var(--accent-color); + border-radius: 8px; + padding: 1rem; + margin: 0 -1rem 1.5rem -1rem; + position: relative; + z-index: 10; +} + +.menu-item.today-ordered { + border: 2px solid var(--accent-color); + box-shadow: 0 0 20px rgba(96, 165, 250, 0.4); + border-radius: 8px; + padding: 1rem; + margin: 0 -1rem 1.5rem -1rem; + background: var(--bg-card); + position: relative; + z-index: 5; + animation: pulse-glow 3s infinite; +} + +@keyframes pulse-glow { + 0% { + box-shadow: 0 0 15px rgba(96, 165, 250, 0.3); + } + + 50% { + box-shadow: 0 0 25px rgba(96, 165, 250, 0.6); + } + + 100% { + box-shadow: 0 0 15px rgba(96, 165, 250, 0.3); + } +} + + +.menu-card:hover { + transform: translateY(-2px); + box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); +} + +.card-header { + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: baseline; + background-color: rgba(100, 116, 139, 0.05); +} + +.day-name { + font-size: 1.125rem; + font-weight: 600; +} + +.day-date { + font-size: 0.875rem; + color: var(--text-secondary); +} + +.card-body { + padding: 1.25rem; + display: grid; + grid-template-rows: auto; + /* Each menu item gets its own row */ + align-content: start; +} + +.empty-state { + color: var(--text-secondary); + font-style: italic; + text-align: center; + padding: 1rem; +} + +/* Menu Items */ +.menu-item { + margin-bottom: 1.5rem; + padding-bottom: 1.5rem; + border-bottom: 1px solid var(--border-color); +} + +.menu-item:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.item-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.5rem; + gap: 1rem; +} + +.item-name { + font-weight: 600; + color: var(--text-primary); + font-size: 1rem; +} + +.item-price { + font-weight: 700; + color: var(--accent-color); + white-space: nowrap; +} + +.item-desc { + font-size: 0.875rem; + color: var(--text-secondary); + line-height: 1.6; + margin-bottom: 0.75rem; +} + +.badges { + display: flex; + gap: 0.5rem; + margin-left: auto; +} + +.item-status-row { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.75rem; +} + +.badge { + display: inline-flex; + align-items: center; + justify-content: center; + height: 24px; + font-size: 0.75rem; + padding: 0 10px; + border-radius: 4px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + line-height: normal; + white-space: nowrap; +} + +.badge.available { + background-color: rgba(16, 185, 129, 0.1); + /* Emerald 500 / 10% */ + color: var(--success-color); + border: 1px solid rgba(16, 185, 129, 0.2); +} + +.badge.sold-out { + background-color: rgba(239, 68, 68, 0.1); + /* Red 500 / 10% */ + color: var(--error-color); + border: 1px solid rgba(239, 68, 68, 0.2); +} + +.badge.ordered { + background-color: rgba(139, 92, 246, 0.1); + /* Violet 500 / 10% */ + color: #8b5cf6; + border: 1px solid rgba(139, 92, 246, 0.2); + gap: 4px; +} + +.badge.ordered .material-icons-round { + font-size: 1rem; +} + +/* Loading */ +.loading-state { + text-align: center; + padding: 4rem; + color: var(--text-secondary); +} + +.spinner { + width: 40px; + height: 40px; + border: 3px solid var(--border-color); + border-top-color: var(--accent-color); + border-radius: 50%; + margin: 0 auto 1rem; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Footer */ +.app-footer { + text-align: center; + padding: 2rem; + color: var(--text-secondary); + font-size: 0.875rem; + border-top: 1px solid var(--border-color); + margin-top: auto; +} + +/* === Order / Cancel Buttons (inline in status row) === */ + +.btn-order { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border: none; + border-radius: 6px; + background: var(--success-color); + color: white; + font-size: 0.75rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; +} + +.btn-order .material-icons-round { + font-size: 16px; +} + +.btn-order:hover:not(:disabled) { + filter: brightness(1.15); + transform: translateY(-1px); +} + +.btn-order:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-order.loading { + pointer-events: none; + opacity: 0.6; +} + +.btn-order-compact { + padding: 2px 4px; + gap: 0; +} + +.btn-order-compact .material-icons-round { + font-size: 16px; +} + +.btn-cancel { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 4px 6px; + border: none; + border-radius: 6px; + background: var(--error-color); + color: white; + font-size: 0.75rem; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; +} + +.btn-cancel .material-icons-round { + font-size: 16px; +} + +.btn-cancel:hover:not(:disabled) { + filter: brightness(1.15); + transform: translateY(-1px); +} + +.btn-cancel:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Past days: hide action buttons */ +.past-day .item-actions { + display: none; +} + +/* Order count badge (for multi-orders) */ +.order-count-badge { + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.3); + color: white; + font-size: 0.65rem; + font-weight: 700; + min-width: 16px; + height: 16px; + padding: 0 4px; + border-radius: 8px; + margin-left: 4px; + line-height: 1; +} + +/* === Toast Notifications === */ +#toast-container { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 10000; + display: flex; + flex-direction: column; + gap: 8px; + pointer-events: none; +} + +.toast { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + border-radius: 8px; + font-size: 0.85rem; + font-weight: 500; + font-family: 'Inter', sans-serif; + color: white; + backdrop-filter: blur(10px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + pointer-events: auto; + transform: translateX(120%); + opacity: 0; + transition: transform 0.3s ease, opacity 0.3s ease; +} + +.toast.show { + transform: translateX(0); + opacity: 1; +} + +.toast .material-icons-round { + font-size: 18px; +} + +.toast-success { + background: rgba(5, 150, 105, 0.95); +} + +.toast-error { + background: rgba(220, 38, 38, 0.95); +} + +.toast-info { + background: rgba(59, 130, 246, 0.95); +} + +/* === Mobile Responsiveness === */ +@media (max-width: 600px) { + .header-content { + flex-direction: column; + gap: 1rem; + padding: 0.75rem; + } + + .week-nav { + width: 100%; + justify-content: center; + } + + .nav-pills { + width: 100%; + justify-content: space-between; + } + + .nav-btn { + flex: 1; + justify-content: center; + padding: 0.5rem; + font-size: 0.85rem; + } + + .days-grid { + grid-template-columns: 1fr; + /* Force single column */ + } + + .main-content { + padding: 1rem; + } + + .week-title { + font-size: 1.5rem; + } + + /* Adjust toast position for mobile */ + .toast-container { + bottom: 1rem; + right: 1rem; + left: 1rem; + /* Center on mobile */ + width: auto; + } + + .menu-card { + margin-bottom: 1rem; + } +} + +/* === Flagging & Notification Styles === */ + +.btn-flag { + display: inline-flex; + align-items: center; + justify-content: center; + background: transparent; + border: 1px solid var(--text-secondary); + color: var(--text-secondary); + border-radius: 6px; + padding: 4px; + cursor: pointer; + transition: all 0.2s; + margin-right: 0.5rem; + width: 28px; + height: 28px; +} + +.btn-flag:hover { + background: rgba(234, 179, 8, 0.1); + /* Yellow-500 / 10% */ + color: #eab308; + border-color: #eab308; +} + +.btn-flag.active { + background: rgba(234, 179, 8, 0.1); + color: #eab308; + border-color: #eab308; +} + +.btn-flag .material-icons-round { + font-size: 1.1rem; +} + +/* Flagged & Sold Out (Yellow Glow) */ +.menu-item.flagged-sold-out { + border: 1px solid #eab308; + box-shadow: 0 0 10px rgba(234, 179, 8, 0.2); + border-radius: 8px; + padding: 1rem; + margin: 0 -1rem 1.5rem -1rem; + background: var(--bg-card); + position: relative; + z-index: 5; + animation: yellow-pulse 3s infinite; +} + +@keyframes yellow-pulse { + 0% { + box-shadow: 0 0 8px rgba(234, 179, 8, 0.2); + } + + 50% { + box-shadow: 0 0 16px rgba(234, 179, 8, 0.5); + } + + 100% { + box-shadow: 0 0 8px rgba(234, 179, 8, 0.2); + } +} + +/* Flagged & Available (Green Glow) */ +.menu-item.flagged-available { + border: 2px solid var(--success-color); + box-shadow: 0 0 15px rgba(16, 185, 129, 0.3); + border-radius: 8px; + padding: 1rem; + margin: 0 -1rem 1.5rem -1rem; + background: var(--bg-card); + position: relative; + z-index: 5; + animation: green-pulse 3s infinite; +} + +@keyframes green-pulse { + 0% { + box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); + } + + 50% { + box-shadow: 0 0 20px rgba(16, 185, 129, 0.6); + } + + 100% { + box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); + } +} \ No newline at end of file diff --git a/screenshots/after_access_code_input_2026-02-02T12-48-10-237Z.png b/screenshots/after_access_code_input_2026-02-02T12-48-10-237Z.png new file mode 100644 index 0000000..b02f5c8 Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T12-48-10-237Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T12-54-20-956Z.png b/screenshots/after_access_code_input_2026-02-02T12-54-20-956Z.png new file mode 100644 index 0000000..b02f5c8 Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T12-54-20-956Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T12-59-29-191Z.png b/screenshots/after_access_code_input_2026-02-02T12-59-29-191Z.png new file mode 100644 index 0000000..9f4cf55 Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T12-59-29-191Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T13-02-10-408Z.png b/screenshots/after_access_code_input_2026-02-02T13-02-10-408Z.png new file mode 100644 index 0000000..9f4cf55 Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T13-02-10-408Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T13-06-06-285Z.png b/screenshots/after_access_code_input_2026-02-02T13-06-06-285Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T13-06-06-285Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T13-08-31-187Z.png b/screenshots/after_access_code_input_2026-02-02T13-08-31-187Z.png new file mode 100644 index 0000000..9f4cf55 Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T13-08-31-187Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T13-14-52-623Z.png b/screenshots/after_access_code_input_2026-02-02T13-14-52-623Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T13-14-52-623Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T13-16-14-800Z.png b/screenshots/after_access_code_input_2026-02-02T13-16-14-800Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T13-16-14-800Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T13-23-25-272Z.png b/screenshots/after_access_code_input_2026-02-02T13-23-25-272Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T13-23-25-272Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T13-27-48-494Z.png b/screenshots/after_access_code_input_2026-02-02T13-27-48-494Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T13-27-48-494Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-02T13-28-36-791Z.png b/screenshots/after_access_code_input_2026-02-02T13-28-36-791Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-02T13-28-36-791Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-03T08-56-50-844Z.png b/screenshots/after_access_code_input_2026-02-03T08-56-50-844Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-03T08-56-50-844Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-03T09-40-21-463Z.png b/screenshots/after_access_code_input_2026-02-03T09-40-21-463Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-03T09-40-21-463Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-03T09-50-35-785Z.png b/screenshots/after_access_code_input_2026-02-03T09-50-35-785Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-03T09-50-35-785Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-03T11-17-16-092Z.png b/screenshots/after_access_code_input_2026-02-03T11-17-16-092Z.png new file mode 100644 index 0000000..29c6a93 Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-03T11-17-16-092Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-05T17-30-44-707Z.png b/screenshots/after_access_code_input_2026-02-05T17-30-44-707Z.png new file mode 100644 index 0000000..9a0572e Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-05T17-30-44-707Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-05T20-06-50-583Z.png b/screenshots/after_access_code_input_2026-02-05T20-06-50-583Z.png new file mode 100644 index 0000000..9a0572e Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-05T20-06-50-583Z.png differ diff --git a/screenshots/after_access_code_input_2026-02-09T11-55-10-362Z.png b/screenshots/after_access_code_input_2026-02-09T11-55-10-362Z.png new file mode 100644 index 0000000..be60aec Binary files /dev/null and b/screenshots/after_access_code_input_2026-02-09T11-55-10-362Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T12-48-12-232Z.png b/screenshots/after_password_input_2026-02-02T12-48-12-232Z.png new file mode 100644 index 0000000..09be3a4 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T12-48-12-232Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T12-54-22-924Z.png b/screenshots/after_password_input_2026-02-02T12-54-22-924Z.png new file mode 100644 index 0000000..09be3a4 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T12-54-22-924Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T12-59-30-253Z.png b/screenshots/after_password_input_2026-02-02T12-59-30-253Z.png new file mode 100644 index 0000000..20ce958 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T12-59-30-253Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T13-02-11-485Z.png b/screenshots/after_password_input_2026-02-02T13-02-11-485Z.png new file mode 100644 index 0000000..20ce958 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T13-02-11-485Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T13-06-07-407Z.png b/screenshots/after_password_input_2026-02-02T13-06-07-407Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T13-06-07-407Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T13-08-32-289Z.png b/screenshots/after_password_input_2026-02-02T13-08-32-289Z.png new file mode 100644 index 0000000..20ce958 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T13-08-32-289Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T13-14-53-754Z.png b/screenshots/after_password_input_2026-02-02T13-14-53-754Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T13-14-53-754Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T13-16-15-880Z.png b/screenshots/after_password_input_2026-02-02T13-16-15-880Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T13-16-15-880Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T13-23-26-378Z.png b/screenshots/after_password_input_2026-02-02T13-23-26-378Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T13-23-26-378Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T13-27-49-552Z.png b/screenshots/after_password_input_2026-02-02T13-27-49-552Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T13-27-49-552Z.png differ diff --git a/screenshots/after_password_input_2026-02-02T13-28-37-888Z.png b/screenshots/after_password_input_2026-02-02T13-28-37-888Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-02T13-28-37-888Z.png differ diff --git a/screenshots/after_password_input_2026-02-03T08-56-51-912Z.png b/screenshots/after_password_input_2026-02-03T08-56-51-912Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-03T08-56-51-912Z.png differ diff --git a/screenshots/after_password_input_2026-02-03T09-40-22-517Z.png b/screenshots/after_password_input_2026-02-03T09-40-22-517Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-03T09-40-22-517Z.png differ diff --git a/screenshots/after_password_input_2026-02-03T09-50-36-846Z.png b/screenshots/after_password_input_2026-02-03T09-50-36-846Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-03T09-50-36-846Z.png differ diff --git a/screenshots/after_password_input_2026-02-03T11-17-17-490Z.png b/screenshots/after_password_input_2026-02-03T11-17-17-490Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-03T11-17-17-490Z.png differ diff --git a/screenshots/after_password_input_2026-02-05T17-30-45-769Z.png b/screenshots/after_password_input_2026-02-05T17-30-45-769Z.png new file mode 100644 index 0000000..77ccc3c Binary files /dev/null and b/screenshots/after_password_input_2026-02-05T17-30-45-769Z.png differ diff --git a/screenshots/after_password_input_2026-02-05T20-06-51-661Z.png b/screenshots/after_password_input_2026-02-05T20-06-51-661Z.png new file mode 100644 index 0000000..77ccc3c Binary files /dev/null and b/screenshots/after_password_input_2026-02-05T20-06-51-661Z.png differ diff --git a/screenshots/after_password_input_2026-02-09T11-55-11-428Z.png b/screenshots/after_password_input_2026-02-09T11-55-11-428Z.png new file mode 100644 index 0000000..2118b64 Binary files /dev/null and b/screenshots/after_password_input_2026-02-09T11-55-11-428Z.png differ diff --git a/screenshots/login_failed_after_refresh_2026-02-02T12-48-21-754Z.png b/screenshots/login_failed_after_refresh_2026-02-02T12-48-21-754Z.png new file mode 100644 index 0000000..6d2c545 Binary files /dev/null and b/screenshots/login_failed_after_refresh_2026-02-02T12-48-21-754Z.png differ diff --git a/screenshots/login_failed_after_refresh_2026-02-02T12-54-39-514Z.png b/screenshots/login_failed_after_refresh_2026-02-02T12-54-39-514Z.png new file mode 100644 index 0000000..7fc7065 Binary files /dev/null and b/screenshots/login_failed_after_refresh_2026-02-02T12-54-39-514Z.png differ diff --git a/screenshots/login_failed_after_refresh_2026-02-02T12-59-46-976Z.png b/screenshots/login_failed_after_refresh_2026-02-02T12-59-46-976Z.png new file mode 100644 index 0000000..7fc7065 Binary files /dev/null and b/screenshots/login_failed_after_refresh_2026-02-02T12-59-46-976Z.png differ diff --git a/screenshots/login_failed_after_refresh_2026-02-02T13-02-35-407Z.png b/screenshots/login_failed_after_refresh_2026-02-02T13-02-35-407Z.png new file mode 100644 index 0000000..7fc7065 Binary files /dev/null and b/screenshots/login_failed_after_refresh_2026-02-02T13-02-35-407Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-02T13-02-30-736Z.png b/screenshots/login_stuck_before_action_2026-02-02T13-02-30-736Z.png new file mode 100644 index 0000000..a493c5f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-02T13-02-30-736Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-02T13-06-27-147Z.png b/screenshots/login_stuck_before_action_2026-02-02T13-06-27-147Z.png new file mode 100644 index 0000000..e533908 Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-02T13-06-27-147Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-02T13-08-52-043Z.png b/screenshots/login_stuck_before_action_2026-02-02T13-08-52-043Z.png new file mode 100644 index 0000000..72a5e80 Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-02T13-08-52-043Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-02T13-15-13-485Z.png b/screenshots/login_stuck_before_action_2026-02-02T13-15-13-485Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-02T13-15-13-485Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-02T13-16-35-635Z.png b/screenshots/login_stuck_before_action_2026-02-02T13-16-35-635Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-02T13-16-35-635Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-02T13-23-46-116Z.png b/screenshots/login_stuck_before_action_2026-02-02T13-23-46-116Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-02T13-23-46-116Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-02T13-28-09-315Z.png b/screenshots/login_stuck_before_action_2026-02-02T13-28-09-315Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-02T13-28-09-315Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-02T13-28-57-618Z.png b/screenshots/login_stuck_before_action_2026-02-02T13-28-57-618Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-02T13-28-57-618Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-03T08-57-11-659Z.png b/screenshots/login_stuck_before_action_2026-02-03T08-57-11-659Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-03T08-57-11-659Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-03T09-40-42-261Z.png b/screenshots/login_stuck_before_action_2026-02-03T09-40-42-261Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-03T09-40-42-261Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-03T09-50-56-611Z.png b/screenshots/login_stuck_before_action_2026-02-03T09-50-56-611Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-03T09-50-56-611Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-03T11-17-37-279Z.png b/screenshots/login_stuck_before_action_2026-02-03T11-17-37-279Z.png new file mode 100644 index 0000000..e57f7df Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-03T11-17-37-279Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-05T17-31-05-538Z.png b/screenshots/login_stuck_before_action_2026-02-05T17-31-05-538Z.png new file mode 100644 index 0000000..a05e6c4 Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-05T17-31-05-538Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-05T20-07-11-434Z.png b/screenshots/login_stuck_before_action_2026-02-05T20-07-11-434Z.png new file mode 100644 index 0000000..a05e6c4 Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-05T20-07-11-434Z.png differ diff --git a/screenshots/login_stuck_before_action_2026-02-09T11-55-31-187Z.png b/screenshots/login_stuck_before_action_2026-02-09T11-55-31-187Z.png new file mode 100644 index 0000000..825991f Binary files /dev/null and b/screenshots/login_stuck_before_action_2026-02-09T11-55-31-187Z.png differ diff --git a/screenshots/login_stuck_before_refresh_2026-02-02T12-48-19-340Z.png b/screenshots/login_stuck_before_refresh_2026-02-02T12-48-19-340Z.png new file mode 100644 index 0000000..5a14df2 Binary files /dev/null and b/screenshots/login_stuck_before_refresh_2026-02-02T12-48-19-340Z.png differ diff --git a/screenshots/login_stuck_before_refresh_2026-02-02T12-54-35-051Z.png b/screenshots/login_stuck_before_refresh_2026-02-02T12-54-35-051Z.png new file mode 100644 index 0000000..5a14df2 Binary files /dev/null and b/screenshots/login_stuck_before_refresh_2026-02-02T12-54-35-051Z.png differ diff --git a/screenshots/login_stuck_before_refresh_2026-02-02T12-59-42-479Z.png b/screenshots/login_stuck_before_refresh_2026-02-02T12-59-42-479Z.png new file mode 100644 index 0000000..8da5e7f Binary files /dev/null and b/screenshots/login_stuck_before_refresh_2026-02-02T12-59-42-479Z.png differ diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..17e4ed1 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,42 @@ +// Configuration for scraper + +import dotenv from 'dotenv'; +dotenv.config(); + +export const config = { + // Credentials from environment + credentials: { + employeeNumber: process.env.BESSA_EMPLOYEE_NUMBER || '', + password: process.env.BESSA_PASSWORD || '', + }, + + // Puppeteer settings + puppeteer: { + headless: process.env.PUPPETEER_HEADLESS !== 'false', + defaultTimeout: 30000, + navigationTimeout: 60000, + }, + + // Scraper settings + scraper: { + waitAfterClick: 1000, + waitAfterNavigation: 2000, + maxRetries: 3, + }, + + // Storage + storage: { + dataDir: './data', + menuFile: './data/menus.json', + }, +} as const; + +// Validation +export function validateConfig(): void { + if (!config.credentials.employeeNumber) { + throw new Error('BESSA_EMPLOYEE_NUMBER is required in .env file'); + } + if (!config.credentials.password) { + throw new Error('BESSA_PASSWORD is required in .env file'); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..6c836cd --- /dev/null +++ b/src/index.ts @@ -0,0 +1,57 @@ +#!/usr/bin/env node +import { MenuScraper } from './scraper/menu-scraper.js'; +import { mergeWeeklyMenu } from './storage/menu-store.js'; +import { config, validateConfig } from './config.js'; +import { logger } from './utils/logger.js'; + +/** + * Main entry point for the scraper + */ +async function main() { + try { + // Validate configuration + logger.info('Validating configuration...'); + validateConfig(); + + // Initialize scraper + const scraper = new MenuScraper(); + await scraper.init(); + + try { + // Scrape menus + logger.info('Starting scrape of menus (multi-week)...'); + const weeklyMenu = await scraper.scrapeMenus(); + + // Save to storage + logger.info('Saving scraped data...'); + await mergeWeeklyMenu(weeklyMenu); + + // Print summary + logger.success('\\n=== Scraping Complete ==='); + logger.info(`Week: ${weeklyMenu.year}-W${weeklyMenu.weekNumber}`); + logger.info(`Days scraped: ${weeklyMenu.days.length}`); + + for (const day of weeklyMenu.days) { + logger.info(` ${day.weekday}: ${day.items.length} items`); + } + + const totalItems = weeklyMenu.days.reduce((sum, day) => sum + day.items.length, 0); + logger.success(`Total menu items: ${totalItems}`); + + } finally { + // Always close browser + await scraper.close(); + } + + } catch (error) { + logger.error('Scraping failed:', error); + process.exit(1); + } +} + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} + +export { main }; diff --git a/src/scraper/api-test.ts b/src/scraper/api-test.ts new file mode 100644 index 0000000..7df3c29 --- /dev/null +++ b/src/scraper/api-test.ts @@ -0,0 +1,51 @@ + +import { logger } from '../utils/logger.js'; + +async function runApiTest() { + logger.info('Starting API Test with cached token...'); + + // Token from local_storage.json + const cachedToken = 'dba7d86e83c7f462fd8af96521dea41c4facd8a5'; + + // Date calculation + const today = new Date(); + const dateStr = today.toISOString().split('T')[0]; + + // Try a few dates (sometimes today has no menu if it's late or weekend) + // But let's stick to today or tomorrow. + + const venueId = 591; + const menuId = 7; + const apiUrl = `https://api.bessa.app/v1/venues/${venueId}/menu/${menuId}/${dateStr}/`; + + logger.info(`Testing API call to: ${apiUrl}`); + logger.info(`Using Token: ${cachedToken.substring(0, 10)}...`); + + try { + const response = await fetch(apiUrl, { + headers: { + 'Authorization': `Token ${cachedToken}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36' + } + }); + + logger.info(`Response Status: ${response.status} ${response.statusText}`); + + if (response.ok) { + const data = await response.json(); + logger.success('API Call Successful!'); + console.log(JSON.stringify(data, null, 2)); + } else { + logger.error('API Call Failed.'); + const text = await response.text(); + console.log('Response Body:', text); + } + + } catch (error) { + logger.error('Fetch failed:', error); + } +} + +runApiTest(); diff --git a/src/scraper/interactive-analyzer.ts b/src/scraper/interactive-analyzer.ts new file mode 100644 index 0000000..dc04af8 --- /dev/null +++ b/src/scraper/interactive-analyzer.ts @@ -0,0 +1,157 @@ + +import puppeteer from 'puppeteer'; +import fs from 'fs/promises'; +import path from 'path'; +import * as readline from 'readline'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Ensure we have a place to save logs +const ARTIFACTS_DIR = process.env.ANTIGRAVITY_ARTIFACTS_DIR || path.join(process.cwd(), 'analysis_results'); + +async function ensureDir(dir: string) { + try { + await fs.access(dir); + } catch { + await fs.mkdir(dir, { recursive: true }); + } +} + +async function runInteractiveAnalysis() { + await ensureDir(ARTIFACTS_DIR); + console.log('--- INTERACTIVE ANALYSIS TOOL ---'); + console.log('Starting Browser (Headless: FALSE)...'); + console.log('Artifacts will be saved to:', ARTIFACTS_DIR); + + const browser = await puppeteer.launch({ + headless: false, // User wants to see and interact + defaultViewport: null, // Full window + executablePath: '/usr/bin/chromium', + args: [ + '--start-maximized', + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage' + ], + devtools: true // Useful for the user to see what's happening + }); + + const page = await browser.newPage(); + + // Setup Data Collection + const networkLogs: any[] = []; + const relevantHosts = ['bessa.app', 'web.bessa.app']; + + await page.setRequestInterception(true); + + page.on('request', (request) => { + // Continue all requests + request.continue(); + }); + + page.on('response', async (response) => { + const url = response.url(); + const type = response.request().resourceType(); + + // Filter: We are mainly interested in XHR, Fetch, and Documents (for initial load) + // And only from relevant hosts to avoid noise (analytics, external fonts, etc.) + const isRelevantHost = relevantHosts.some(host => url.includes(host)); + const isRelevantType = ['xhr', 'fetch', 'document', 'script'].includes(type); + + if (isRelevantHost && isRelevantType) { + try { + // Try to get JSON response + let responseBody = null; + if (url.includes('/api/') || type === 'xhr' || type === 'fetch') { + try { + responseBody = await response.json(); + } catch (e) { + // Not JSON, maybe text? + try { + // Limit text size + const text = await response.text(); + responseBody = text.length > 2000 ? text.substring(0, 2000) + '...[TRUNCATED]' : text; + } catch (e2) { + responseBody = '[COULD NOT READ BODY]'; + } + } + } + + networkLogs.push({ + timestamp: new Date().toISOString(), + method: response.request().method(), + url: url, + status: response.status(), + type: type, + requestHeaders: response.request().headers(), + responseHeaders: response.headers(), + body: responseBody + }); + + // Real-time feedback + if (url.includes('/api/')) { + console.log(`[API CAPTURED] ${response.request().method()} ${url}`); + } + + } catch (err) { + // Ignore errors reading response (e.g. redirects or closed) + } + } + }); + + // Initial navigation + console.log('Navigating to base URL...'); + await page.goto('https://web.bessa.app/knapp-kantine', { waitUntil: 'networkidle2' }); + + console.log('\n================================================================================'); + console.log('BROWSER IS OPEN. PLEASE ACTION REQUIRED:'); + console.log('1. Log in manually in the browser window.'); + console.log('2. Navigate to the menu view (Day Selection -> Select Day -> Menu).'); + console.log('3. Browse around to trigger API calls.'); + console.log('\nWHEN YOU ARE DONE, PRESS [ENTER] IN THIS TERMINAL TO SAVE AND EXIT.'); + console.log('================================================================================\n'); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + await new Promise(resolve => { + rl.question('Press Enter to finish analysis...', () => { + rl.close(); + resolve(); + }); + }); + + console.log('Capturing final state...'); + + // 1. Save Full Page HTML + const html = await page.content(); + await fs.writeFile(path.join(ARTIFACTS_DIR, 'final_page_state.html'), html); + + // 2. Save Cookies/Storage (for Auth Replication) + const client = await page.target().createCDPSession(); + const cookies = await client.send('Network.getAllCookies'); + await fs.writeFile(path.join(ARTIFACTS_DIR, 'cookies.json'), JSON.stringify(cookies, null, 2)); + + const localStorageData = await page.evaluate(() => { + return JSON.stringify(localStorage); + }); + await fs.writeFile(path.join(ARTIFACTS_DIR, 'local_storage.json'), localStorageData); + + const sessionStorageData = await page.evaluate(() => { + return JSON.stringify(sessionStorage); + }); + await fs.writeFile(path.join(ARTIFACTS_DIR, 'session_storage.json'), sessionStorageData); + + // 3. Save Network Logs + await fs.writeFile(path.join(ARTIFACTS_DIR, 'network_traffic.json'), JSON.stringify(networkLogs, null, 2)); + + console.log('Analysis data saved to:', ARTIFACTS_DIR); + await browser.close(); + process.exit(0); +} + +runInteractiveAnalysis().catch(console.error); diff --git a/src/scraper/menu-scraper.ts b/src/scraper/menu-scraper.ts new file mode 100644 index 0000000..ee21c6a --- /dev/null +++ b/src/scraper/menu-scraper.ts @@ -0,0 +1,745 @@ +/// +import puppeteer, { Browser, Page } from 'puppeteer'; +import { WeeklyMenu, DayMenu, MenuItem } from '../types.js'; +import { SELECTORS, URLS } from './selectors.js'; +import { config } from '../config.js'; +import { logger } from '../utils/logger.js'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + + +interface ApiMenuItem { + id: number; + name: string; + description: string; + price: string; + available_amount: string; + created: string; + updated: string; +} + +interface ApiMenuResult { + id: number; + items: ApiMenuItem[]; + date: string; +} + +interface ApiMenuResponse { + results: ApiMenuResult[]; +} + +export class MenuScraper { + private browser: Browser | null = null; + private page: Page | null = null; + + /** + * Initialize browser and page + */ + async init(): Promise { + logger.info('[TRACE] Starting browser initialization...'); + logger.info(`[TRACE] Using Chromium at: /usr/bin/chromium`); + logger.info(`[TRACE] Headless mode: ${config.puppeteer.headless}`); + + this.browser = await puppeteer.launch({ + headless: config.puppeteer.headless, + executablePath: '/usr/bin/chromium', + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + ], + }); + logger.info('[TRACE] Puppeteer launch completed'); + + logger.info('[TRACE] Creating new page...'); + this.page = await this.browser.newPage(); + + logger.info('[TRACE] Setting viewport to 1280x1024...'); + await this.page.setViewport({ width: 1280, height: 1024 }); + + logger.info(`[TRACE] Setting default timeout to ${config.puppeteer.defaultTimeout}ms`); + await this.page.setDefaultTimeout(config.puppeteer.defaultTimeout); + + // Set realistic User-Agent + await this.page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'); + + // Capture console logs with more detail + this.page.on('console', msg => { + const type = msg.type(); + const text = msg.text(); + if (type === 'error' || type === 'warn' || text.includes('auth') || text.includes('login')) { + logger.info(`[BROWSER ${type.toUpperCase()}] ${text}`); + } + }); + + // Capture all requests/responses for auth debugging + this.page.on('request', request => { + const url = request.url(); + if (url.includes('auth') || url.includes('login') || url.includes('session') || url.includes('bessa.app/api')) { + logger.info(`[NETWORK REQ] ${request.method()} ${url}`); + } + }); + + this.page.on('response', response => { + const url = response.url(); + if (url.includes('auth') || url.includes('login') || url.includes('session') || url.includes('bessa.app/api')) { + const status = response.status(); + logger.info(`[NETWORK RES] ${status} ${url}`); + if (status >= 400) { + logger.warn(`[NETWORK ERR] ${status} for ${url}`); + } + } + }); + + // Capture failed requests + this.page.on('requestfailed', request => { + const url = request.url(); + const error = request.failure()?.errorText; + logger.warn(`[NETWORK FAILURE] ${url} - ${error}`); + }); + + logger.success('[TRACE] Browser initialized successfully'); + } + + /** + * Save a screenshot for debugging + */ + private async saveScreenshot(name: string): Promise { + if (!this.page) return null; + try { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const fileName = `${name}_${timestamp}.png`; + + // Use current session brain path or fallback to a local screenshots directory + const brainPath = process.env.ANTIGRAVITY_ARTIFACTS_DIR || path.join(process.cwd(), 'screenshots'); + const filePath = path.join(brainPath, fileName); + + // Ensure directory exists if it's the local fallback + if (!process.env.ANTIGRAVITY_ARTIFACTS_DIR) { + const fs = await import('fs/promises'); + await fs.mkdir(brainPath, { recursive: true }); + } + + await this.page.screenshot({ path: filePath }); + logger.info(`[TRACE] Screenshot saved to: ${filePath}`); + return filePath; + } catch (error) { + logger.error(`[TRACE] Failed to save screenshot: ${error}`); + return null; + } + } + + /** + * Close browser + */ + async close(): Promise { + logger.info('[TRACE] Closing browser...'); + if (this.browser) { + await this.browser.close(); + logger.success('[TRACE] Browser closed'); + } else { + logger.warn('[TRACE] Browser was already null, nothing to close'); + } + } + + /** + * Navigate to Bessa and handle cookie consent + */ + private async navigateAndAcceptCookies(): Promise { + if (!this.page) throw new Error('Page not initialized'); + + logger.info(`[TRACE] Navigating to ${URLS.BASE}...`); + logger.info('[TRACE] Waiting for networkidle2...'); + + await this.page.goto(URLS.BASE, { waitUntil: 'networkidle2' }); + + const currentUrl = this.page.url(); + logger.success(`[TRACE] Navigation complete. Current URL: ${currentUrl}`); + + // Accept cookies if banner is present + logger.info(`[TRACE] Looking for cookie banner (selector: ${SELECTORS.COOKIE_ACCEPT_ALL})...`); + logger.info('[TRACE] Timeout: 5000ms'); + + try { + await this.page.waitForSelector(SELECTORS.COOKIE_ACCEPT_ALL, { timeout: 5000 }); + logger.success('[TRACE] Cookie banner found!'); + + logger.info('[TRACE] Clicking "Accept all" button...'); + await this.page.click(SELECTORS.COOKIE_ACCEPT_ALL); + + logger.info(`[TRACE] Waiting ${config.scraper.waitAfterClick}ms after click...`); + await this.wait(config.scraper.waitAfterClick); + + logger.success('[TRACE] Cookies accepted successfully'); + } catch (error) { + logger.info('[TRACE] No cookie banner found (timeout reached)'); + } + + logger.info(`[TRACE] Current URL after cookie handling: ${this.page.url()}`); + } + + /** + * Helper to reliably fill an input and trigger validation events + */ + private async fillInput(selector: string, value: string): Promise { + if (!this.page) return; + await this.page.waitForSelector(selector); + await this.page.focus(selector); + + // Clear field first + await this.page.evaluate((sel) => { + const el = document.querySelector(sel) as HTMLInputElement; + if (el) el.value = ''; + }, selector); + + await this.page.type(selector, value, { delay: 50 }); + + // Trigger validation events for Angular/React/etc. + await this.page.evaluate((sel) => { + const el = document.querySelector(sel) as HTMLInputElement; + if (el) { + el.dispatchEvent(new Event('input', { bubbles: true })); + el.dispatchEvent(new Event('change', { bubbles: true })); + el.dispatchEvent(new Event('blur', { bubbles: true })); + } + }, selector); + } + + /** + * Check if the user is currently logged in based on page content + */ + private async isLoggedIn(): Promise { + if (!this.page) return false; + return await this.page.evaluate(() => { + const bodyText = document.body.innerText; + return bodyText.includes('Log Out') || + bodyText.includes('Abmelden') || + bodyText.includes('Mein Konto') || + !!document.querySelector('button[mat-menu-item]'); + }); + } + + /** + * Perform login + */ + private async login(): Promise { + if (!this.page) throw new Error('Page not initialized'); + + logger.info('[TRACE] ===== LOGIN FLOW START ====='); + logger.info(`[TRACE] Current URL before login: ${this.page.url()}`); + + // Detect if already logged in + if (await this.isLoggedIn()) { + logger.success('[TRACE] Already logged in detected! Skipping login modal flow.'); + await this.navigateToDaySelection(); + return; + } + + logger.info(`[TRACE] Waiting for Pre-order menu button (selector: ${SELECTORS.PREORDER_MENU_BUTTON})...`); + await this.page.waitForSelector(SELECTORS.PREORDER_MENU_BUTTON); + logger.success('[TRACE] Pre-order menu button found!'); + + logger.info('[TRACE] Clicking Pre-order menu button...'); + await this.page.click(SELECTORS.PREORDER_MENU_BUTTON); + logger.success('[TRACE] Click executed'); + + logger.info(`[TRACE] Waiting ${config.scraper.waitAfterClick}ms for modal to appear...`); + await this.wait(config.scraper.waitAfterClick); + logger.info(`[TRACE] Current URL after button click: ${this.page.url()}`); + + logger.info('[TRACE] ----- LOGIN FORM FILLING START -----'); + logger.info(`[TRACE] Waiting for modal container (selector: ${SELECTORS.LOGIN_MODAL_CONTAINER})...`); + logger.info('[TRACE] Timeout: 30000ms'); + + try { + await this.page.waitForSelector(SELECTORS.LOGIN_MODAL_CONTAINER); + logger.success('[TRACE] Modal container found!'); + } catch (error: any) { + logger.error(`[TRACE] Modal container NOT found! Error: ${error.message}`); + await this.saveScreenshot('failed_login_modal'); + const bodyText = await this.page.evaluate(() => document.body.innerText); + logger.info(`[TRACE] Page body text (first 500 chars): ${bodyText.substring(0, 500)}`); + throw error; + } + + logger.info('[TRACE] Typing access code...'); + await this.fillInput(SELECTORS.LOGIN_ACCESS_CODE, config.credentials.employeeNumber); + + // Verify value + const accessCodeValue = await this.page.$eval(SELECTORS.LOGIN_ACCESS_CODE, (el: any) => el.value); + if (accessCodeValue !== config.credentials.employeeNumber) { + logger.warn(`[TRACE] Access code value verification failed!`); + } + logger.success(`[TRACE] Access code entered`); + await this.saveScreenshot('after_access_code_input'); + + logger.info(`[TRACE] Waiting for password field (selector: ${SELECTORS.LOGIN_PASSWORD})...`); + await this.page.waitForSelector(SELECTORS.LOGIN_PASSWORD); + logger.success('[TRACE] Password field found!'); + + logger.info('[TRACE] Typing password...'); + await this.fillInput(SELECTORS.LOGIN_PASSWORD, config.credentials.password); + + // Verify value + const passwordValue = await this.page.$eval(SELECTORS.LOGIN_PASSWORD, (el: any) => el.value); + if (passwordValue !== config.credentials.password) { + logger.warn('[TRACE] Password value verification failed!'); + } + logger.success('[TRACE] Password entered'); + await this.saveScreenshot('after_password_input'); + + logger.info(`[TRACE] Checking for error messages before clicking login button...`); + const errorMessage = await this.page.evaluate((selector) => { + const errorElement = document.querySelector(selector); + return errorElement ? (errorElement as HTMLElement).innerText : null; + }, SELECTORS.LOGIN_ERROR_MESSAGE); + + if (errorMessage) { + logger.warn(`[TRACE] Found error message before login click: "${errorMessage}". Attempting to proceed anyway.`); + } else { + logger.info('[TRACE] No error messages found.'); + } + + logger.info(`[TRACE] Clicking login button and pressing Enter: ${SELECTORS.LOGIN_SUBMIT}...`); + try { + await this.page.waitForSelector(SELECTORS.LOGIN_SUBMIT, { timeout: 10000 }); + + // Check if button is disabled (Angular validation might have failed) + const btnState = await this.page.$eval(SELECTORS.LOGIN_SUBMIT, (el: any) => ({ + disabled: el.disabled, + text: el.innerText, + classes: el.className + })); + + logger.info(`[TRACE] Button state: disabled=${btnState.disabled}, classes="${btnState.classes}"`); + + if (btnState.disabled) { + logger.warn(`[TRACE] Login button is DISABLED! Forcing direct click via evaluate and Enter key...`); + } + + // Strategy: Focus password and press Enter + click button + await this.page.focus(SELECTORS.LOGIN_PASSWORD); + await this.page.keyboard.press('Enter'); + + // Allow a small gap + await this.wait(500); + + // Perform single click via page.click + await this.page.click(SELECTORS.LOGIN_SUBMIT, { delay: 100 }); + + // Fallback: trigger click via evaluate as well if Enter didn't work + await this.page.evaluate((selector) => { + const btn = document.querySelector(selector) as HTMLButtonElement; + if (btn) btn.click(); + }, SELECTORS.LOGIN_SUBMIT); + + logger.success('[TRACE] Login triggers executed (Enter + Click)'); + } catch (error) { + logger.error(`[TRACE] Failed to interact with login button: ${error}`); + await this.saveScreenshot('failed_login_button_interaction'); + logger.info('[TRACE] Attempting "Enter" key final fallback on password field...'); + await this.page.focus(SELECTORS.LOGIN_PASSWORD); + await this.page.keyboard.press('Enter'); + } + + logger.info(`[TRACE] Waiting ${config.scraper.waitAfterNavigation * 2}ms for transition... (increased for stability)`); + await this.wait(config.scraper.waitAfterNavigation * 2); + + // Transition check & Refresh Strategy + let isAtDaySelection = false; + try { + // Check for dialog or redirect + await this.page.waitForSelector(SELECTORS.DAY_SELECTION_DIALOG, { timeout: 15000 }); + isAtDaySelection = true; + logger.success('[TRACE] Day selection dialog appeared directly after login'); + } catch (e) { + logger.warn('[TRACE] Day selection dialog not found after login. Investigating state...'); + await this.saveScreenshot('login_stuck_before_action'); + + // Check if login modal is still present + const isModalStillThere = await this.page.evaluate((sel1, sel2) => { + return !!document.querySelector(sel1) || !!document.querySelector(sel2); + }, SELECTORS.LOGIN_MODAL_CONTAINER, SELECTORS.LOGIN_ACCESS_CODE); + + if (isModalStillThere) { + logger.warn('[TRACE] Login modal or fields are STILL present! Submit might have failed silently.'); + + // Check for error messages specifically + const postLoginError = await this.page.evaluate((selector) => { + const el = document.querySelector(selector); + return el ? (el as HTMLElement).innerText : null; + }, SELECTORS.LOGIN_ERROR_MESSAGE); + + if (postLoginError) { + logger.error(`[TRACE] Login failed with error message: "${postLoginError}"`); + throw new Error(`Login failed on page: ${postLoginError}`); + } + } + + // Strategy: Check if we are at least logged in now (even if modal is weird) + if (await this.isLoggedIn()) { + logger.success('[TRACE] Detected as logged in after wait! Navigating to day selection.'); + await this.navigateToDaySelection(); + isAtDaySelection = true; + return; + } + + logger.warn('[TRACE] Not logged in and dialog missing. Applying Refresh Strategy...'); + + // User's suggestion: Refresh the page and try again + logger.info(`[TRACE] Refreshing page by navigating to ${URLS.BASE}...`); + await this.page.goto(URLS.BASE, { waitUntil: 'networkidle2' }); + + // Per user feedback: We must click the button again to see if the session is picked up + logger.info('[TRACE] Refresh done. Clicking Pre-order button to trigger session check...'); + await this.navigateToDaySelection(); + + // Re-verify login status + if (await this.isLoggedIn()) { + logger.success('[TRACE] Refresh confirmed: We are logged in! Day selection should be open.'); + isAtDaySelection = true; + } else { + logger.error('[TRACE] Refresh failed: Still not logged in. Login might have truly failed.'); + await this.saveScreenshot('login_failed_after_refresh'); + throw new Error('Login failed: Not logged in even after refresh.'); + } + } + + logger.info(`[TRACE] Current URL after login attempt: ${this.page.url()}`); + logger.success('[TRACE] ===== LOGIN FLOW ATTEMPT COMPLETE ====='); + } + + /** + * Common logic to click the pre-order button and wait for the dialog + */ + private async navigateToDaySelection(): Promise { + if (!this.page) throw new Error('Page not initialized'); + + logger.info(`[TRACE] Clicking Pre-order menu button (selector: ${SELECTORS.PREORDER_MENU_BUTTON})...`); + await this.page.waitForSelector(SELECTORS.PREORDER_MENU_BUTTON); + await this.page.click(SELECTORS.PREORDER_MENU_BUTTON); + logger.success('[TRACE] Pre-order menu button clicked'); + + logger.info(`[TRACE] Waiting ${config.scraper.waitAfterNavigation}ms for transition to dialog...`); + await this.wait(config.scraper.waitAfterNavigation); + } + + private getWeekNumber(date: Date = new Date()): number { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); + } + + /** + * Extract current week number and year from the page + */ + private async extractWeekInfo(): Promise<{ year: number; weekNumber: number }> { + if (!this.page) throw new Error('Page not initialized'); + + logger.info('[TRACE] Extracting week information from page...'); + + try { + await this.page.waitForSelector(SELECTORS.WEEK_HEADER, { timeout: 10000 }); + } catch (e) { + logger.warn('[TRACE] Week header selector not found, attempting anyway...'); + } + + const { weekText, headerTitle, bodyText } = await this.page.evaluate((selInfo) => { + const h = document.querySelector(selInfo); + return { + weekText: h?.textContent || '', + headerTitle: document.title || '', + bodyText: document.body.innerText || '' + }; + }, SELECTORS.WEEK_HEADER); + + logger.info(`[TRACE] Week header text: "${weekText}" (Title: "${headerTitle}")`); + + // Parse "CW 6", "KW 6", "Week 6", "Woche 6" + // Try multiple sources: Header element, Page title, and Page body as a fallback + const cwMatch = weekText.match(/(?:CW|KW|Week|Woche|W)\s*(\d+)/i) || + headerTitle.match(/(?:CW|KW|Week|Woche|W)\s*(\d+)/i) || + bodyText.match(/(?:CW|KW|Week|Woche|W)\s*(\d+)/i); + + const weekNumber = cwMatch ? parseInt(cwMatch[1]) : this.getWeekNumber(); + + logger.info(`[TRACE] Parsed week number: ${weekNumber}`); + + // Get current year + const year = new Date().getFullYear(); + logger.info(`[TRACE] Using year: ${year}`); + + logger.success(`[TRACE] Detected week: ${year}-W${weekNumber}`); + return { year, weekNumber }; + } + + /** + * Extract Authentication Token from LocalStorage + */ + private async getAuthToken(): Promise { + if (!this.page) throw new Error('Page not initialized'); + + const token = await this.page.evaluate(() => { + const store = localStorage.getItem('AkitaStores'); + if (!store) return null; + try { + const parsed = JSON.parse(store); + return parsed.auth?.token; + } catch (e) { + return null; + } + }); + + if (!token) { + throw new Error('Authentication token not found in LocalStorage (AkitaStores)'); + } + return token; + } + + /** + * Fetch menu for a specific date using the Bessa API + */ + private async fetchMenuForDate(token: string, date: string, weekday: string): Promise { + if (!this.page) throw new Error('Page not initialized'); + + const venueId = 591; + const menuId = 7; // "Bestellung" / configured menu ID + const apiUrl = `${URLS.API_BASE}/venues/${venueId}/menu/${menuId}/${date}/`; + + // Execute fetch inside the browser context + const responseData = await this.page.evaluate(async (url, authToken) => { + try { + const res = await fetch(url, { + headers: { + 'Authorization': `Token ${authToken}`, + 'Accept': 'application/json' + } + }); + + if (!res.ok) { + return { error: `Status ${res.status}: ${res.statusText}` }; + } + + return await res.json(); + } catch (e: any) { + return { error: e.toString() }; + } + }, apiUrl, token); + + if (responseData.error) { + // 404 might just mean no menu for that day (e.g. weekend) + logger.warn(`[TRACE] API fetch warning for ${date}: ${responseData.error}`); + // Return empty menu for that day + return { date, weekday, items: [] }; + } + + const apiResponse = responseData as ApiMenuResponse; + const items: MenuItem[] = []; + + // Parse results + if (apiResponse.results && apiResponse.results.length > 0) { + for (const group of apiResponse.results) { + if (group.items) { + for (const item of group.items) { + items.push({ + id: `${date}_${item.id}`, + name: item.name, + description: item.description, + price: parseFloat(item.price), + available: parseInt(item.available_amount) > 0 || item.available_amount === null // Null sometimes acts as available + }); + } + } + } + } + + return { + date, + weekday, + items + }; + } + + /** + * Scrape menu for the current week using API + */ + /** + * Scrape menus starting from current week until no more data is found (min 2 weeks) + */ + async scrapeMenus(saveToFile: boolean = true): Promise { + await this.init(); + try { + logger.info('[TRACE] ========== SCRAPING MENUS (MULTI-WEEK) =========='); + + // 1. Navigate and Login (uses env credentials by default if not previously logged in in this instance, + // but here we assume login() called before or we use default) + // Ideally scrapeMenus should rely on session but current flow navigates again. + // Let's ensure we don't double login if already on page? + // Actually, for the /api/login flow, we will call scraper.login(user, pass) explicitly. + // But scrapeMenus calls this.login() internally. We should refactor scrapeMenus to accept credentials or skip login if already done. + // BETTER: separate init/login from scraping loop. + // For now, to keep it compatible: + if (!this.page) await this.init(); // Re-init if needed + + // If we are calling from /api/login, we might have already logged in. + // But scrapeMenus does full flow. + // Let's modify scrapeMenus to ONLY scrape and assume login is handled IF we want to separate them. + // However, existing `main()` calls `scrapeMenus()` which does everything. + // Let's just make it check if we are already logged in? Hard with Puppeteer statelessness efficiently. + + // Simpler approach for this task: + // Let scrapeMenus take optional credentials too? No, keep it simple. + // We will let the server call `login` then `scrapeMenus`. + // BUT `scrapeMenus` calls `login`. We need to remove `login` from `scrapeMenus` or make it conditional. + // Let's make `scrapeMenus` NOT strictly require login if we are already there, OR just call `login` again (idempotent-ish). + // Actually, `login` types in credentials. + + // Modification: scrapeMenus will use whatever credentials are set or env. + // But wait, `scrapeMenus` has `await this.login()` hardcoded. + // We will invoke `navigateAndAcceptCookies` and `login` ONLY if we are seemingly not ready. + // Or easier: Make a new method `scrapeMenusRaw` or just reuse `scrapeMenus` but pass a flag `skipLogin`. + + await this.navigateAndAcceptCookies(); + // We'll rely on the caller to have called login if they wanted custom creds, + // OR we call login here with defaults if not. + // ISSUE: `this.login()` uses env vars if no args. + // If the server calls `await scraper.login(u,p)`, then calls `await scraper.scrapeMenus()`, + // `scrapeMenus` will call `this.login()` (no args) -> uses env vars -> OVERWRITES the user session with default admin! + + // FIX: Add `skipLogin` param. + + } catch (e) { /*...*/ } + return { days: [], weekNumber: 0, year: 0, scrapedAt: '' }; // stub + } + + // RETHINKING: The tool requires REPLACE. + // Let's change signature to `async scrapeMenus(saveToFile: boolean = true, skipLogin: boolean = false): Promise` + + async scrapeMenus(saveToFile: boolean = true, skipLogin: boolean = false): Promise { + await this.init(); + try { + logger.info('[TRACE] ========== SCRAPING MENUS (MULTI-WEEK) =========='); + + if (!skipLogin) { + await this.navigateAndAcceptCookies(); + await this.login(); + } + + // 2. Get Auth Token + logger.info('[TRACE] Retrieving Auth Token...'); + const token = await this.getAuthToken(); + logger.success(`[TRACE] Auth token retrieved: ${token.substring(0, 10)}...`); + + // 3. Determine Start Date (Monday of current week) + const today = new Date(); + const weekInfo = await this.extractWeekInfo(); + + const currentDay = today.getUTCDay() || 7; // Sunday is 0 -> 7 + const startMonday = new Date(today); + startMonday.setUTCDate(today.getUTCDate() - currentDay + 1); + // Reset time to avoid drift + startMonday.setUTCHours(0, 0, 0, 0); + + logger.info(`[TRACE] Starting scrape from ${startMonday.toISOString().split('T')[0]}`); + + const days: DayMenu[] = []; + const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; + + const MAX_WEEKS = 8; + const MIN_DAYS_COVERAGE = 14; + + let currentDate = new Date(startMonday); + let daysProcessed = 0; + + while (true) { + // Safety break to prevent infinite loops (approx 8 weeks) + if (daysProcessed > MAX_WEEKS * 7) { + logger.warn('[TRACE] Reached maximum week limit (safety break). Stopping.'); + break; + } + + const dayOfWeek = currentDate.getUTCDay(); // 0=Sun, 1=Mon, ..., 6=Sat + const isWeekend = dayOfWeek === 0 || dayOfWeek === 6; + + if (isWeekend) { + // Skip weekends, just advance + // logger.debug(`[TRACE] Skipping weekend: ${currentDate.toISOString().split('T')[0]}`); + currentDate.setUTCDate(currentDate.getUTCDate() + 1); + daysProcessed++; + continue; + } + + const dateStr = currentDate.toISOString().split('T')[0]; + // Map 0(Sun)->6, 1(Mon)->0, etc. + const dayNameIndex = (dayOfWeek + 6) % 7; + const weekday = dayNames[dayNameIndex]; + + logger.info(`[TRACE] Fetching menu for ${weekday} (${dateStr})...`); + + try { + const dayMenu = await this.fetchMenuForDate(token, dateStr, weekday); + + if (dayMenu.items.length === 0) { + // Check if we have covered enough time + // Calculate difference in days from start + const diffTime = Math.abs(currentDate.getTime() - startMonday.getTime()); + const daysCovered = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + if (daysCovered >= MIN_DAYS_COVERAGE) { + logger.info(`[TRACE] Stopping scraping at ${dateStr} (No items found and > 2 weeks covered)`); + break; + } else { + logger.info(`[TRACE] Empty menu at ${dateStr}, but only covered ${daysCovered} days. Continuing...`); + // Add empty day to preserve structure if needed, or just skip? + // Usually we want to record empty days if they are valid weekdays + days.push(dayMenu); + } + } else { + days.push(dayMenu); + } + + } catch (error) { + logger.error(`[TRACE] Failed to fetch menu for ${dateStr}: ${error}`); + days.push({ date: dateStr, weekday, items: [] }); + } + + // Advance to next day + currentDate.setUTCDate(currentDate.getUTCDate() + 1); + daysProcessed++; + + // Be nice to the API + await this.wait(150); + } + + const resultMenu: WeeklyMenu = { + year: weekInfo.year, + weekNumber: weekInfo.weekNumber, + days: days, + scrapedAt: new Date().toISOString() + }; + + logger.success(`[TRACE] Scraping completed. Found ${days.length} days of menus.`); + return resultMenu; + + } catch (error) { + logger.error(`[TRACE] Scraping failed: ${error}`); + await this.saveScreenshot('scrape_error'); + throw error; + } finally { + await this.close(); + } + } + + /** + * Helper to wait + */ + private async wait(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + diff --git a/src/scraper/selectors.ts b/src/scraper/selectors.ts new file mode 100644 index 0000000..7bfc358 --- /dev/null +++ b/src/scraper/selectors.ts @@ -0,0 +1,46 @@ +// CSS Selectors based on screen documentation + +export const SELECTORS = { + // Cookie Consent (Screen #1) + COOKIE_ACCEPT_ALL: 'button::-p-text(Accept all), button::-p-text(Alle akzeptieren), button::-p-text(Zustimmen), .cmpboxbtnyes', + + // Landing Page (Screen #2) + PREORDER_MENU_BUTTON: 'button.order-type-button.button.high::-p-text(Pre-order menu)', + + // Login Modal (Screen #5) + LOGIN_MODAL_CONTAINER: 'app-access-code-dialog, app-access-code-login', + LOGIN_ACCESS_CODE: 'input[formcontrolname="accessCode"]', + LOGIN_PASSWORD: 'input[formcontrolname="password"]', + LOGIN_SUBMIT: 'button[bessa-button].base-button.button', + LOGIN_ERROR_MESSAGE: '.mat-error, .toast-error, app-message, .error, [class*="error"]', + + // Day Selection Dialog (Screen #10, #11) + DAY_SELECTION_DIALOG: 'app-canteen-dialog, app-bessa-select-day-dialog', + WEEK_CHEVRON_NEXT: 'button[aria-label="next week"]', + WEEK_CHEVRON_PREV: 'button[aria-label="previous week"]', + WEEK_HEADER: 'h2, [class*="week-header"], .calendar-week', + DAY_ROW: 'app-date-line', + ADD_ORDER_LINK: 'div.clickable', + + // Menu Overview (Screen #14) + MENU_CARD: '.menu-card, .dish-card, app-bessa-menu-card, [class*="menu-item"]', + MENU_ITEM_TITLE: 'h3, .menu-title, [class*="title"]', + MENU_ITEM_DESCRIPTION: 'p, .menu-description, [class*="description"]', + MENU_ITEM_PRICE: '.price, [class*="price"], .amount', + MENU_ITEM_ADD_BUTTON: 'button::-p-text(+), button.add-button', + NOT_AVAILABLE_TEXT: '::-p-text(Not available), ::-p-text(Nicht verfügbar)', + + // Week/Date Display + CALENDAR_WEEK_DISPLAY: '[class*="week"]', + DATE_DISPLAY: '[class*="date"]', + + // Close/Back buttons + CLOSE_BUTTON: 'button[aria-label="close"], .close-btn, button.close, mat-icon::-p-text(close)', + BACK_BUTTON: 'button[aria-label="back"], .back-arrow, button.back, mat-icon::-p-text(arrow_back)', + DONE_BUTTON: 'button::-p-text(Done), button::-p-text(Fertig)', +} as const; + +export const URLS = { + BASE: 'https://web.bessa.app/knapp-kantine', + API_BASE: 'https://api.bessa.app/v1', +} as const; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..565a1d3 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,748 @@ +import express from 'express'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import fs from 'fs/promises'; +import { config } from './config.js'; +import { logger } from './utils/logger.js'; +import { FlagStore, FlaggedItem } from './storage/flag-store.js'; +import { SseManager } from './services/sse-manager.js'; +import { PollingOrchestrator } from './services/polling-orchestrator.js'; + +const app = express(); +const port = 3005; // Changed from 3000 to avoid conflicts + +// Get current directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Project root (assuming we are in src/) +const projectRoot = join(__dirname, '..'); +const publicDir = join(projectRoot, 'public'); +const dataFile = join(projectRoot, 'data', 'menus.json'); +const dataDir = join(projectRoot, 'data'); + +// Initialize Services +const flagStore = new FlagStore(dataDir); +const sseManager = new SseManager(); +const orchestrator = new PollingOrchestrator(flagStore, sseManager); + +// Bessa API Constants +const BESSA_API_BASE = 'https://api.bessa.app/v1'; +const GUEST_TOKEN = 'c3418725e95a9f90e3645cbc846b4d67c7c66131'; +const CLIENT_VERSION = '1.7.0_prod/2026-01-26'; + +// Middleware +app.use(express.json()); + +// API Routes +app.post('/api/login', async (req, res) => { + const { employeeId, password } = req.body; + + if (!employeeId || !password) { + return res.status(400).json({ error: 'Employee ID and password are required' }); + } + + // Transform employee ID to email format as expected by Bessa API + const email = `knapp-${employeeId}@bessa.app`; + + try { + const response = await fetch(`${BESSA_API_BASE}/auth/login/`, { + method: 'POST', + headers: { + 'Authorization': `Token ${GUEST_TOKEN}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-Client-Version': CLIENT_VERSION + }, + body: JSON.stringify({ email, password }) + }); + + const data = await response.json(); + + if (response.ok) { + const token = data.key; + + // Fetch user details to get First Name + try { + const userResponse = await fetch(`${BESSA_API_BASE}/auth/user/`, { + headers: { + 'Authorization': `Token ${token}`, + 'Accept': 'application/json', + 'X-Client-Version': CLIENT_VERSION + } + }); + + if (userResponse.ok) { + const userData = await userResponse.json(); + res.json({ + key: token, + firstName: userData.first_name, + lastName: userData.last_name + }); + } else { + // Fallback if user fetch fails + logger.warn(`Failed to fetch user details for ${email}`); + res.json({ key: token }); + } + } catch (userError) { + logger.error(`Error fetching user details: ${userError}`); + res.json({ key: token }); + } + } else { + logger.error(`Login failed for ${email}: ${JSON.stringify(data)}`); + res.status(response.status).json({ error: data.non_field_errors?.[0] || 'Login failed' }); + } + } catch (error) { + logger.error(`Login error: ${error}`); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.get('/api/me', async (req, res) => { + const authHeader = req.headers.authorization; + if (!authHeader) { + return res.status(401).json({ error: 'No token provided' }); + } + + try { + const userResponse = await fetch(`${BESSA_API_BASE}/auth/user/`, { + headers: { + 'Authorization': authHeader, + 'Accept': 'application/json', + 'X-Client-Version': CLIENT_VERSION + } + }); + + if (userResponse.ok) { + const userData = await userResponse.json(); + res.json({ + firstName: userData.first_name, + lastName: userData.last_name, + email: userData.email + }); + } else { + res.status(userResponse.status).json({ error: 'Failed to fetch user details' }); + } + } catch (error) { + logger.error(`Error fetching user details: ${error}`); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.get('/api/user/orders', async (req, res) => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + return res.status(401).json({ error: 'Authorization header is required' }); + } + + try { + const response = await fetch(`${BESSA_API_BASE}/venues/591/menu/dates/`, { + method: 'GET', + headers: { + 'Authorization': authHeader, + 'Accept': 'application/json', + 'X-Client-Version': CLIENT_VERSION + } + }); + + const data = await response.json(); + + if (response.ok) { + // Return full order details per date for orderMap building + const dateOrders = data.results.map((day: any) => ({ + date: day.date, + orders: (day.orders || []).map((order: any) => ({ + id: order.id, + state: order.order_state, + total: order.total, + items: (order.items || []).map((item: any) => ({ + name: item.name, + articleId: item.article, + price: item.price + })) + })) + })); + res.json({ dateOrders }); + } else { + logger.error(`Failed to fetch orders: ${JSON.stringify(data)}`); + res.status(response.status).json({ error: 'Failed to fetch orders' }); + } + } catch (error) { + logger.error(`Orders fetch error: ${error}`); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Place an order via Bessa API +app.post('/api/order', async (req, res) => { + const authHeader = req.headers.authorization; + if (!authHeader) { + return res.status(401).json({ error: 'Authorization header is required' }); + } + + const { date, articleId, name, price, vat, description } = req.body; + if (!date || !articleId || !name || price === undefined) { + return res.status(400).json({ error: 'Missing required fields: date, articleId, name, price' }); + } + + try { + // Fetch user details for customer object + const userResponse = await fetch(`${BESSA_API_BASE}/auth/user/`, { + headers: { + 'Authorization': authHeader, + 'Accept': 'application/json', + 'X-Client-Version': CLIENT_VERSION + } + }); + + if (!userResponse.ok) { + return res.status(401).json({ error: 'Failed to fetch user details' }); + } + + const userData = await userResponse.json(); + const now = new Date().toISOString(); + + // Construct order payload matching exact Bessa format + const orderPayload = { + uuid: crypto.randomUUID(), + created: now, + updated: now, + order_type: 7, + items: [ + { + article: articleId, + course_group: null, + modifiers: [], + uuid: crypto.randomUUID(), + name: name, + description: description || '', + price: String(parseFloat(price)), + amount: 1, + vat: vat || '10.00', + comment: '' + } + ], + table: null, + total: parseFloat(price), + tip: 0, + currency: 'EUR', + venue: 591, + states: [], + order_state: 1, + date: `${date}T10:00:00.000Z`, + payment_method: 'payroll', + customer: { + first_name: userData.first_name, + last_name: userData.last_name, + email: userData.email, + newsletter: false + }, + preorder: false, + delivery_fee: 0, + cash_box_table_name: null, + take_away: false + }; + + logger.info(`Placing order: ${name} for ${date} (article ${articleId})`); + + const orderResponse = await fetch(`${BESSA_API_BASE}/user/orders/`, { + method: 'POST', + headers: { + 'Authorization': authHeader, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-Client-Version': CLIENT_VERSION + }, + body: JSON.stringify(orderPayload) + }); + + const orderData = await orderResponse.json(); + + if (orderResponse.ok || orderResponse.status === 201) { + logger.success(`Order placed: ID ${orderData.id} (${name})`); + res.status(201).json({ + orderId: orderData.id, + hashId: orderData.hash_id, + state: orderData.order_state, + total: orderData.total + }); + } else { + logger.error(`Order failed: ${JSON.stringify(orderData)}`); + res.status(orderResponse.status).json({ + error: orderData.detail || orderData.non_field_errors?.[0] || 'Order failed' + }); + } + } catch (error) { + logger.error(`Order error: ${error}`); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Cancel an order via Bessa API +app.post('/api/order/cancel', async (req, res) => { + const authHeader = req.headers.authorization; + if (!authHeader) { + return res.status(401).json({ error: 'Authorization header is required' }); + } + + const { orderId } = req.body; + if (!orderId) { + return res.status(400).json({ error: 'Missing required field: orderId' }); + } + + try { + logger.info(`Cancelling order: ${orderId}`); + + const cancelResponse = await fetch(`${BESSA_API_BASE}/user/orders/${orderId}/cancel/`, { + method: 'PATCH', + headers: { + 'Authorization': authHeader, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-Client-Version': CLIENT_VERSION + }, + body: JSON.stringify({}) + }); + + const cancelData = await cancelResponse.json(); + + if (cancelResponse.ok) { + logger.success(`Order ${orderId} cancelled`); + res.json({ success: true, orderId: cancelData.order_id, state: cancelData.state }); + } else { + logger.error(`Cancel failed for ${orderId}: ${JSON.stringify(cancelData)}`); + res.status(cancelResponse.status).json({ + error: cancelData.detail || 'Cancellation failed' + }); + } + } catch (error) { + logger.error(`Cancel error: ${error}`); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// --- Flagging & Polling API --- + +app.get('/api/flags', async (req, res) => { + const authHeader = req.headers.authorization; + if (!authHeader) return res.status(401).json({ error: 'Unauthorized' }); + + // In a real app we would filter by user, but for now return all active flags + // or arguably only the flags for this user? + // Requirement says "Flagged items ... flagged by user". + // But polling is distributed. + // Let's return all flags so UI can show them? Or just user's flags? + // For "Yellow Glow" we likely want to see what *I* flagged. + // Let's filter by pseudo-user-id if possible, but we don't strictly have one except the Bessa ID. + // Let's assume the client sends a userID or we trust the client to filter. + // For simplicity, return all, client filters? No, improved privacy: + // We don't have a robust user session here, just the token. + // We'll trust the client to send 'X-User-Id' for now or just return all and let client handle it. + // Going with returning ALL for simplicity of the "Shared/Distributed" nature if we wanted shared flags, + // but the requirement implies personal flagging. + // Implementation Plan didn't specify strict user separation. + // Let's return ALL for now to debug easily. + const flags = await flagStore.getAllFlags(); + res.json(flags); +}); + +app.post('/api/flags', async (req, res) => { + const { id, date, articleId, userId, cutoff, description, name } = req.body; + if (!id || !date || !articleId || !userId || !cutoff) { + return res.status(400).json({ error: 'Missing required fields' }); + } + + const item: FlaggedItem = { + id, date, articleId, userId, cutoff, description, name, + createdAt: new Date().toISOString() + }; + + const success = await flagStore.addFlag(item); + if (success) { + logger.info(`Flag added: ${name} (${id}) by ${userId}`); + res.status(201).json({ success: true }); + } else { + res.status(409).json({ error: 'Flag already exists' }); + } +}); + +app.delete('/api/flags/:id', async (req, res) => { + const { id } = req.params; + const success = await flagStore.removeFlag(id); + if (success) { + logger.info(`Flag removed: ${id}`); + res.json({ success: true }); + } else { + res.status(404).json({ error: 'Flag not found' }); + } +}); + +app.post('/api/check-item', async (req, res) => { + const authHeader = req.headers.authorization; + if (!authHeader) return res.status(401).json({ error: 'Unauthorized' }); + + const { date, articleId } = req.body; + if (!date || !articleId) return res.status(400).json({ error: 'Missing date or articleId' }); + + try { + // Fetch menu details for the specific date using User's Token + // URL Pattern: /venues/591/menu/7/{date}/ + // Assumption: Menu ID 7 is standard. + const response = await fetch(`${BESSA_API_BASE}/venues/591/menu/7/${date}/`, { + method: 'GET', + headers: { + 'Authorization': authHeader, + 'Accept': 'application/json', + 'X-Client-Version': CLIENT_VERSION + } + }); + + if (!response.ok) { + // If 404, maybe no menu for that day? + if (response.status === 404) { + return res.json({ available: false, error: 'Menu not found' }); + } + return res.status(response.status).json({ error: 'Failed to fetch menu from Bessa' }); + } + + const data = await response.json(); + const results = data.results || []; + + // Find the item + let foundItem = null; + for (const group of results) { + if (group.items) { + foundItem = group.items.find((i: any) => i.article === articleId || i.id === articleId); + if (foundItem) break; + } + } + + if (foundItem) { + // Check availability + const isUnlimited = foundItem.amount_tracking === false; + const hasStock = parseInt(foundItem.available_amount) > 0; + const isAvailable = isUnlimited || hasStock; + + logger.info(`Check Item ${articleId} on ${date}: ${isAvailable ? 'AVAILABLE' : 'SOLD OUT'}`); + res.json({ available: isAvailable }); + } else { + logger.warn(`Check Item ${articleId} on ${date}: Item not found in menu`); + res.json({ available: false, error: 'Item not found in menu' }); + } + + } catch (error) { + logger.error(`Check Item Error: ${error}`); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.post('/api/poll-result', async (req, res) => { + const { flagId, isAvailable } = req.body; + if (!flagId) return res.status(400).json({ error: 'Missing flagId' }); + + await orchestrator.handlePollResult(flagId, isAvailable); + res.json({ success: true }); +}); + +app.get('/api/events', (req, res) => { + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.flushHeaders(); + + const clientId = sseManager.addClient(res); + + // Send initial ping/id + sseManager.sendToClient(clientId, 'connected', { clientId }); +}); + +// SSE endpoint for menu refresh progress +app.get('/api/refresh-progress', async (req, res) => { + logger.info(`[DEBUG] Received SSE request with token query: ${req.query.token ? 'YES' : 'NO'}`); + + // Get token from query parameter (EventSource doesn't support custom headers) + const token = req.query.token as string; + const authHeader = token ? `Token ${token}` : `Token ${GUEST_TOKEN}`; + + // Set headers for SSE + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.flushHeaders(); + + const sendProgress = (data: any) => { + res.write(`data: ${JSON.stringify(data)}\n\n`); + }; + + try { + sendProgress({ step: 'start', message: 'Hole verfügbare Daten...', current: 0, total: 100 }); + + // 1. Fetch available dates + logger.info('Fetching available dates...'); + const datesResponse = await fetch(`${BESSA_API_BASE}/venues/591/menu/dates/`, { + method: 'GET', + headers: { + 'Authorization': authHeader, + 'Accept': 'application/json', + 'X-Client-Version': CLIENT_VERSION + } + }); + + if (!datesResponse.ok) { + throw new Error(`Failed to fetch dates: ${datesResponse.status}`); + } + + const datesData = await datesResponse.json(); + let availableDates = datesData.results || []; + + // Filter for future dates or recent past (e.g. last 7 days + future) + const today = new Date(); + today.setDate(today.getDate() - 7); + const cutoffDate = today.toISOString().split('T')[0]; + + availableDates = availableDates + .filter((d: any) => d.date >= cutoffDate) + .sort((a: any, b: any) => a.date.localeCompare(b.date)); + + // Limit to reasonable amount (e.g. next 30 days) + availableDates = availableDates.slice(0, 30); + const totalDates = availableDates.length; + + sendProgress({ step: 'dates_fetched', message: `${totalDates} Tage gefunden. Lade Details...`, current: 0, total: totalDates }); + + // 2. Fetch details for each date + const allDays: any[] = []; + let completed = 0; + + for (const dateObj of availableDates) { + const dateStr = dateObj.date; + + sendProgress({ + step: 'fetching_details', + message: `Lade Menü für ${dateStr}...`, + current: completed + 1, + total: totalDates + }); + + try { + // Menu ID 7 seems to be the standard lunch menu + const menuDetailUrl = `${BESSA_API_BASE}/venues/591/menu/7/${dateStr}/`; + const detailResponse = await fetch(menuDetailUrl, { + method: 'GET', + headers: { + 'Authorization': authHeader, + 'Accept': 'application/json', + 'X-Client-Version': CLIENT_VERSION + } + }); + + if (detailResponse.ok) { + const detailData = await detailResponse.json(); + + // Structure: { results: [ { name: "Menü", items: [...] } ] } + const menuGroups = detailData.results || []; + let dayItems: any[] = []; + + for (const group of menuGroups) { + if (group.items && Array.isArray(group.items)) { + dayItems = dayItems.concat(group.items); + } + } + + if (dayItems.length > 0) { + allDays.push({ + date: dateStr, + // Use the dateObj to get weekday if possible, or compute it + menu_items: dayItems, + orders: dateObj.orders || [] // Store orders for cutoff extraction + }); + } + } + } catch (err) { + logger.error(`Failed to fetch details for ${dateStr}: ${err}`); + } + + completed++; + // Small delay + await new Promise(resolve => setTimeout(resolve, 100)); + } + + // Group by Week + const weeksMap = new Map(); + + // Helper to get ISO week year + const getWeekYear = (d: Date) => { + const date = new Date(d.getTime()); + date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); + return date.getFullYear(); + }; + + for (const day of allDays) { + const date = new Date(day.date); + const weekNum = getISOWeek(date); + const year = getWeekYear(date); + const key = `${year}-${weekNum}`; + + if (!weeksMap.has(key)) { + weeksMap.set(key, { + year: year, + weekNumber: weekNum, + days: [] + }); + } + + const weekday = date.toLocaleDateString('en-US', { weekday: 'long' }); + + // Calculate order cutoff time: same day at 10:00 AM local time + const orderCutoffDate = new Date(day.date); + orderCutoffDate.setHours(10, 0, 0, 0); // 10:00 AM local time + const orderCutoff = orderCutoffDate.toISOString(); + + weeksMap.get(key).days.push({ + date: day.date, + weekday: weekday, + orderCutoff: orderCutoff, // Add the cutoff time + items: (day.menu_items || []).map((item: any) => { + const isUnlimited = item.amount_tracking === false; + const hasStock = parseInt(item.available_amount) > 0; + + return { + id: `${day.date}_${item.id}`, + name: item.name || 'Unknown', + description: item.description || '', + price: parseFloat(item.price) || 0, + available: isUnlimited || hasStock, + availableAmount: parseInt(item.available_amount) || 0, + amountTracking: item.amount_tracking !== false // Default to true if missing + }; + }) + }); + } + + const menuData = { + weeks: Array.from(weeksMap.values()).sort((a: any, b: any) => { + if (a.year !== b.year) return a.year - b.year; + return a.weekNumber - b.weekNumber; + }), + scrapedAt: new Date().toISOString() + }; + + // Smart merge: preserve current-week data on refresh, purge older weeks + sendProgress({ step: 'saving', message: 'Daten werden gespeichert...', current: totalDates, total: totalDates }); + + const currentISOWeek = getISOWeek(new Date()); + const currentISOYear = getWeekYear(new Date()); + + let finalData = menuData; + + try { + const existingRaw = await fs.readFile(dataFile, 'utf-8'); + const existingData = JSON.parse(existingRaw); + + if (existingData.weeks && Array.isArray(existingData.weeks)) { + const mergedWeeks = new Map(); + + // Add all fresh weeks first + for (const week of menuData.weeks) { + mergedWeeks.set(`${week.year}-${week.weekNumber}`, week); + } + + // Merge existing current-week data (preserve days not in fresh data) + for (const existingWeek of existingData.weeks) { + const key = `${existingWeek.year}-${existingWeek.weekNumber}`; + const isCurrentOrFuture = + existingWeek.year > currentISOYear || + (existingWeek.year === currentISOYear && existingWeek.weekNumber >= currentISOWeek); + + if (!isCurrentOrFuture) { + // Older week: purge (don't keep) + continue; + } + + if (mergedWeeks.has(key)) { + // Merge: keep existing days that aren't in fresh data + const freshWeek = mergedWeeks.get(key); + const freshDates = new Set(freshWeek.days.map((d: any) => d.date)); + + for (const existDay of existingWeek.days) { + if (!freshDates.has(existDay.date)) { + freshWeek.days.push(existDay); + } + } + + // Sort days by date + freshWeek.days.sort((a: any, b: any) => a.date.localeCompare(b.date)); + } else { + // Future week not in fresh data: keep as-is + mergedWeeks.set(key, existingWeek); + } + } + + finalData = { + weeks: Array.from(mergedWeeks.values()).sort((a: any, b: any) => { + if (a.year !== b.year) return a.year - b.year; + return a.weekNumber - b.weekNumber; + }), + scrapedAt: new Date().toISOString() + }; + } + } catch (e) { + // No existing data or parse error — use fresh data as-is + logger.info('No existing menu data to merge, using fresh data.'); + } + + await fs.writeFile(dataFile, JSON.stringify(finalData, null, 2), 'utf-8'); + + sendProgress({ step: 'complete', message: 'Aktualisierung abgeschlossen!', current: totalDates, total: totalDates }); + res.write('event: done\ndata: {}\n\n'); + res.end(); + + } catch (error) { + logger.error(`Refresh error: ${error}`); + sendProgress({ step: 'error', message: `Fehler: ${error}`, current: 0, total: 100 }); + res.write('event: error\ndata: {}\n\n'); + res.end(); + } +}); + +// Helper function for ISO week number +function getISOWeek(date: Date): number { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); +} + +app.get('/api/menus', async (req, res) => { + try { + await fs.access(dataFile); + const data = await fs.readFile(dataFile, 'utf-8'); + res.header('Content-Type', 'application/json'); + res.send(data); + } catch (error) { + logger.error(`Failed to read menu data: ${error}`); + // If file doesn't exist, return empty structure + res.json({ days: [], updated: null }); + } +}); + +// Serve Static Files +app.use(express.static(publicDir)); + +// Fallback to index.html for any other request +app.use((req, res) => { + if (req.method === 'GET') { + res.sendFile(join(publicDir, 'index.html')); + } +}); + +// Start Server +app.listen(port, () => { + logger.success(`Web Interface running at http://localhost:${port}`); + logger.info(`Serving static files from: ${publicDir}`); + + // Start Polling Orchestrator + orchestrator.start(); +}); diff --git a/src/services/polling-orchestrator.ts b/src/services/polling-orchestrator.ts new file mode 100644 index 0000000..b837e94 --- /dev/null +++ b/src/services/polling-orchestrator.ts @@ -0,0 +1,92 @@ + +import { FlagStore, FlaggedItem } from '../storage/flag-store.js'; +import { SseManager } from './sse-manager.js'; +import { logger } from '../utils/logger.js'; + +export class PollingOrchestrator { + private flagStore: FlagStore; + private sseManager: SseManager; + private intervalId: NodeJS.Timeout | null = null; + private intervalMs: number = 5 * 60 * 1000; // 5 minutes + + constructor(flagStore: FlagStore, sseManager: SseManager) { + this.flagStore = flagStore; + this.sseManager = sseManager; + } + + start(): void { + if (this.intervalId) return; + + logger.info('Starting Polling Orchestrator...'); + // Run immediately then interval + this.distributeTasks(); + this.intervalId = setInterval(() => this.distributeTasks(), this.intervalMs); + } + + stop(): void { + if (this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = null; + } + } + + async distributeTasks(): Promise { + const clients = this.sseManager.getAllClientIds(); + if (clients.length === 0) { + logger.info('No active clients to poll. Skipping cycle.'); + return; + } + + // Clean up expired flags first + await this.flagStore.pruneExpiredFlags(); + + const flags = await this.flagStore.getAllFlags(); + if (flags.length === 0) return; + + logger.info(`Distributing ${flags.length} polling tasks across ${clients.length} clients.`); + + // Simple Load Balancing: Round Robin + let clientIndex = 0; + + for (const flag of flags) { + const clientId = clients[clientIndex]; + + // Send poll request to client + this.sseManager.sendToClient(clientId, 'poll_request', { + flagId: flag.id, + date: flag.date, + articleId: flag.articleId, + name: flag.name + }); + + logger.info(`Assigned flag ${flag.id} to client ${clientId}`); + + // Move to next client + clientIndex = (clientIndex + 1) % clients.length; + } + } + + async handlePollResult(flagId: string, isAvailable: boolean): Promise { + if (!isAvailable) return; + + const flag = await this.flagStore.getFlag(flagId); + if (!flag) return; // Flag might have been removed + + logger.success(`Item ${flag.name} (${flag.id}) is now AVAILABLE! Broadcasting...`); + + // Notify ALL clients + this.sseManager.broadcast('item_update', { + flagId: flag.id, + status: 'available', + name: flag.name, + date: flag.date, + articleId: flag.articleId + }); + + // Remove flag since it's now available? + // Or keep it until cutoff? Requirement says "remove when cutoff reached" + // But if it becomes available, we might want to keep checking if it becomes unavailable again? + // Let's keep it for now, user can manually remove or it expires. + // Actually, if user orders it, they should likely unflag it. + } +} diff --git a/src/services/sse-manager.ts b/src/services/sse-manager.ts new file mode 100644 index 0000000..6f20422 --- /dev/null +++ b/src/services/sse-manager.ts @@ -0,0 +1,69 @@ + +import { Response } from 'express'; +import { logger } from '../utils/logger.js'; +import { randomUUID } from 'crypto'; + +interface ConnectedClient { + id: string; + res: Response; + userId?: string; // If authenticated +} + +export class SseManager { + private clients: Map = new Map(); + + addClient(res: Response, userId?: string): string { + const id = randomUUID(); + const client: ConnectedClient = { id, res, userId }; + this.clients.set(id, client); + + // Remove client on connection close + res.on('close', () => { + this.clients.delete(id); + logger.info(`SSE Client disconnected: ${id}`); + }); + + logger.info(`SSE Client connected: ${id} (User: ${userId || 'Guest'})`); + return id; + } + + removeClient(id: string): void { + const client = this.clients.get(id); + if (client) { + client.res.end(); + this.clients.delete(id); + } + } + + sendToClient(clientId: string, event: string, data: any): boolean { + const client = this.clients.get(clientId); + if (!client) return false; + + client.res.write(`event: ${event}\n`); + client.res.write(`data: ${JSON.stringify(data)}\n\n`); + return true; + } + + broadcast(event: string, data: any): void { + this.clients.forEach(client => { + client.res.write(`event: ${event}\n`); + client.res.write(`data: ${JSON.stringify(data)}\n\n`); + }); + } + + getActiveClientCount(): number { + return this.clients.size; + } + + getAllClientIds(): string[] { + return Array.from(this.clients.keys()); + } + + // Helper to get a random client for load balancing + getRandomClient(): string | null { + const keys = Array.from(this.clients.keys()); + if (keys.length === 0) return null; + const randomIndex = Math.floor(Math.random() * keys.length); + return keys[randomIndex]; + } +} diff --git a/src/storage/flag-store.ts b/src/storage/flag-store.ts new file mode 100644 index 0000000..c307cb6 --- /dev/null +++ b/src/storage/flag-store.ts @@ -0,0 +1,109 @@ + +import fs from 'fs/promises'; +import { join } from 'path'; +import { logger } from '../utils/logger.js'; + +export interface FlaggedItem { + id: string; // composite: date_articleId + date: string; + articleId: number; + userId: string; // Who flagged it (first user) + cutoff: string; // ISO date string + createdAt: string; + description?: string; // Optional: Store name/desc for notifications + name?: string; +} + +export class FlagStore { + private filePath: string; + private flags: Map = new Map(); + private initialized: boolean = false; + + constructor(dataDir: string) { + this.filePath = join(dataDir, 'flags.json'); + } + + async init(): Promise { + if (this.initialized) return; + + try { + const data = await fs.readFile(this.filePath, 'utf-8'); + const parsed = JSON.parse(data); + if (Array.isArray(parsed)) { + parsed.forEach((item: FlaggedItem) => { + this.flags.set(item.id, item); + }); + } + logger.info(`Loaded ${this.flags.size} flags from storage.`); + } catch (error) { + // If file doesn't exist, start empty + logger.info('No existing flags found, starting with empty store.'); + } + + this.initialized = true; + } + + async save(): Promise { + try { + const data = Array.from(this.flags.values()); + await fs.writeFile(this.filePath, JSON.stringify(data, null, 2), 'utf-8'); + } catch (error) { + logger.error(`Failed to save flags: ${error}`); + } + } + + async addFlag(item: FlaggedItem): Promise { + if (!this.initialized) await this.init(); + + if (this.flags.has(item.id)) { + return false; // Already exists + } + + this.flags.set(item.id, item); + await this.save(); + return true; + } + + async removeFlag(id: string): Promise { + if (!this.initialized) await this.init(); + + if (this.flags.has(id)) { + this.flags.delete(id); + await this.save(); + return true; + } + return false; + } + + async getFlag(id: string): Promise { + if (!this.initialized) await this.init(); + return this.flags.get(id); + } + + async getAllFlags(): Promise { + if (!this.initialized) await this.init(); + return Array.from(this.flags.values()); + } + + async pruneExpiredFlags(): Promise { + if (!this.initialized) await this.init(); + + const now = new Date(); + let pruned = 0; + + for (const [id, item] of this.flags.entries()) { + const cutoff = new Date(item.cutoff); + if (now > cutoff) { + this.flags.delete(id); + pruned++; + } + } + + if (pruned > 0) { + await this.save(); + logger.info(`Pruned ${pruned} expired flags.`); + } + + return pruned; + } +} diff --git a/src/storage/menu-store.ts b/src/storage/menu-store.ts new file mode 100644 index 0000000..77dc97d --- /dev/null +++ b/src/storage/menu-store.ts @@ -0,0 +1,87 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { MenuDatabase, WeeklyMenu } from '../types.js'; +import { config } from '../config.js'; +import { logger } from '../utils/logger.js'; + +/** + * Load existing menu database from JSON file + */ +export async function loadMenus(): Promise { + try { + const content = await fs.readFile(config.storage.menuFile, 'utf-8'); + return JSON.parse(content); + } catch (error: any) { + if (error.code === 'ENOENT') { + logger.info('No existing menus.json found, creating new database'); + return { + lastUpdated: new Date().toISOString(), + weeks: [], + }; + } + throw error; + } +} + +/** + * Save menu database to JSON file + */ +export async function saveMenus(db: MenuDatabase): Promise { + // Ensure data directory exists + await fs.mkdir(config.storage.dataDir, { recursive: true }); + + // Update timestamp + db.lastUpdated = new Date().toISOString(); + + // Write with pretty formatting + await fs.writeFile( + config.storage.menuFile, + JSON.stringify(db, null, 2), + 'utf-8' + ); + + logger.success(`Saved menu database to ${config.storage.menuFile}`); +} + +/** + * Merge a new weekly menu into the database + * Replaces existing week if found, otherwise adds it + */ +export async function mergeWeeklyMenu(weeklyMenu: WeeklyMenu): Promise { + const db = await loadMenus(); + + // Find and replace existing week, or add new one + const existingIndex = db.weeks.findIndex( + w => w.year === weeklyMenu.year && w.weekNumber === weeklyMenu.weekNumber + ); + + if (existingIndex >= 0) { + db.weeks[existingIndex] = weeklyMenu; + logger.info(`Updated existing week ${weeklyMenu.year}-W${weeklyMenu.weekNumber}`); + } else { + db.weeks.push(weeklyMenu); + logger.info(`Added new week ${weeklyMenu.year}-W${weeklyMenu.weekNumber}`); + } + + // Sort weeks by year and week number + db.weeks.sort((a, b) => { + if (a.year !== b.year) return a.year - b.year; + return a.weekNumber - b.weekNumber; + }); + + await saveMenus(db); +} + +/** + * Get menu for a specific date + */ +export async function getMenuForDate(date: string): Promise { + const db = await loadMenus(); + + for (const week of db.weeks) { + const day = week.days.find(d => d.date === date); + if (day) return day; + } + + return null; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..8e5b28f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,27 @@ +// TypeScript type definitions for menu data structures + +export interface MenuItem { + id: string; // e.g., "2026-02-03_M1_Herzhaftes" (date + menu ID for uniqueness) + name: string; // e.g., "M1 Herzhaftes" + description: string; // Zutaten + Allergen-Codes + price: number; // 5.50 + available: boolean; +} + +export interface DayMenu { + date: string; // ISO format: "2026-02-03" + weekday: string; // "Monday", "Tuesday", ... + items: MenuItem[]; +} + +export interface WeeklyMenu { + year: number; // 2026 (year before week for readability) + weekNumber: number; // 6 + days: DayMenu[]; + scrapedAt: string; // ISO timestamp +} + +export interface MenuDatabase { + lastUpdated: string; // ISO timestamp + weeks: WeeklyMenu[]; +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..2c0f988 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,25 @@ +// Simple logger utility + +export const logger = { + info: (message: string, ...args: any[]) => { + console.log(`[INFO] ${message}`, ...args); + }, + + success: (message: string, ...args: any[]) => { + console.log(`✅ ${message}`, ...args); + }, + + error: (message: string, ...args: any[]) => { + console.error(`❌ [ERROR] ${message}`, ...args); + }, + + debug: (message: string, ...args: any[]) => { + if (process.env.DEBUG) { + console.log(`[DEBUG] ${message}`, ...args); + } + }, + + warn: (message: string, ...args: any[]) => { + console.warn(`⚠️ [WARN] ${message}`, ...args); + }, +}; diff --git a/test_day.json b/test_day.json new file mode 100644 index 0000000..86eebba --- /dev/null +++ b/test_day.json @@ -0,0 +1,85 @@ + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Host api.bessa.app:443 was resolved. +* IPv6: 2a05:d014:6d3:a901:93fa:c6a7:8fc7:f92d +* IPv4: 3.124.122.174 +* Trying 3.124.122.174:443... +* Connected to api.bessa.app (3.124.122.174) port 443 +* ALPN: curl offers h2,http/1.1 +} [5 bytes data] +* TLSv1.3 (OUT), TLS handshake, Client hello (1): +} [512 bytes data] +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs + 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0{ [5 bytes data] +* TLSv1.3 (IN), TLS handshake, Server hello (2): +{ [122 bytes data] +* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): +{ [19 bytes data] +* TLSv1.3 (IN), TLS handshake, Certificate (11): +{ [2925 bytes data] +* TLSv1.3 (IN), TLS handshake, CERT verify (15): +{ [520 bytes data] +* TLSv1.3 (IN), TLS handshake, Finished (20): +{ [52 bytes data] +* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): +} [1 bytes data] +* TLSv1.3 (OUT), TLS handshake, Finished (20): +} [52 bytes data] +* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS +* ALPN: server accepted h2 +* Server certificate: +* subject: CN=api.bessa.app +* start date: Dec 18 10:19:21 2025 GMT +* expire date: Mar 18 10:19:20 2026 GMT +* subjectAltName: host "api.bessa.app" matched cert's "api.bessa.app" +* issuer: C=US; O=Let's Encrypt; CN=R12 +* SSL certificate verify ok. +* Certificate level 0: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption +* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption +* Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption +} [5 bytes data] +* using HTTP/2 +* [HTTP/2] [1] OPENED stream for https://api.bessa.app/v1/venues/591/menu/7/2026-02-10/ +* [HTTP/2] [1] [:method: GET] +* [HTTP/2] [1] [:scheme: https] +* [HTTP/2] [1] [:authority: api.bessa.app] +* [HTTP/2] [1] [:path: /v1/venues/591/menu/7/2026-02-10/] +* [HTTP/2] [1] [user-agent: curl/8.5.0] +* [HTTP/2] [1] [accept: */*] +* [HTTP/2] [1] [authorization: Token c3418725e95a9f90e3645cbc846b4d67c7c66131] +* [HTTP/2] [1] [x-client-version: 1.7.0_prod/2026-01-26] +} [5 bytes data] +> GET /v1/venues/591/menu/7/2026-02-10/ HTTP/2 +> Host: api.bessa.app +> User-Agent: curl/8.5.0 +> Accept: */* +> Authorization: Token c3418725e95a9f90e3645cbc846b4d67c7c66131 +> X-Client-Version: 1.7.0_prod/2026-01-26 +> +{ [5 bytes data] +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +{ [57 bytes data] +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +{ [57 bytes data] +* old SSL session ID is stale, removing +{ [5 bytes data] +< HTTP/2 200 +< server: nginx/1.27.3 +< date: Mon, 09 Feb 2026 08:54:00 GMT +< content-type: application/json +< content-length: 4685 +< vary: Accept, Accept-Language, origin +< allow: GET, HEAD, OPTIONS +< x-frame-options: DENY +< content-language: en-us +< strict-transport-security: max-age=16000000 +< x-content-type-options: nosniff +< referrer-policy: same-origin +< cross-origin-opener-policy: same-origin +< strict-transport-security: max-age=31536000 +< +{ [4685 bytes data] +{"next":null,"previous":null,"results":[{"id":11452,"items":[{"id":178714,"price":"5.50","name":"M1 Herzhaftes","description":"Erdäpfelrahmsuppe L,M,C,G, potato soup, Acht Schätze (Rind) L,M,C,F,O, mit Eiernudeln A,C, Kuchen A,C,G,H,O, Silced beef asia with egg noodles","allergens":"","nutrition_facts":"","hash_id":"ar_7BzGM","image_thumbnail":null,"modifiers":[],"created":"2025-12-04T13:02:07.266780Z","updated":"2026-02-03T13:15:20.845052Z","deleted":null,"uuid":"4064152d-6e82-4f4d-9bea-0936ab307b1c","image":null,"number":"dfaa35f6-4604-4e14-9f86-1862a05a5881","type":1,"vat":"10.00","points":"0.00","minimum_amount":"0","available_amount":"136","amount_tracking":true,"dispenser_id":"","course_group":null},{"id":178728,"price":"5.50","name":"M3 Süß","description":"Erdäpfelrahmsuppe L,M,C,G, potato soup, Milchrahmstrudel mit Vanillesauce A,C,G, milk cream strudel with vanilla sauce","allergens":"","nutrition_facts":"","hash_id":"ar_KalR9","image_thumbnail":null,"modifiers":[],"created":"2025-12-04T13:02:07.268455Z","updated":"2026-02-03T13:14:37.728274Z","deleted":null,"uuid":"12b75bc5-88ab-4df3-ad9b-00b864729ce3","image":null,"number":"a33ce321-1943-430e-a435-b83f87ff1080","type":1,"vat":"10.00","points":"0.00","minimum_amount":"0","available_amount":"34","amount_tracking":true,"dispenser_id":"","course_group":null},{"id":178735,"price":"5.50","name":"M4 Ginko vegan","description":"Tomaten Kokoscremesuppe, tomato coconut soup, Hirselaibchen A, mit Fisolen Karottengemüse u. Schnittlauchdip F,M, millet patties with fisole vegetable","allergens":"","nutrition_facts":"","hash_id":"ar_YLxwb","image_thumbnail":null,"modifiers":[],"created":"2025-12-04T13:02:07.269340Z","updated":"2026-02-06T09:35:17.671235Z","deleted":null,"uuid":"6b3017a7-c950-4428-a474-8c61a819402d","image":null,"number":"88b59af4-f3fb-45a7-ba0f-9fce88d4710f","type":1,"vat":"10.00","points":"0.00","minimum_amount":"0","available_amount":"63","amount_tracking":true,"dispenser_id":"","course_group":null},{"id":178742,"price":"5.50","name":"M5 Salat mit Gebäck","description":"Erdäpflerahmsuppe G,L,M, Salatteller mit Prosciutto und Parmesan, salad with prosciutto and parmesan","allergens":"","nutrition_facts":"","hash_id":"ar_Qydpp","image_thumbnail":null,"modifiers":[],"created":"2025-12-04T13:02:07.270215Z","updated":"2026-02-03T13:16:43.665954Z","deleted":null,"uuid":"1c1a14dd-cf4f-4355-a4ba-cb8543ae7e5a","image":null,"number":"ea309dc8-38bf-4e78-9b51-7c9a7efb2763","type":1,"vat":"10.00","points":"0.00","minimum_amount":"0","available_amount":"0","amount_tracking":true,"dispenser_id":"","course_group":null},{"id":178749,"price":"3.00","name":"M6 Suppe, kleiner Salat + Dessert D","description":"Suppe, kleiner Salat + Dessert","allergens":"","nutrition_facts":"","hash_id":"ar_Nyoxb","image_thumbnail":null,"modifiers":[],"created":"2025-12-04T13:02:07.271125Z","updated":"2026-02-03T13:17:39.906619Z","deleted":null,"uuid":"c2956eb9-6ee0-4e65-96a7-b8b0615f314c","image":null,"number":"f6d5bd6e-2e14-4930-8f27-be027fa477b2","type":1,"vat":"10.00","points":"0.00","minimum_amount":"0","available_amount":"29","amount_tracking":true,"dispenser_id":"","course_group":null},{"id":178756,"price":"4.30","name":"M7F Kleines Hauptspeisenmenü DI 2","description":"Erdäpflerahmsuppe G,L,M, potato soup, kleine Hauptspeise von Menü 1","allergens":"","nutrition_facts":"","hash_id":"ar_BzGr7","image_thumbnail":null,"modifiers":[],"created":"2025-12-04T13:02:07.272048Z","updated":"2026-02-03T13:18:12.181876Z","deleted":null,"uuid":"6ad46891-a26b-4e22-9a34-ae1cbb225788","ima 100 4685 100 4685 0 0 18586 0 --:--:-- --:--:-- --:--:-- 18591 +* Connection #0 to host api.bessa.app left intact +ge":null,"number":"59b371ea-ab48-4efc-9b63-04cf3df9ffc5","type":1,"vat":"10.00","points":"0.00","minimum_amount":"0","available_amount":"3","amount_tracking":true,"dispenser_id":"","course_group":null},{"id":178721,"price":"5.50","name":"M2 Gourmetteller","description":"Erdäpflerahmsuppe G,L,M, potato soup, Selschfleischknödel mit Sauerkraut L,M,C,O, smoked meat dumpling with carbbage","allergens":"","nutrition_facts":"","hash_id":"ar_ZGb2V","image_thumbnail":null,"modifiers":[],"created":"2025-12-04T13:02:07.267451Z","updated":"2026-02-03T13:13:23.315080Z","deleted":null,"uuid":"32753644-de1b-4d68-82d1-001cbf5beeab","image":null,"number":"868291bd-490d-47ef-b5be-e2002ce3bd10","type":1,"vat":"10.00","points":"0.00","minimum_amount":"0","available_amount":"206","amount_tracking":true,"dispenser_id":"","course_group":null}],"name":"Menü","description":"Menü","children":[],"created":"2025-11-26T13:34:51.236986Z","updated":"2026-01-29T07:38:42.691415Z","deleted":null,"uuid":"89c73f18-f3c8-45f3-8227-66a98db86396","sort":0,"image":null,"from_hour":null,"to_hour":null,"collapse":false}]} \ No newline at end of file diff --git a/test_orders.json b/test_orders.json new file mode 100644 index 0000000..71246f7 --- /dev/null +++ b/test_orders.json @@ -0,0 +1 @@ +{"orders":[{"id":1522667,"venue":591,"states":[{"state":5,"timestamp":"2026-02-05T09:27:14.291812Z","cancel_message":""}],"customer":{"id":169488,"newsletter":false,"created":"2025-12-09T08:16:54.918222Z","updated":"2025-12-09T08:16:54.918246Z","deleted":null,"uuid":"3424958e-b0a3-43c5-ac07-f93abef07b54","first_name":"Michael","last_name":"Kaufmann","company_name":"","is_primary":false,"phone":"","tax_id":"","type":0,"email":"knapp-2041@bessa.app"},"address":null,"cash_box_table_name":null,"preorder":true,"payment_meta_data":{"timestamp":"2026-02-05T09:27:14.255483Z","transaction_id":"10227","merchant_receipt":"User: \"10227\" - \"Michael Kaufmann\" created a new order."},"discount":null,"notifications":{"food":false,"drink":false},"hash_id":"o_eEzom","items":[{"id":3892875,"article":178723,"course_group":null,"modifiers":[],"created":"2026-02-05T09:27:14.294494Z","updated":"2026-02-05T09:27:14.294507Z","deleted":null,"uuid":"79127077-242d-4237-9525-e92aa5c3efca","name":"M2 Gourmetteller","description":"Rindsuppe m. Kaspressknödel A,C,L,M,G, beef soup with cheese dumpling,Quiche Lorrain (Schinken) A,C,G,L,M, c quiche lorrain with ham, Donut A,C,G,H","price":"5.50","amount":1,"vat":"10.00","comment":"","venue":591,"order":1522667}],"table":null,"payment_method_id":null,"order_type":7,"created":"2026-02-05T09:27:14.281048Z","updated":"2026-02-05T09:27:14.281067Z","deleted":null,"uuid":"975197dd-eb03-4370-bc4e-6fa984d374df","payment_method":"payroll","order_state":5,"date":"2026-02-09T10:00:00Z","user_date":"2026-02-09T10:00:00Z","delivered":null,"expires":"2026-02-09T11:15:00Z","total":"5.50","tip":"0.00","delivery_fee":"0.00","service_fee":"0.00","packaging_fee":"0.00","currency":"eur","client_secret":null,"cash_box_order_id":null,"receipt":null,"cancel_message":"","cancel_reason":"none","redeemed_cards":null,"is_testing":false,"pickup_code":"404","number":404,"service":0,"comment":"","take_away":false,"identity":null},{"id":1522668,"venue":591,"states":[{"state":5,"timestamp":"2026-02-05T09:27:42.019668Z","cancel_message":""}],"customer":{"id":169488,"newsletter":false,"created":"2025-12-09T08:16:54.918222Z","updated":"2025-12-09T08:16:54.918246Z","deleted":null,"uuid":"3424958e-b0a3-43c5-ac07-f93abef07b54","first_name":"Michael","last_name":"Kaufmann","company_name":"","is_primary":false,"phone":"","tax_id":"","type":0,"email":"knapp-2041@bessa.app"},"address":null,"cash_box_table_name":null,"preorder":true,"payment_meta_data":{"timestamp":"2026-02-05T09:27:41.972304Z","transaction_id":"10227","merchant_receipt":"User: \"10227\" - \"Michael Kaufmann\" created a new order."},"discount":null,"notifications":{"food":false,"drink":false},"hash_id":"o_b5dpa","items":[{"id":3892876,"article":178714,"course_group":null,"modifiers":[],"created":"2026-02-05T09:27:42.025714Z","updated":"2026-02-05T09:27:42.025728Z","deleted":null,"uuid":"373fe6ac-3fb5-4ccf-a6cd-3e3817fba69f","name":"M1 Herzhaftes","description":"Erdäpfelrahmsuppe L,M,C,G, potato soup, Acht Schätze (Rind) L,M,C,F,O, mit Eiernudeln A,C, Kuchen A,C,G,H,O, Silced beef asia with egg noodles","price":"5.50","amount":1,"vat":"10.00","comment":"","venue":591,"order":1522668}],"table":null,"payment_method_id":null,"order_type":7,"created":"2026-02-05T09:27:42.001341Z","updated":"2026-02-05T09:27:42.001364Z","deleted":null,"uuid":"5f86b333-9546-4a90-a869-d6e59441ec9a","payment_method":"payroll","order_state":5,"date":"2026-02-10T10:00:00Z","user_date":"2026-02-10T10:00:00Z","delivered":null,"expires":"2026-02-10T11:15:00Z","total":"5.50","tip":"0.00","delivery_fee":"0.00","service_fee":"0.00","packaging_fee":"0.00","currency":"eur","client_secret":null,"cash_box_order_id":null,"receipt":null,"cancel_message":"","cancel_reason":"none","redeemed_cards":null,"is_testing":false,"pickup_code":"377","number":377,"service":0,"comment":"","take_away":false,"identity":null},{"id":1522671,"venue":591,"states":[{"state":5,"timestamp":"2026-02-05T09:28:59.631701Z","cancel_message":""}],"customer":{"id":169488,"newsletter":false,"created":"2025-12-09T08:16:54.918222Z","updated":"2025-12-09T08:16:54.918246Z","deleted":null,"uuid":"3424958e-b0a3-43c5-ac07-f93abef07b54","first_name":"Michael","last_name":"Kaufmann","company_name":"","is_primary":false,"phone":"","tax_id":"","type":0,"email":"knapp-2041@bessa.app"},"address":null,"cash_box_table_name":null,"preorder":true,"payment_meta_data":{"timestamp":"2026-02-05T09:28:59.600005Z","transaction_id":"10227","merchant_receipt":"User: \"10227\" - \"Michael Kaufmann\" created a new order."},"discount":null,"notifications":{"food":false,"drink":false},"hash_id":"o_vbeVw","items":[{"id":3892879,"article":178718,"course_group":null,"modifiers":[],"created":"2026-02-05T09:28:59.634388Z","updated":"2026-02-05T09:28:59.634403Z","deleted":null,"uuid":"32fc3b50-f024-4bd6-ac98-a84b5d237326","name":"M1 Herzhaftes","description":"Knoblauchcremesuppe G,L,M, garlic cream soup, Champignonrahmschnitzel mit Kräuterspätzle L,A,M,O,C,G, mushroom cream schnitzel with spaetzle, Vanillepudding G, vanilla pudding","price":"5.50","amount":1,"vat":"10.00","comment":"","venue":591,"order":1522671}],"table":null,"payment_method_id":null,"order_type":7,"created":"2026-02-05T09:28:59.623919Z","updated":"2026-02-05T09:28:59.623939Z","deleted":null,"uuid":"f6bb6f9d-8acc-4d06-9d52-f445919c9c51","payment_method":"payroll","order_state":5,"date":"2026-02-11T10:00:00Z","user_date":"2026-02-11T10:00:00Z","delivered":null,"expires":"2026-02-11T11:15:00Z","total":"5.50","tip":"0.00","delivery_fee":"0.00","service_fee":"0.00","packaging_fee":"0.00","currency":"eur","client_secret":null,"cash_box_order_id":null,"receipt":null,"cancel_message":"","cancel_reason":"none","redeemed_cards":null,"is_testing":false,"pickup_code":"365","number":365,"service":0,"comment":"","take_away":false,"identity":null},{"id":1522672,"venue":591,"states":[{"state":5,"timestamp":"2026-02-05T09:29:39.076987Z","cancel_message":""}],"customer":{"id":169488,"newsletter":false,"created":"2025-12-09T08:16:54.918222Z","updated":"2025-12-09T08:16:54.918246Z","deleted":null,"uuid":"3424958e-b0a3-43c5-ac07-f93abef07b54","first_name":"Michael","last_name":"Kaufmann","company_name":"","is_primary":false,"phone":"","tax_id":"","type":0,"email":"knapp-2041@bessa.app"},"address":null,"cash_box_table_name":null,"preorder":true,"payment_meta_data":{"timestamp":"2026-02-05T09:29:39.038013Z","transaction_id":"10227","merchant_receipt":"User: \"10227\" - \"Michael Kaufmann\" created a new order."},"discount":null,"notifications":{"food":false,"drink":false},"hash_id":"o_KOP4p","items":[{"id":3892880,"article":178744,"course_group":null,"modifiers":[],"created":"2026-02-05T09:29:39.079965Z","updated":"2026-02-05T09:29:39.079983Z","deleted":null,"uuid":"b737bbc4-9c32-45a4-b6f2-684c5571ce33","name":"M5F Salat mit Gebäck DO 2","description":"Brokkoli Cheddasuppe, L,M,C,G, Chef Salat mit Schinken und Mozzarella, Mais, L,M,G, salad with ham and mozzarella","price":"5.50","amount":1,"vat":"10.00","comment":"","venue":591,"order":1522672}],"table":null,"payment_method_id":null,"order_type":7,"created":"2026-02-05T09:29:39.066841Z","updated":"2026-02-05T09:29:39.066907Z","deleted":null,"uuid":"f42f9a86-7c81-412f-adb6-cb4b7f371cf8","payment_method":"payroll","order_state":5,"date":"2026-02-12T10:00:00Z","user_date":"2026-02-12T10:00:00Z","delivered":null,"expires":"2026-02-12T11:15:00Z","total":"5.50","tip":"0.00","delivery_fee":"0.00","service_fee":"0.00","packaging_fee":"0.00","currency":"eur","client_secret":null,"cash_box_order_id":null,"receipt":null,"cancel_message":"","cancel_reason":"none","redeemed_cards":null,"is_testing":false,"pickup_code":"269","number":269,"service":0,"comment":"","take_away":false,"identity":null}]} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fe14d07 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": [ + "ES2022" + ], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file