2 Commits 9853804243 ... dac21eb286

Tác giả SHA1 Thông báo Ngày
  Medowar dac21eb286 making the update script also write the version to file 1 tuần trước cách đây
  Medowar 4b0677dace reworking nametag to integrate in the existing orderflow 1 tuần trước cách đây

+ 1 - 1
admin/index.php

@@ -35,7 +35,7 @@ foreach ($backorderGroups as $group) {
     $backorderCount += (int) $group['to_be_backordered'] + (int) $group['ordered'];
 }
 
-$openNametagCount = count(getOpenNametagOrders());
+$openNametagCount = count(getOutstandingNametagOrderItems());
 
 $stats = [
     'open' => 0,

+ 71 - 149
admin/nametags.php

@@ -11,37 +11,15 @@ $pageTitle = "Namensschilder";
 $message = "";
 $messageType = "";
 
-if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['add_nametag'])) {
-    if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
-        $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
-        $messageType = "error";
-    } else {
-        $result = addNametagOrder(
-            $_POST['person_name'] ?? "",
-            $_POST['organization_id'] ?? "",
-            $_POST['quantity'] ?? 1,
-            $_POST['note'] ?? "",
-        );
-        $message = $result["success"]
-            ? "Namensschild-Bestellung wurde hinzugefügt."
-            : $result["message"];
-        $messageType = $result["success"] ? "success" : "error";
-
-        if ($result["success"]) {
-            logAccess("Admin added nametag order", [
-                "admin" => $_SESSION['admin_username'] ?? "unknown",
-                "person_name" => $_POST['person_name'] ?? "",
-            ]);
-        }
-    }
-}
-
 if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['mark_ordered'])) {
     if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
         $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
         $messageType = "error";
     } else {
-        $result = markNametagOrdered($_POST['id'] ?? "");
+        $result = markNametagOrderItemOrdered(
+            $_POST['order_id'] ?? "",
+            (int) ($_POST['item_index'] ?? -1),
+        );
         $message = $result["success"]
             ? "Namensschild wurde als bestellt markiert."
             : $result["message"];
@@ -50,7 +28,8 @@ if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['mark_ordered'])) {
         if ($result["success"]) {
             logAccess("Admin marked nametag ordered", [
                 "admin" => $_SESSION['admin_username'] ?? "unknown",
-                "id" => $_POST['id'] ?? "",
+                "order_id" => $_POST['order_id'] ?? "",
+                "item_index" => $_POST['item_index'] ?? -1,
             ]);
         }
     }
@@ -61,7 +40,10 @@ if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['mark_delivered'])) {
         $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
         $messageType = "error";
     } else {
-        $result = markNametagDelivered($_POST['id'] ?? "");
+        $result = markNametagOrderItemDelivered(
+            $_POST['order_id'] ?? "",
+            (int) ($_POST['item_index'] ?? -1),
+        );
         $message = $result["success"]
             ? "Namensschild wurde als geliefert markiert."
             : $result["message"];
@@ -70,35 +52,16 @@ if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['mark_delivered'])) {
         if ($result["success"]) {
             logAccess("Admin marked nametag delivered", [
                 "admin" => $_SESSION['admin_username'] ?? "unknown",
-                "id" => $_POST['id'] ?? "",
+                "order_id" => $_POST['order_id'] ?? "",
+                "item_index" => $_POST['item_index'] ?? -1,
             ]);
         }
     }
 }
 
-if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['delete_nametag'])) {
-    if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
-        $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
-        $messageType = "error";
-    } else {
-        $result = deleteNametagOrder($_POST['id'] ?? "");
-        $message = $result["success"]
-            ? "Position wurde gelöscht."
-            : $result["message"];
-        $messageType = $result["success"] ? "success" : "error";
-
-        if ($result["success"]) {
-            logAccess("Admin deleted nametag order", [
-                "admin" => $_SESSION['admin_username'] ?? "unknown",
-                "id" => $_POST['id'] ?? "",
-            ]);
-        }
-    }
-}
-
-$openNametags = getOpenNametagOrders();
-$orderedNametags = getOrderedNametagOrders();
-$organizations = getOrganizations(true);
+$nametagProductName = getNametagProductName();
+$outstandingNametags = getOutstandingNametagOrderItems();
+$orderedNametags = getOrderedNametagOrderItems();
 
 $bodyClass = "admin-page";
 include __DIR__ . "/../includes/header.php";
