functions.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. <?php
  2. require_once __DIR__ . '/../config.php';
  3. /**
  4. * Read JSON file and return decoded data
  5. */
  6. function readJsonFile($file) {
  7. if (!file_exists($file)) {
  8. return [];
  9. }
  10. $content = file_get_contents($file);
  11. if (empty($content)) {
  12. return [];
  13. }
  14. $data = json_decode($content, true);
  15. return $data ? $data : [];
  16. }
  17. /**
  18. * Write data to JSON file
  19. */
  20. function writeJsonFile($file, $data) {
  21. $dir = dirname($file);
  22. if (!is_dir($dir)) {
  23. mkdir($dir, 0755, true);
  24. }
  25. file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
  26. }
  27. /**
  28. * Get all products
  29. */
  30. function getProducts() {
  31. $data = readJsonFile(PRODUCTS_FILE);
  32. return isset($data['products']) ? $data['products'] : [];
  33. }
  34. /**
  35. * Get product by ID
  36. */
  37. function getProductById($id) {
  38. $products = getProducts();
  39. foreach ($products as $product) {
  40. if ($product['id'] == $id) {
  41. return $product;
  42. }
  43. }
  44. return null;
  45. }
  46. /**
  47. * Save products
  48. */
  49. function saveProducts($products) {
  50. $data = ['products' => $products];
  51. writeJsonFile(PRODUCTS_FILE, $data);
  52. }
  53. /**
  54. * Get all reservations
  55. */
  56. function getReservations() {
  57. $data = readJsonFile(RESERVATIONS_FILE);
  58. return isset($data['reservations']) ? $data['reservations'] : [];
  59. }
  60. /**
  61. * Get reservation by code
  62. */
  63. function getReservationByCode($code) {
  64. $reservations = getReservations();
  65. foreach ($reservations as $reservation) {
  66. if ($reservation['code'] === $code) {
  67. return $reservation;
  68. }
  69. }
  70. return null;
  71. }
  72. /**
  73. * Save reservations
  74. */
  75. function saveReservations($reservations) {
  76. $data = ['reservations' => $reservations];
  77. writeJsonFile(RESERVATIONS_FILE, $data);
  78. }
  79. /**
  80. * Generate unique reservation code
  81. */
  82. function generateReservationCode() {
  83. $code = '';
  84. $characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude confusing characters
  85. $max = strlen($characters) - 1;
  86. do {
  87. $code = '';
  88. for ($i = 0; $i < 6; $i++) {
  89. $code .= $characters[random_int(0, $max)];
  90. }
  91. // Check if code already exists
  92. $existing = getReservationByCode($code);
  93. } while ($existing !== null);
  94. return $code;
  95. }
  96. /**
  97. * Generate reservation ID
  98. */
  99. function generateReservationId() {
  100. $reservations = getReservations();
  101. $count = count($reservations) + 1;
  102. $year = date('Y');
  103. return sprintf('RES-%s-%03d', $year, $count);
  104. }
  105. /**
  106. * Check if product has enough stock
  107. * For apparel: checks stock for specific size
  108. * For merch: checks general stock
  109. */
  110. function checkStock($productId, $quantity, $size = null) {
  111. $product = getProductById($productId);
  112. if (!$product) {
  113. return false;
  114. }
  115. // For apparel with sizes, check stock per size
  116. if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
  117. if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
  118. return false;
  119. }
  120. $sizeStock = isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0;
  121. return $sizeStock >= $quantity;
  122. }
  123. // For merch or apparel without size-specific stock, use general stock
  124. $stock = isset($product['stock']) ? (int)$product['stock'] : 0;
  125. return $stock >= $quantity;
  126. }
  127. /**
  128. * Get stock for a product (per size for apparel, general for merch)
  129. */
  130. function getStock($product, $size = null) {
  131. if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
  132. if (isset($product['stock_by_size']) && is_array($product['stock_by_size'])) {
  133. return isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0;
  134. }
  135. }
  136. return isset($product['stock']) ? (int)$product['stock'] : 0;
  137. }
  138. /**
  139. * Get total stock for a product (sum of all sizes for apparel)
  140. */
  141. function getTotalStock($product) {
  142. if ($product['category'] === 'apparel' && isset($product['stock_by_size']) && is_array($product['stock_by_size'])) {
  143. return array_sum($product['stock_by_size']);
  144. }
  145. return isset($product['stock']) ? (int)$product['stock'] : 0;
  146. }
  147. /**
  148. * Allocate stock for reservation
  149. */
  150. function allocateStock($productId, $quantity, $size = null) {
  151. $products = getProducts();
  152. foreach ($products as &$product) {
  153. if ($product['id'] == $productId) {
  154. // For apparel with sizes, allocate per size
  155. if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
  156. if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
  157. $product['stock_by_size'] = [];
  158. }
  159. if (!isset($product['stock_by_size'][$size])) {
  160. $product['stock_by_size'][$size] = 0;
  161. }
  162. $product['stock_by_size'][$size] -= $quantity;
  163. if ($product['stock_by_size'][$size] < 0) {
  164. $product['stock_by_size'][$size] = 0;
  165. }
  166. } else {
  167. // For merch or general stock
  168. if (!isset($product['stock'])) {
  169. $product['stock'] = 0;
  170. }
  171. $product['stock'] -= $quantity;
  172. if ($product['stock'] < 0) {
  173. $product['stock'] = 0;
  174. }
  175. }
  176. break;
  177. }
  178. }
  179. saveProducts($products);
  180. }
  181. /**
  182. * Release stock from reservation
  183. */
  184. function releaseStock($productId, $quantity, $size = null) {
  185. $products = getProducts();
  186. foreach ($products as &$product) {
  187. if ($product['id'] == $productId) {
  188. // For apparel with sizes, release per size
  189. if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
  190. if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
  191. $product['stock_by_size'] = [];
  192. }
  193. if (!isset($product['stock_by_size'][$size])) {
  194. $product['stock_by_size'][$size] = 0;
  195. }
  196. $product['stock_by_size'][$size] += $quantity;
  197. } else {
  198. // For merch or general stock
  199. if (!isset($product['stock'])) {
  200. $product['stock'] = 0;
  201. }
  202. $product['stock'] += $quantity;
  203. }
  204. break;
  205. }
  206. }
  207. saveProducts($products);
  208. }
  209. /**
  210. * Create new reservation
  211. */
  212. function createReservation($customerName, $customerEmail, $items) {
  213. $reservations = getReservations();
  214. // Validate stock for all items
  215. foreach ($items as $item) {
  216. $size = isset($item['size']) ? $item['size'] : null;
  217. if (!checkStock($item['product_id'], $item['quantity'], $size)) {
  218. $product = getProductById($item['product_id']);
  219. $productName = $product ? $product['name'] : 'Produkt';
  220. $sizeInfo = $size ? " (Größe: $size)" : '';
  221. return ['success' => false, 'message' => "Nicht genügend Lagerbestand für: $productName$sizeInfo"];
  222. }
  223. }
  224. // Allocate stock
  225. foreach ($items as $item) {
  226. $size = isset($item['size']) ? $item['size'] : null;
  227. allocateStock($item['product_id'], $item['quantity'], $size);
  228. }
  229. // Create reservation
  230. $now = new DateTime();
  231. $expires = clone $now;
  232. $expires->modify('+' . RESERVATION_EXPIRY_DAYS . ' days');
  233. $reservation = [
  234. 'id' => generateReservationId(),
  235. 'code' => generateReservationCode(),
  236. 'customer_name' => $customerName,
  237. 'customer_email' => $customerEmail,
  238. 'items' => $items,
  239. 'created' => $now->format('Y-m-d H:i:s'),
  240. 'expires' => $expires->format('Y-m-d H:i:s'),
  241. 'status' => 'open',
  242. 'picked_up' => false,
  243. 'type' => 'regular'
  244. ];
  245. $reservations[] = $reservation;
  246. saveReservations($reservations);
  247. // Send confirmation emails
  248. sendReservationEmails($reservation);
  249. return ['success' => true, 'reservation' => $reservation];
  250. }
  251. /**
  252. * Create new backorder reservation
  253. */
  254. function createBackorderReservation($customerName, $customerEmail, $items) {
  255. $reservations = getReservations();
  256. $now = new DateTime();
  257. $reservation = [
  258. 'id' => generateReservationId(),
  259. 'code' => generateReservationCode(),
  260. 'customer_name' => $customerName,
  261. 'customer_email' => $customerEmail,
  262. 'items' => $items,
  263. 'created' => $now->format('Y-m-d H:i:s'),
  264. 'expires' => '',
  265. 'status' => 'open',
  266. 'picked_up' => false,
  267. 'type' => 'backorder',
  268. 'backorder_status' => 'pending'
  269. ];
  270. $reservations[] = $reservation;
  271. saveReservations($reservations);
  272. // Send confirmation emails
  273. sendBackorderEmails($reservation);
  274. return ['success' => true, 'reservation' => $reservation];
  275. }
  276. /**
  277. * Mark reservation as picked up
  278. */
  279. function markReservationPickedUp($reservationId) {
  280. $reservations = getReservations();
  281. foreach ($reservations as &$reservation) {
  282. if ($reservation['id'] === $reservationId) {
  283. $reservation['picked_up'] = true;
  284. $reservation['status'] = 'picked_up';
  285. break;
  286. }
  287. }
  288. saveReservations($reservations);
  289. }
  290. /**
  291. * Check and expire old reservations
  292. */
  293. function expireOldReservations() {
  294. $reservations = getReservations();
  295. $now = new DateTime();
  296. $changed = false;
  297. foreach ($reservations as &$reservation) {
  298. if ($reservation['status'] === 'open' && !$reservation['picked_up']) {
  299. if (isset($reservation['type']) && $reservation['type'] === 'backorder') {
  300. continue;
  301. }
  302. if (empty($reservation['expires'])) {
  303. continue;
  304. }
  305. $expires = new DateTime($reservation['expires']);
  306. if ($now > $expires) {
  307. $reservation['status'] = 'expired';
  308. // Release stock
  309. foreach ($reservation['items'] as $item) {
  310. $size = isset($item['size']) ? $item['size'] : null;
  311. releaseStock($item['product_id'], $item['quantity'], $size);
  312. }
  313. $changed = true;
  314. }
  315. }
  316. }
  317. if ($changed) {
  318. saveReservations($reservations);
  319. }
  320. }
  321. /**
  322. * Check if all items are in stock
  323. */
  324. function canFulfillReservationItems($items) {
  325. foreach ($items as $item) {
  326. $size = isset($item['size']) ? $item['size'] : null;
  327. if (!checkStock($item['product_id'], $item['quantity'], $size)) {
  328. return false;
  329. }
  330. }
  331. return true;
  332. }
  333. /**
  334. * Mark backorder as available
  335. */
  336. function markBackorderAvailable($reservationId) {
  337. $reservations = getReservations();
  338. foreach ($reservations as &$reservation) {
  339. if ($reservation['id'] === $reservationId) {
  340. if (!isset($reservation['type']) || $reservation['type'] !== 'backorder') {
  341. return ['success' => false, 'message' => 'Diese Reservierung ist keine Nachbestellung.'];
  342. }
  343. if (isset($reservation['backorder_status']) && $reservation['backorder_status'] === 'notified') {
  344. return ['success' => false, 'message' => 'Diese Nachbestellung wurde bereits informiert.'];
  345. }
  346. if (!canFulfillReservationItems($reservation['items'])) {
  347. return ['success' => false, 'message' => 'Nicht alle Artikel sind verfügbar.'];
  348. }
  349. foreach ($reservation['items'] as $item) {
  350. $size = isset($item['size']) ? $item['size'] : null;
  351. allocateStock($item['product_id'], $item['quantity'], $size);
  352. }
  353. $reservation['backorder_status'] = 'notified';
  354. saveReservations($reservations);
  355. sendBackorderAvailableEmail($reservation);
  356. return ['success' => true, 'reservation' => $reservation];
  357. }
  358. }
  359. return ['success' => false, 'message' => 'Nachbestellung nicht gefunden.'];
  360. }
  361. /**
  362. * Sanitize input
  363. */
  364. function sanitize($input) {
  365. return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
  366. }
  367. /**
  368. * Format price
  369. */
  370. function formatPrice($price) {
  371. return number_format($price, 2, ',', '.') . ' €';
  372. }
  373. /**
  374. * Format date
  375. */
  376. function formatDate($dateString) {
  377. $date = new DateTime($dateString);
  378. return $date->format('d.m.Y H:i');
  379. }
  380. /**
  381. * Send email
  382. */
  383. function sendEmail($to, $subject, $message, $isHtml = true) {
  384. $headers = [];
  385. $headers[] = 'From: ' . FROM_NAME . ' <' . FROM_EMAIL . '>';
  386. $headers[] = 'Reply-To: ' . FROM_EMAIL;
  387. $headers[] = 'X-Mailer: PHP/' . phpversion();
  388. if ($isHtml) {
  389. $headers[] = 'MIME-Version: 1.0';
  390. $headers[] = 'Content-type: text/html; charset=UTF-8';
  391. }
  392. return mail($to, $subject, $message, implode("\r\n", $headers));
  393. }
  394. /**
  395. * Send reservation confirmation emails
  396. */
  397. function sendReservationEmails($reservation) {
  398. $products = getProducts();
  399. // Build items list
  400. $itemsHtml = '<ul>';
  401. foreach ($reservation['items'] as $item) {
  402. $product = getProductById($item['product_id']);
  403. if ($product) {
  404. $sizeInfo = '';
  405. if (isset($item['size']) && !empty($item['size'])) {
  406. $sizeInfo = ' - Größe: ' . htmlspecialchars($item['size']);
  407. }
  408. $itemsHtml .= '<li>' . htmlspecialchars($product['name']) . $sizeInfo . ' - Menge: ' . $item['quantity'] . '</li>';
  409. }
  410. }
  411. $itemsHtml .= '</ul>';
  412. // Customer email
  413. $customerSubject = 'Ihre Reservierung bei ' . SITE_NAME;
  414. $customerMessage = '
  415. <html>
  416. <head>
  417. <meta charset="UTF-8">
  418. </head>
  419. <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
  420. <h2 style="color: #c41e3a;">Reservierung bestätigt</h2>
  421. <p>Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',</p>
  422. <p>vielen Dank für Ihre Reservierung bei ' . SITE_NAME . '.</p>
  423. <div style="background: #fff3cd; border: 2px solid #ffc107; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px; text-align: center;">
  424. <h3 style="margin-top: 0;">Ihr Abholcode:</h3>
  425. <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #856404; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
  426. </div>
  427. <h3>Reservierungsdetails:</h3>
  428. <p><strong>Reservierungsnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
  429. <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
  430. <p><strong>Gültig bis:</strong> ' . formatDate($reservation['expires']) . '</p>
  431. <h3>Reservierte Artikel:</h3>
  432. ' . $itemsHtml . '
  433. <p><strong>Wichtig:</strong> Bitte bringen Sie diesen Abholcode zur Abholung mit. Die Reservierung ist bis zum ' . formatDate($reservation['expires']) . ' gültig.</p>
  434. <p>Mit freundlichen Grüßen<br>' . SITE_NAME . '</p>
  435. </body>
  436. </html>';
  437. sendEmail($reservation['customer_email'], $customerSubject, $customerMessage);
  438. // Admin email
  439. $adminSubject = 'Neue Reservierung: ' . $reservation['code'];
  440. $adminMessage = '
  441. <html>
  442. <head>
  443. <meta charset="UTF-8">
  444. </head>
  445. <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
  446. <h2 style="color: #c41e3a;">Neue Reservierung</h2>
  447. <p>Eine neue Reservierung wurde erstellt:</p>
  448. <div style="background: #d1ecf1; border: 2px solid #bee5eb; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px;">
  449. <h3 style="margin-top: 0;">Abholcode:</h3>
  450. <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #0c5460; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
  451. </div>
  452. <h3>Kundendaten:</h3>
  453. <p><strong>Name:</strong> ' . htmlspecialchars($reservation['customer_name']) . '</p>
  454. <p><strong>E-Mail:</strong> ' . htmlspecialchars($reservation['customer_email']) . '</p>
  455. <h3>Reservierungsdetails:</h3>
  456. <p><strong>Reservierungsnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
  457. <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
  458. <p><strong>Gültig bis:</strong> ' . formatDate($reservation['expires']) . '</p>
  459. <h3>Reservierte Artikel:</h3>
  460. ' . $itemsHtml . '
  461. </body>
  462. </html>';
  463. sendEmail(ADMIN_EMAIL, $adminSubject, $adminMessage);
  464. }
  465. /**
  466. * Send backorder confirmation emails
  467. */
  468. function sendBackorderEmails($reservation) {
  469. // Build items list
  470. $itemsHtml = '<ul>';
  471. foreach ($reservation['items'] as $item) {
  472. $product = getProductById($item['product_id']);
  473. if ($product) {
  474. $sizeInfo = '';
  475. if (isset($item['size']) && !empty($item['size'])) {
  476. $sizeInfo = ' - Größe: ' . htmlspecialchars($item['size']);
  477. }
  478. $itemsHtml .= '<li>' . htmlspecialchars($product['name']) . $sizeInfo . ' - Menge: ' . $item['quantity'] . '</li>';
  479. }
  480. }
  481. $itemsHtml .= '</ul>';
  482. // Customer email
  483. $customerSubject = 'Nachbestellung bei ' . SITE_NAME;
  484. $customerMessage = '
  485. <html>
  486. <head>
  487. <meta charset="UTF-8">
  488. </head>
  489. <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
  490. <h2 style="color: #c41e3a;">Nachbestellung bestätigt</h2>
  491. <p>Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',</p>
  492. <p>vielen Dank für Ihre Nachbestellung bei ' . SITE_NAME . '.</p>
  493. <div style="background: #fff3cd; border: 2px solid #ffc107; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px; text-align: center;">
  494. <h3 style="margin-top: 0;">Ihr Abholcode:</h3>
  495. <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #856404; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
  496. </div>
  497. <h3>Nachbestellungsdetails:</h3>
  498. <p><strong>Nachbestellungsnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
  499. <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
  500. <h3>Nachbestellte Artikel:</h3>
  501. ' . $itemsHtml . '
  502. <div style="background: #f8d7da; border: 2px solid #f5c6cb; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px;">
  503. <strong>Hinweis:</strong> Die Lieferzeiten sind nicht bekannt, da die Bestellung in Chargen erfolgt.
  504. </div>
  505. <p>Wir informieren Sie, sobald die komplette Nachbestellung verfügbar ist.</p>
  506. <p>Mit freundlichen Grüßen<br>' . SITE_NAME . '</p>
  507. </body>
  508. </html>';
  509. sendEmail($reservation['customer_email'], $customerSubject, $customerMessage);
  510. // Admin email
  511. $adminSubject = 'Neue Nachbestellung: ' . $reservation['code'];
  512. $adminMessage = '
  513. <html>
  514. <head>
  515. <meta charset="UTF-8">
  516. </head>
  517. <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
  518. <h2 style="color: #c41e3a;">Neue Nachbestellung</h2>
  519. <p>Eine neue Nachbestellung wurde erstellt:</p>
  520. <div style="background: #d1ecf1; border: 2px solid #bee5eb; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px;">
  521. <h3 style="margin-top: 0;">Abholcode:</h3>
  522. <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #0c5460; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
  523. </div>
  524. <h3>Kundendaten:</h3>
  525. <p><strong>Name:</strong> ' . htmlspecialchars($reservation['customer_name']) . '</p>
  526. <p><strong>E-Mail:</strong> ' . htmlspecialchars($reservation['customer_email']) . '</p>
  527. <h3>Nachbestellungsdetails:</h3>
  528. <p><strong>Nachbestellungsnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
  529. <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
  530. <h3>Nachbestellte Artikel:</h3>
  531. ' . $itemsHtml . '
  532. <p><strong>Hinweis:</strong> Lieferzeiten sind nicht bekannt, Bestellung in Chargen.</p>
  533. </body>
  534. </html>';
  535. sendEmail(ADMIN_EMAIL, $adminSubject, $adminMessage);
  536. }
  537. /**
  538. * Send backorder availability email
  539. */
  540. function sendBackorderAvailableEmail($reservation) {
  541. $itemsHtml = '<ul>';
  542. foreach ($reservation['items'] as $item) {
  543. $product = getProductById($item['product_id']);
  544. if ($product) {
  545. $sizeInfo = '';
  546. if (isset($item['size']) && !empty($item['size'])) {
  547. $sizeInfo = ' - Größe: ' . htmlspecialchars($item['size']);
  548. }
  549. $itemsHtml .= '<li>' . htmlspecialchars($product['name']) . $sizeInfo . ' - Menge: ' . $item['quantity'] . '</li>';
  550. }
  551. }
  552. $itemsHtml .= '</ul>';
  553. $subject = 'Ihre Nachbestellung ist verfügbar';
  554. $message = '
  555. <html>
  556. <head>
  557. <meta charset="UTF-8">
  558. </head>
  559. <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
  560. <h2 style="color: #28a745;">Nachbestellung verfügbar</h2>
  561. <p>Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',</p>
  562. <p>Ihre komplette Nachbestellung ist jetzt verfügbar.</p>
  563. <div style="background: #d4edda; border: 2px solid #c3e6cb; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px; text-align: center;">
  564. <h3 style="margin-top: 0;">Ihr Abholcode:</h3>
  565. <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #155724; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
  566. </div>
  567. <h3>Verfügbare Artikel:</h3>
  568. ' . $itemsHtml . '
  569. <p>Bitte bringen Sie den Abholcode zur Abholung mit.</p>
  570. <p>Mit freundlichen Grüßen<br>' . SITE_NAME . '</p>
  571. </body>
  572. </html>';
  573. sendEmail($reservation['customer_email'], $subject, $message);
  574. }