瀏覽代碼

Remove optional email order confirmation flow.

Orders are now forwarded to admins immediately on create, with confirmation settings, token handling, and related UI/docs removed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Medowar 6 天之前
父節點
當前提交
53944c08d6

+ 2 - 2
README.md

@@ -10,7 +10,7 @@ Dieses Projekt ist ein internes Bestellsystem für persönliche Schutzausrüstun
 - Produktdetailseiten mit Größenwahl
 - Warenkorb ohne Mengensteuerung
 - Checkout mit Name, E-Mail, Organisation und Kommentar
-- Optionaler Bestätigungslink vor interner Weiterleitung
+- Direkte interne Weiterleitung nach Bestellabschluss
 - Adminbereich für Bestellungen, Produkte, Kategorien, Organisationen, Einstellungen, FAQ und Admins
 - Positionsbezogene Bearbeitung und Stornierung von Bestellungen
 
@@ -34,7 +34,7 @@ Dieses Projekt ist ein internes Bestellsystem für persönliche Schutzausrüstun
 
 1. `config.php` prüfen und insbesondere `SITE_URL`, `FROM_EMAIL` und die Bestell-Voreinstellungen anpassen.
 2. Adminzugänge in `data/admins.json` pflegen.
-3. Empfängeradresse, Bestätigungspflicht und PDF-Anhang im Admin unter `Einstellungen` prüfen.
+3. Empfängeradresse und PDF-Anhang im Admin unter `Einstellungen` prüfen.
 4. Organisationen im Admin unter `Organisationen verwalten` pflegen.
 
 ## Hinweise

+ 0 - 2
admin/backorders.php

@@ -7,8 +7,6 @@ if (empty($_SESSION['admin_logged_in'])) {
     exit();
 }
 
-expirePendingOrders();
-
 $pageTitle = "Nachbestellungen";
 $message = "";
 $messageType = "";

+ 1 - 5
admin/index.php

@@ -7,8 +7,6 @@ if (empty($_SESSION['admin_logged_in'])) {
     exit;
 }
 
-expirePendingOrders();
-
 $pageTitle = 'Admin Dashboard';
 $orders = getOrders();
 