@@ -119,67 +82,13 @@ include __DIR__ . "/../includes/header.php";
 <?php endif; ?>
 
 <p class="text-muted">
-    Namensschilder werden personalisiert und separat vom Gerätewart bestellt.
-    Hier werden alle noch zu bestellenden Namensschilder mit dem Namen der Person aufgeführt.
+    Zeigt alle Bestellpositionen für den Artikel "<?php echo escape(
+        $nametagProductName,
+    ); ?>" mit dem Namen der Person.
 </p>
 
-<div class="admin-panel backorder-add-panel">
-    <h3>Namensschild-Bestellung anlegen</h3>
-    <form method="POST" class="admin-filter-form backorder-add-form">
-        <?php echo csrfField(); ?>
-        <div class="admin-filter-field">
-            <label for="nametag_person_name">Name der Person</label>
-            <input
-                type="text"
-                id="nametag_person_name"
-                name="person_name"
-                required
-                maxlength="120"
-            >
-        </div>
-        <div class="admin-filter-field">
-            <label for="nametag_organization">Organisation</label>
-            <select id="nametag_organization" name="organization_id">
-                <option value="">Keine</option>
-                <?php foreach ($organizations as $organization): ?>
-                    <option value="<?php echo escape(
-                        $organization["id"],
-                    ); ?>">
-                        <?php echo escape($organization["label"]); ?>
-                    </option>
-                <?php endforeach; ?>
-            </select>
-        </div>
-        <div class="admin-filter-field">
-            <label for="nametag_quantity">Anzahl</label>
-            <input
-                type="number"
-                id="nametag_quantity"
-                name="quantity"
-                min="1"
-                max="20"
-                value="1"
-                class="backorder-qty-input"
-            >
-        </div>
-        <div class="admin-filter-field admin-filter-field-wide">
-            <label for="nametag_note">Hinweis</label>
-            <input
-                type="text"
-                id="nametag_note"
-                name="note"
-                maxlength="200"
-                placeholder="Optional, z. B. Funktion oder Schreibweise"
-            >
-        </div>
-        <button type="submit" name="add_nametag" class="btn">
-            Zur Bestellliste hinzufügen
-        </button>
-    </form>
-</div>
-
 <h3 class="section-title mt-4">Ausstehende Bestellungen</h3>
-<?php if (empty($openNametags)): ?>
+<?php if (empty($outstandingNametags)): ?>
     <div class="alert alert-info">
         <p>Keine ausstehenden Namensschild-Bestellungen vorhanden.</p>
     </div>
@@ -190,51 +99,59 @@ include __DIR__ . "/../includes/header.php";
                 <tr>
                     <th>Name</th>
                     <th>Organisation</th>
-                    <th>Anzahl</th>
-                    <th>Hinweis</th>
-                    <th>Angelegt</th>
+                    <th>Variante</th>
+                    <th>Bestellnummer</th>
+                    <th>Status</th>
+                    <th>Erstellt</th>
                     <th>Aktionen</th>
                 </tr>
             </thead>
             <tbody>
-                <?php foreach ($openNametags as $entry): ?>
+                <?php foreach ($outstandingNametags as $ref): ?>
                     <tr>
                         <td data-label="Name"><strong><?php echo escape(
-                            $entry["person_name"],
+                            $ref["person_name"],
                         ); ?></strong></td>
