Anpassung Design
This commit is contained in:
+143
-22
@@ -55,11 +55,11 @@ final class BookingService
|
||||
$paymentStatus = (string) ($input['payment_status'] ?? $booking['payment_status']);
|
||||
|
||||
if (!array_key_exists($status, $this->getStatusOptions())) {
|
||||
throw new RuntimeException('Der gewaehlte Status ist ungueltig.');
|
||||
throw new RuntimeException('Der gewählte Status ist ungültig.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($paymentStatus, $this->getPaymentStatusOptions())) {
|
||||
throw new RuntimeException('Der gewaehlte Zahlungsstatus ist ungueltig.');
|
||||
throw new RuntimeException('Der gewählte Zahlungsstatus ist ungültig.');
|
||||
}
|
||||
|
||||
if ($this->isBlockingStatus($status)) {
|
||||
@@ -88,7 +88,7 @@ final class BookingService
|
||||
}
|
||||
|
||||
if (!in_array($booking['status'], ['reserved', 'confirmed', 'completed'], true)) {
|
||||
throw new RuntimeException('Fuer diesen Auftrag kann noch keine Rechnung erstellt werden.');
|
||||
throw new RuntimeException('Für diesen Auftrag kann noch keine Rechnung erstellt werden.');
|
||||
}
|
||||
|
||||
if (!empty($booking['invoice_id'])) {
|
||||
@@ -104,7 +104,7 @@ final class BookingService
|
||||
|
||||
$lineItems = [
|
||||
[
|
||||
'label' => 'Fotobox-Miete ' . formatDate($booking['start_date']) . ' bis ' . formatDate($booking['end_date']),
|
||||
'label' => 'Fotobox-Miete ' . formatDate($booking['start_date']) . ' bis ' . formatDate($booking['end_date']) . ' (' . $booking['total_days'] . ' Miettag' . ($booking['total_days'] === 1 ? '' : 'e') . ')',
|
||||
'quantity' => $booking['total_days'],
|
||||
'unit_price_cents' => $booking['price_per_day_cents'],
|
||||
'total_cents' => $booking['subtotal_cents'],
|
||||
@@ -128,7 +128,7 @@ final class BookingService
|
||||
'line_items' => $lineItems,
|
||||
'subtotal_cents' => $booking['subtotal_cents'],
|
||||
'total_cents' => $booking['subtotal_cents'],
|
||||
'notes' => trim((string) ($input['invoice_notes'] ?? 'Vielen Dank fuer deinen Auftrag.')),
|
||||
'notes' => trim((string) ($input['invoice_notes'] ?? 'Vielen Dank für deinen Auftrag.')),
|
||||
];
|
||||
|
||||
array_unshift($records, $invoice);
|
||||
@@ -139,7 +139,7 @@ final class BookingService
|
||||
$this->bookingRepository->transaction(function (array &$records) use ($bookingId, $invoice): void {
|
||||
$index = $this->findBookingIndex($records, $bookingId);
|
||||
if ($index === null) {
|
||||
throw new RuntimeException('Der Auftrag wurde beim Verknuepfen der Rechnung nicht gefunden.');
|
||||
throw new RuntimeException('Der Auftrag wurde beim Verknüpfen der Rechnung nicht gefunden.');
|
||||
}
|
||||
|
||||
$records[$index]['invoice_id'] = $invoice['id'];
|
||||
@@ -169,6 +169,25 @@ final class BookingService
|
||||
return $invoices;
|
||||
}
|
||||
|
||||
public function getBookingsByStatuses(array $statuses): array
|
||||
{
|
||||
$bookings = array_values(array_filter(
|
||||
$this->getBookings(),
|
||||
static fn(array $booking): bool => in_array((string) $booking['status'], $statuses, true)
|
||||
));
|
||||
|
||||
usort($bookings, static function (array $a, array $b): int {
|
||||
$dateComparison = strcmp($a['start_date'], $b['start_date']);
|
||||
if ($dateComparison !== 0) {
|
||||
return $dateComparison;
|
||||
}
|
||||
|
||||
return strcmp($a['created_at'], $b['created_at']);
|
||||
});
|
||||
|
||||
return $bookings;
|
||||
}
|
||||
|
||||
public function getHighlightedBookings(): array
|
||||
{
|
||||
$bookings = array_values(array_filter(
|
||||
@@ -217,6 +236,65 @@ final class BookingService
|
||||
];
|
||||
}
|
||||
|
||||
public function getCalendarGroups(): array
|
||||
{
|
||||
$groups = [];
|
||||
|
||||
foreach ($this->getBookingsByStatuses(['requested', 'reserved', 'confirmed', 'completed']) as $booking) {
|
||||
$monthKey = substr((string) $booking['start_date'], 0, 7);
|
||||
$label = $this->formatMonthLabel($monthKey);
|
||||
|
||||
$groups[$label][] = $booking;
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
public function getCustomers(): array
|
||||
{
|
||||
$customers = [];
|
||||
|
||||
foreach ($this->getBookings() as $booking) {
|
||||
$customer = $booking['customer'];
|
||||
$key = strtolower(trim((string) $customer['email'])) . '|' . strtolower(trim((string) $customer['phone']));
|
||||
|
||||
if (!isset($customers[$key])) {
|
||||
$customers[$key] = [
|
||||
'name' => $customer['name'],
|
||||
'company' => $customer['company'],
|
||||
'email' => $customer['email'],
|
||||
'phone' => $customer['phone'],
|
||||
'city' => $customer['city'],
|
||||
'booking_count' => 0,
|
||||
'revenue_cents' => 0,
|
||||
'last_booking_date' => $booking['start_date'],
|
||||
'last_reference' => $booking['reference'],
|
||||
'last_status_label' => $booking['status_label'],
|
||||
];
|
||||
}
|
||||
|
||||
$customers[$key]['booking_count']++;
|
||||
$customers[$key]['revenue_cents'] += (int) $booking['subtotal_cents'];
|
||||
|
||||
if ($booking['start_date'] >= $customers[$key]['last_booking_date']) {
|
||||
$customers[$key]['last_booking_date'] = $booking['start_date'];
|
||||
$customers[$key]['last_reference'] = $booking['reference'];
|
||||
$customers[$key]['last_status_label'] = $booking['status_label'];
|
||||
}
|
||||
}
|
||||
|
||||
usort($customers, static function (array $a, array $b): int {
|
||||
$nameComparison = strcmp($a['name'], $b['name']);
|
||||
if ($nameComparison !== 0) {
|
||||
return $nameComparison;
|
||||
}
|
||||
|
||||
return strcmp($a['email'], $b['email']);
|
||||
});
|
||||
|
||||
return $customers;
|
||||
}
|
||||
|
||||
public function getAdminDefaults(): array
|
||||
{
|
||||
return [
|
||||
@@ -243,7 +321,7 @@ final class BookingService
|
||||
return [
|
||||
'requested' => 'Neue Anfrage',
|
||||
'reserved' => 'Reserviert',
|
||||
'confirmed' => 'Bestaetigt',
|
||||
'confirmed' => 'Bestätigt',
|
||||
'completed' => 'Abgeschlossen',
|
||||
'cancelled' => 'Storniert',
|
||||
];
|
||||
@@ -278,16 +356,18 @@ final class BookingService
|
||||
$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';
|
||||
|
||||
foreach ([
|
||||
'Name' => $customerName,
|
||||
'E-Mail' => $email,
|
||||
'Telefon' => $phone,
|
||||
'Strasse' => $street,
|
||||
'Straße' => $street,
|
||||
'PLZ' => $postalCode,
|
||||
'Ort' => $city,
|
||||
'Startdatum' => $startDate,
|
||||
'Enddatum' => $endDate,
|
||||
'Abholdatum' => $startDate,
|
||||
'Rückgabedatum' => $endDate,
|
||||
] as $label => $value) {
|
||||
if ($value === '') {
|
||||
throw new RuntimeException($label . ' ist ein Pflichtfeld.');
|
||||
@@ -295,28 +375,36 @@ final class BookingService
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new RuntimeException('Bitte gib eine gueltige E-Mail-Adresse an.');
|
||||
throw new RuntimeException('Bitte gib eine gültige E-Mail-Adresse an.');
|
||||
}
|
||||
|
||||
if (!$adminMode && !$privacyAccepted) {
|
||||
throw new RuntimeException('Bitte bestätige die Datenschutzerklärung.');
|
||||
}
|
||||
|
||||
if (!$adminMode && !$termsAccepted) {
|
||||
throw new RuntimeException('Bitte bestätige die Mietbedingungen.');
|
||||
}
|
||||
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
|
||||
throw new RuntimeException('Bitte waehle gueltige Mietdaten aus.');
|
||||
throw new RuntimeException('Bitte wähle gültige Mietdaten aus.');
|
||||
}
|
||||
|
||||
$totalDays = $this->calculateRentalDays($startDate, $endDate);
|
||||
if ($totalDays < 1) {
|
||||
throw new RuntimeException('Das Mietende darf nicht vor dem Mietbeginn liegen.');
|
||||
throw new RuntimeException('Die Rückgabe muss nach der Abholung liegen. Ein Miettag entspricht zum Beispiel Montag auf Dienstag.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($status, $this->getStatusOptions())) {
|
||||
throw new RuntimeException('Der Buchungsstatus ist ungueltig.');
|
||||
throw new RuntimeException('Der Buchungsstatus ist ungültig.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($paymentStatus, $this->getPaymentStatusOptions())) {
|
||||
throw new RuntimeException('Der Zahlungsstatus ist ungueltig.');
|
||||
throw new RuntimeException('Der Zahlungsstatus ist ungültig.');
|
||||
}
|
||||
|
||||
$paymentLabels = [
|
||||
'invoice_transfer' => 'Ueberweisung auf Rechnung',
|
||||
'invoice_transfer' => 'Rechnung / Überweisung',
|
||||
'paypal' => 'PayPal',
|
||||
];
|
||||
|
||||
@@ -327,15 +415,15 @@ final class BookingService
|
||||
];
|
||||
|
||||
if (!array_key_exists($paymentMethod, $paymentLabels)) {
|
||||
throw new RuntimeException('Die gewaehlte Zahlungsart ist ungueltig.');
|
||||
throw new RuntimeException('Die gewählte Zahlungsart ist ungültig.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($deliveryMode, $deliveryLabels)) {
|
||||
throw new RuntimeException('Die gewaehlte Lieferart ist ungueltig.');
|
||||
throw new RuntimeException('Die gewählte Lieferart ist ungültig.');
|
||||
}
|
||||
|
||||
if ($pricePerDay < 0) {
|
||||
throw new RuntimeException('Der Tagespreis ist ungueltig.');
|
||||
throw new RuntimeException('Der Tagespreis ist ungültig.');
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -363,6 +451,8 @@ final class BookingService
|
||||
'payment_status_label' => $this->getPaymentStatusOptions()[$paymentStatus],
|
||||
'price_per_day_cents' => $pricePerDay,
|
||||
'subtotal_cents' => $totalDays * $pricePerDay,
|
||||
'privacy_accepted' => $privacyAccepted,
|
||||
'terms_accepted' => $termsAccepted,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -392,6 +482,11 @@ final class BookingService
|
||||
'invoice_id' => null,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
'customer_consents' => [
|
||||
'privacy_accepted' => $payload['privacy_accepted'],
|
||||
'terms_accepted' => $payload['terms_accepted'],
|
||||
'recorded_at' => $now,
|
||||
],
|
||||
'customer' => [
|
||||
'name' => $payload['customer_name'],
|
||||
'company' => $payload['company'],
|
||||
@@ -417,9 +512,9 @@ final class BookingService
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($startDate <= $record['end_date'] && $endDate >= $record['start_date']) {
|
||||
if ($startDate < $record['end_date'] && $endDate > $record['start_date']) {
|
||||
throw new RuntimeException(
|
||||
'Die Fotobox ist im gewaehlten Zeitraum bereits blockiert. Bitte waehle einen anderen Termin.'
|
||||
'Die Fotobox ist im gewählten Zeitraum bereits blockiert. Bitte wähle einen anderen Termin.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -430,7 +525,7 @@ final class BookingService
|
||||
$start = new DateTimeImmutable($startDate);
|
||||
$end = new DateTimeImmutable($endDate);
|
||||
|
||||
return (int) $start->diff($end)->format('%r%a') + 1;
|
||||
return (int) $start->diff($end)->format('%r%a');
|
||||
}
|
||||
|
||||
private function nextInvoiceNumber(array $records): string
|
||||
@@ -466,4 +561,30 @@ final class BookingService
|
||||
{
|
||||
return $prefix . '_' . strtolower(bin2hex(random_bytes(6)));
|
||||
}
|
||||
|
||||
private function formatMonthLabel(string $monthKey): string
|
||||
{
|
||||
[$year, $month] = explode('-', $monthKey) + [null, null];
|
||||
$monthNumber = (int) $month;
|
||||
$monthLabels = [
|
||||
1 => 'Januar',
|
||||
2 => 'Februar',
|
||||
3 => 'März',
|
||||
4 => 'April',
|
||||
5 => 'Mai',
|
||||
6 => 'Juni',
|
||||
7 => 'Juli',
|
||||
8 => 'August',
|
||||
9 => 'September',
|
||||
10 => 'Oktober',
|
||||
11 => 'November',
|
||||
12 => 'Dezember',
|
||||
];
|
||||
|
||||
if (!isset($monthLabels[$monthNumber]) || $year === null) {
|
||||
return $monthKey;
|
||||
}
|
||||
|
||||
return $monthLabels[$monthNumber] . ' ' . $year;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user