@@ -27,9 +25,7 @@ $stats = [
 ];
 
 foreach ($orders as $order) {
-    if ($order['confirmation_status'] === 'pending') {
-        continue;
-    } elseif ($order['status'] === 'cancelled') {
+    if ($order['status'] === 'cancelled') {
         $stats['cancelled']++;
     } elseif ($order['status'] === 'processed') {
         $stats['processed']++;

+ 1 - 19
admin/order.php

@@ -7,8 +7,6 @@ if (empty($_SESSION['admin_logged_in'])) {
     exit();
 }
 
-expirePendingOrders();
-
 $message = "";
 $messageType = "";
 
@@ -179,16 +177,6 @@ include __DIR__ . "/../includes/header.php";
         <p><strong>Erstellt:</strong> <?php echo escape(
             formatDate($order["created_at"]),
         ); ?></p>
-        <?php if ($order["confirmed_at"] !== ""): ?>
-            <p><strong>Bestätigt:</strong> <?php echo escape(
-                formatDate($order["confirmed_at"]),
-            ); ?></p>
-        <?php endif; ?>
-        <?php if ($order["confirmation_status"] === "pending"): ?>
-            <p><strong>Bestätigung offen bis:</strong> <?php echo escape(
-                formatDate($order["confirmation_expires_at"]),
-            ); ?></p>
-        <?php endif; ?>
         <?php if ($order["admin_notified_at"] !== ""): ?>
             <p><strong>Intern weitergeleitet:</strong> <?php echo escape(
                 formatDate($order["admin_notified_at"]),
@@ -282,13 +270,7 @@ include __DIR__ . "/../includes/header.php";
                                 <?php endif; ?>
                             </td>
                             <td data-label="Aktionen">
-                                <?php if (
-                                    $order["status"] !== "cancelled" &&
-                                    $order["confirmation_status"] !==
-                                        "pending" &&
-                                    $order["confirmation_status"] !==
-                                        "expired"
-                                ): ?>
+                                <?php if ($order["status"] !== "cancelled"): ?>
                                     <form method="POST" class="inline-form">
                                         <?php echo csrfField(); ?>
                                         <input type="hidden" name="order_id" value="<?php echo escape(

+ 1 - 14
admin/orders.php

@@ -7,8 +7,6 @@ if (empty($_SESSION['admin_logged_in'])) {
     exit();
 }
 
-expirePendingOrders();
-
 if (isset($_GET['details']) && trim((string) $_GET['details']) !== "") {
     header(
         "Location: order.php?id=" .
@@ -39,13 +37,8 @@ if ($filter !== "all") {
     $orders = array_values(
         array_filter($orders, function ($order) use ($filter) {
             switch ($filter) {
-                case "unconfirmed":
-                    return $order["confirmation_status"] === "pending";
-                case "expired":
-                    return $order["confirmation_status"] === "expired";
                 case "open":
-                    return $order["confirmation_status"] !== "pending" &&
-                        $order["status"] === "open";
+                    return $order["status"] === "open";
                 case "partial":
                     return $order["status"] === "partial";
                 case "processed":
@@ -83,12 +76,6 @@ include __DIR__ . "/../includes/header.php";
                 <option value="all" <?php echo $filter === "all"
                     ? "selected"
                     : ""; ?>>Alle</option>
-                <option value="unconfirmed" <?php echo $filter === "unconfirmed"
-                    ? "selected"
-                    : ""; ?>>Unbestätigt</option>
-                <option value="expired" <?php echo $filter === "expired"
-                    ? "selected"
-                    : ""; ?>>Bestätigung abgelaufen</option>
                 <option value="open" <?php echo $filter === "open"
                     ? "selected"
                     : ""; ?>>Offen</option>

+ 0 - 23
admin/settings.php

@@ -19,11 +19,6 @@ if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_settings'])) {
     } else {
         $settings = [
             "order_recipient_email" => $_POST['order_recipient_email'] ?? "",
-            "order_confirmation_required" => isset(
-                $_POST['order_confirmation_required'],
-            ),
-            "order_confirmation_expiry_days" =>
-                (int) ($_POST['order_confirmation_expiry_days'] ?? 7),
             "attach_order_pdf_to_admin_email" => isset(
                 $_POST['attach_order_pdf_to_admin_email'],
             ),
@@ -69,24 +64,6 @@ include __DIR__ . "/../includes/header.php";
             ); ?>">
         </div>
 
-        <div class="form-group">
-            <label class="checkbox-label">
-                <input type="checkbox" name="order_confirmation_required" value="1" <?php echo !empty(
-                    $settings["order_confirmation_required"]
-                )
-                    ? "checked"
-                    : ""; ?>>
-                Bestellungen müssen vor interner Weiterleitung per E-Mail bestätigt werden
-            </label>
-        </div>
-
-        <div class="form-group">
-            <label for="order_confirmation_expiry_days">Bestätigungsfrist in Tagen *</label>
-            <input type="number" id="order_confirmation_expiry_days" name="order_confirmation_expiry_days" min="1" required value="<?php echo (int) $settings[
-                "order_confirmation_expiry_days"
-            ]; ?>">
-        </div>
-
         <div class="form-group">
             <label class="checkbox-label">
                 <input type="checkbox" name="attach_order_pdf_to_admin_email" value="1" <?php echo !empty(

+ 0 - 12
assets/css/style.css

@@ -730,24 +730,12 @@ body.admin-page .container {
     border-color: rgba(25, 107, 59, 0.2);
 }
 
-.status-expired {
-    color: var(--brand-danger);
-    background: #fff3f1;
-    border-color: rgba(180, 35, 24, 0.2);
-}
-
 .status-hidden {
     color: #9ca3af;
     border-color: #6b7280;
     background: #f1f3f5;
 }
 
-.status-unconfirmed {
-    color: var(--brand-primary);
-    background: #fff0a3;
-    border-color: #e0c038;
-}
-
 .status-partial {
     color: #7c6517;
     background: #f9efcb;

+ 1 - 5
checkout.php

@@ -155,11 +155,7 @@ $_POST['organization_id'] === $organization["id"]
             </div>
 
             <div class="alert alert-info">
-                <?php if (isOrderConfirmationRequired()): ?>
-                    Nach dem Absenden erhalten Sie eine E-Mail mit einem Bestätigungslink. Erst danach wird die Bestellung intern weitergeleitet.
-                <?php else: ?>
-                    Nach dem Absenden wird die Bestellung direkt an die Gerätewarte weitergeleitet.
-                <?php endif; ?>
+                Nach dem Absenden wird die Bestellung direkt an die Gerätewarte weitergeleitet.
             </div>
 
             <?php echo csrfField(); ?>

+ 0 - 2
config.sample.php

@@ -23,8 +23,6 @@ define('DISCLAIMER_LINES', [
 // Order settings
 define('ORDER_PREFIX', 'FWFS');
 define('ORDER_RECIPIENT_EMAIL', 'psa@feuerwehr-freising.de');
-define('ORDER_CONFIRMATION_REQUIRED', true);
-define('ORDER_CONFIRMATION_EXPIRY_DAYS', 7);
 define('ATTACH_ORDER_PDF_TO_ADMIN_EMAIL', true);
 
 // Email settings

+ 0 - 2
data/settings.json

@@ -1,8 +1,6 @@
 {
     "settings": {
         "order_recipient_email": "bestellungen@mailpit.medowar.de",
-        "order_confirmation_required": false,
-        "order_confirmation_expiry_days": 7,
         "attach_order_pdf_to_admin_email": true,
         "startpage_intro_text": "Dieses System dient der internen Bestellung persönlicher Schutzausrüstung der Stadt Freising.\r\nDie Bearbeitung erfolgt durch die Gerätewarte der Feuerwehr Freising."
     }

+ 27 - 75
docs/ADMIN_BUSINESS_LOGIC.md

@@ -2,7 +2,7 @@
 
 Dieses Dokument beschreibt die **Geschäftslogik der Bestellverwaltung** im Admin-Bereich. Es richtet sich an Operatoren, die Bestellungen im Alltag bearbeiten: Welche Status es gibt, was sie bedeuten, wo Bestellungen und Positionen angezeigt werden, und welche Aktionen wo möglich sind.
 
-Technische Details zum Kundenprozess (Checkout, E-Mail-Bestätigung) stehen in [ORDER_PROCESS.md](ORDER_PROCESS.md). Login und Admin-Konten: [ADMIN_SYSTEM.md](ADMIN_SYSTEM.md).
+Technische Details zum Kundenprozess (Checkout) stehen in [ORDER_PROCESS.md](ORDER_PROCESS.md). Login und Admin-Konten: [ADMIN_SYSTEM.md](ADMIN_SYSTEM.md).
 
 ---
 
@@ -21,30 +21,27 @@ Weitere Admin-Seiten (Produkte, Kategorien, Organisationen, FAQ, Einstellungen,
 
 ---
 
-## Drei unabhängige Status-Dimensionen
+## Zwei unabhängige Status-Dimensionen
 
 Eine Bestellung hat **keinen einzelnen Status**, sondern mehrere Felder, die unabhängig voneinander wirken:
 
 ```mermaid
 flowchart TB
     subgraph orderLevel [Bestellung]
-        confStatus["confirmation_status"]
         opStatus["status"]
     end
     subgraph lineLevel [Position]
         backorder["backorder_status"]
         processed["is_processed"]
     end
-    confStatus --> opStatus
     backorder --> processed
 ```
 
-- **Bestellung — Bestätigung** (`confirmation_status`): Hat der Kunde die E-Mail bestätigt? Blockiert Bearbeitung, solange unbestätigt oder abgelaufen.
 - **Bestellung — Bearbeitung** (`status`): Wie weit ist die Abarbeitung insgesamt? Wird automatisch aus den Positionen berechnet (`open`, `partial`, `processed`) oder manuell gesetzt (`cancelled`).
 - **Position — Nachbestellung** (`backorder_status`): Liegt der Artikel auf Lager oder muss extern nachbestellt werden?
 - **Position — Bearbeitet** (`is_processed`): Wurde diese Position ausgegeben/abarbeitet?
 
-In Tabellen und Listen erscheint **ein kombiniertes Anzeige-Label** (z. B. „Unbestätigt" oder „Offen"). Die Nachbestellung wird auf Positionsebene geführt und erscheint zusätzlich als Badge „Nachbestellung" auf der Detailseite, wenn mindestens eine Position einen Nachbestell-Status hat.
+In Tabellen und Listen erscheint **ein Anzeige-Label** (z. B. „Offen" oder „Teilweise bearbeitet"). Die Nachbestellung wird auf Positionsebene geführt und erscheint zusätzlich als Badge „Nachbestellung" auf der Detailseite, wenn mindestens eine Position einen Nachbestell-Status hat.
 
 ---
 
@@ -57,24 +54,11 @@ Das sichtbare Label folgt einer **festen Priorität**. Höhere Priorität übers
 | Anzeige-Label | Bedingung | Bedeutung für Operatoren |
 | --- | --- | --- |
 | **Storniert** | Bestellung storniert | Bestellung ist ungültig; nur „Stornierung aufheben" möglich |
-| **Unbestätigt** | E-Mail-Bestätigung ausstehend | Kunde hat noch nicht bestätigt; **keine Bearbeitung** möglich |
-| **Bestätigung abgelaufen** | Bestätigungsfrist abgelaufen | Kunde hat nicht rechtzeitig bestätigt; **keine Bearbeitung** möglich |
 | **Bearbeitet** | Alle Positionen bearbeitet | Bestellung abgeschlossen |
 | **Teilweise bearbeitet** | Mindestens eine, nicht alle Positionen bearbeitet | Bestellung in Arbeit |
 | **Offen** | Sonst | Noch keine Position bearbeitet |
 
-**Wichtig:** Die internen Werte `not_required` (Bestätigung nicht nötig) und `confirmed` (Kunde hat bestätigt) erscheinen **nie als eigenes Label**. Sobald keine Blockade durch Unbestätigt/Abgelaufen besteht, sehen Operatoren den operativen Status: Offen, Teilweise bearbeitet oder Bearbeitet.
-
-### Interne Bestätigungsstatus (Referenz)
-
-| Wert | Wann gesetzt | Operator-Relevanz |
-| --- | --- | --- |
-| `not_required` | Bestätigung in Einstellungen deaktiviert | Bestellung sofort bearbeitbar |
-| `pending` | Bestätigung aktiviert, Kunde hat noch nicht bestätigt | Label „Unbestätigt"; Bearbeitung gesperrt |
-| `confirmed` | Kunde hat per Link bestätigt | Bearbeitung freigegeben |
-| `expired` | Frist (`confirmation_expires_at`) überschritten | Label „Bestätigung abgelaufen"; Bearbeitung gesperrt |
-
-Auf der Detailseite sind ggf. sichtbar: Bestätigungsfrist (bei Unbestätigt), Bestätigungszeitpunkt, Zeitpunkt der internen Weiterleitung.
+Auf der Detailseite ist ggf. sichtbar: Zeitpunkt der internen Weiterleitung (`admin_notified_at`).
 
 ### Interner Bearbeitungsstatus der Bestellung
 
@@ -116,19 +100,17 @@ Stornierte Bestellungen werden **nicht** automatisch aus Positionen neu berechne
 
 | Karte | Was gezählt wird |
 | --- | --- |
-| **Offen** | Bestellungen mit operativem Status `open`, **ohne** Unbestätigt; **inkl.** abgelaufener Bestätigungen (siehe Bekannte Unstimmigkeiten) |
-| **Teilweise bearbeitet** | `status = partial`, ohne Unbestätigt |
-| **Bearbeitet** | `status = processed`, ohne Unbestätigt |
+| **Offen** | Bestellungen mit operativem Status `open` |
+| **Teilweise bearbeitet** | `status = partial` |
+| **Bearbeitet** | `status = processed` |
 | **Storniert** | `status = cancelled` |
 | **Nachbestellung** | Summe aller Positionen in `to_be_backordered` oder `ordered` (inkl. manueller Nachbestellungen) |
 
-**Unbestätigte Bestellungen** (`pending`) werden in **keiner** Statistik-Karte gezählt.
-
 #### „Letzte offene Bestellungen" (max. 5)
 
 Eine Bestellung erscheint hier nur, wenn:
 
-1. Das Anzeige-Label **Offen** oder **Teilweise bearbeitet** ist (nicht Unbestätigt, nicht Abgelaufen, nicht Bearbeitet, nicht Storniert), **und**
+1. Das Anzeige-Label **Offen** oder **Teilweise bearbeitet** ist (nicht Bearbeitet, nicht Storniert), **und**
 2. Mindestens **eine Position** existiert, die **nicht** bearbeitet ist **und** **keinen** Nachbestell-Status hat.
 
 Sortierung: neueste zuerst.
@@ -152,17 +134,13 @@ Filter und Suchfeld (Bestellnummer):
 
 | Filter | Was angezeigt wird |
 | --- | --- |
-| **Alle** | Alle Bestellungen (inkl. Unbestätigt) |
-| **Unbestätigt** | `confirmation_status = pending` |
-| **Bestätigung abgelaufen** | `confirmation_status = expired` |
-| **Offen** | Nicht Unbestätigt **und** `status = open` |
+| **Alle** | Alle Bestellungen |
+| **Offen** | `status = open` |
 | **Teilweise bearbeitet** | `status = partial` |
 | **Bearbeitet** | `status = processed` |
 | **Storniert** | `status = cancelled` |
 
-Die Status-Spalte zeigt das **kombinierte Anzeige-Label** (Bestätigung hat Vorrang vor operativem Status).
-
-**Hinweis zum Filter „Offen":** Enthält auch Bestellungen mit abgelaufener Bestätigung, solange der operative Status `open` ist (siehe Bekannte Unstimmigkeiten).
+Die Status-Spalte zeigt das **Anzeige-Label** aus dem operativen Bestellstatus.
 
 ---
 
@@ -172,8 +150,8 @@ Jede Bestellung ist immer über die Bestellliste oder Dashboard-Links erreichbar
 
 | Aktion | Wann sichtbar / erlaubt |
 | --- | --- |
-| **Als bearbeitet / offen markieren** | Bestellung nicht storniert; nicht Unbestätigt; nicht Abgelaufen |
-| **Als Nachbestellung markieren / aufheben** | Wie oben; nur Wechsel zwischen *(leer)* und **Nachzubestellen** — **nicht** von **Wartet auf Lieferung** |
+| **Als bearbeitet / offen markieren** | Bestellung nicht storniert |
+| **Als Nachbestellung markieren / aufheben** | Bestellung nicht storniert; nur Wechsel zwischen *(leer)* und **Nachzubestellen** — **nicht** von **Wartet auf Lieferung** |
 | **Bestellung stornieren** | Nicht storniert; nicht vollständig Bearbeitet |
 | **Stornierung aufheben** | Bestellung storniert |
 
@@ -188,10 +166,7 @@ Positionen im Status **Wartet auf Lieferung** (`ordered`): Kein Nachbestell-Togg
 #### Was erscheint in der Liste?
 
 - Alle **manuellen** Nachbestell-Einträge mit Status Nachzubestellen oder Wartet auf Lieferung.
-- Bestellpositionen mit Nachbestell-Status, **ausgenommen** Bestellungen die:
-  - storniert sind, oder
-  - Unbestätigt sind, oder
-  - abgelaufene Bestätigung haben.
+- Bestellpositionen mit Nachbestell-Status, **ausgenommen** stornierte Bestellungen.
 
 Positionen werden nach **Produkt und Größe** gruppiert. Spalten **Nachzubestellen** und **Wartet auf Lieferung** werden getrennt gezählt.
 
@@ -212,9 +187,7 @@ Manuelle Nachbestellungen können über ein Formular auf derselben Seite hinzuge
 
 | Zustand / Bedingung | Dashboard-Statistik | Letzte offene Bestellungen | Offene Positionen | Bestellliste-Filter | Bestelldetail | Nachbestellungen |
 | --- | --- | --- | --- | --- | --- | --- |
-| **Unbestätigt** (`pending`) | — (nicht gezählt) | — | — | Unbestätigt, Alle | Ja (nur Ansehen) | — |
-| **Bestätigung abgelaufen** (`expired`) | Zählt als **Offen** | — | — | Abgelaufen, **auch Offen** | Ja (nur Ansehen) | — |
-| **Offen** (bestätigt/nicht nötig) | Offen | Ja*, wenn offene Positionen ohne NB | Ja*, pro Position | Offen, Alle | Ja, bearbeitbar | Ja, wenn NB markiert |
+| **Offen** | Offen | Ja*, wenn offene Positionen ohne NB | Ja*, pro Position | Offen, Alle | Ja, bearbeitbar | Ja, wenn NB markiert |
 | **Teilweise bearbeitet** | Teilweise | Ja*, wenn offene Positionen ohne NB | Ja*, pro Position | Teilweise, Alle | Ja, bearbeitbar | Ja, wenn NB markiert |
 | **Bearbeitet** | Bearbeitet | — | — | Bearbeitet, Alle | Ja (nur Ansehen) | Ja, wenn NB markiert |
 | **Storniert** | Storniert | — | — | Storniert, Alle | Ja (Stornierung aufheben) | — |
@@ -229,7 +202,7 @@ Manuelle Nachbestellungen können über ein Formular auf derselben Seite hinzuge
 
 ## Typische Operator-Workflows
 
-### 1. Neue bestätigte Bestellung abarbeiten
+### 1. Neue Bestellung abarbeiten
 
 1. **Dashboard** → „Offene Positionen" oder „Letzte offene Bestellungen" prüfen.
 2. **Details** öffnen.
@@ -255,20 +228,13 @@ stateDiagram-v2
     }
 ```
 
-### 3. Unbestätigte Bestellung
-
-1. **Bestellliste** → Filter **Unbestätigt**.
-2. Abwarten, bis Kunde bestätigt, oder Frist abläuft.
-3. Nach Bestätigung: normal auf Dashboard sichtbar.
-4. Nach Ablauf: Label **Bestätigung abgelaufen** — keine Bearbeitung möglich.
-
-### 4. Manuelle Nachbestellung (ohne Kundenbestellung)
+### 3. Manuelle Nachbestellung (ohne Kundenbestellung)
 
 1. **Nachbestellungen** → Formular „Manuelle Nachbestellung".
 2. Produkt, Größe, Anzahl wählen.
 3. Weiter wie Workflow 2 (bestellt → geliefert).
 
-### 5. Bestellung stornieren
+### 4. Bestellung stornieren
 
 1. **Bestelldetail** → **„Bestellung stornieren"** (nicht möglich bei Status Bearbeitet).
 2. Optional Stornogrund angeben.
@@ -280,10 +246,8 @@ stateDiagram-v2
 
 Unter **Einstellungen** (`admin/settings.php`):
 
-- **E-Mail-Bestätigung erforderlich**: Neue Bestellungen starten als **Unbestätigt** statt sofort bearbeitbar.
-- **Bestätigungsfrist (Tage)**: Nach Ablauf → **Bestätigung abgelaufen**.
-
-Diese Einstellungen betreffen nur **neu eingehende** Bestellungen, nicht bereits gespeicherte.
+- **Empfängeradresse für interne Bestellmails**
+- **PDF an interne Bestell-E-Mails anhängen**
 
 ---
 
@@ -291,40 +255,28 @@ Diese Einstellungen betreffen nur **neu eingehende** Bestellungen, nicht bereits
 
 Die folgenden Punkte sind **keine Bedienanleitung**, sondern dokumentierte Abweichungen in der Anzeige-Logik. Sie können später im Code behoben werden.
 
-1. **`expirePendingOrders()` speichert Ablauf ggf. nicht dauerhaft**  
-   In `includes/functions.php`: Der Vergleich `$updated !== $order` ist bei Arrays per Referenz immer falsch. Abgelaufene Bestätigungen werden beim Lesen berechnet, `orders.json` kann aber weiterhin `pending` enthalten, bis eine andere Speicherung erfolgt.
-
-2. **Dashboard-Zähler „Offen" inkl. abgelaufener Bestätigungen**  
-   Abgelaufene Bestellungen erhöhen die Karte **Offen**, zeigen in Listen aber **Bestätigung abgelaufen** — die Zahl kann irreführend sein.
-
-3. **Filter „Offen" in der Bestellliste inkl. abgelaufener Bestätigungen**  
-   Der Filter schließt nur `pending` aus, nicht `expired`. Abgelaufene Bestellungen mit operativem Status `open` erscheinen unter **Offen** und **Bestätigung abgelaufen**.
-
-4. **Kein Dashboard-Zähler für Unbestätigt**  
-   Unbestätigte Bestellungen fehlen in allen Statistik-Karten; sie sind nur über Bestellliste → Filter **Unbestätigt** oder **Alle** auffindbar.
-
-5. **Bestellung nur mit Nachbestellpositionen**  
+1. **Bestellung nur mit Nachbestellpositionen**  
    Operativer Status kann **Offen** bleiben (0 Positionen bearbeitet), aber alle Positionen haben Nachbestell-Status → Bestellung erscheint **nicht** in Dashboard-Tabellen, nur in **Alle** und **Nachbestellungen**.
 
-6. **Status „Wartet auf Lieferung" nicht auf Detailseite änderbar**  
+2. **Status „Wartet auf Lieferung" nicht auf Detailseite änderbar**  
    Bewusste Trennung: Weiterführung nur über **Nachbestellungen**. Operatoren, die nur die Detailseite nutzen, finden keinen Button dafür.
 
-7. **Stornierte Bestellungen können Nachbestell-Flags in den Daten behalten**  
+3. **Stornierte Bestellungen können Nachbestell-Flags in den Daten behalten**  
    In der Nachbestellungen-Ansicht ausgeblendet, in den Rohdaten ggf. noch vorhanden.
 
-8. **Position bearbeitbar trotz Nachbestell-Flag**  
+4. **Position bearbeitbar trotz Nachbestell-Flag**  
    „Als bearbeitet markieren" löscht den Nachbestell-Status nicht automatisch. Umgekehrt blockiert ein gesetzter Nachbestell-Status auf bereits bearbeiteten Positionen keine erneute Markierung als offen/bearbeitet.
 
-9. **FIFO-Sortierung bei Bulk-Aktionen inkonsistent**  
+5. **FIFO-Sortierung bei Bulk-Aktionen inkonsistent**  
    Anzeige in Nachbestellungen sortiert u. a. nach `sort_at` (Bestell-/Bestellzeitpunkt). Bulk-Updates sortieren Kandidaten nur nach `created_at` — Reihenfolge kann bei „Wartet auf Lieferung" abweichen.
 
-10. **`MANUAL_BACKORDERS_FILE` fehlt in `config.sample.php`**  
+6. **`MANUAL_BACKORDERS_FILE` fehlt in `config.sample.php`**  
     Manuelle Nachbestellungen setzen die Konstante voraus; fehlt sie in der produktiven `config.php`, kann die Nachbestellungen-Seite fehlschlagen.
 
 ---
 
 ## Querverweise
 
-- [ORDER_PROCESS.md](ORDER_PROCESS.md) — Gesamtprozess von Bestellung bis Abarbeitung, E-Mail-Bestätigung, technische Abläufe
+- [ORDER_PROCESS.md](ORDER_PROCESS.md) — Gesamtprozess von Bestellung bis Abarbeitung, technische Abläufe
 - [ADMIN_SYSTEM.md](ADMIN_SYSTEM.md) — Admin-Login und Kontenverwaltung
 - [CONFIG_REFERENCE.md](CONFIG_REFERENCE.md) — Konfigurationsoptionen (sofern relevant für Betrieb)

+ 2 - 4
docs/CONFIG_REFERENCE.md

@@ -12,12 +12,10 @@
 | `SITE_IMPRINT_URL` | Ziel-URL für den Impressumslink |
 | `SITE_PRIVACY_URL` | Ziel-URL für den Datenschutzlink |
 | `SITE_FULL_NAME` | Kombinierter Anzeigename aus Marke und Service-Header |
-| `SITE_URL` | Basispfad oder Basis-URL für Links, Assets und Bestätigungslinks |
+| `SITE_URL` | Basispfad oder Basis-URL für Links und Assets |
 | `DISCLAIMER_LINES` | Hinweistext auf der Startseite |
 | `ORDER_PREFIX` | Präfix für Bestellnummern |
 | `ORDER_RECIPIENT_EMAIL` | Standard-Empfänger für interne Bestellmails |
-| `ORDER_CONFIRMATION_REQUIRED` | Standard, ob Bestellungen vor interner Weiterleitung bestätigt werden müssen |
-| `ORDER_CONFIRMATION_EXPIRY_DAYS` | Standardfrist für Bestätigungslinks |
 | `ATTACH_ORDER_PDF_TO_ADMIN_EMAIL` | Standard, ob interne Bestellmails ein PDF erhalten |
 | `ADMIN_EMAIL` | Fallback für Admin-Profile ohne gültige Mailadresse |
 | `FROM_EMAIL` | Absenderadresse ausgehender Mails |
@@ -32,4 +30,4 @@
 
 ## Hinweis
 
-Die Konstanten definieren die Startwerte. Änderbare Betriebsparameter wie Bestätigungspflicht oder interne Empfängeradresse können zusätzlich im Adminbereich unter `Einstellungen` angepasst werden.
+Die Konstanten definieren die Startwerte. Änderbare Betriebsparameter wie interne Empfängeradresse können zusätzlich im Adminbereich unter `Einstellungen` angepasst werden.

+ 9 - 49
docs/MAIL_PROCESS.md

@@ -5,7 +5,7 @@
 Der Versand aller Bestellmails ist zentral in `includes/functions.php` implementiert und wird synchron innerhalb der jeweiligen HTTP-Requests ausgeführt.
 
 - Kernfunktion für Versand: `sendEmail(...)`
-- Fachliche Mail-Trigger: `createOrder(...)` und `confirmOrderByToken(...)`
+- Fachlicher Mail-Trigger: `createOrder(...)`
 - Laufzeit-Einstellungen: `getSystemSettings()` aus `data/settings.json`
 - Startwerte/Fallbacks: `config.php`
 
@@ -20,70 +20,33 @@ Die folgenden Parameter steuern den Mailfluss:
 | Schlüssel | Quelle | Wirkung |
 |---|---|---|
 | `order_recipient_email` | Admin-Einstellungen (`data/settings.json`) | Empfänger für interne Bestellmail |
-| `order_confirmation_required` | Admin-Einstellungen | Erzwingt Bestätigungsmail vor interner Weiterleitung |
-| `order_confirmation_expiry_days` | Admin-Einstellungen | Gültigkeit des Bestätigungslinks |
 | `attach_order_pdf_to_admin_email` | Admin-Einstellungen | Hängt PDF an interne Bestellmail an |
 | `FROM_EMAIL`, `FROM_NAME` | `config.php` | Absender/Anzeigename für alle ausgehenden Mails |
-| `SITE_URL` | `config.php` | Basis für Bestätigungslink in Mails |
 
 ## Mail-Typen und Auslöser
 
-### 1) Bestätigung an Besteller
-
-- Funktion: `sendOrderConfirmationRequestEmail($order)`
-- Trigger: direkt nach `createOrder(...)`, wenn `order_confirmation_required = true`
-- Empfänger: `order.customer_email`
-- Inhalt: Bestellzusammenfassung + Button/Link auf `order-confirm.php?token=...`
-
-### 2) Eingangsbestätigung an Besteller (ohne Pflichtbestätigung)
+### 1) Eingangsbestätigung an Besteller
 
 - Funktion: `sendOrderCreatedCustomerEmail($order)`
-- Trigger: direkt nach `createOrder(...)`, wenn `order_confirmation_required = false`
+- Trigger: direkt nach `createOrder(...)`
 - Empfänger: `order.customer_email`
 - Inhalt: Bestellung erfasst und intern weitergeleitet
 
-### 3) Bestätigungsinfo an Besteller (nach Klick auf Token-Link)
-
-- Funktion: `sendOrderConfirmedCustomerEmail($order)`
-- Trigger: nach erfolgreicher Token-Bestätigung in `confirmOrderByToken(...)`
-- Empfänger: `order.customer_email`
-- Inhalt: Bestellung bestätigt und intern weitergeleitet
-
-### 4) Interne Bestellmail
+### 2) Interne Bestellmail
 
 - Funktion: `sendConfirmedOrderAdminNotification($order)`
-- Trigger:
-  - direkt nach `createOrder(...)`, wenn `order_confirmation_required = false`
-  - nach erfolgreicher Token-Bestätigung in `confirmOrderByToken(...)`, wenn vorher `pending`
+- Trigger: direkt nach `createOrder(...)`
 - Empfänger: `getOrderRecipientEmail()` (normalisiert/validiert)
 - Inhalt: HTML-Bestellzusammenfassung
 - Optional: PDF-Anhang `bestellung-<order-id>.pdf` bei aktivem `attach_order_pdf_to_admin_email`
 - PDF-Erzeugung: `renderOrderPdf($order)` (intern `prepareOrderForDocument()` + `generateOrderPdf()`); enthält keine Bearbeitungs-/Lieferstatus-Felder aus der Admin-Oberfläche
 - Admin-Nachdruck: `admin/order-pdf.php?id=<order-id>` (Schaltfläche „Bestellung drucken“ auf `admin/order.php`)
 
-## Ablauf nach Konfiguration
-
-### Fall A: Bestätigung erforderlich
-
-1. `createOrder(...)` speichert Bestellung mit `confirmation_status = pending`.
-2. Besteller erhält Bestätigungsmail mit Token-Link.
-3. Klick auf Link ruft `order-confirm.php` auf und startet `confirmOrderByToken(...)`.
-4. Bei gültigem, nicht abgelaufenem Token:
-   - `confirmation_status` wird auf `confirmed` gesetzt,
-   - interne Bestellmail wird versendet,
-   - Besteller erhält Bestätigungsinfo.
-
-### Fall B: Keine Bestätigung erforderlich
-
-1. `createOrder(...)` speichert Bestellung mit `confirmation_status = not_required` und `confirmed_at`.
-2. Interne Bestellmail wird sofort versendet.
-3. Besteller erhält direkt Eingangsbestätigung.
-
-## Ablauf bei Fristablauf
+## Ablauf
 
-- Offene Bestätigungen (`pending`) wechseln nach Frist auf `expired`.
-- Der Status wird in `refreshOrderState(...)` berechnet und über `expirePendingOrders()` beim Aufruf von `admin/index.php`, `admin/orders.php` und `order-confirm.php` fortgeschrieben.
-- Für `expired` gibt es keine zusätzliche Mail.
+1. `createOrder(...)` speichert die Bestellung.
+2. Interne Bestellmail wird versendet.
+3. Besteller erhält Eingangsbestätigung.
 
 ## Technische Versanddetails
 
@@ -93,9 +56,6 @@ Die folgenden Parameter steuern den Mailfluss:
   - `From: <FROM_NAME> <FROM_EMAIL>`
   - `Reply-To: FROM_EMAIL`
   - `X-Mailer: PHP/<version>`
-- Bestätigungslink wird über `buildAbsoluteUrl(...)` erzeugt:
-  - absolute `SITE_URL` wird direkt verwendet,
-  - bei relativer `SITE_URL` wird aus Request-Kontext (`HTTP_HOST`, HTTPS) eine absolute URL gebaut.
 
 ## Fehlerverhalten und Nachvollziehbarkeit
 

+ 5 - 25
docs/ORDER_PROCESS.md

@@ -2,14 +2,14 @@
 
 ## Überblick
 
-Das System bildet keinen klassischen E-Commerce-Checkout ab, sondern einen internen PSA-Anforderungsprozess mit optionaler E-Mail-Bestätigung vor der internen Weiterleitung.
+Das System bildet keinen klassischen E-Commerce-Checkout ab, sondern einen internen PSA-Anforderungsprozess mit direkter interner Weiterleitung nach dem Absenden.
 
 - Einstieg: `index.php` -> `product.php` -> `cart.php` -> `checkout.php`
 - Persistenz: `data/orders.json`
 - Kernlogik: `includes/functions.php`
 - Interne Bearbeitung: `admin/orders.php`
 - Nachbestellungen: `admin/backorders.php`
-- Einstellungen für Bestätigung/Weiterleitung: `admin/settings.php`
+- Einstellungen für Weiterleitung: `admin/settings.php`
 
 Hinweis:
 
@@ -25,30 +25,13 @@ Hinweis:
    - Organisation (Pflicht, muss aktiv sein)
    - Kommentar (optional)
 4. `createOrder(...)` erzeugt eine Bestellnummer (`ORDER_PREFIX-JAHR-LFDNR`, z. B. `FWFS-2026-001`) und speichert die Bestellung.
-5. Nach dem Speichern wird der Warenkorb geleert und auf `order-success.php` weitergeleitet.
+5. Nach dem Speichern werden interne Admin-Benachrichtigung und Kunden-Eingangsbestätigung per E-Mail versendet, der Warenkorb geleert und auf `order-success.php` weitergeleitet.
 
-## Potenzielle Freigaben / Bestätigungen
-
-### 1) Organisation als Vorbedingung
+## Vorbedingungen
 
 Eine Bestellung ist nur möglich, wenn die gewählte Organisation existiert und als aktiv markiert ist. Inaktive/ungültige Organisationen blockieren den Abschluss.
 
-### 2) Optionale E-Mail-Bestätigung durch Besteller
-
-Die Einstellung `order_confirmation_required` steuert, ob eine Bestätigung nötig ist:
-
-- **Aktiviert**:
-  - Status beim Anlegen: `confirmation_status = pending`
-  - Es wird eine Bestätigungs-Mail mit Token-Link (`order-confirm.php?token=...`) versendet.
-  - Interne Weiterleitung an die Empfängeradresse erfolgt erst nach erfolgreicher Bestätigung.
-  - Frist über `order_confirmation_expiry_days`; danach `confirmation_status = expired`.
-- **Deaktiviert**:
-  - Status beim Anlegen: `confirmation_status = not_required`
-  - Bestellung wird direkt intern weitergeleitet.
-
-### 3) Keine separate Admin-Freigabe vor Eingang
-
-Es gibt keinen expliziten Admin-Approve-Schritt, der den Eingang einer Bestellung freischaltet. Die formale Freigabelogik ist die optionale E-Mail-Bestätigung durch den Besteller.
+Es gibt keinen separaten Admin-Freigabeschritt vor dem Eingang einer Bestellung.
 
 ## Interner Prozess nach Eingang
 
@@ -58,11 +41,9 @@ In `admin/orders.php` werden Bestellungen nach Status geführt und manuell bearb
 - `partial`: mindestens eine Position bearbeitet
 - `processed`: alle Positionen bearbeitet
 - `cancelled`: Bestellung storniert
-- zusätzlich `pending`/`expired` über `confirmation_status`
 
 Regeln:
 
-- Positionsbearbeitung ist gesperrt, solange Bestellung `pending` oder `expired` ist.
 - Stornierung ist jederzeit möglich (solange noch nicht storniert).
 - Zeitstempel für interne Weiterleitung (`admin_notified_at`) wird gesetzt, wenn die Admin-Benachrichtigungsmail erfolgreich versendet wurde.
 
@@ -101,6 +82,5 @@ Positionen mit Nachbestell-Status (**Nachzubestellen** oder **Wartet auf Lieferu
 - Keine Mengenlogik im Warenkorb (pro Produkt nur ein Eintrag, keine Stückzahl).
 - Keine Lieferadresse / kein Versandprozess / kein Fulfillment-Tracking.
 - Keine Endnutzer-Bestellhistorie oder Kundenkonto-Workflow.
-- Optionaler Double-Opt-in-ähnlicher Schritt per E-Mail-Bestätigung vor interner Weiterleitung.
 - Bearbeitung ist positionsbasiert im Admin (operativer Abarbeitungsstatus statt klassischer Versandstatus).
 - Legacy-Routen `reservation.php` und `orders.php` (Frontend) sowie `admin/reservations.php` leiten auf die Bestellverwaltung um.

+ 2 - 3
docs/STYLE_SYSTEM.md

@@ -187,7 +187,6 @@ This is the portable public style interface. Implement these selectors and seman
 - `.status-open`: open/pending/active text+border.
 - `.status-notified`: notified/informed text+border.
 - `.status-picked`: completed/processed text+border.
-- `.status-expired`: expired/invalid text+border.
 - `.status-hidden`: hidden/archived text+border.
 
 ### Structural / Operations / Modal
@@ -353,7 +352,7 @@ Email styles are inline HTML/CSS and must keep the same dark-theme palette.
 - [ ] RGBA shadow/focus values match exactly.
 
 ### 2) Service UI parity
-- [ ] Header, cards, detail views, action rows, forms, and confirmation/detail pages match hierarchy and style.
+- [ ] Header, cards, detail views, action rows, forms, and detail pages match hierarchy and style.
 
 ### 3) Operations parity
 - [ ] Dashboard stats, data tables, status pills, panels, modal overlays, and action buttons match behavior and appearance.
@@ -366,7 +365,7 @@ Email styles are inline HTML/CSS and must keep the same dark-theme palette.
 - [ ] Printing detail pages hides header/footer/nav/buttons and preserves key blocks from page breaks.
 
 ### 6) Email parity
-- [ ] Notification/confirmation emails render with dark card theme, accent highlights, and warning block usage where applicable.
+- [ ] Notification emails render with dark card theme, accent highlights, and warning block usage where applicable.
 
 ### 7) Accessibility sanity
 - [ ] Text contrast remains readable on dark surfaces.

+ 6 - 332
includes/functions.php

@@ -1014,14 +1014,6 @@ function getDefaultSystemSettings()
         "order_recipient_email" => defined("ORDER_RECIPIENT_EMAIL")
             ? ORDER_RECIPIENT_EMAIL
             : getDefaultAdminEmail(),
-        "order_confirmation_required" => defined("ORDER_CONFIRMATION_REQUIRED")
-            ? (bool) ORDER_CONFIRMATION_REQUIRED
-            : false,
-        "order_confirmation_expiry_days" => defined(
-            "ORDER_CONFIRMATION_EXPIRY_DAYS",
-        )
-            ? (int) ORDER_CONFIRMATION_EXPIRY_DAYS
-            : 7,
         "attach_order_pdf_to_admin_email" => defined(
             "ATTACH_ORDER_PDF_TO_ADMIN_EMAIL",
         )
@@ -1046,23 +1038,12 @@ function normalizeSystemSettings($settings)
         $recipientEmail = $defaults["order_recipient_email"];
     }
 
-    $expiryDays = isset($settings["order_confirmation_expiry_days"])
-        ? (int) $settings["order_confirmation_expiry_days"]
-        : $defaults["order_confirmation_expiry_days"];
-    if ($expiryDays < 1) {
-        $expiryDays = 7;
-    }
-
     $startpageIntroText = trim(
         (string) ($settings["startpage_intro_text"] ?? $defaults["startpage_intro_text"]),
     );
 
     return [
         "order_recipient_email" => $recipientEmail,
-        "order_confirmation_required" => !empty(
-            $settings["order_confirmation_required"]
-        ),
-        "order_confirmation_expiry_days" => $expiryDays,
         "attach_order_pdf_to_admin_email" => !empty(
             $settings["attach_order_pdf_to_admin_email"]
         ),
@@ -1089,18 +1070,6 @@ function getOrderRecipientEmail()
     return $settings["order_recipient_email"];
 }
 
-function isOrderConfirmationRequired()
-{
-    $settings = getSystemSettings();
-    return !empty($settings["order_confirmation_required"]);
-}
-
-function getOrderConfirmationExpiryDays()
-{
-    $settings = getSystemSettings();
-    return max(1, (int) $settings["order_confirmation_expiry_days"]);
-}
-
 function shouldAttachOrderPdfToAdminEmail()
 {
     $settings = getSystemSettings();
@@ -1293,19 +1262,6 @@ function normalizeOrderRecord($order)
         $createdAt = date("Y-m-d H:i:s");
     }
 
-    $confirmationStatus = trim(
-        (string) ($order["confirmation_status"] ?? "confirmed"),
-    );
-    $allowedConfirmationStatuses = [
-        "not_required",
-        "pending",
-        "confirmed",
-        "expired",
-    ];
-    if (!in_array($confirmationStatus, $allowedConfirmationStatuses, true)) {
-        $confirmationStatus = "confirmed";
-    }
-
     $status = trim((string) ($order["status"] ?? "open"));
     $allowedStatuses = ["open", "partial", "processed", "cancelled"];
     if (!in_array($status, $allowedStatuses, true)) {
@@ -1321,14 +1277,6 @@ function normalizeOrderRecord($order)
         "comment" => trim((string) ($order["comment"] ?? "")),
         "items" => $items,
         "status" => $status,
-        "confirmation_status" => $confirmationStatus,
-        "confirmation_token" => trim(
-            (string) ($order["confirmation_token"] ?? ""),
-        ),
-        "confirmation_expires_at" => trim(
-            (string) ($order["confirmation_expires_at"] ?? ""),
-        ),
-        "confirmed_at" => trim((string) ($order["confirmed_at"] ?? "")),
         "created_at" => $createdAt,
         "updated_at" => trim((string) ($order["updated_at"] ?? $createdAt)),
         "cancelled_at" => trim((string) ($order["cancelled_at"] ?? "")),
@@ -1354,16 +1302,6 @@ function refreshOrderState($order)
         return $order;
     }
 
-    if (
-        ($order["confirmation_status"] ?? "") === "pending" &&
-        !empty($order["confirmation_expires_at"])
-    ) {
-        $expiresAt = strtotime((string) $order["confirmation_expires_at"]);
-        if ($expiresAt !== false && time() > $expiresAt) {
-            $order["confirmation_status"] = "expired";
-        }
-    }
-
     $processedCount = 0;
     foreach ($order["items"] as $item) {
         if (!empty($item["is_processed"])) {
@@ -1382,25 +1320,6 @@ function refreshOrderState($order)
     return $order;
 }
 
-function expirePendingOrders()
-{
-    $orders = getOrders();
-    $changed = false;
-
-    foreach ($orders as &$order) {
-        $updated = refreshOrderState($order);
-        if ($updated !== $order) {
-            $order = $updated;
-            $changed = true;
-        }
-    }
-    unset($order);
-
-    if ($changed) {
-        saveOrders($orders);
-    }
-}
-
 function getOrderById($orderId)
 {
     $orderId = trim((string) $orderId);
@@ -1417,74 +1336,6 @@ function getOrderById($orderId)
     return null;
 }
 
-function findOrderByConfirmationToken($token)
-{
-    $token = trim((string) $token);
-    if ($token === "") {
-        return null;
-    }
-
-    foreach (getOrders() as $order) {
-        if (($order["confirmation_token"] ?? "") === $token) {
-            return $order;
-        }
-    }
-
-    return null;
-}
-
-function buildOrderConfirmationUrl($token)
-{
-    $path = "/order-confirm.php?token=" . urlencode($token);
-    return buildAbsoluteUrl($path);
-}
-
-function buildAbsoluteUrl($path)
-{
-    $path = "/" . ltrim((string) $path, "/");
-    $siteUrl = defined("SITE_URL") ? trim((string) SITE_URL) : "";
-
-    if (strpos($siteUrl, "://") !== false) {
-        return rtrim($siteUrl, "/") . $path;
-    }
-
-    $basePath = trim($siteUrl);
-    if ($basePath !== "" && $basePath !== "/") {
-        $path = "/" . trim($basePath, "/") . $path;
-    }
-
-    $scheme = isHttpsRequest() ? "https" : "http";
-    $host = $_SERVER["HTTP_HOST"] ?? "";
-    if ($host === "") {
-        return $path;
-    }
-
-    return $scheme . "://" . $host . $path;
-}
-
-function isHttpsRequest(): bool
-{
-    if (
-        !empty($_SERVER["HTTPS"]) &&
-        strtolower((string) $_SERVER["HTTPS"]) !== "off"
-    ) {
-        return true;
-    }
-    if (
-        !empty($_SERVER["HTTP_X_FORWARDED_PROTO"]) &&
-        strtolower((string) $_SERVER["HTTP_X_FORWARDED_PROTO"]) === "https"
-    ) {
-        return true;
-    }
-    if (
-        !empty($_SERVER["SERVER_PORT"]) &&
-        (int) $_SERVER["SERVER_PORT"] === 443
-    ) {
-        return true;
-    }
-    return false;
-}
-
 function createOrder(
     $customerName,
     $customerEmail,
@@ -1527,16 +1378,6 @@ function createOrder(
     }
 
     $now = date("Y-m-d H:i:s");
-    $requiresConfirmation = isOrderConfirmationRequired();
-    $confirmationToken = $requiresConfirmation ? bin2hex(random_bytes(24)) : "";
-    $confirmationExpiresAt = "";
-    if ($requiresConfirmation) {
-        $expires = new DateTimeImmutable();
-        $expires = $expires->modify(
-            "+" . getOrderConfirmationExpiryDays() . " days",
-        );
-        $confirmationExpiresAt = $expires->format("Y-m-d H:i:s");
-    }
 
     $order = [
         "id" => generateOrderId(),
@@ -1547,12 +1388,6 @@ function createOrder(
         "comment" => $comment,
         "items" => $items,
         "status" => "open",
-        "confirmation_status" => $requiresConfirmation
-            ? "pending"
-            : "not_required",
-        "confirmation_token" => $confirmationToken,
-        "confirmation_expires_at" => $confirmationExpiresAt,
-        "confirmed_at" => $requiresConfirmation ? "" : $now,
         "created_at" => $now,
         "updated_at" => $now,
         "cancelled_at" => "",
@@ -1571,16 +1406,12 @@ function createOrder(
         "organization" => $organization["label"],
     ]);
 
-    if ($requiresConfirmation) {
-        sendOrderConfirmationRequestEmail($order);
-    } else {
-        $result = sendConfirmedOrderAdminNotification($order);
-        if ($result) {
-            markOrderAdminNotified($order["id"]);
-            $order = getOrderById($order["id"]);
-        }
-        sendOrderCreatedCustomerEmail($order);
+    $result = sendConfirmedOrderAdminNotification($order);
+    if ($result) {
+        markOrderAdminNotified($order["id"]);
+        $order = getOrderById($order["id"]);
     }
+    sendOrderCreatedCustomerEmail($order);
 
     return ["success" => true, "order" => $order];
 }
@@ -1602,74 +1433,6 @@ function markOrderAdminNotified($orderId)
     saveOrders($orders);
 }
 
-function confirmOrderByToken($token)
-{
-    $orders = getOrders();
-    $now = date("Y-m-d H:i:s");
-
-    foreach ($orders as &$order) {
-        if (($order["confirmation_token"] ?? "") !== $token) {
-            continue;
-        }
-
-        $order = refreshOrderState($order);
-
-        if ($order["status"] === "cancelled") {
-            return [
-                "success" => false,
-                "message" =>
-                    "Diese Bestellung wurde storniert und kann nicht mehr bestätigt werden.",
-            ];
-        }
-        if ($order["confirmation_status"] === "confirmed") {
-            return [
-                "success" => false,
-                "message" => "Diese Bestellung wurde bereits bestätigt.",
-            ];
-        }
-        if ($order["confirmation_status"] === "expired") {
-            return [
-                "success" => false,
-                "message" => "Der Bestätigungslink ist abgelaufen.",
-            ];
-        }
-        if ($order["confirmation_status"] !== "pending") {
-            return [
-                "success" => false,
-                "message" =>
-                    "Für diese Bestellung ist keine Bestätigung erforderlich.",
-            ];
-        }
-
-        $expiresAt = strtotime((string) $order["confirmation_expires_at"]);
-        if ($expiresAt !== false && time() > $expiresAt) {
-            $order["confirmation_status"] = "expired";
-            $order["updated_at"] = $now;
-            saveOrders($orders);
-            return [
-                "success" => false,
-                "message" => "Der Bestätigungslink ist abgelaufen.",
-            ];
-        }
-
-        $order["confirmation_status"] = "confirmed";
-        $order["confirmed_at"] = $now;
-        $order["updated_at"] = $now;
-        saveOrders($orders);
-
-        $sent = sendConfirmedOrderAdminNotification($order);
-        if ($sent) {
-            markOrderAdminNotified($order["id"]);
-        }
-        sendOrderConfirmedCustomerEmail(getOrderById($order["id"]));
-
-        return ["success" => true, "order" => getOrderById($order["id"])];
-    }
-    unset($order);
-
-    return ["success" => false, "message" => "Bestellung nicht gefunden."];
-}
-
 function toggleOrderItemProcessed($orderId, $itemIndex)
 {
     $orders = getOrders();
@@ -1687,20 +1450,6 @@ function toggleOrderItemProcessed($orderId, $itemIndex)
                     "Stornierte Bestellungen können nicht mehr bearbeitet werden.",
             ];
         }
-        if (($order["confirmation_status"] ?? "") === "pending") {
-            return [
-                "success" => false,
-                "message" =>
-                    "Unbestätigte Bestellungen können noch nicht bearbeitet werden.",
-            ];
-        }
-        if (($order["confirmation_status"] ?? "") === "expired") {
-            return [
-                "success" => false,
-                "message" =>
-                    "Abgelaufene unbestätigte Bestellungen können nicht bearbeitet werden.",
-            ];
-        }
         if (!isset($order["items"][$itemIndex])) {
             return [
                 "success" => false,
@@ -1796,20 +1545,6 @@ function orderItemCanBeManaged($order)
                 "Stornierte Bestellungen können nicht mehr bearbeitet werden.",
         ];
     }
-    if (($order["confirmation_status"] ?? "") === "pending") {
-        return [
-            "success" => false,
-            "message" =>
-                "Unbestätigte Bestellungen können noch nicht bearbeitet werden.",
-        ];
-    }
-    if (($order["confirmation_status"] ?? "") === "expired") {
-        return [
-            "success" => false,
-            "message" =>
-                "Abgelaufene unbestätigte Bestellungen können nicht bearbeitet werden.",
-        ];
-    }
 
     return ["success" => true];
 }
@@ -2037,9 +1772,6 @@ function collectBackorderItemRefs()
         if (($order["status"] ?? "") === "cancelled") {
             continue;
         }
-        if (in_array($order["confirmation_status"] ?? "", ["pending", "expired"], true)) {
-            continue;
-        }
 
         foreach ($order["items"] as $itemIndex => $item) {
             $status = (string) ($item["backorder_status"] ?? "");
@@ -2317,12 +2049,6 @@ function getOrderStatusLabel($order)
     if (($order["status"] ?? "") === "cancelled") {
         return "Storniert";
     }
-    if (($order["confirmation_status"] ?? "") === "pending") {
-        return "Unbestätigt";
-    }
-    if (($order["confirmation_status"] ?? "") === "expired") {
-        return "Bestätigung abgelaufen";
-    }
     if (($order["status"] ?? "") === "processed") {
         return "Bearbeitet";
     }
@@ -2337,12 +2063,6 @@ function getOrderStatusClass($order)
     if (($order["status"] ?? "") === "cancelled") {
         return "status-cancelled";
     }
-    if (($order["confirmation_status"] ?? "") === "pending") {
-        return "status-unconfirmed";
-    }
-    if (($order["confirmation_status"] ?? "") === "expired") {
-        return "status-expired";
-    }
     if (($order["status"] ?? "") === "processed") {
         return "status-processed";
     }
@@ -2620,38 +2340,6 @@ function buildOrderSummaryHtml($order, $title, $introHtml, $extraHtml = "")
     </html>';
 }
 
-function sendOrderConfirmationRequestEmail($order)
-{
-    $subject = SITE_SERVICE_NAME . ": Bestellung bestätigen - " . $order["id"];
-    $link = buildOrderConfirmationUrl($order["confirmation_token"]);
-    $expiryText = formatDate($order["confirmation_expires_at"]);
-    $intro =
-        "<p>Guten Tag " .
-        escape($order["customer_name"]) .
-        ",</p><p>bitte bestätigen Sie Ihre Bestellung im " .
-        escape(SITE_SERVICE_NAME) .
-        " der Stadt Freising über den folgenden Link.</p>";
-    $extra =
-        '
-        <p><a href="' .
-        escape($link) .
-        '" style="display: inline-block; padding: 0.75rem 1.5rem; background: #ffd71c; color: #111111; text-decoration: none; border: 2px solid #ffd71c; border-radius: 14px; font-weight: 700;">Bestellung bestätigen</a></p>
-        <p>Der Link ist gültig bis: <strong>' .
-        escape($expiryText) .
-        '</strong></p>
-        <p>Falls der Button nicht funktioniert, verwenden Sie bitte diesen Link:<br>' .
-        escape($link) .
-        "</p>";
-    $message = buildOrderSummaryHtml(
-        $order,
-        "Bestellung bestätigen",
-        $intro,
-        $extra,
-    );
-
-    return sendEmail($order["customer_email"], $subject, $message);
-}
-
 function sendOrderCreatedCustomerEmail($order)
 {
     $subject = SITE_SERVICE_NAME . ": Ihre Bestellung - " . $order["id"];
@@ -2666,20 +2354,6 @@ function sendOrderCreatedCustomerEmail($order)
     return sendEmail($order["customer_email"], $subject, $message);
 }
 
-function sendOrderConfirmedCustomerEmail($order)
-{
-    $subject = SITE_SERVICE_NAME . ": Bestellung bestätigt - " . $order["id"];
-    $intro =
-        "<p>Guten Tag " .
-        escape($order["customer_name"]) .
-        ",</p><p>Ihre Bestellung wurde bestätigt und an " .
-        escape(SITE_DEPARTMENT_NAME) .
-        " weitergeleitet.</p>";
-    $message = buildOrderSummaryHtml($order, "Bestellung bestätigt", $intro);
-
-    return sendEmail($order["customer_email"], $subject, $message);
-}
-
 function sendConfirmedOrderAdminNotification($order)
 {
     $recipient = getOrderRecipientEmail();
@@ -2689,7 +2363,7 @@ function sendConfirmedOrderAdminNotification($order)
 
     $subject = SITE_SERVICE_NAME . ": Neue Bestellung - " . $order["id"];
     $intro =
-        "<p>Eine neue PSA-Bestellung wurde freigegeben und muss bearbeitet werden.</p>";
+        "<p>Eine neue PSA-Bestellung ist eingegangen und muss bearbeitet werden.</p>";
     $message = buildOrderSummaryHtml($order, "Neue PSA-Bestellung", $intro);
 
     $attachments = [];

+ 0 - 29
order-confirm.php

@@ -1,29 +0,0 @@
-<?php
-require_once __DIR__ . '/config.php';
-require_once __DIR__ . '/includes/functions.php';
-
-expirePendingOrders();
-$result = confirmOrderByToken($_GET['token'] ?? '');
-$pageTitle = 'Bestellung bestätigen';
-
-include __DIR__ . '/includes/header.php';
-?>
-
-<h2>Bestellung bestätigen</h2>
-
-<?php if ($result['success']): ?>
-    <div class="alert alert-success">
-        <p>Die Bestellung wurde bestätigt und an <?php echo escape(SITE_DEPARTMENT_NAME); ?> weitergeleitet.</p>
-    </div>
-    <div class="panel">
-        <p><strong>Bestellnummer:</strong> <span class="order-highlight"><?php echo escape($result['order']['id']); ?></span></p>
-    </div>
-<?php else: ?>
-    <div class="alert alert-error">
-        <p><?php echo escape($result['message']); ?></p>
-    </div>
-<?php endif; ?>
-
-<a href="index.php" class="btn">Zur Startseite</a>
-
-<?php include __DIR__ . '/includes/footer.php'; ?>

+ 1 - 5
order-success.php

@@ -17,11 +17,7 @@ include __DIR__ . '/includes/header.php';
 <?php else: ?>
     <div class="panel">
         <p><strong>Bestellnummer:</strong> <span class="order-highlight"><?php echo escape($order['id']); ?></span></p>
-        <?php if ($order['confirmation_status'] === 'pending'): ?>
-            <p>Bitte bestätigen Sie die Bestellung über den Link in der E-Mail an <strong><?php echo escape($order['customer_email']); ?></strong>.</p>
-        <?php else: ?>
-            <p>Ihre Bestellung wurde erfasst und an <?php echo escape(SITE_DEPARTMENT_NAME); ?> weitergeleitet.</p>
-        <?php endif; ?>
+        <p>Ihre Bestellung wurde erfasst und an <?php echo escape(SITE_DEPARTMENT_NAME); ?> weitergeleitet.</p>
     </div>
 <?php endif; ?>