-                        <td data-label="Organisation"><?php echo $entry[
+                        <td data-label="Organisation"><?php echo $ref[
                             "organization_label"
                         ] !== ""
-                            ? escape($entry["organization_label"])
+                            ? escape($ref["organization_label"])
                             : "-"; ?></td>
-                        <td data-label="Anzahl"><?php echo (int) $entry[
-                            "quantity"
-                        ]; ?></td>
-                        <td data-label="Hinweis"><?php echo $entry["note"] !== ""
-                            ? escape($entry["note"])
+                        <td data-label="Variante"><?php echo $ref["size"] !== ""
+                            ? escape($ref["size"])
                             : "-"; ?></td>
-                        <td data-label="Angelegt"><?php echo escape(
-                            formatDate($entry["created_at"]),
+                        <td data-label="Bestellnummer">
+                            <a href="order.php?id=<?php echo urlencode(
+                                $ref["order_id"],
+                            ); ?>"><?php echo escape($ref["order_id"]); ?></a>
+                        </td>
+                        <td data-label="Status">
+                            <?php if ($ref["backorder_status"] !== ""): ?>
+                                <span class="status <?php echo escape(
+                                    getBackorderStatusClass($ref["backorder_status"]),
+                                ); ?>"><?php echo escape(
+    getBackorderStatusLabel($ref["backorder_status"]),
+); ?></span>
+                            <?php else: ?>
+                                -
+                            <?php endif; ?>
+                        </td>
+                        <td data-label="Erstellt"><?php echo escape(
+                            formatDate($ref["created_at"]),
                         ); ?></td>
                         <td data-label="Aktionen">
                             <form method="POST" class="backorder-action-form">
                                 <?php echo csrfField(); ?>
-                                <input type="hidden" name="id" value="<?php echo escape(
-                                    $entry["id"],
+                                <input type="hidden" name="order_id" value="<?php echo escape(
+                                    $ref["order_id"],
                                 ); ?>">
+                                <input type="hidden" name="item_index" value="<?php echo (int) $ref[
+                                    "item_index"
+                                ]; ?>">
                                 <button type="submit" name="mark_ordered" class="btn btn-small">
                                     Als bestellt markieren
                                 </button>
                             </form>
-                            <form method="POST" class="backorder-action-form inline-form" onsubmit="return confirm('Position wirklich löschen?');">
-                                <?php echo csrfField(); ?>
-                                <input type="hidden" name="id" value="<?php echo escape(
-                                    $entry["id"],
-                                ); ?>">
-                                <button type="submit" name="delete_nametag" class="btn btn-small btn-secondary">
-                                    Löschen
-                                </button>
-                            </form>
                         </td>
                     </tr>
                 <?php endforeach; ?>
@@ -255,42 +172,47 @@ include __DIR__ . "/../includes/header.php";
                 <tr>
                     <th>Name</th>
                     <th>Organisation</th>
-                    <th>Anzahl</th>
-                    <th>Hinweis</th>
+                    <th>Variante</th>
+                    <th>Bestellnummer</th>
                     <th>Status</th>
                     <th>Bestellt am</th>
                     <th>Aktionen</th>
                 </tr>
             </thead>
             <tbody>
-                <?php foreach ($orderedNametags as $entry): ?>
+                <?php foreach ($orderedNametags as $ref): ?>
                     <tr>
                         <td data-label="Name"><strong><?php echo escape(
-                            $entry["person_name"],
+                            $ref["person_name"],
                         ); ?></strong></td>
-                        <td data-label="Organisation"><?php echo $entry[
+                        <td data-label="Organisation"><?php echo $ref[
                             "organization_label"
                         ] !== ""
-                            ? escape($entry["organization_label"])
+                            ? escape($ref["organization_label"])
                             : "-"; ?></td>
-                        <td data-label="Anzahl"><?php echo (int) $entry[
-                            "quantity"
-                        ]; ?></td>
-                        <td data-label="Hinweis"><?php echo $entry["note"] !== ""
-                            ? escape($entry["note"])
+                        <td data-label="Variante"><?php echo $ref["size"] !== ""
+                            ? escape($ref["size"])
                             : "-"; ?></td>
+                        <td data-label="Bestellnummer">
+                            <a href="order.php?id=<?php echo urlencode(
+                                $ref["order_id"],
+                            ); ?>"><?php echo escape($ref["order_id"]); ?></a>
+                        </td>
                         <td data-label="Status">
                             <span class="status status-backorder-waiting">Wartet auf Lieferung</span>
                         </td>
                         <td data-label="Bestellt am"><?php echo escape(
-                            formatDate($entry["ordered_at"]),
+                            formatDate($ref["ordered_at"]),
                         ); ?></td>
                         <td data-label="Aktionen">
                             <form method="POST" class="backorder-action-form">
                                 <?php echo csrfField(); ?>
