Buchungskalender eingefügt
This commit is contained in:
@@ -200,6 +200,79 @@ final class BookingService
|
||||
return array_slice($bookings, 0, 4);
|
||||
}
|
||||
|
||||
public function getPublicAvailabilityBookings(): array
|
||||
{
|
||||
$today = gmdate('Y-m-d');
|
||||
|
||||
return array_values(array_filter(
|
||||
$this->getBookingsByStatuses(['requested', 'reserved', 'confirmed']),
|
||||
static fn(array $booking): bool => (string) $booking['end_date'] >= $today
|
||||
));
|
||||
}
|
||||
|
||||
public function getPublicCalendarMonths(int $monthCount = 4): array
|
||||
{
|
||||
$months = [];
|
||||
$monthCount = max(1, $monthCount);
|
||||
$today = gmdate('Y-m-d');
|
||||
$bookings = $this->getPublicAvailabilityBookings();
|
||||
$monthCursor = (new DateTimeImmutable('first day of this month'))->setTime(0, 0);
|
||||
|
||||
for ($index = 0; $index < $monthCount; $index++) {
|
||||
$monthStart = $monthCursor->modify('+' . $index . ' month');
|
||||
$monthEnd = $monthStart->modify('first day of next month');
|
||||
$monthKey = $monthStart->format('Y-m');
|
||||
$entries = array_values(array_filter(
|
||||
$bookings,
|
||||
fn(array $booking): bool => $this->bookingIntersectsRange(
|
||||
$booking,
|
||||
$monthStart->format('Y-m-d'),
|
||||
$monthEnd->format('Y-m-d')
|
||||
)
|
||||
));
|
||||
|
||||
$days = [];
|
||||
for ($blank = 1; $blank < (int) $monthStart->format('N'); $blank++) {
|
||||
$days[] = ['is_padding' => true];
|
||||
}
|
||||
|
||||
$dayCursor = $monthStart;
|
||||
while ($dayCursor < $monthEnd) {
|
||||
$date = $dayCursor->format('Y-m-d');
|
||||
$dayBooking = $this->findBookingForDate($entries, $date);
|
||||
|
||||
$days[] = [
|
||||
'is_padding' => false,
|
||||
'date' => $date,
|
||||
'day' => (int) $dayCursor->format('j'),
|
||||
'is_today' => $date === $today,
|
||||
'is_booked' => $dayBooking !== null,
|
||||
'status' => $dayBooking['status'] ?? '',
|
||||
'status_label' => $dayBooking['status_label'] ?? '',
|
||||
];
|
||||
|
||||
$dayCursor = $dayCursor->modify('+1 day');
|
||||
}
|
||||
|
||||
while (count($days) % 7 !== 0) {
|
||||
$days[] = ['is_padding' => true];
|
||||
}
|
||||
|
||||
$months[] = [
|
||||
'label' => $this->formatMonthLabel($monthKey),
|
||||
'month_key' => $monthKey,
|
||||
'entry_count' => count($entries),
|
||||
'days' => $days,
|
||||
'entries' => array_map(
|
||||
fn(array $booking): array => $this->buildPublicCalendarEntry($booking),
|
||||
$entries
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return $months;
|
||||
}
|
||||
|
||||
public function getDashboardStats(): array
|
||||
{
|
||||
$bookings = $this->getBookings();
|
||||
@@ -303,6 +376,7 @@ final class BookingService
|
||||
'payment_status' => 'unpaid',
|
||||
'payment_method' => 'invoice_transfer',
|
||||
'delivery_mode' => 'self_pickup',
|
||||
'delivery_zone' => 'self_pickup',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -336,6 +410,20 @@ final class BookingService
|
||||
];
|
||||
}
|
||||
|
||||
public function getDeliveryModeOptions(): array
|
||||
{
|
||||
return [
|
||||
'self_pickup' => 'Selbstabholung',
|
||||
'delivery_setup' => 'Lieferung und Aufbau',
|
||||
'on_site_support' => 'Lieferung, Aufbau und Vor-Ort-Betreuung',
|
||||
];
|
||||
}
|
||||
|
||||
public function getDeliveryZoneOptions(): array
|
||||
{
|
||||
return $this->config['pricing']['delivery_rates'] ?? [];
|
||||
}
|
||||
|
||||
private function normalizeBookingInput(array $input, bool $adminMode): array
|
||||
{
|
||||
$customerName = trim((string) ($input['customer_name'] ?? ''));
|
||||
@@ -351,11 +439,11 @@ final class BookingService
|
||||
$endDate = trim((string) ($input['end_date'] ?? ''));
|
||||
$paymentMethod = trim((string) ($input['payment_method'] ?? 'invoice_transfer'));
|
||||
$deliveryMode = trim((string) ($input['delivery_mode'] ?? 'self_pickup'));
|
||||
$deliveryZone = trim((string) ($input['delivery_zone'] ?? 'self_pickup'));
|
||||
$notesCustomer = trim((string) ($input['notes_customer'] ?? ''));
|
||||
$internalNotes = trim((string) ($input['internal_notes'] ?? ''));
|
||||
$status = trim((string) ($input['status'] ?? ($adminMode ? 'confirmed' : 'requested')));
|
||||
$paymentStatus = trim((string) ($input['payment_status'] ?? 'unpaid'));
|
||||
$pricePerDay = (int) ($input['price_per_day_cents'] ?? $this->config['pricing']['default_day_rate_cents']);
|
||||
$privacyAccepted = (string) ($input['privacy_accepted'] ?? '') === '1';
|
||||
$termsAccepted = (string) ($input['terms_accepted'] ?? '') === '1';
|
||||
|
||||
@@ -408,11 +496,8 @@ final class BookingService
|
||||
'paypal' => 'PayPal',
|
||||
];
|
||||
|
||||
$deliveryLabels = [
|
||||
'self_pickup' => 'Selbstabholung',
|
||||
'delivery_setup' => 'Lieferung und Aufbau',
|
||||
'on_site_support' => 'Lieferung, Aufbau und Vor-Ort-Betreuung',
|
||||
];
|
||||
$deliveryLabels = $this->getDeliveryModeOptions();
|
||||
$deliveryZones = $this->getDeliveryZoneOptions();
|
||||
|
||||
if (!array_key_exists($paymentMethod, $paymentLabels)) {
|
||||
throw new RuntimeException('Die gewählte Zahlungsart ist ungültig.');
|
||||
@@ -422,6 +507,18 @@ final class BookingService
|
||||
throw new RuntimeException('Die gewählte Lieferart ist ungültig.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($deliveryZone, $deliveryZones)) {
|
||||
throw new RuntimeException('Bitte wähle ein gültiges Liefergebiet aus.');
|
||||
}
|
||||
|
||||
if ($deliveryMode === 'self_pickup') {
|
||||
$deliveryZone = 'self_pickup';
|
||||
} elseif ($deliveryZone === 'self_pickup') {
|
||||
throw new RuntimeException('Bitte wähle für die Lieferung ein passendes Liefergebiet aus.');
|
||||
}
|
||||
|
||||
$pricePerDay = $this->resolveRateForSelection($deliveryMode, $deliveryZone);
|
||||
|
||||
if ($pricePerDay < 0) {
|
||||
throw new RuntimeException('Der Tagespreis ist ungültig.');
|
||||
}
|
||||
@@ -443,6 +540,8 @@ final class BookingService
|
||||
'payment_method_label' => $paymentLabels[$paymentMethod],
|
||||
'delivery_mode' => $deliveryMode,
|
||||
'delivery_mode_label' => $deliveryLabels[$deliveryMode],
|
||||
'delivery_zone' => $deliveryZone,
|
||||
'delivery_zone_label' => $deliveryZones[$deliveryZone]['label'],
|
||||
'notes_customer' => $notesCustomer,
|
||||
'internal_notes' => $internalNotes,
|
||||
'status' => $status,
|
||||
@@ -472,6 +571,8 @@ final class BookingService
|
||||
'payment_method_label' => $payload['payment_method_label'],
|
||||
'delivery_mode' => $payload['delivery_mode'],
|
||||
'delivery_mode_label' => $payload['delivery_mode_label'],
|
||||
'delivery_zone' => $payload['delivery_zone'],
|
||||
'delivery_zone_label' => $payload['delivery_zone_label'],
|
||||
'start_date' => $payload['start_date'],
|
||||
'end_date' => $payload['end_date'],
|
||||
'total_days' => $payload['total_days'],
|
||||
@@ -562,6 +663,46 @@ final class BookingService
|
||||
return $prefix . '_' . strtolower(bin2hex(random_bytes(6)));
|
||||
}
|
||||
|
||||
private function resolveRateForSelection(string $deliveryMode, string $deliveryZone): int
|
||||
{
|
||||
$zones = $this->getDeliveryZoneOptions();
|
||||
|
||||
if ($deliveryMode === 'self_pickup') {
|
||||
return (int) ($zones['self_pickup']['price_cents'] ?? $this->config['pricing']['default_day_rate_cents']);
|
||||
}
|
||||
|
||||
return (int) ($zones[$deliveryZone]['price_cents'] ?? $this->config['pricing']['default_day_rate_cents']);
|
||||
}
|
||||
|
||||
private function bookingIntersectsRange(array $booking, string $rangeStart, string $rangeEnd): bool
|
||||
{
|
||||
return $booking['start_date'] < $rangeEnd && $booking['end_date'] > $rangeStart;
|
||||
}
|
||||
|
||||
private function findBookingForDate(array $bookings, string $date): ?array
|
||||
{
|
||||
foreach ($bookings as $booking) {
|
||||
if ($booking['start_date'] <= $date && $booking['end_date'] > $date) {
|
||||
return $booking;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function buildPublicCalendarEntry(array $booking): array
|
||||
{
|
||||
$deliveryLabel = (string) ($booking['delivery_zone_label'] ?: $booking['delivery_mode_label']);
|
||||
|
||||
return [
|
||||
'date_label' => formatDate($booking['start_date']) . ' bis ' . formatDate($booking['end_date']),
|
||||
'status' => $booking['status'],
|
||||
'status_label' => $booking['status_label'],
|
||||
'delivery_label' => $deliveryLabel,
|
||||
'day_count_label' => $booking['total_days'] . ' Miettag' . ($booking['total_days'] === 1 ? '' : 'e'),
|
||||
];
|
||||
}
|
||||
|
||||
private function formatMonthLabel(string $monthKey): string
|
||||
{
|
||||
[$year, $month] = explode('-', $monthKey) + [null, null];
|
||||
|
||||
Reference in New Issue
Block a user