4 Komitmen b05b7abe9e ... bc9215893a

Pembuat SHA1 Pesan Tanggal
  Medowar bc9215893a updating test orders 6 hari lalu
  Medowar 9cca03c136 adding hint for multiple category selection 6 hari lalu
  Medowar 708065d7aa changing views in Admin Dashboard to only show orders with actions pending 6 hari lalu
  Medowar 769246081c adding docs 6 hari lalu
10 mengubah file dengan 595 tambahan dan 58 penghapusan
  1. 2 0
      README.md
  2. 9 12
      admin/index.php
  3. 1 0
      admin/products.php
  4. 5 37
      data/orders.json
  5. 2 0
      docs/.htaccess
  6. 11 9
      docs/ADMIN_BUSINESS_LOGIC.md
  7. 214 0
      docs/SHOP_LOGIC.md
  8. 200 0
      docs/assets/docs.css
  9. 11 0
      docs/assets/marked.min.js
  10. 140 0
      docs/index.php

+ 2 - 0
README.md

@@ -42,6 +42,8 @@ Dieses Projekt ist ein internes Bestellsystem für persönliche Schutzausrüstun
 
 Weitere Konstanten: [docs/CONFIG_REFERENCE.md](docs/CONFIG_REFERENCE.md).
 
+Technische Dokumentation im Browser: [docs/index.php](docs/index.php) (Markdown wird mit [marked](https://marked.js.org/) gerendert).
+
 ## Hinweise
 
 - Bestellungen werden nicht im Browser für Endnutzer gespeichert oder nachverfolgt.

+ 9 - 12
admin/index.php

@@ -42,7 +42,10 @@ $recentOrders = array_values(array_filter($orders, function ($order) {
         return false;
     }
     foreach ($order['items'] as $item) {
-        if (empty($item['is_processed'])) {
+        if (!empty($item['is_processed'])) {
+            continue;
+        }
+        if (trim((string) ($item['backorder_status'] ?? '')) === '') {
             return true;
         }
     }
@@ -63,6 +66,9 @@ foreach ($orders as $order) {
         if (!empty($item['is_processed'])) {
             continue;
         }
+        if (trim((string) ($item['backorder_status'] ?? '')) !== '') {
+            continue;
+        }
         $outstandingItems[] = [
             'order_id' => $order['id'],
             'customer_name' => $order['customer_name'],
@@ -71,7 +77,6 @@ foreach ($orders as $order) {
             'product_name' => $item['product_name'],
             'size' => $item['size'],
             'availability_label' => $item['availability_label'],
-            'backorder_status' => $item['backorder_status'] ?? '',
         ];
     }
 }
@@ -137,10 +142,10 @@ include __DIR__ . '/../includes/header.php';
     </div>
 </div>
 
-<h3 class="section-title mt-4">Letzte offene Bestellungen</h3>
+<h3 class="section-title mt-4">Bestellungen mit Aktion erforderlich</h3>
 
 <?php if (empty($recentOrders)): ?>
-    <p>Keine offenen Bestellungen vorhanden.</p>
+    <p>Keine Bestellungen mit erforderlicher Aktion.</p>
 <?php else: ?>
     <div class="table-responsive">
         <table class="responsive-table">
@@ -195,7 +200,6 @@ include __DIR__ . '/../includes/header.php';
                     <th>Artikel</th>
                     <th>Größe</th>
                     <th>Lieferhinweis</th>
-                    <th>Nachbestellung</th>
                     <th>Erstellt</th>
                     <th>Aktionen</th>
                 </tr>
@@ -209,13 +213,6 @@ include __DIR__ . '/../includes/header.php';
                         <td data-label="Artikel"><?php echo escape($row['product_name']); ?></td>
                         <td data-label="Größe"><?php echo $row['size'] !== '' ? escape($row['size']) : '-'; ?></td>
                         <td data-label="Lieferhinweis"><?php echo $row['availability_label'] !== '' ? escape($row['availability_label']) : '-'; ?></td>
-                        <td data-label="Nachbestellung">
-                            <?php if (($row['backorder_status'] ?? '') !== ''): ?>
-                                <span class="status <?php echo escape(getBackorderStatusClass($row['backorder_status'])); ?>"><?php echo escape(getBackorderStatusLabel($row['backorder_status'])); ?></span>
-                            <?php else: ?>
-                                -
-                            <?php endif; ?>
-                        </td>
                         <td data-label="Erstellt"><?php echo escape(formatDate($row['created_at'])); ?></td>
                         <td data-label="Aktionen"><a href="order.php?id=<?php echo urlencode($row['order_id']); ?>" class="btn btn-small">Details</a></td>
                     </tr>

+ 1 - 0
admin/products.php

@@ -386,6 +386,7 @@ if (empty($currentSizes)) {
                     </option>
                 <?php endforeach; ?>
             </select>
+            <small>Für Mehrfachauswahl von Kategorien STRG + Klick.</small>
         </div>
 
         <div class="form-group">

+ 5 - 37
data/orders.json

@@ -30,10 +30,6 @@
                 }
             ],
             "status": "cancelled",
-            "confirmation_status": "not_required",
-            "confirmation_token": "",
-            "confirmation_expires_at": "",
-            "confirmed_at": "2026-05-11 22:14:17",
             "created_at": "2026-05-11 22:14:17",
             "updated_at": "2026-05-11 22:15:23",
             "cancelled_at": "2026-05-11 22:15:23",
@@ -71,10 +67,6 @@
                 }
             ],
             "status": "processed",
-            "confirmation_status": "not_required",
-            "confirmation_token": "",
-            "confirmation_expires_at": "",
-            "confirmed_at": "2026-05-11 22:14:38",
             "created_at": "2026-05-11 22:14:38",
             "updated_at": "2026-05-11 22:14:58",
             "cancelled_at": "",
@@ -112,10 +104,6 @@
                 }
             ],
             "status": "processed",