-                                <input type="hidden" name="id" value="<?php echo escape(
-                                    $entry["id"],
+                                <input type="hidden" name="order_id" value="<?php echo escape(
+                                    $ref["order_id"],
                                 ); ?>">
+                                <input type="hidden" name="item_index" value="<?php echo (int) $ref[
+                                    "item_index"
+                                ]; ?>">
                                 <button type="submit" name="mark_delivered" class="btn btn-small btn-secondary">
                                     Lieferung eingetroffen
                                 </button>

+ 9 - 0
admin/settings.php

@@ -184,6 +184,7 @@ if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_settings'])) {
             "attach_order_pdf_to_admin_email" => isset(
                 $_POST['attach_order_pdf_to_admin_email'],
             ),
+            "nametag_product_name" => $_POST['nametag_product_name'] ?? "",
         ]);
 
         if (saveSystemSettings($settings)) {
@@ -275,6 +276,14 @@ include __DIR__ . "/../includes/header.php";
             </label>
         </div>
 
+        <div class="form-group">
+            <label for="nametag_product_name">Artikelname für Namensschilder *</label>
+            <input type="text" id="nametag_product_name" name="nametag_product_name" required value="<?php echo escape(
+                $settings["nametag_product_name"],
+            ); ?>">
+            <small>Muss exakt dem Produktnamen im Katalog entsprechen. Wird von der Namensschilder-Übersicht verwendet.</small>
+        </div>
+
         <button type="submit" name="save_settings" class="btn">Speichern</button>
     </form>
 </div>

+ 0 - 1
config.feuerwehr.php

@@ -48,7 +48,6 @@ define('UPLOADS_DIR', DATA_DIR . 'uploads/');
 define('PRODUCTS_FILE', DATA_DIR . 'products.json');
 define('ORDERS_FILE', DATA_DIR . 'orders.json');
 define('MANUAL_BACKORDERS_FILE', DATA_DIR . 'manual_backorders.json');
-define('NAMETAGS_FILE', DATA_DIR . 'nametags.json');
 define('ORGANIZATIONS_FILE', DATA_DIR . 'organizations.json');
 define('SETTINGS_FILE', DATA_DIR . 'settings.json');
 define('ADMINS_FILE', DATA_DIR . 'admins.json');

+ 0 - 1
config.sample.php

@@ -48,7 +48,6 @@ define('UPLOADS_DIR', DATA_DIR . 'uploads/');
 define('PRODUCTS_FILE', DATA_DIR . 'products.json');
 define('ORDERS_FILE', DATA_DIR . 'orders.json');
 define('MANUAL_BACKORDERS_FILE', DATA_DIR . 'manual_backorders.json');
-define('NAMETAGS_FILE', DATA_DIR . 'nametags.json');
 define('ORGANIZATIONS_FILE', DATA_DIR . 'organizations.json');
 define('SETTINGS_FILE', DATA_DIR . 'settings.json');
 define('ADMINS_FILE', DATA_DIR . 'admins.json');

+ 0 - 3
data/nametags.json

@@ -1,3 +0,0 @@
-{
-    "entries": []
-}

+ 139 - 199
includes/functions.php

@@ -1301,6 +1301,7 @@ function getDefaultSystemSettings()
             ? (bool) ATTACH_ORDER_PDF_TO_ADMIN_EMAIL
             : true,
         "startpage_intro_text" => $startpageIntroText,
+        "nametag_product_name" => "Namensschild",
     ];
 }
 
