backorders.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. require_once __DIR__ . "/../config.php";
  3. require_once __DIR__ . "/../includes/functions.php";
  4. if (empty($_SESSION['admin_logged_in'])) {
  5. header("Location: login.php");
  6. exit();
  7. }
  8. expirePendingOrders();
  9. $pageTitle = "Nachbestellungen";
  10. $message = "";
  11. $messageType = "";
  12. if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['add_manual_backorder'])) {
  13. if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
  14. $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
  15. $messageType = "error";
  16. } else {
  17. $result = addManualBackorderItems(
  18. $_POST['product_id'] ?? 0,
  19. $_POST['size'] ?? "",
  20. $_POST['quantity'] ?? 0,
  21. );
  22. $message = $result["success"]
  23. ? ($result["added"] ?? 0) . " Position(en) zur Nachbestellung hinzugefügt."
  24. : $result["message"];
  25. $messageType = $result["success"] ? "success" : "error";
  26. if ($result["success"]) {
  27. logAccess("Admin added manual backorder items", [
  28. "admin" => $_SESSION['admin_username'] ?? "unknown",
  29. "product_id" => $_POST['product_id'] ?? 0,
  30. "size" => $_POST['size'] ?? "",
  31. "quantity" => $_POST['quantity'] ?? 0,
  32. ]);
  33. }
  34. }
  35. }
  36. if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['mark_ordered'])) {
  37. if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
  38. $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
  39. $messageType = "error";
  40. } else {
  41. $result = markBackorderItemsOrdered(
  42. $_POST['product_id'] ?? 0,
  43. $_POST['size'] ?? "",
  44. $_POST['quantity'] ?? 0,
  45. );
  46. $message = $result["success"]
  47. ? ($result["updated"] ?? 0) .
  48. " Position(en) als bestellt markiert."
  49. : $result["message"];
  50. $messageType = $result["success"] ? "success" : "error";
  51. if ($result["success"]) {
  52. logAccess("Admin marked backorder items ordered", [
  53. "admin" => $_SESSION['admin_username'] ?? "unknown",
  54. "product_id" => $_POST['product_id'] ?? 0,
  55. "size" => $_POST['size'] ?? "",
  56. "quantity" => $_POST['quantity'] ?? 0,
  57. ]);
  58. }
  59. }
  60. }
  61. if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['mark_delivered'])) {
  62. if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
  63. $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
  64. $messageType = "error";
  65. } else {
  66. $result = markBackorderItemsDelivered(
  67. $_POST['product_id'] ?? 0,
  68. $_POST['size'] ?? "",
  69. $_POST['quantity'] ?? 0,
  70. );
  71. $message = $result["success"]
  72. ? ($result["updated"] ?? 0) .
  73. " Position(en) als geliefert markiert."
  74. : $result["message"];
  75. $messageType = $result["success"] ? "success" : "error";
  76. if ($result["success"]) {
  77. logAccess("Admin marked backorder items delivered", [
  78. "admin" => $_SESSION['admin_username'] ?? "unknown",
  79. "product_id" => $_POST['product_id'] ?? 0,
  80. "size" => $_POST['size'] ?? "",
  81. "quantity" => $_POST['quantity'] ?? 0,
  82. ]);
  83. }
  84. }
  85. }
  86. $groups = getBackorderGroups();
  87. $products = getProducts();
  88. $productSizeMap = [];
  89. foreach ($products as $product) {
  90. $sizes = getProductSizes($product);
  91. if (empty($sizes)) {
  92. $sizes = ["Standard"];
  93. }
  94. $productSizeMap[(int) $product["id"]] = $sizes;
  95. }
  96. $bodyClass = "admin-page";
  97. include __DIR__ . "/../includes/header.php";
  98. ?>
  99. <div class="admin-header">
  100. <h2>Nachbestellungen</h2>
  101. <div>
  102. <a href="index.php" class="btn btn-secondary">Zurück zum Dashboard</a>
  103. <a href="orders.php" class="btn btn-secondary">Bestellungen</a>
  104. </div>
  105. </div>
  106. <?php if ($message !== ""): ?>
  107. <div class="alert alert-<?php echo escape($messageType); ?>">
  108. <?php echo escape($message); ?>
  109. </div>
  110. <?php endif; ?>
  111. <p class="text-muted">
  112. Artikel werden nach Produkt und Größe zusammengefasst. Aktionen bearbeiten die ältesten Bestellungen zuerst (FIFO).
  113. </p>
  114. <div class="admin-panel backorder-add-panel">
  115. <h3>Artikel manuell hinzufügen</h3>
  116. <p class="text-muted">
  117. Für Bedarf ohne zugehörige Kundenbestellung (z.&nbsp;B. Lagerauffüllung).
  118. </p>
  119. <?php if (empty($products)): ?>
  120. <p>Keine Artikel im Katalog vorhanden.</p>
  121. <?php else: ?>
  122. <form method="POST" class="admin-filter-form backorder-add-form">
  123. <?php echo csrfField(); ?>
  124. <div class="admin-filter-field">
  125. <label for="manual_backorder_product">Artikel</label>
  126. <select id="manual_backorder_product" name="product_id" required>
  127. <option value="">Bitte wählen</option>
  128. <?php foreach ($products as $product): ?>
  129. <option value="<?php echo (int) $product["id"]; ?>">
  130. <?php echo escape($product["name"]); ?>
  131. </option>
  132. <?php endforeach; ?>
  133. </select>
  134. </div>
  135. <div class="admin-filter-field">
  136. <label for="manual_backorder_size">Größe</label>
  137. <select id="manual_backorder_size" name="size" required disabled>
  138. <option value="">Zuerst Artikel wählen</option>
  139. </select>
  140. </div>
  141. <div class="admin-filter-field">
  142. <label for="manual_backorder_quantity">Anzahl</label>
  143. <input
  144. type="number"
  145. id="manual_backorder_quantity"
  146. name="quantity"
  147. min="1"
  148. max="100"
  149. value="1"
  150. required
  151. class="backorder-qty-input"
  152. >
  153. </div>
  154. <button type="submit" name="add_manual_backorder" class="btn">
  155. Zur Nachbestellung hinzufügen
  156. </button>
  157. </form>
  158. <?php endif; ?>
  159. </div>
  160. <?php if (empty($groups)): ?>
  161. <div class="alert alert-info">
  162. <p>Keine Nachbestellungen vorhanden.</p>
  163. </div>
  164. <?php else: ?>
  165. <div class="table-responsive">
  166. <table class="responsive-table">
  167. <thead>
  168. <tr>
  169. <th>Artikel</th>
  170. <th>Größe</th>
  171. <th>Nachzubestellen</th>
  172. <th>Wartet auf Lieferung</th>
  173. <th>Als bestellt markieren</th>
  174. <th>Lieferung eingetroffen</th>
  175. </tr>
  176. </thead>
  177. <tbody>
  178. <?php foreach ($groups as $group): ?>
  179. <tr>
  180. <td data-label="Artikel"><?php echo escape(
  181. $group["product_name"],
  182. ); ?></td>
  183. <td data-label="Größe"><?php echo $group["size"] !== ""
  184. ? escape($group["size"])
  185. : "-"; ?></td>
  186. <td data-label="Nachzubestellen">
  187. <strong><?php echo (int) $group[
  188. "to_be_backordered"
  189. ]; ?></strong>
  190. </td>
  191. <td data-label="Wartet auf Lieferung">
  192. <strong><?php echo (int) $group["ordered"]; ?></strong>
  193. </td>
  194. <td data-label="Als bestellt markieren">
  195. <?php if ($group["to_be_backordered"] > 0): ?>
  196. <form method="POST" class="backorder-action-form">
  197. <?php echo csrfField(); ?>
  198. <input type="hidden" name="product_id" value="<?php echo (int) $group[
  199. "product_id"
  200. ]; ?>">
  201. <input type="hidden" name="size" value="<?php echo escape(
  202. $group["size"],
  203. ); ?>">
  204. <label class="sr-only" for="qty_ordered_<?php echo (int) $group[
  205. "product_id"
  206. ]; ?>_<?php echo escape(
  207. preg_replace("/[^a-z0-9]/i", "_", $group["size"]),
  208. ); ?>">Menge</label>
  209. <input
  210. type="number"
  211. id="qty_ordered_<?php echo (int) $group[
  212. "product_id"
  213. ]; ?>_<?php echo escape(
  214. preg_replace("/[^a-z0-9]/i", "_", $group["size"]),
  215. ); ?>"
  216. name="quantity"
  217. min="1"
  218. max="<?php echo (int) $group[
  219. "to_be_backordered"
  220. ]; ?>"
  221. value="1"
  222. class="backorder-qty-input"
  223. >
  224. <button type="submit" name="mark_ordered" class="btn btn-small">
  225. Als bestellt markieren
  226. </button>
  227. </form>
  228. <?php else: ?>
  229. -
  230. <?php endif; ?>
  231. </td>
  232. <td data-label="Lieferung eingetroffen">
  233. <?php if ($group["ordered"] > 0): ?>
  234. <form method="POST" class="backorder-action-form">
  235. <?php echo csrfField(); ?>
  236. <input type="hidden" name="product_id" value="<?php echo (int) $group[
  237. "product_id"
  238. ]; ?>">
  239. <input type="hidden" name="size" value="<?php echo escape(
  240. $group["size"],
  241. ); ?>">
  242. <label class="sr-only" for="qty_delivered_<?php echo (int) $group[
  243. "product_id"
  244. ]; ?>_<?php echo escape(
  245. preg_replace("/[^a-z0-9]/i", "_", $group["size"]),
  246. ); ?>">Menge</label>
  247. <input
  248. type="number"
  249. id="qty_delivered_<?php echo (int) $group[
  250. "product_id"
  251. ]; ?>_<?php echo escape(
  252. preg_replace("/[^a-z0-9]/i", "_", $group["size"]),
  253. ); ?>"
  254. name="quantity"
  255. min="1"
  256. max="<?php echo (int) $group["ordered"]; ?>"
  257. value="1"
  258. class="backorder-qty-input"
  259. >
  260. <button type="submit" name="mark_delivered" class="btn btn-small btn-secondary">
  261. Lieferung eingetroffen
  262. </button>
  263. </form>
  264. <?php else: ?>
  265. -
  266. <?php endif; ?>
  267. </td>
  268. </tr>
  269. <?php endforeach; ?>
  270. </tbody>
  271. </table>
  272. </div>
  273. <?php endif; ?>
  274. <?php if (!empty($productSizeMap)): ?>
  275. <script>
  276. (function () {
  277. const sizesByProduct = <?php echo json_encode(
  278. $productSizeMap,
  279. JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT,
  280. ); ?>;
  281. const productSelect = document.getElementById("manual_backorder_product");
  282. const sizeSelect = document.getElementById("manual_backorder_size");
  283. if (!productSelect || !sizeSelect) {
  284. return;
  285. }
  286. function updateSizeOptions() {
  287. const productId = productSelect.value;
  288. const sizes = sizesByProduct[productId] || [];
  289. sizeSelect.innerHTML = "";
  290. if (sizes.length === 0) {
  291. sizeSelect.disabled = true;
  292. const option = document.createElement("option");
  293. option.value = "";
  294. option.textContent = "Zuerst Artikel wählen";
  295. sizeSelect.appendChild(option);
  296. return;
  297. }
  298. sizes.forEach(function (size) {
  299. const option = document.createElement("option");
  300. option.value = size;
  301. option.textContent = size;
  302. sizeSelect.appendChild(option);
  303. });
  304. sizeSelect.disabled = false;
  305. }
  306. productSelect.addEventListener("change", updateSizeOptions);
  307. })();
  308. </script>
  309. <?php endif; ?>
  310. <?php include __DIR__ . "/../includes/footer.php"; ?>