-            "confirmation_status": "not_required",
-            "confirmation_token": "",
-            "confirmation_expires_at": "",
-            "confirmed_at": "2026-05-11 22:17:07",
             "created_at": "2026-05-11 22:17:07",
             "updated_at": "2026-05-30 10:53:57",
             "cancelled_at": "",
@@ -157,18 +145,14 @@
                     "size": "M",
                     "availability_label": "",
                     "is_processed": false,
-                    "backorder_status": "",
-                    "backordered_at": "2026-05-30 11:08:23",
+                    "backorder_status": "to_be_backordered",
+                    "backordered_at": "2026-05-30 13:29:32",
                     "ordered_at": ""
                 }
             ],
             "status": "partial",
-            "confirmation_status": "not_required",
-            "confirmation_token": "",
-            "confirmation_expires_at": "",
-            "confirmed_at": "2026-05-30 09:58:30",
             "created_at": "2026-05-30 09:58:30",
-            "updated_at": "2026-05-30 11:10:17",
+            "updated_at": "2026-05-30 13:29:32",
             "cancelled_at": "",
             "cancelled_by": "",
             "cancellation_reason": "",
@@ -194,10 +178,6 @@
                 }
             ],
             "status": "open",
-            "confirmation_status": "not_required",
-            "confirmation_token": "",
-            "confirmation_expires_at": "",
-            "confirmed_at": "2026-05-30 10:11:13",
             "created_at": "2026-05-30 10:11:13",
             "updated_at": "2026-05-30 11:10:19",
             "cancelled_at": "",
@@ -225,10 +205,6 @@
                 }
             ],
             "status": "open",
-            "confirmation_status": "not_required",
-            "confirmation_token": "",
-            "confirmation_expires_at": "",
-            "confirmed_at": "2026-05-30 10:28:37",
             "created_at": "2026-05-30 10:28:37",
             "updated_at": "2026-05-30 11:10:21",
             "cancelled_at": "",
@@ -256,10 +232,6 @@
                 }
             ],
             "status": "open",
-            "confirmation_status": "not_required",
-            "confirmation_token": "",
-            "confirmation_expires_at": "",
-            "confirmed_at": "2026-05-30 10:44:49",
             "created_at": "2026-05-30 10:44:49",
             "updated_at": "2026-05-30 11:10:21",
             "cancelled_at": "",
@@ -282,17 +254,13 @@
                     "availability_label": "",
                     "is_processed": false,
                     "backorder_status": "",
-                    "backordered_at": "",
+                    "backordered_at": "2026-05-30 13:25:18",
                     "ordered_at": ""
                 }
             ],
             "status": "open",
-            "confirmation_status": "not_required",
-            "confirmation_token": "",
-            "confirmation_expires_at": "",
-            "confirmed_at": "2026-05-30 11:11:13",
             "created_at": "2026-05-30 11:11:13",