@@ -1323,12 +1324,20 @@ function normalizeSystemSettings($settings)
         (string) ($settings["startpage_intro_text"] ?? $defaults["startpage_intro_text"]),
     );
 
+    $nametagProductName = trim(
+        (string) ($settings["nametag_product_name"] ?? $defaults["nametag_product_name"]),
+    );
+    if ($nametagProductName === "") {
+        $nametagProductName = $defaults["nametag_product_name"];
+    }
+
     return [
         "order_recipient_email" => $recipientEmail,
         "attach_order_pdf_to_admin_email" => !empty(
             $settings["attach_order_pdf_to_admin_email"]
         ),
         "startpage_intro_text" => $startpageIntroText,
+        "nametag_product_name" => $nametagProductName,
     ];
 }
 
@@ -1357,6 +1366,12 @@ function shouldAttachOrderPdfToAdminEmail()
     return !empty($settings["attach_order_pdf_to_admin_email"]);
 }
 
+function getNametagProductName()
+{
+    $settings = getSystemSettings();
+    return $settings["nametag_product_name"];
+}
+
 function getStartpageIntroLines()
 {
     $settings = getSystemSettings();
@@ -2389,249 +2404,174 @@ function markBackorderItemsDelivered($productId, $size, $quantity)
     return applyBackorderBulkUpdate($productId, $size, "ordered", "", $quantity);
 }
 
