$products]; writeJsonFile(PRODUCTS_FILE, $data); } /** * Get all reservations */ function getReservations() { $data = readJsonFile(RESERVATIONS_FILE); return isset($data['reservations']) ? $data['reservations'] : []; } /** * Get reservation by code */ function getReservationByCode($code) { $reservations = getReservations(); foreach ($reservations as $reservation) { if ($reservation['code'] === $code) { return $reservation; } } return null; } /** * Save reservations */ function saveReservations($reservations) { $data = ['reservations' => $reservations]; writeJsonFile(RESERVATIONS_FILE, $data); } /** * Generate unique reservation code */ function generateReservationCode() { $code = ''; $characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude confusing characters $max = strlen($characters) - 1; do { $code = ''; for ($i = 0; $i < 6; $i++) { $code .= $characters[random_int(0, $max)]; } // Check if code already exists $existing = getReservationByCode($code); } while ($existing !== null); return $code; } /** * Generate reservation ID */ function generateReservationId() { $reservations = getReservations(); $count = count($reservations) + 1; $year = date('Y'); return sprintf('RES-%s-%03d', $year, $count); } /** * Check if product has enough stock * For apparel: checks stock for specific size * For merch: checks general stock */ function checkStock($productId, $quantity, $size = null) { $product = getProductById($productId); if (!$product) { return false; } // For apparel with sizes, check stock per size if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) { if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) { return false; } $sizeStock = isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0; return $sizeStock >= $quantity; } // For merch or apparel without size-specific stock, use general stock $stock = isset($product['stock']) ? (int)$product['stock'] : 0; return $stock >= $quantity; } /** * Get stock for a product (per size for apparel, general for merch) */ function getStock($product, $size = null) { if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) { if (isset($product['stock_by_size']) && is_array($product['stock_by_size'])) { return isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0; } } return isset($product['stock']) ? (int)$product['stock'] : 0; } /** * Get total stock for a product (sum of all sizes for apparel) */ function getTotalStock($product) { if ($product['category'] === 'apparel' && isset($product['stock_by_size']) && is_array($product['stock_by_size'])) { return array_sum($product['stock_by_size']); } return isset($product['stock']) ? (int)$product['stock'] : 0; } /** * Allocate stock for reservation */ function allocateStock($productId, $quantity, $size = null) { $products = getProducts(); foreach ($products as &$product) { if ($product['id'] == $productId) { // For apparel with sizes, allocate per size if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) { if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) { $product['stock_by_size'] = []; } if (!isset($product['stock_by_size'][$size])) { $product['stock_by_size'][$size] = 0; } $product['stock_by_size'][$size] -= $quantity; if ($product['stock_by_size'][$size] < 0) { $product['stock_by_size'][$size] = 0; } } else { // For merch or general stock if (!isset($product['stock'])) { $product['stock'] = 0; } $product['stock'] -= $quantity; if ($product['stock'] < 0) { $product['stock'] = 0; } } break; } } saveProducts($products); } /** * Release stock from reservation */ function releaseStock($productId, $quantity, $size = null) { $products = getProducts(); foreach ($products as &$product) { if ($product['id'] == $productId) { // For apparel with sizes, release per size if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) { if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) { $product['stock_by_size'] = []; } if (!isset($product['stock_by_size'][$size])) { $product['stock_by_size'][$size] = 0; } $product['stock_by_size'][$size] += $quantity; } else { // For merch or general stock if (!isset($product['stock'])) { $product['stock'] = 0; } $product['stock'] += $quantity; } break; } } saveProducts($products); } /** * Create new reservation */ function createReservation($customerName, $customerEmail, $items) { $reservations = getReservations(); // Validate stock for all items foreach ($items as $item) { $size = isset($item['size']) ? $item['size'] : null; if (!checkStock($item['product_id'], $item['quantity'], $size)) { $product = getProductById($item['product_id']); $productName = $product ? $product['name'] : 'Produkt'; $sizeInfo = $size ? " (Größe: $size)" : ''; return ['success' => false, 'message' => "Nicht genügend Lagerbestand für: $productName$sizeInfo"]; } } // Allocate stock foreach ($items as $item) { $size = isset($item['size']) ? $item['size'] : null; allocateStock($item['product_id'], $item['quantity'], $size); } // Create reservation $now = new DateTime(); $expires = clone $now; $expires->modify('+' . RESERVATION_EXPIRY_DAYS . ' days'); $reservation = [ 'id' => generateReservationId(), 'code' => generateReservationCode(), 'customer_name' => $customerName, 'customer_email' => $customerEmail, 'items' => $items, 'created' => $now->format('Y-m-d H:i:s'), 'expires' => $expires->format('Y-m-d H:i:s'), 'status' => 'open', 'picked_up' => false, 'type' => 'regular' ]; $reservations[] = $reservation; saveReservations($reservations); // Send confirmation emails sendReservationEmails($reservation); return ['success' => true, 'reservation' => $reservation]; } /** * Create new backorder reservation */ function createBackorderReservation($customerName, $customerEmail, $items) { $reservations = getReservations(); $now = new DateTime(); $reservation = [ 'id' => generateReservationId(), 'code' => generateReservationCode(), 'customer_name' => $customerName, 'customer_email' => $customerEmail, 'items' => $items, 'created' => $now->format('Y-m-d H:i:s'), 'expires' => '', 'status' => 'open', 'picked_up' => false, 'type' => 'backorder', 'backorder_status' => 'pending' ]; $reservations[] = $reservation; saveReservations($reservations); // Send confirmation emails sendBackorderEmails($reservation); return ['success' => true, 'reservation' => $reservation]; } /** * Mark reservation as picked up */ function markReservationPickedUp($reservationId) { $reservations = getReservations(); foreach ($reservations as &$reservation) { if ($reservation['id'] === $reservationId) { $reservation['picked_up'] = true; $reservation['status'] = 'picked_up'; break; } } saveReservations($reservations); } /** * Check and expire old reservations */ function expireOldReservations() { $reservations = getReservations(); $now = new DateTime(); $changed = false; foreach ($reservations as &$reservation) { if ($reservation['status'] === 'open' && !$reservation['picked_up']) { if (isset($reservation['type']) && $reservation['type'] === 'backorder') { continue; } if (empty($reservation['expires'])) { continue; } $expires = new DateTime($reservation['expires']); if ($now > $expires) { $reservation['status'] = 'expired'; // Release stock foreach ($reservation['items'] as $item) { $size = isset($item['size']) ? $item['size'] : null; releaseStock($item['product_id'], $item['quantity'], $size); } $changed = true; } } } if ($changed) { saveReservations($reservations); } } /** * Check if all items are in stock */ function canFulfillReservationItems($items) { foreach ($items as $item) { $size = isset($item['size']) ? $item['size'] : null; if (!checkStock($item['product_id'], $item['quantity'], $size)) { return false; } } return true; } /** * Mark backorder as available */ function markBackorderAvailable($reservationId) { $reservations = getReservations(); foreach ($reservations as &$reservation) { if ($reservation['id'] === $reservationId) { if (!isset($reservation['type']) || $reservation['type'] !== 'backorder') { return ['success' => false, 'message' => 'Diese Reservierung ist keine Nachbestellung.']; } if (isset($reservation['backorder_status']) && $reservation['backorder_status'] === 'notified') { return ['success' => false, 'message' => 'Diese Nachbestellung wurde bereits informiert.']; } if (!canFulfillReservationItems($reservation['items'])) { return ['success' => false, 'message' => 'Nicht alle Artikel sind verfügbar.']; } foreach ($reservation['items'] as $item) { $size = isset($item['size']) ? $item['size'] : null; allocateStock($item['product_id'], $item['quantity'], $size); } $reservation['backorder_status'] = 'notified'; saveReservations($reservations); sendBackorderAvailableEmail($reservation); return ['success' => true, 'reservation' => $reservation]; } } return ['success' => false, 'message' => 'Nachbestellung nicht gefunden.']; } /** * Sanitize input */ function sanitize($input) { return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8'); } /** * Format price */ function formatPrice($price) { return number_format($price, 2, ',', '.') . ' €'; } /** * Format date */ function formatDate($dateString) { $date = new DateTime($dateString); return $date->format('d.m.Y H:i'); } /** * Send email */ function sendEmail($to, $subject, $message, $isHtml = true) { $headers = []; $headers[] = 'From: ' . FROM_NAME . ' <' . FROM_EMAIL . '>'; $headers[] = 'Reply-To: ' . FROM_EMAIL; $headers[] = 'X-Mailer: PHP/' . phpversion(); if ($isHtml) { $headers[] = 'MIME-Version: 1.0'; $headers[] = 'Content-type: text/html; charset=UTF-8'; } return mail($to, $subject, $message, implode("\r\n", $headers)); } /** * Send reservation confirmation emails */ function sendReservationEmails($reservation) { $products = getProducts(); // Build items list $itemsHtml = '
Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',
vielen Dank für Ihre Reservierung bei ' . SITE_NAME . '.
Reservierungsnummer: ' . htmlspecialchars($reservation['id']) . '
Erstellt am: ' . formatDate($reservation['created']) . '
Gültig bis: ' . formatDate($reservation['expires']) . '
Wichtig: Bitte bringen Sie diesen Abholcode zur Abholung mit. Die Reservierung ist bis zum ' . formatDate($reservation['expires']) . ' gültig.
Mit freundlichen Grüßen
' . SITE_NAME . '
Eine neue Reservierung wurde erstellt:
Name: ' . htmlspecialchars($reservation['customer_name']) . '
E-Mail: ' . htmlspecialchars($reservation['customer_email']) . '
Reservierungsnummer: ' . htmlspecialchars($reservation['id']) . '
Erstellt am: ' . formatDate($reservation['created']) . '
Gültig bis: ' . formatDate($reservation['expires']) . '
Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',
vielen Dank für Ihre Nachbestellung bei ' . SITE_NAME . '.
Nachbestellungsnummer: ' . htmlspecialchars($reservation['id']) . '
Erstellt am: ' . formatDate($reservation['created']) . '
Wir informieren Sie, sobald die komplette Nachbestellung verfügbar ist.
Mit freundlichen Grüßen
' . SITE_NAME . '
Eine neue Nachbestellung wurde erstellt:
Name: ' . htmlspecialchars($reservation['customer_name']) . '
E-Mail: ' . htmlspecialchars($reservation['customer_email']) . '
Nachbestellungsnummer: ' . htmlspecialchars($reservation['id']) . '
Erstellt am: ' . formatDate($reservation['created']) . '
Hinweis: Lieferzeiten sind nicht bekannt, Bestellung in Chargen.
'; sendEmail(ADMIN_EMAIL, $adminSubject, $adminMessage); } /** * Send backorder availability email */ function sendBackorderAvailableEmail($reservation) { $itemsHtml = 'Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',
Ihre komplette Nachbestellung ist jetzt verfügbar.
Bitte bringen Sie den Abholcode zur Abholung mit.
Mit freundlichen Grüßen
' . SITE_NAME . '