-            "updated_at": "2026-05-30 11:11:13",
+            "updated_at": "2026-05-30 13:27:48",
             "cancelled_at": "",
             "cancelled_by": "",
             "cancellation_reason": "",

+ 2 - 0
docs/.htaccess

@@ -0,0 +1,2 @@
+# Documentation viewer: PHP serves Markdown; raw .md stays blocked by parent rules.
+Options -Indexes

+ 11 - 9
docs/ADMIN_BUSINESS_LOGIC.md

@@ -106,12 +106,14 @@ Stornierte Bestellungen werden **nicht** automatisch aus Positionen neu berechne
 | **Storniert** | `status = cancelled` |
 | **Nachbestellung** | Summe aller Positionen in `to_be_backordered` oder `ordered` (inkl. manueller Nachbestellungen) |
 
-#### „Letzte offene Bestellungen" (max. 5)
+#### „Bestellungen mit Aktion erforderlich" (max. 5)
 
 Eine Bestellung erscheint hier nur, wenn:
 
 1. Das Anzeige-Label **Offen** oder **Teilweise bearbeitet** ist (nicht Bearbeitet, nicht Storniert), **und**
-2. Mindestens **eine Position** existiert, die **nicht** bearbeitet ist (unabhängig vom Nachbestell-Status).
+2. Mindestens **eine Position** existiert, die **nicht** bearbeitet ist **und keinen** Nachbestell-Status hat (`to_be_backordered` / `ordered`).
+
+Bestellungen, bei denen alle offenen Positionen nur noch Nachbestell-Status haben, erscheinen **nicht** (Weiterbearbeitung über **Nachbestellungen**).
 
 Spalte **Nachbestellung:** Zusammenfassung der Nachbestell-Status aller **offenen** Positionen (`-`, ein Status-Badge, oder **Gemischt**).
 
@@ -122,9 +124,9 @@ Sortierung: neueste zuerst.
 Eine Position erscheint hier nur, wenn:
 
 1. Die zugehörige Bestellung das Label **Offen** oder **Teilweise bearbeitet** hat, **und**
-2. Die Position **nicht** bearbeitet ist (auch mit Nachbestell-Status).
+2. Die Position **nicht** bearbeitet ist **und keinen** Nachbestell-Status hat.
 
-Spalte **Nachbestellung:** pro Zeile **Nachzubestellen**, **Wartet auf Lieferung** oder `-`.
+Positionen mit **Nachzubestellen** oder **Wartet auf Lieferung** erscheinen nur unter **Nachbestellungen**.
 
 Sortierung: **älteste zuerst** (FIFO-Arbeitsliste).
 
@@ -193,10 +195,10 @@ Manuelle Nachbestellungen können über ein Formular auf derselben Seite hinzuge
 
 Übersicht: Wo erscheint ein Zustand?
 
-| Zustand / Bedingung | Dashboard-Statistik | Letzte offene Bestellungen | Offene Positionen | Bestellliste-Filter | Bestelldetail | Nachbestellungen |
+| Zustand / Bedingung | Dashboard-Statistik | Aktion erforderlich | Offene Positionen | Bestellliste-Filter | Bestelldetail | Nachbestellungen |
 | --- | --- | --- | --- | --- | --- | --- |
-| **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 |
+| **Offen** | Offen | Ja*, wenn offene Positionen ohne NB | Ja*, nur Positionen ohne NB | Offen, Alle | Ja, bearbeitbar | Ja, wenn NB markiert |
+| **Teilweise bearbeitet** | Teilweise | Ja*, wenn offene Positionen ohne NB | Ja*, nur Positionen ohne NB | 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) | — |
 | Position **Nachzubestellen** | In NB-Statistik | Position ausgeblendet | Position ausgeblendet | Bestellung in Liste | Toggle auf Detail | Ja |
@@ -204,7 +206,7 @@ Manuelle Nachbestellungen können über ein Formular auf derselben Seite hinzuge
 | Nur NB-Positionen, Bestellung operativ **Offen** | Offen | — (keine qualif. Position) | — | Alle, Offen | Ja | Ja |
 | Manuelle Nachbestellung | In NB-Statistik | — | — | — | — | Ja |
 