-function nametagStatusOpen(): string
-{
-    return "open";
-}
-
-function nametagStatusOrdered(): string
-{
-    return "ordered";
-}
-
-function getNametagOrders()
+function collectNametagOrderItemRefs()
 {
-    $data = readJsonFile(NAMETAGS_FILE);
-    $entries =
-        isset($data["entries"]) && is_array($data["entries"])
-            ? $data["entries"]
-            : [];
-    $normalized = [];
+    $nametagProductName = getNametagProductName();
+    $refs = [];
 
-    foreach ($entries as $entry) {
-        if (!is_array($entry)) {
+    foreach (getOrders() as $order) {
+        if (($order["status"] ?? "") === "cancelled") {
             continue;
         }
 
-        $id = trim((string) ($entry["id"] ?? ""));
-        $personName = trim((string) ($entry["person_name"] ?? ""));
-        $status = trim((string) ($entry["status"] ?? ""));
-        if (
-            $id === "" ||
-            $personName === "" ||
-            !in_array($status, [nametagStatusOpen(), nametagStatusOrdered()], true)
-        ) {
-            continue;
-        }
+        foreach ($order["items"] as $itemIndex => $item) {
+            if (!empty($item["is_processed"])) {
+                continue;
+            }
+            if (
+                trim((string) ($item["product_name"] ?? "")) !==
+                $nametagProductName
+            ) {
+                continue;
+            }
 
-        $normalized[] = [
-            "id" => $id,
-            "person_name" => $personName,
-            "organization_id" => trim((string) ($entry["organization_id"] ?? "")),
-            "organization_label" => trim(
-                (string) ($entry["organization_label"] ?? ""),
-            ),
-            "quantity" => max(1, (int) ($entry["quantity"] ?? 1)),
-            "note" => trim((string) ($entry["note"] ?? "")),
-            "status" => $status,
-            "created_at" => trim((string) ($entry["created_at"] ?? "")),
-            "ordered_at" => trim((string) ($entry["ordered_at"] ?? "")),
-        ];
+            $refs[] = [
+                "order_id" => $order["id"],
+                "item_index" => (int) $itemIndex,
+                "person_name" => $order["customer_name"],
+                "organization_label" => $order["organization_label"],
+                "size" => $item["size"],
+                "availability_label" => $item["availability_label"],
+                "backorder_status" => trim(
+                    (string) ($item["backorder_status"] ?? ""),
+                ),
+                "created_at" => $order["created_at"],
+                "ordered_at" => trim((string) ($item["ordered_at"] ?? "")),
+            ];
+        }
     }
 
-    return $normalized;
+    return $refs;
 }
 
-function saveNametagOrders($entries)
+function getOutstandingNametagOrderItems()
 {
-    return writeJsonFile(NAMETAGS_FILE, [
-        "entries" => array_values($entries),
-    ]);
+    $refs = array_values(
+        array_filter(collectNametagOrderItemRefs(), function ($ref) {
+            return $ref["backorder_status"] === "" ||
+                $ref["backorder_status"] === backorderStatusToBeBackordered();
+        }),
+    );
+    usort($refs, function ($left, $right) {
+        return strcmp($left["created_at"], $right["created_at"]);
+    });
+    return $refs;
 }
 
-function generateNametagOrderId()
+function getOrderedNametagOrderItems()
 {
-    return "nt-" . date("YmdHis") . "-" . bin2hex(random_bytes(4));
+    $refs = array_values(
+        array_filter(collectNametagOrderItemRefs(), function ($ref) {
+            return $ref["backorder_status"] === backorderStatusOrdered();
+        }),
+    );
+    usort($refs, function ($left, $right) {
+        return strcmp($left["ordered_at"], $right["ordered_at"]);
+    });
+    return $refs;
 }
 
-function addNametagOrder($personName, $organizationId, $quantity, $note)
+function markNametagOrderItemOrdered($orderId, $itemIndex)
 {
-    $personName = trim((string) $personName);
-    $organizationId = trim((string) $organizationId);
-    $quantity = (int) $quantity;
-    $note = trim((string) $note);
-
-    if ($personName === "") {
-        return ["success" => false, "message" => "Bitte einen Namen eingeben."];
-    }
-
-    if ($quantity <= 0) {
-        $quantity = 1;
-    }
-    if ($quantity > 20) {
-        return [
-            "success" => false,
-            "message" => "Maximal 20 Namensschilder pro Position.",
-        ];
-    }
+    $nametagProductName = getNametagProductName();
+    $orders = getOrders();
+    $now = date("Y-m-d H:i:s");
 
-    $organizationLabel = "";
-    if ($organizationId !== "") {
-        $organization = getOrganizationById($organizationId);
-        if ($organization === null) {
-            return ["success" => false, "message" => "Ungültige Organisation."];
+    foreach ($orders as &$order) {
+        if ($order["id"] !== $orderId) {
+            continue;
         }
-        $organizationLabel = $organization["label"];
-    }
 
-    $entries = getNametagOrders();
-    $entries[] = [
-        "id" => generateNametagOrderId(),
-        "person_name" => $personName,
-        "organization_id" => $organizationId,
-        "organization_label" => $organizationLabel,
-        "quantity" => $quantity,
-        "note" => $note,
-        "status" => nametagStatusOpen(),
-        "created_at" => date("Y-m-d H:i:s"),
-        "ordered_at" => "",
-    ];
-
-    if (!saveNametagOrders($entries)) {
-        return [
-            "success" => false,
-            "message" => "Namensschild-Bestellung konnte nicht gespeichert werden.",
-        ];
-    }
-
-    return ["success" => true];
-}
+        $guard = orderItemCanBeManaged($order);
+        if (!$guard["success"]) {
+            return $guard;
+        }
 
-function markNametagOrdered($id)
-{
-    $id = trim((string) $id);
-    $entries = getNametagOrders();
-    $now = date("Y-m-d H:i:s");
-    $found = false;
+        if (!isset($order["items"][$itemIndex])) {
+            return ["success" => false, "message" => "Position nicht gefunden."];
+        }
 
-    foreach ($entries as &$entry) {
-        if ($entry["id"] !== $id) {
-            continue;
+        $item = $order["items"][$itemIndex];
+        if (
+            trim((string) ($item["product_name"] ?? "")) !== $nametagProductName
+        ) {
+            return [
+                "success" => false,
+                "message" => "Position ist kein Namensschild.",
+            ];
         }
-        if ($entry["status"] !== nametagStatusOpen()) {
+        if (!empty($item["is_processed"])) {
+            return [
+                "success" => false,
+                "message" =>
+                    "Bearbeitete Positionen können nicht als bestellt markiert werden.",
+            ];
+        }
+        $currentBackorderStatus = trim(
+            (string) ($item["backorder_status"] ?? ""),
+        );
+        if (
+            $currentBackorderStatus !== "" &&
+            $currentBackorderStatus !== backorderStatusToBeBackordered()
+        ) {
             return [
                 "success" => false,
                 "message" => "Position wurde bereits bestellt.",
             ];
         }
-        $entry["status"] = nametagStatusOrdered();
-        $entry["ordered_at"] = $now;
-        $found = true;
-        break;
-    }
-    unset($entry);
 
-    if (!$found) {
-        return ["success" => false, "message" => "Position nicht gefunden."];
-    }
+        $order["items"][$itemIndex]["backorder_status"] = backorderStatusOrdered();
+        $order["items"][$itemIndex]["ordered_at"] = $now;
+        $order["updated_at"] = $now;
+        saveOrders($orders);
 
-    if (!saveNametagOrders($entries)) {
-        return [
-            "success" => false,
-            "message" => "Namensschild-Bestellung konnte nicht gespeichert werden.",
-        ];
+        return ["success" => true];
     }
+    unset($order);
 
-    return ["success" => true];
+    return ["success" => false, "message" => "Bestellung nicht gefunden."];
 }
 
-function markNametagDelivered($id)
+function markNametagOrderItemDelivered($orderId, $itemIndex)
 {
-    $id = trim((string) $id);
-    $entries = getNametagOrders();
-    $target = null;
+    $orders = getOrders();
+    $now = date("Y-m-d H:i:s");
 
-    foreach ($entries as $entry) {
-        if ($entry["id"] === $id) {
-            $target = $entry;
-            break;
+    foreach ($orders as &$order) {
+        if ($order["id"] !== $orderId) {
+            continue;
         }
-    }
-
-    if ($target === null) {
-        return ["success" => false, "message" => "Position nicht gefunden."];
-    }
-    if ($target["status"] !== nametagStatusOrdered()) {
-        return [
-            "success" => false,
-            "message" => "Position wartet nicht auf Lieferung.",
-        ];
-    }
-
-    $remaining = array_values(
-        array_filter($entries, function ($entry) use ($id) {
-            return $entry["id"] !== $id;
-        }),
-    );
 
-    if (!saveNametagOrders($remaining)) {
-        return [
-            "success" => false,
-            "message" => "Namensschild-Bestellung konnte nicht gespeichert werden.",
-        ];
-    }
+        $guard = orderItemCanBeManaged($order);
+        if (!$guard["success"]) {
+            return $guard;
+        }
 
-    return ["success" => true];
-}
+        if (!isset($order["items"][$itemIndex])) {
+            return ["success" => false, "message" => "Position nicht gefunden."];
+        }
 
-function deleteNametagOrder($id)
-{
-    $id = trim((string) $id);
-    $entries = getNametagOrders();
-    $filtered = array_values(
-        array_filter($entries, function ($entry) use ($id) {
-            return $entry["id"] !== $id;
-        }),
-    );
+        $item = $order["items"][$itemIndex];
+        if (
+            trim((string) ($item["backorder_status"] ?? "")) !==
+            backorderStatusOrdered()
+        ) {
+            return [
+                "success" => false,
+                "message" => "Position wartet nicht auf Lieferung.",
+            ];
+        }
 
-    if (count($filtered) === count($entries)) {
-        return ["success" => false, "message" => "Position nicht gefunden."];
-    }
+        $order["items"][$itemIndex]["backorder_status"] = backorderStatusEmpty();
+        $order["items"][$itemIndex]["ordered_at"] = "";
+        $order["updated_at"] = $now;
+        saveOrders($orders);
 
-    if (!saveNametagOrders($filtered)) {
-        return [
-            "success" => false,
-            "message" => "Namensschild-Bestellung konnte nicht gelöscht werden.",
-        ];
+        return ["success" => true];
     }
+    unset($order);
 
-    return ["success" => true];
-}
-
-function getOpenNametagOrders()
-{
-    $entries = array_values(
-        array_filter(getNametagOrders(), function ($entry) {
-            return $entry["status"] === nametagStatusOpen();
-        }),
-    );
-    usort($entries, function ($left, $right) {
-        return strcmp($left["created_at"], $right["created_at"]);
-    });
-    return $entries;
-}
-
-function getOrderedNametagOrders()
-{
-    $entries = array_values(
-        array_filter(getNametagOrders(), function ($entry) {
-            return $entry["status"] === nametagStatusOrdered();
-        }),
-    );
-    usort($entries, function ($left, $right) {
-        return strcmp($left["ordered_at"], $right["ordered_at"]);
-    });
-    return $entries;
+    return ["success" => false, "message" => "Bestellung nicht gefunden."];
 }
 
 function orderHasBackorder($order)

+ 1 - 1
includes/version.php

@@ -1,3 +1,3 @@
 <?php
 
-define("APP_VERSION", "v1.3.11");
+define("APP_VERSION", "v1.3.13");

+ 7 - 35
scripts/create-update-zip.sh

@@ -21,46 +21,20 @@ is_excluded_path() {
 }
 
 read_current_version() {
-    sed -nE 's/.*define\(["'\'']APP_VERSION["'\''],[[:space:]]*["'\'']([^"'\'']+)["'\'']\).*/\1/p' "$version_file" | head -n 1
+    sed -nE 's/.*define\("APP_VERSION",\s*"([^"]+)"\).*/\1/p' "$version_file" | head -n 1
 }
 
 write_current_version() {
     local version="$1"
 
-    NEW_APP_VERSION="$version" perl -0pi -e '
-        BEGIN { $version = $ENV{"NEW_APP_VERSION"}; $updated = 0; }
-        $updated += s/define\(["'\'']APP_VERSION["'\''],\s*["'\''][^"'\'']+["'\'']\);/define("APP_VERSION", "$version");/g;
-        END { exit($updated > 0 ? 0 : 2); }
-    ' "$version_file" || die "Version string could not be updated in $version_file"
-}
-
-sha256_file() {
-    local file="$1"
-
-    if command -v shasum >/dev/null 2>&1; then
-        shasum -a 256 "$file" | awk '{print $1}'
-    elif command -v sha256sum >/dev/null 2>&1; then
-        sha256sum "$file" | awk '{print $1}'
-    else
-        die "Required command not found: shasum or sha256sum"
-    fi
-}
-
-file_size() {
-    local file="$1"
-
-    if stat -f '%z' "$file" >/dev/null 2>&1; then
-        stat -f '%z' "$file"
-    else
-        stat -c '%s' "$file"
-    fi
+    sed -i -E "s/define\(\"APP_VERSION\", \"[^\"]+\"\);/define(\"APP_VERSION\", \"$version\");/" "$version_file"
+    [[ "$(read_current_version)" == "$version" ]] || die "Version string could not be updated in $version_file"
 }
 
 require_command git
 require_command zip
 require_command sed
-require_command awk
-require_command perl
+require_command sha256sum
 
 repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || die "Not inside a git repository."
 current_dir="$(pwd -P)"
@@ -100,9 +74,7 @@ trap cleanup EXIT
 
 while IFS= read -r path; do
     [[ -n "$path" ]] || continue
-    if is_excluded_path "$path"; then
-        continue
-    fi
+    is_excluded_path "$path" && continue
     [[ -f "$path" ]] || continue
     printf '%s\n' "$path" >> "$tmp_file_list"
 done < <(git ls-files)
@@ -116,8 +88,8 @@ rm -f "$output_file"
 zip -q -@ "$output_file" < "$tmp_file_list"
 [[ -f "$output_file" ]] || die "ZIP archive was not created."
 
-checksum="$(sha256_file "$output_file")"
-size="$(file_size "$output_file")"
+checksum="$(sha256sum "$output_file" | awk '{print $1}')"
+size="$(stat -c '%s' "$output_file")"
 
 printf 'Update ZIP created\n'
 printf 'Version: %s\n' "$version"