backorders.php 13 KB

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