From eb45693257b738599bce6e1590843b8b6a5a05e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 30 Sep 2026 18:04:33 +0200 Subject: [PATCH] feat: panel layout for the accommodation booking edit and detail pages --- .../AccommodationBooking/EditController.php | 20 +- .../Groups/AccommodationBookingPeriodType.php | 5 +- .../_period_row.html.twig | 32 ++ .../_price_period.html.twig | 14 + .../accommodation_booking/edit.html.twig | 327 +++++++------ .../modal_period.html.twig | 64 ++- .../accommodation_booking/show.html.twig | 449 +++++++++--------- templates/components/form/section.html.twig | 11 + .../EditControllerTest.php | 7 +- 9 files changed, 531 insertions(+), 398 deletions(-) create mode 100644 templates/admin/accommodation_booking/_period_row.html.twig create mode 100644 templates/admin/accommodation_booking/_price_period.html.twig create mode 100644 templates/components/form/section.html.twig diff --git a/src/Controller/Admin/AccommodationBooking/EditController.php b/src/Controller/Admin/AccommodationBooking/EditController.php index bb73e028..a2bd2f52 100644 --- a/src/Controller/Admin/AccommodationBooking/EditController.php +++ b/src/Controller/Admin/AccommodationBooking/EditController.php @@ -12,6 +12,7 @@ use App\Form\Admin\Groups\AccommodationBookingType; use App\Repository\Groups\AdditionalServiceRepository; use App\Repository\Groups\BoardServiceRepository; use App\Repository\UserRepository; +use App\Service\AccommodationBookingBreakdownCalculator; use App\Service\AccommodationBookingService; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -33,6 +34,7 @@ class EditController extends AbstractController private readonly AdditionalServiceRepository $additionalServiceRepo, private readonly UserRepository $userRepo, private readonly AccommodationBookingService $bookingService, + private readonly AccommodationBookingBreakdownCalculator $breakdownCalculator, ) { } @@ -128,10 +130,7 @@ class EditController extends AbstractController // The submitted changes stay on the entity but are never flushed, so the form // comes back filled in with the error on it. Logging is safe here: the database // handler writes through DBAL, not the EntityManager. - return $this->render('admin/accommodation_booking/edit.html.twig', [ - 'booking' => $booking, - 'form' => $form, - ]); + return $this->renderForm($booking, $form); } // Editing never moves the booking on and never mails the customer: every status @@ -160,9 +159,22 @@ class EditController extends AbstractController return $this->redirectToRoute('app_admin_accommodationbooking_show', $parameters); } + return $this->renderForm($booking, $form); + } + + /** + * The sidebar shows the stored price, not one computed from the submitted values: after a + * refused save the entity carries the unsaved changes, and the price there is labelled as the + * state of the last save. + * + * @param FormInterface $form + */ + private function renderForm(AccommodationBooking $booking, FormInterface $form): Response + { return $this->render('admin/accommodation_booking/edit.html.twig', [ 'booking' => $booking, 'form' => $form, + 'priceBreakdown' => $this->breakdownCalculator->compute($booking), ]); } diff --git a/src/Form/Admin/Groups/AccommodationBookingPeriodType.php b/src/Form/Admin/Groups/AccommodationBookingPeriodType.php index 10a896ae..b3eb1b29 100644 --- a/src/Form/Admin/Groups/AccommodationBookingPeriodType.php +++ b/src/Form/Admin/Groups/AccommodationBookingPeriodType.php @@ -18,10 +18,11 @@ class AccommodationBookingPeriodType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options): void { $builder + // "Überschrift" rather than "Bezeichnung", which the lines below use for their own field. ->add('label', TextType::class, [ - 'label' => 'Bezeichnung', + 'label' => 'Überschrift (optional)', 'required' => false, - 'help' => 'Überschrift in der Preisaufstellung, z. B. „Vorausteam“. Ohne Bezeichnung steht dort nur der Zeitraum.', + 'attr' => ['placeholder' => 'z. B. Vorausteam'], ]) ->add('nights', IntegerType::class, [ 'label' => 'Nächte', diff --git a/templates/admin/accommodation_booking/_period_row.html.twig b/templates/admin/accommodation_booking/_period_row.html.twig new file mode 100644 index 00000000..47b8632c --- /dev/null +++ b/templates/admin/accommodation_booking/_period_row.html.twig @@ -0,0 +1,32 @@ +{#- One split-off period on the edit page, with its actions. Context: booking, period. -#} +{% set period_params = { id: period.id } %} +{% if app.request.query.get('r') %} + {% set period_params = period_params | merge({ r: app.request.query.get('r') }) %} +{% endif %} + + {{ period.dateFrom | date('d.m.Y') }} – {{ period.dateTo | date('d.m.Y') }} + {{ period.nights }} {{ period.nights != 1 ? 'Nächte' : 'Nacht' }} + + {% if period.label %} +
{{ period.label }}
+ {% endif %} + {% for line in period.lines %} +
{{ line.label }}
+ {% else %} +
keine Positionen
+ {% endfor %} + + + {{ (period.total / 100) | format_currency(booking.pricingCurrency ?? booking.accommodation.currency) }} + + + + + bearbeiten + + + entfernen + + + + diff --git a/templates/admin/accommodation_booking/_price_period.html.twig b/templates/admin/accommodation_booking/_price_period.html.twig new file mode 100644 index 00000000..b0ee7d82 --- /dev/null +++ b/templates/admin/accommodation_booking/_price_period.html.twig @@ -0,0 +1,14 @@ +{#- One split-off period of the price table on the detail page. Context: period, currency. -#} + + + {% if period.label ?? null %}{{ period.label }} · {% endif %} + {{ period.dateFrom | date('d.m.Y') }} – {{ period.dateTo | date('d.m.Y') }}, + {{ period.nights }} {{ period.nights != 1 ? 'Nächte' : 'Nacht' }} + + +{% for line in period.lines %} + + {{ line.label }} + {{ (line.amount / 100) | format_currency(currency) }} + +{% endfor %} diff --git a/templates/admin/accommodation_booking/edit.html.twig b/templates/admin/accommodation_booking/edit.html.twig index dc6d748a..3751a735 100644 --- a/templates/admin/accommodation_booking/edit.html.twig +++ b/templates/admin/accommodation_booking/edit.html.twig @@ -22,155 +22,200 @@ {% form_theme form 'forms_admin.html.twig' %} {{ form_start(form) }} -
-

Kontaktdaten

- {{ form_row(form.groupName) }} - {{ form_row(form.salutation) }} - {{ form_row(form.firstName) }} - {{ form_row(form.lastName) }} - {{ form_row(form.email) }} - {{ form_row(form.phone) }} - {{ form_row(form.street) }} - {{ form_row(form.zip) }} - {{ form_row(form.city) }} + {{ form_errors(form) }} + {# Ordered by what gets negotiated: the stay first, the contact data last. The sidebar keeps + status, price and the save button in view on wide screens. #} +
+
+ +
+ {{ form_row(form.dateFrom) }} + {{ form_row(form.dateTo) }} +
+
+ {{ form_row(form.paxCount) }} + {{ form_row(form.minorsCount) }} + {{ form_row(form.childrenCount) }} +
-

Reisedaten

- {{ form_row(form.dateFrom) }} - {{ form_row(form.dateTo) }} - {{ form_row(form.paxCount) }} - {{ form_row(form.minorsCount) }} - {{ form_row(form.childrenCount) }} - - {# Saved by their own modals, which reprice the booking on the spot — see SplitPeriodController. #} -
-

Zeiträume

-

- Anreise und Abreise umfassen den ganzen Aufenthalt. Abgeteilte Zeiträume, etwa für ein - Vorausteam, werden mit eigenen Positionen berechnet; Personen und Leistungen oben gelten - nur für den Zeitraum der Gruppe. -

- - {% if booking.leadingPeriod %} - {% set p = booking.leadingPeriod %} - {{ block('period_row') }} - {% endif %} - - - - -
{{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }}{{ booking.mainNights }} {{ booking.mainNights != 1 ? 'Nächte' : 'Nacht' }}Gruppe - {# One period per side, so only the free sides are offered. #} - {% if booking.leadingPeriod is null or booking.trailingPeriod is null %} - - {% if booking.leadingPeriod is null %} - - am Anfang abteilen - - {% endif %} - {% if booking.trailingPeriod is null %} - - am Ende abteilen - - {% endif %} - + {# Saved by their own modals, which reprice the booking on the spot — see SplitPeriodController. #} +
+
Zeiträume
+

+ Anreise und Abreise umfassen den ganzen Aufenthalt. Abgeteilte Zeiträume, etwa für ein + Vorausteam, werden mit eigenen Positionen berechnet; Personen und Leistungen gelten + nur für den Zeitraum der Gruppe. +

+ + {% if booking.leadingPeriod %} + {% include 'admin/accommodation_booking/_period_row.html.twig' with { period: booking.leadingPeriod } %} {% endif %} - - - {% if booking.trailingPeriod %} - {% set p = booking.trailingPeriod %} - {{ block('period_row') }} + + + + + + + {% if booking.trailingPeriod %} + {% include 'admin/accommodation_booking/_period_row.html.twig' with { period: booking.trailingPeriod } %} + {% endif %} +
{{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }}{{ booking.mainNights }} {{ booking.mainNights != 1 ? 'Nächte' : 'Nacht' }}Gruppe + {# One period per side, so only the free sides are offered. #} + {% if booking.leadingPeriod is null or booking.trailingPeriod is null %} + + {% if booking.leadingPeriod is null %} + + am Anfang abteilen + + {% endif %} + {% if booking.trailingPeriod is null %} + + am Ende abteilen + + {% endif %} + + {% endif %} +
+
+ + + {% if form.boardService is defined or form.selectedAdditionalServices is defined %} + +
+ {% if form.boardService is defined %} + {{ form_row(form.boardService) }} + {% endif %} + {% if form.selectedAdditionalServices is defined %} +
+ {{ form_label(form.selectedAdditionalServices) }} + {# Rows written out here rather than through the theme's choice_widget_expanded, + whose first:pt-0 would shift only the first cell of the two columns. #} +
+ {% for child in form.selectedAdditionalServices %} +
+
+ +
+
+ {{ form_widget(child) }} +
+
+ {% endfor %} +
+ {{ form_errors(form.selectedAdditionalServices) }} +
+ {% endif %} +
+
+ {% endif %} + + +
+ {{ form_row(form.accommodationDiscount) }} + {{ form_row(form.boardServiceDiscount) }} + {{ form_row(form.additionalServicesDiscount) }} +
+
+ {{ form_row(form.totalDiscountLabel, { row_attr: { class: 'sm:col-span-2' } }) }} + {{ form_row(form.totalDiscountAmount) }} +
+
+ + +
+ {{ form_row(form.groupName, { row_attr: { class: 'sm:col-span-2' } }) }} + {{ form_row(form.salutation) }} + + {{ form_row(form.firstName) }} + {{ form_row(form.lastName) }} + {{ form_row(form.email) }} + {{ form_row(form.phone) }} + {{ form_row(form.street, { row_attr: { class: 'sm:col-span-2' } }) }} + {{ form_row(form.zip) }} + {{ form_row(form.city) }} +
+
+ + + {{ form_widget(form.remarks) }} + {{ form_errors(form.remarks) }} + + + +
-
+
- {% if form.boardService is defined or form.selectedAdditionalServices is defined %} -

Leistungen

- {% if form.boardService is defined %} - {{ form_row(form.boardService) }} + {% if priceBreakdown is not null %} + {% set currency = booking.pricingCurrency ?? priceBreakdown.currency %} + + + {% if priceBreakdown.periods is not empty %} + + + + + + + + + {% endif %} + {% if priceBreakdown.discountedTotal != priceBreakdown.total %} + {% if priceBreakdown.periods is empty %} + + + + + {% endif %} + + + + + {% endif %} + + + + +
Gruppe{{ ((priceBreakdown.total - priceBreakdown.periodsTotal) / 100) | format_currency(currency) }}
Zeiträume{{ (priceBreakdown.periodsTotal / 100) | format_currency(currency) }}
Preis{{ (priceBreakdown.total / 100) | format_currency(currency) }}
Rabatte– {{ ((priceBreakdown.total - priceBreakdown.discountedTotal) / 100) | format_currency(currency) }}
Gesamtpreis{{ (priceBreakdown.discountedTotal / 100) | format_currency(currency) }}
+

+ Stand der letzten Speicherung. Änderungen oben werden erst beim Speichern neu berechnet. +

+
{% endif %} - {% if form.selectedAdditionalServices is defined %} - {{ form_row(form.selectedAdditionalServices) }} - {% endif %} - {% endif %} -

Status & Rabatt

- {# Read-only: the status only ever moves through a named action on the detail page, - so that it cannot be set without the email and timestamp that belong to it. #} -

- Status: - {% include 'admin/accommodation_booking/_status_label.html.twig' %} - · Herkunft: {{ booking.origin.label }} -

- {% if booking.acceptedAt is not null %} -

- Angenommen am {{ booking.acceptedAt | date('d.m.Y, H:i') }} -

- {% endif %} - {% if booking.confirmedAt is not null %} -

- Bestätigt am {{ booking.confirmedAt | date('d.m.Y, H:i') }} -

- {% endif %} - {% if form.managedBy is defined %} -
- {{ form_row(form.managedBy) }} + - {% endif %} -
- {{ form_row(form.accommodationDiscount) }} - {{ form_row(form.boardServiceDiscount) }} - {{ form_row(form.additionalServicesDiscount) }} -
-
- {{ form_row(form.totalDiscountLabel) }} - {{ form_row(form.totalDiscountAmount) }} -
-
- {{ form_row(form.remarks) }} -
-
-
- - zurück - - +
{{ form_rest(form) }} {{ form_end(form) }} {% endblock %} - -{% block period_row %} - {% set period_params = { id: p.id } %} - {% if app.request.query.get('r') %} - {% set period_params = period_params | merge({ r: app.request.query.get('r') }) %} - {% endif %} - - {{ p.dateFrom | date('d.m.Y') }} – {{ p.dateTo | date('d.m.Y') }} - {{ p.nights }} {{ p.nights != 1 ? 'Nächte' : 'Nacht' }} - - {% if p.label %} -
{{ p.label }}
- {% endif %} - {% for line in p.lines %} -
{{ line.label }}
- {% else %} -
keine Positionen
- {% endfor %} - - - {{ (p.total / 100) | format_currency(booking.pricingCurrency ?? booking.accommodation.currency) }} - - - - - bearbeiten - - - entfernen - - - - -{% endblock %} diff --git a/templates/admin/accommodation_booking/modal_period.html.twig b/templates/admin/accommodation_booking/modal_period.html.twig index 45d20e29..1387229f 100644 --- a/templates/admin/accommodation_booking/modal_period.html.twig +++ b/templates/admin/accommodation_booking/modal_period.html.twig @@ -1,12 +1,21 @@ {% extends 'htmx_modal_admin.html.twig' %} {% form_theme form 'forms_admin.html.twig' %} +{# One line as a row of bare widgets; the column headings above name them once for all rows. + Plain flex rather than an arbitrary grid template: the modal is loaded into a page whose + stylesheet may predate any class used only here. #} {% macro line_row(line) %} -
-
{{ form_row(line.label) }}
-
{{ form_row(line.amount) }}
+
+
+ {{ form_widget(line.label, { attr: { placeholder: 'z. B. Übernachtung' } }) }} + {{ form_errors(line.label) }} +
+
+ {{ form_widget(line.amount) }} + {{ form_errors(line.amount) }} +
-
{% do form.lines.setRendered %} -
+ +
diff --git a/templates/admin/accommodation_booking/show.html.twig b/templates/admin/accommodation_booking/show.html.twig index e90500cb..417a35b6 100644 --- a/templates/admin/accommodation_booking/show.html.twig +++ b/templates/admin/accommodation_booking/show.html.twig @@ -1,103 +1,72 @@ {% extends 'layout_admin.html.twig' %} {% block content %} + {% set edit_params = { 'id': booking.id } %} + {% if app.request.query.get('r') %} + {% set edit_params = edit_params | merge({ 'r': app.request.query.get('r') }) %} + {% endif %} + {{ booking.recordLabel }} · {% include 'admin/accommodation_booking/_status_label.html.twig' %} {{ booking.groupName }} +

{{ booking.accommodation.name }}

-
-
-

Kontakt

-
-
Gruppenname
-
{{ booking.groupName }}
-
Anrede
-
{{ booking.salutation | default('–') }}
-
Name
-
{{ booking.firstName }} {{ booking.lastName }}
-
E-Mail
-
{{ booking.email }}
-
Telefon
-
{{ booking.phone | default('–') }}
-
Adresse
-
- {% if booking.street %} - {{ booking.street }}
- {{ booking.zip }} {{ booking.city }} - {% else %} - – - {% endif %} -
-
-
- -
-

Reisedaten

-
-
Gruppenhaus
-
{{ booking.accommodation.name }}
-
Anreise
-
{{ booking.dateFrom | date('d.m.Y') }}
-
Abreise
-
{{ booking.dateTo | date('d.m.Y') }}
-
Nächte
-
{{ booking.nights }}
- {% if booking.periods is not empty %} -
Zeitraum Gruppe
-
- {{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }}, - {{ booking.mainNights }} {{ booking.mainNights != 1 ? 'Nächte' : 'Nacht' }} -
- {% endif %} -
Anzahl Personen
-
{{ booking.paxCount }}
- {% if booking.minorsCount > 0 %} -
davon Kinder (0–3 Jahre)
-
{{ booking.minorsCount }}
- {% endif %} - {% if booking.childrenCount > 0 %} -
davon Kinder (4–{{ booking.accommodation.maxAdolescentAge }} Jahre)
-
{{ booking.childrenCount }}
- {% endif %} -
Herkunft
-
{{ booking.origin.label }}
-
Status
-
{% include 'admin/accommodation_booking/_status_label.html.twig' %}
- {% if booking.acceptedAt is not null %} -
Angenommen am
-
{{ booking.acceptedAt | date('d.m.Y, H:i') }}
- {% endif %} - {% if booking.confirmedAt is not null %} -
Bestätigt am
-
{{ booking.confirmedAt | date('d.m.Y, H:i') }}
- {% endif %} -
-
- - {% if booking.boardServiceLabel or booking.additionalServices | length > 0 %} -
-

Gebuchte Leistungen

+ {# Same arrangement as the edit form: what was booked in the main column, status, the next + step and the actions in a sidebar that stays in view on wide screens. #} +
+
+
- {% if booking.boardServiceLabel %} -
Verpflegung
-
{{ booking.boardServiceLabel }}
+
Gruppenhaus
+
{{ booking.accommodation.name }}
+
Anreise
+
{{ booking.dateFrom | date('d.m.Y') }}
+
Abreise
+
{{ booking.dateTo | date('d.m.Y') }}
+
Nächte
+
{{ booking.nights }}
+ {% if booking.periods is not empty %} +
Zeitraum Gruppe
+
+ {{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }}, + {{ booking.mainNights }} {{ booking.mainNights != 1 ? 'Nächte' : 'Nacht' }} +
+ {% endif %} +
Anzahl Personen
+
{{ booking.paxCount }}
+ {% if booking.minorsCount > 0 %} +
davon Kinder (0–3 Jahre)
+
{{ booking.minorsCount }}
+ {% endif %} + {% if booking.childrenCount > 0 %} +
davon Kinder (4–{{ booking.accommodation.maxAdolescentAge }} Jahre)
+
{{ booking.childrenCount }}
{% endif %} - {% for service in booking.additionalServices %} -
{{ loop.first ? 'Zusatzleistungen' : '' }}
-
{{ service.label }}
- {% endfor %}
-
- {% endif %} + - {% if priceBreakdown is not null %} -
-

Preise

+ {% if booking.boardServiceLabel or booking.additionalServices | length > 0 %} + +
+ {% if booking.boardServiceLabel %} +
Verpflegung
+
{{ booking.boardServiceLabel }}
+ {% endif %} + {% for service in booking.additionalServices %} +
{{ loop.first ? 'Zusatzleistungen' : '' }}
+
{{ service.label }}
+ {% endfor %} +
+
+ {% endif %} + + {% if priceBreakdown is not null %} {% set currency = booking.pricingCurrency ?? priceBreakdown.currency %} + {% for period in priceBreakdown.periods | filter(p => p.position == 'leading') %} - {{ block('period_rows') }} + {% include 'admin/accommodation_booking/_price_period.html.twig' %} {% endfor %} {% if priceBreakdown.periods is not empty %} @@ -158,7 +127,7 @@ {% endif %} {% for period in priceBreakdown.periods | filter(p => p.position == 'trailing') %} - {{ block('period_rows') }} + {% include 'admin/accommodation_booking/_price_period.html.twig' %} {% endfor %} {% endif %}
@@ -192,153 +161,181 @@
-
- {% endif %} + + {% endif %} - {% if booking.remarks %} -
-

Bemerkungen

-

{{ booking.remarks | nl2br }}

-
- {% endif %} -
+ +
+
Gruppenname
+
{{ booking.groupName }}
+
Anrede
+
{{ booking.salutation | default('–') }}
+
Name
+
{{ booking.firstName }} {{ booking.lastName }}
+
E-Mail
+
{{ booking.email }}
+
Telefon
+
{{ booking.phone | default('–') }}
+
Adresse
+
+ {% if booking.street %} + {{ booking.street }}
+ {{ booking.zip }} {{ booking.city }} + {% else %} + – + {% endif %} +
+
+
-
- Eingegangen: {{ booking.createdAt | date('d.m.Y, H:i') }} - {% if booking.updatedAt is not null and booking.updatedAt != booking.createdAt %} - · Aktualisiert: {{ booking.updatedAt | date('d.m.Y, H:i') }} - {% endif %} -
- - {% if booking.draft or booking.requested %} -
-

Angebot

-

- Das Angebot wurde dem Kunden noch nicht zugestellt. Beim Versand wird der Zugangslink - erzeugt und die Buchung wartet anschließend auf die Annahme durch den Kunden. -

-
- - -
+ {% if booking.remarks %} + +

{{ booking.remarks | nl2br }}

+
+ {% endif %}
- {% endif %} - {% if booking.received %} -
-

Bestätigung

-

- Die Buchung des Kunden ist eingegangen, aber es wurde noch keine Bestätigung verschickt. -

- -
- {% endif %} +
{% endblock %} - -{# One split-off period of the breakdown: its range as a heading, then its lines. #} -{% block period_rows %} - - - {% if period.label ?? null %}{{ period.label }} · {% endif %} - {{ period.dateFrom | date('d.m.Y') }} – {{ period.dateTo | date('d.m.Y') }}, - {{ period.nights }} {{ period.nights != 1 ? 'Nächte' : 'Nacht' }} - - - {% for line in period.lines %} - - {{ line.label }} - {{ (line.amount / 100) | format_currency(currency) }} - - {% endfor %} -{% endblock %} diff --git a/templates/components/form/section.html.twig b/templates/components/form/section.html.twig new file mode 100644 index 00000000..d990328d --- /dev/null +++ b/templates/components/form/section.html.twig @@ -0,0 +1,11 @@ +{#- A titled panel grouping the fields of a long admin form. No overflow-hidden: the dots + dropdowns inside must be able to open past its edge. -#} +
+
+ {{ title }} + {% block actions %}{% endblock %} +
+
+ {% block content %}{% endblock %} +
+
diff --git a/tests/Controller/Admin/AccommodationBooking/EditControllerTest.php b/tests/Controller/Admin/AccommodationBooking/EditControllerTest.php index e3d457a3..51736503 100644 --- a/tests/Controller/Admin/AccommodationBooking/EditControllerTest.php +++ b/tests/Controller/Admin/AccommodationBooking/EditControllerTest.php @@ -12,6 +12,7 @@ use App\Exception\ImplausibleBookingPriceException; use App\Repository\Groups\AdditionalServiceRepository; use App\Repository\Groups\BoardServiceRepository; use App\Repository\UserRepository; +use App\Service\AccommodationBookingBreakdownCalculator; use App\Service\AccommodationBookingService; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\DataProvider; @@ -72,6 +73,7 @@ class EditControllerTest extends TestCase $this->createStub(AdditionalServiceRepository::class), $this->createStub(UserRepository::class), $bookingService, + $this->createStub(AccommodationBookingBreakdownCalculator::class), $form, ); @@ -108,6 +110,7 @@ class EditControllerTest extends TestCase $this->createStub(AdditionalServiceRepository::class), $this->createStub(UserRepository::class), $bookingService, + $this->createStub(AccommodationBookingBreakdownCalculator::class), $form, ); @@ -171,6 +174,7 @@ class EditControllerTest extends TestCase $this->createStub(AdditionalServiceRepository::class), $userRepo, $this->createStub(AccommodationBookingService::class), + $this->createStub(AccommodationBookingBreakdownCalculator::class), $form, $granted, ); @@ -189,10 +193,11 @@ final class TestableEditController extends EditController AdditionalServiceRepository $additionalServiceRepo, UserRepository $userRepo, AccommodationBookingService $bookingService, + AccommodationBookingBreakdownCalculator $breakdownCalculator, private readonly FormInterface $form, private readonly bool $granted = false, ) { - parent::__construct($entityManager, $logger, $boardServiceRepo, $additionalServiceRepo, $userRepo, $bookingService); + parent::__construct($entityManager, $logger, $boardServiceRepo, $additionalServiceRepo, $userRepo, $bookingService, $breakdownCalculator); } protected function createForm(string $type, mixed $data = null, array $options = []): FormInterface