-*NB = Nachbestell-Status. „Ja*" = nur wenn mindestens eine unverarbeitete Position **ohne** Nachbestell-Status existiert.
+*NB = Nachbestell-Status. „Ja*" = nur für unverarbeitete Positionen **ohne** Nachbestell-Status (Bestellungen: mindestens eine solche Position; Positionen: jede Zeile einzeln).
 
 ---
 
@@ -212,7 +214,7 @@ Manuelle Nachbestellungen können über ein Formular auf derselben Seite hinzuge
 
 ### 1. Neue Bestellung abarbeiten
 
-1. **Dashboard** → „Offene Positionen" oder „Letzte offene Bestellungen" prüfen.
+1. **Dashboard** → „Offene Positionen" oder „Bestellungen mit Aktion erforderlich" prüfen.
 2. **Details** öffnen.
 3. Pro Position **„Als bearbeitet markieren"**, sobald ausgegeben.
 4. Wenn alle Positionen bearbeitet: Status wechselt automatisch zu **Bearbeitet**.

+ 214 - 0
docs/SHOP_LOGIC.md

@@ -0,0 +1,214 @@
+# Bestellportal Dokumentation
+
+Dieser Leitfaden erklärt, **wie Sie Bestellungen im Admin-Bereich bearbeiten**.
+
+Für technische Details siehe [ADMIN_BUSINESS_LOGIC.md](ADMIN_BUSINESS_LOGIC.md).
+
+---
+
+## Die wichtigsten Seiten
+
+
+| Seite                | Wofür Sie sie nutzen                                                |
+| -------------------- | ------------------------------------------------------------------- |
+| **Dashboard**        | Schneller Überblick: Was ist offen? Was muss heute erledigt werden? |
+| **Bestellliste**     | Alle Bestellungen suchen und filtern                                |
+| **Bestelldetail**    | Einzelne Bestellung öffnen, Positionen abhaken, stornieren          |
+| **Nachbestellungen** | Artikel, die extern nachbestellt werden müssen                      |
+| **Einstellungen**    | E-Mail-Adresse für interne Bestellbenachrichtigungen, PDF-Anhang    |
+
+
+Produkte, Kategorien, Organisationen, FAQ und Admin-Konten haben **nichts direkt mit dem Bestellstatus** zu tun — dort pflegen Sie Inhalte und Zugänge.
+
+---
+
+## Zwei Dinge pro Bestellung im Kopf behalten
+
+Jede Bestellung hat **zwei getrennte Informationen**:
+
+1. **Wie weit ist die Bestellung insgesamt?**
+  Offen → Teilweise erledigt → Erledigt (oder: Storniert)
+2. **Braucht ein einzelner Artikel eine Nachbestellung beim Lieferanten?**
+  Das gilt **pro Position**, nicht für die ganze Bestellung.
+
+Beides läuft unabhängig voneinander. Eine Position kann z. B. schon als „ausgegeben“ markiert sein, während gleichzeitig noch auf Lieferantenware gewartet wird.
+
+---
+
+## Status einer Bestellung — was die Bezeichnungen bedeuten
+
+In Listen und auf dem Dashboard sehen Sie eines dieser Labels:
+
+
+| Label                    | Bedeutung                                                        |
+| ------------------------ | ---------------------------------------------------------------- |
+| **Offen**                | Noch nichts ausgegeben — alles steht aus                         |
+| **Teilweise bearbeitet** | Einige Artikel sind schon raus, andere noch nicht                |
+| **Bearbeitet**           | Alles ausgegeben — Bestellung abgeschlossen                      |
+| **Storniert**            | Bestellung ist ungültig; nur noch „Stornierung aufheben“ möglich |
+
+
+**Storniert** hat immer Vorrang vor allen anderen Anzeigen.
+
+---
+
+## Status einer einzelnen Position
+
+### Ausgegeben oder noch offen?
+
+- Auf der **Bestelldetail**-Seite markieren Sie jede Position als **bearbeitet** (ausgegeben) oder **offen**.
+- Der Gesamtstatus der Bestellung passt sich automatisch an:
+  - Keine Position erledigt → **Offen**
+  - Alle erledigt → **Bearbeitet**
+  - Dazwischen → **Teilweise bearbeitet**
+
+### Nachbestellung nötig?
+
+
+| Anzeige                  | Bedeutung                                       | Was Sie tun                                      |
+| ------------------------ | ----------------------------------------------- | ------------------------------------------------ |
+| *(kein Hinweis)*         | Artikel ist normal offen — aus Lager abgeben    | Auf Bestelldetail ausgeben und abhaken           |
+| **Nachzubestellen**      | Artikel fehlt — muss extern bestellt werden     | Auf Seite **Nachbestellungen** weiterbearbeiten  |
+| **Wartet auf Lieferung** | Beim Lieferanten bestellt — Lieferung steht aus | Auf **Nachbestellungen** warten, bis Ware da ist |
+
+
+**Manuelle Nachbestellungen** sind Artikel, die Sie **ohne Kundenbestellung** selbst nachbestellen (z. B. Lagerauffüllung). Sie erscheinen nur unter **Nachbestellungen**.
+
+---
+
+## Wo finde ich was?
+
+### Dashboard
+
+**Zählkarten oben**
+
+- **Offen**, **Teilweise bearbeitet**, **Bearbeitet**, **Storniert** — Anzahl Bestellungen je Status
+- **Nachbestellung** — wie viele Positionen gerade nachbestellt werden oder auf Lieferung warten
+
+**„Bestellungen mit Aktion erforderlich“**
+
+- Zeigt bis zu 5 Bestellungen, bei denen auf der Bestelldetailseite noch etwas zu tun ist (Ausgabe, Nachbestellung markieren usw.)
+- Nur Bestellungen mit mindestens einer **noch nicht ausgegebenen** Position **ohne** Nachbestell-Status
+- Liegen nur noch Positionen mit Nachbestell-Status offen, erscheint die Bestellung hier **nicht** — weiter unter **Nachbestellungen**
+
+**„Offene Positionen“**
+
+- Ihre **Arbeitsliste**: einzelne Artikel, die noch ausgegeben werden müssen
+- Älteste zuerst — gut zum systematischen Abarbeiten
+- Auch hier: Positionen in Nachbestellung erscheinen **nicht**
+
+### Bestellliste
+
+- Suchen nach Bestellnummer
+- Filtern nach: Alle, Offen, Teilweise bearbeitet, Bearbeitet, Storniert
+
+### Bestelldetail
+
+Hier bearbeiten Sie **eine konkrete Bestellung**:
+
+
+| Aktion                                    | Wann möglich                                                                  |
+| ----------------------------------------- | ----------------------------------------------------------------------------- |
+| Position als bearbeitet / offen markieren | Bestellung nicht storniert                                                    |
+| Als Nachbestellung markieren / aufheben   | Bestellung nicht storniert; nur solange noch **nicht** „Wartet auf Lieferung“ |
+| Bestellung stornieren                     | Nicht storniert und noch nicht vollständig erledigt                           |
+| Stornierung aufheben                      | Bestellung ist storniert                                                      |
+
+
+**Hinweise:**
+
+- „Als bearbeitet markieren“ **entfernt nicht** automatisch einen Nachbestell-Hinweis.
+- „Als Nachbestellung markieren“ geht **nicht** bei schon ausgegebenen Positionen.
+- Status **„Wartet auf Lieferung“** ändern Sie **nur** auf der Seite **Nachbestellungen** (nicht auf der Detailseite).
+
+### Nachbestellungen
+
+- Alle Positionen, die nachbestellt werden müssen oder auf Lieferung warten
+- Nach **Produkt und Größe** gruppiert — praktisch zum Sammelbestellen beim Lieferanten
+- **Manuelle Nachbestellung** hinzufügen über das Formular auf derselben Seite
+
+**Massenaktionen** (älteste zuerst):
+
+
+| Aktion                     | Wirkung                                                    |
+| -------------------------- | ---------------------------------------------------------- |
+| **Als bestellt markieren** | Extern bestellt → Status wird „Wartet auf Lieferung“       |
+| **Lieferung eingetroffen** | Ware ist da → Position ist wieder normal offen zur Ausgabe |
+
+
+---
+
+## Typische Abläufe — Schritt für Schritt
+
+### Neue Bestellung ausgeben
+
+1. **Dashboard** → „Offene Positionen“ oder „Bestellungen mit Aktion erforderlich“
+2. Bestellung öffnen (**Details**)
+3. Jede Position **„Als bearbeitet markieren"**, sobald der Artikel raus ist
+4. Sind alle Positionen erledigt, steht die Bestellung automatisch auf **Bearbeitet**
+
+### Artikel nicht auf Lager
+
+1. **Bestelldetail** → Position **„Als Nachbestellung markieren"**
+2. Position verschwindet aus den Dashboard-Arbeitslisten
+3. **Nachbestellungen** → beim Lieferanten bestellen → **„Als bestellt markieren"**
+4. Ware angekommen → **„Lieferung eingetroffen"**
+5. Zurück auf **Bestelldetail** → Artikel ausgeben → **„Als bearbeitet markieren"**
+
+### Lager auffüllen (ohne Kundenbestellung)
+
+1. **Nachbestellungen** → Formular **„Manuelle Nachbestellung"**
+2. Produkt, Größe und Anzahl eintragen
+3. Weiter wie oben: bestellen → Lieferung → ggf. ausgeben
+
+### Bestellung stornieren
+
+1. **Bestelldetail** → **„Bestellung stornieren"** (nicht möglich, wenn schon vollständig erledigt)
+2. Optional einen Grund angeben
+3. Stornierte Bestellungen finden Sie in der Bestellliste unter Filter **Storniert**
+4. Fehler? **„Stornierung aufheben"** auf der Detailseite
+
+---
+
+## Einstellungen (Grundkonfiguration)
+
+Unter **Einstellungen** können Sie anpassen:
+
+- **Empfängeradresse für interne Bestellmails** — wohin Benachrichtigungen über neue Bestellungen gehen
+- **PDF an interne Bestell-E-Mails anhängen** — ja/nein
+
+Der Text auf der Startseite wird separat unter **FAQ** gepflegt.
+
+---
+
+## Kurz-Übersicht: Wo verschwindet eine Position?
+
+
+| Situation                           | Dashboard „Offene Positionen“ | Nachbestellungen                                 |
+| ----------------------------------- | ----------------------------- | ------------------------------------------------ |
+| Normal offen, noch nicht ausgegeben | Ja                            | Nein                                             |
+| Nachzubestellen                     | Nein                          | Ja                                               |
+| Wartet auf Lieferung                | Nein                          | Ja                                               |
+| Schon ausgegeben                    | Nein                          | Nein (außer Nachbestell-Hinweis bleibt bestehen) |
+| Bestellung storniert                | Nein                          | Nein                                             |
+
+
+---
+
+## Wenn etwas unerwartet wirkt
+
+- **Position fehlt auf dem Dashboard, Bestellung ist aber offen**  
+Wahrscheinlich ist sie als Nachbestellung markiert oder wartet auf Lieferung → **Nachbestellungen** prüfen.
+- **„Wartet auf Lieferung“ lässt sich auf der Detailseite nicht ändern**  
+Das ist beabsichtigt. Weiterbearbeitung nur über **Nachbestellungen**.
+- **Bestellung kann nicht storniert werden**  
+Entweder ist sie schon vollständig **Bearbeitet**, oder sie ist bereits storniert.
+
+---
+
+## Weitere Dokumentation
+
+- [ADMIN_BUSINESS_LOGIC.md](ADMIN_BUSINESS_LOGIC.md) — ausführliche Beschreibung für Operatoren und technische Details
+- [ADMIN_SYSTEM.md](ADMIN_SYSTEM.md) — Anmeldung und Admin-Konten
+- [ORDER_PROCESS.md](ORDER_PROCESS.md) — Ablauf aus Kundensicht (Bestellung aufgeben)
+

+ 200 - 0
docs/assets/docs.css

@@ -0,0 +1,200 @@
+:root {
+    color-scheme: light;
+    --docs-bg: #f5f6f8;
+    --docs-surface: #fff;
+    --docs-text: #1a1a1a;
+    --docs-muted: #5c6370;
+    --docs-accent: #003366;
+    --docs-border: #d8dde6;
+    --docs-code-bg: #eef1f5;
+    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+    line-height: 1.6;
+}
+
+*,
+*::before,
+*::after {
+    box-sizing: border-box;
+}
+
+body {
+    margin: 0;
+    background: var(--docs-bg);
+    color: var(--docs-text);
+}
+
+.docs-header {
+    background: var(--docs-accent);
+    color: #fff;
+}
+
+.docs-header-inner {
+    max-width: 72rem;
+    margin: 0 auto;
+    padding: 0.75rem 1.25rem;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 1rem;
+}
+
+.docs-brand,
+.docs-back {
+    color: inherit;
+    text-decoration: none;
+}
+
+.docs-back {
+    font-size: 0.9rem;
+    opacity: 0.9;
+}
+
+.docs-back:hover,
+.docs-brand:hover {
+    text-decoration: underline;
+}
+
+.docs-layout {
+    max-width: 72rem;
+    margin: 0 auto;
+    padding: 1.25rem;
+    display: grid;
+    grid-template-columns: minmax(12rem, 16rem) 1fr;
+    gap: 1.5rem;
+    align-items: start;
+}
+
+@media (max-width: 768px) {
+    .docs-layout {
+        grid-template-columns: 1fr;
+    }
+}
+
+.docs-nav {
+    background: var(--docs-surface);
+    border: 1px solid var(--docs-border);
+    border-radius: 0.5rem;
+    padding: 1rem;
+    position: sticky;
+    top: 1rem;
+}
+
+.docs-nav-title {
+    margin: 0 0 0.5rem;
+    font-size: 0.75rem;
+    text-transform: uppercase;
+    letter-spacing: 0.04em;
+    color: var(--docs-muted);
+}
+
+.docs-nav ul {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+.docs-nav li + li {
+    margin-top: 0.25rem;
+}
+
+.docs-nav a {
+    display: block;
+    padding: 0.35rem 0.5rem;
+    border-radius: 0.25rem;
+    color: var(--docs-accent);
+    text-decoration: none;
+    font-size: 0.9rem;
+}
+
+.docs-nav a:hover {
+    background: var(--docs-code-bg);
+}
+
+.docs-nav a[aria-current="page"] {
+    background: var(--docs-accent);
+    color: #fff;
+}
+
+.docs-main {
+    background: var(--docs-surface);
+    border: 1px solid var(--docs-border);
+    border-radius: 0.5rem;
+    padding: 1.5rem 2rem;
+    min-width: 0;
+}
+
+.docs-index-list {
+    padding-left: 1.25rem;
+}
+
+.docs-index-list a {
+    color: var(--docs-accent);
+}
+
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3 {
+    line-height: 1.25;
+    margin-top: 1.5em;
+    margin-bottom: 0.5em;
+}
+
+.markdown-body h1:first-child {
+    margin-top: 0;
+}
+
+.markdown-body p,
+.markdown-body ul,
+.markdown-body ol,
+.markdown-body pre,
+.markdown-body table {
+    margin: 0.75em 0;
+}
+
+.markdown-body a {
+    color: var(--docs-accent);
+}
+
+.markdown-body code {
+    font-family: ui-monospace, "Cascadia Code", "Source Code Pro", monospace;
+    font-size: 0.9em;
+    background: var(--docs-code-bg);
+    padding: 0.1em 0.35em;
+    border-radius: 0.2em;
+}
+
+.markdown-body pre {
+    background: var(--docs-code-bg);
+    padding: 1rem;
+    overflow-x: auto;
+    border-radius: 0.35rem;
+}
+
+.markdown-body pre code {
+    padding: 0;
+    background: none;
+}
+
+.markdown-body table {
+    border-collapse: collapse;
+    width: 100%;
+    font-size: 0.95rem;
+}
+
+.markdown-body th,
+.markdown-body td {
+    border: 1px solid var(--docs-border);
+    padding: 0.4rem 0.6rem;
+    text-align: left;
+}
+
+.markdown-body th {
+    background: var(--docs-code-bg);
+}
+
+.markdown-body blockquote {
+    margin: 1em 0;
+    padding-left: 1em;
+    border-left: 4px solid var(--docs-border);
+    color: var(--docs-muted);
+}

File diff ditekan karena terlalu besar
+ 11 - 0
docs/assets/marked.min.js


+ 140 - 0
docs/index.php

@@ -0,0 +1,140 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * Simple documentation viewer (Markdown → HTML via marked).
+ */
+
+$docsDir = __DIR__;
+$docMap = [];
+
+foreach (glob($docsDir . '/*.md') ?: [] as $path) {
+    $base = basename($path, '.md');
+    if (preg_match('/^[A-Z0-9_]+$/', $base) !== 1) {
+        continue;
+    }
+    $docMap[$base] = $path;
+}
+
+ksort($docMap);
+
+$requested = isset($_GET['doc']) ? (string) $_GET['doc'] : '';
+$activeDoc = null;
+$markdown = null;
+$pageTitle = 'Dokumentation';
+
+if ($requested !== '') {
+    if (!isset($docMap[$requested])) {
+        http_response_code(404);
+        $pageTitle = 'Nicht gefunden';
+    } else {
+        $activeDoc = $requested;
+        $markdown = file_get_contents($docMap[$requested]);
+        if ($markdown === false) {
+            http_response_code(500);
+            $pageTitle = 'Fehler';
+            $markdown = null;
+        } else {
+            $pageTitle = docTitle($activeDoc);
+        }
+    }
+}
+
+function docTitle(string $key): string
+{
+    return ucwords(strtolower(str_replace('_', ' ', $key)));
+}
+
+function escape(string $value): string
+{
+    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+}
+
+$baseHref = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/docs')), '/') . '/';
+if ($baseHref === '/') {
+    $baseHref = '/docs/';
+}
+?>
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title><?php echo escape($pageTitle); ?> – PSA Dokumentation</title>
+    <link rel="stylesheet" href="<?php echo escape($baseHref); ?>assets/docs.css">
+</head>
+<body>
+    <header class="docs-header">
+        <div class="docs-header-inner">
+            <a class="docs-brand" href="<?php echo escape($baseHref); ?>index.php">PSA Dokumentation</a>
+            <a class="docs-back" href="../index.php">Zur Anwendung</a>
+        </div>
+    </header>
+    <div class="docs-layout">
+        <nav class="docs-nav" aria-label="Dokumentation">
+            <p class="docs-nav-title">Inhalt</p>
+            <ul>
+                <?php foreach ($docMap as $key => $_path): ?>
+                    <li>
+                        <a href="<?php echo escape($baseHref); ?>index.php?doc=<?php echo escape($key); ?>"
+                           <?php echo $key === $activeDoc ? 'aria-current="page"' : ''; ?>>
+                            <?php echo escape(docTitle($key)); ?>
+                        </a>
+                    </li>
+                <?php endforeach; ?>
+            </ul>
+        </nav>
+        <main class="docs-main">
+            <?php if ($requested === ''): ?>
+                <h1>Dokumentation</h1>
+                <p>Technische und fachliche Hinweise zum PSA-Bestellsystem.</p>
+                <ul class="docs-index-list">
+                    <?php foreach ($docMap as $key => $_path): ?>
+                        <li>
+                            <a href="<?php echo escape($baseHref); ?>index.php?doc=<?php echo escape($key); ?>">
+                                <?php echo escape(docTitle($key)); ?>
+                            </a>
+                        </li>
+                    <?php endforeach; ?>
+                </ul>
+            <?php elseif ($markdown === null): ?>
+                <h1><?php echo escape($pageTitle); ?></h1>
+                <p>Das angeforderte Dokument ist nicht verfügbar.</p>
+                <p><a href="<?php echo escape($baseHref); ?>index.php">Zur Übersicht</a></p>
+            <?php else: ?>
+                <article id="doc-content" class="markdown-body"></article>
+                <script type="application/json" id="doc-source"><?php
+                    echo json_encode(
+                        $markdown,
+                        JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE
+                    );
+                ?></script>
+            <?php endif; ?>
+        </main>
+    </div>
+    <?php if ($markdown !== null): ?>
+        <script src="<?php echo escape($baseHref); ?>assets/marked.min.js"></script>
+        <script>
+            (function () {
+                var source = document.getElementById('doc-source');
+                var target = document.getElementById('doc-content');
+                if (!source || !target || typeof marked === 'undefined') {
+                    return;
+                }
+                var text = JSON.parse(source.textContent || '""');
+                target.innerHTML = marked.parse(text, { gfm: true, breaks: false });
+                target.querySelectorAll('a[href]').forEach(function (link) {
+                    var href = link.getAttribute('href');
+                    if (!href || /^[a-z]+:/i.test(href) || href.charAt(0) === '#') {
+                        return;
+                    }
+                    var name = href.split('/').pop().replace(/\.md$/i, '');
+                    if (/^[A-Z0-9_]+$/i.test(name)) {
+                        link.setAttribute('href', 'index.php?doc=' + encodeURIComponent(name.toUpperCase()));
+                    }
+                });
+            })();
+        </script>
+    <?php endif; ?>
+</body>
+</html>

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini