Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions assets/controllers/customers_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export default class extends Controller {
url,
method: 'GET',
target,
onSuccess: (html) => {
target.innerHTML = html;
// rAF stellt sicher, dass Bootstrap das neue DOM
// registriert hat, bevor Listener angehängt werden
requestAnimationFrame(() => initReservationHistoryPagination());
}
});
}

Expand All @@ -89,3 +95,69 @@ export default class extends Controller {
});
}
}

function initReservationHistoryPagination() {
const perPage = 10;
const tbody = document.querySelector('#reservationHistoryCollapse tbody');
if (!tbody) return;
const rows = Array.from(tbody.querySelectorAll('tr'));

// Alten Pager aus vorherigem Modal-Aufruf entfernen
const oldPager = document.getElementById('res-history-pager');
if (oldPager) oldPager.remove();

if (rows.length <= perPage) return;

let currentPage = 1;
const pages = Math.ceil(rows.length / perPage);

function showPage(page) {
currentPage = page;
rows.forEach((row, i) => {
row.style.display = (i >= (page-1)*perPage && i < page*perPage) ? '' : 'none';
});
renderPager();
}

function renderPager() {
let pager = document.getElementById('res-history-pager');
if (!pager) {
pager = document.createElement('div');
pager.id = 'res-history-pager';
pager.className = 'px-3 py-2';
document.querySelector('#reservationHistoryCollapse .accordion-body').appendChild(pager);
}
pager.innerHTML = '';
const ul = document.createElement('ul');
ul.className = 'pagination pagination-sm mb-0';

const prev = document.createElement('li');
prev.className = 'page-item' + (currentPage === 1 ? ' disabled' : '');
prev.innerHTML = '<a class="page-link" href="#">&laquo;</a>';
if (currentPage > 1) prev.querySelector('a').addEventListener('click', e => { e.preventDefault(); showPage(currentPage-1); });
ul.appendChild(prev);

for (let i = 1; i <= pages; i++) {
const li = document.createElement('li');
li.className = 'page-item' + (i === currentPage ? ' active' : '');
const p = i;
li.innerHTML = '<a class="page-link" href="#">' + i + '</a>';
li.querySelector('a').addEventListener('click', e => { e.preventDefault(); showPage(p); });
ul.appendChild(li);
}

const next = document.createElement('li');
next.className = 'page-item' + (currentPage === pages ? ' disabled' : '');
next.innerHTML = '<a class="page-link" href="#">&raquo;</a>';
if (currentPage < pages) next.querySelector('a').addEventListener('click', e => { e.preventDefault(); showPage(currentPage+1); });
ul.appendChild(next);
pager.appendChild(ul);
}

const collapse = document.getElementById('reservationHistoryCollapse');
if (collapse) {
collapse.addEventListener('shown.bs.collapse', () => showPage(1), { once: true });
if (collapse.classList.contains('show')) showPage(1);
}
}

18 changes: 14 additions & 4 deletions src/Controller/CustomerServiceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use App\Entity\CustomerAddresses;
use App\Entity\Enum\IDCardType;
use App\Entity\Template;
use App\Repository\ReservationRepository;
use App\Service\CSRFProtectionService;
use App\Service\CustomerService;
use App\Service\TemplatesService;
Expand Down Expand Up @@ -100,14 +101,23 @@ public function searchCustomersAction(ManagerRegistry $doctrine, Request $reques
}

#[Route('/{id}/get', name: 'customers.get.customer', methods: ['GET'], defaults: ['id' => '0'])]
public function getCustomerAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf, $id)
{
public function getCustomerAction(
ManagerRegistry $doctrine,
CSRFProtectionService $csrf,
ReservationRepository $reservationRepo,
$id
) {
$em = $doctrine->getManager();
$customer = $em->getRepository(Customer::class)->find($id);

$reservations = $customer
? $reservationRepo->loadAllReservationsForCustomer($customer)
: [];

return $this->render('Customers/customer_form_show.html.twig', [
'customer' => $customer,
'token' => $csrf->getCSRFTokenForForm(),
'customer' => $customer,
'token' => $csrf->getCSRFTokenForForm(),
'reservations' => $reservations,
]);
}

Expand Down
22 changes: 22 additions & 0 deletions src/Repository/ReservationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -513,4 +513,26 @@ public function findImportedWithoutBookerPaginated(int $page, int $perPage): arr
->getQuery()
->getResult();
}

/**
* Alle Reservierungen eines Gastes – als Buchender (booker)
* oder als Mitreisender – dedupliziert, nach Anreise absteigend.
*
* @return Reservation[]
*/
public function loadAllReservationsForCustomer(\App\Entity\Customer $customer): array
{
return $this->createQueryBuilder('r')
->select('r', 'a', 'rs', 'i')
->leftJoin('r.appartment', 'a')
->leftJoin('r.reservationStatus', 'rs')
->leftJoin('r.invoices', 'i')
->where('r.booker = :customer')
->orWhere(':customer MEMBER OF r.customers')
->setParameter('customer', $customer)
->orderBy('r.startDate', 'DESC')
->distinct()
->getQuery()
->getResult();
}
}
16 changes: 9 additions & 7 deletions templates/Customers/customer_form_show.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
</small>
</h4>
</div>
</div>
<div class="row">
{% include 'Customers/_show_info.html.twig' with {'customer' : customer} %}
</div>
{% include 'Customers/customer_show_address_fields_short.html.twig' with {'customer' : customer} %}
</div>

</div>
<div class="row">
{% include 'Customers/_show_info.html.twig' with {'customer' : customer} %}
</div>
{% include 'Customers/customer_show_address_fields_short.html.twig' with {'customer' : customer} %}
{% include 'Customers/customer_reservation_history.html.twig' with {'reservations': reservations} %}

</div>

<form id="customer-form-{{ customer.id }}"
data-controller="customer-form"
data-customer-form-delete-url-value="{{ path('customers.delete.customer', {'id': customer.id}) }}">
Expand Down
81 changes: 81 additions & 0 deletions templates/Customers/customer_reservation_history.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<div class="row">
<div class="col">
<div class="accordion accordion-flush" id="reservationHistoryAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="reservationHistoryHeading">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#reservationHistoryCollapse"
aria-expanded="false"
aria-controls="reservationHistoryCollapse">
{{ 'customer.reservation.history'|trans }}
<span class="badge bg-secondary ms-2">{{ reservations|length }}</span>
</button>
</h2>
<div id="reservationHistoryCollapse"
class="accordion-collapse collapse"
aria-labelledby="reservationHistoryHeading"
data-bs-parent="#reservationHistoryAccordion">
<div class="accordion-body p-0 mt-3">
{% if reservations is empty %}
<p class="text-muted small p-3 mb-0">{{ 'customer.reservation.history.empty'|trans }}</p>
{% else %}
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th>{{ 'housekeeping.summary.arrive'|trans({}, 'Housekeeping') }}</th>
<th>{{ 'housekeeping.summary.depart'|trans({}, 'Housekeeping') }}</th>
<th>{{ 'registrationbook.stays'|trans }}</th>
<th>{{ 'statistics.tourism.room_label'|trans }}</th>
<th>{{ 'reservation.status'|trans }}</th>
<th>{{ 'invoice.number.short'|trans }}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for res in reservations %}
<tr class="{{ res.reservationStatus and res.reservationStatus.code == 'canceled_noshow' ? 'text-muted' : '' }}">
<td>{{ res.startDate|date('d.m.y') }}</td>
<td>{{ res.endDate|date('d.m.y') }}</td>
<td>{{ res.amount }}</td>
<td>{{ res.appartment ? res.appartment.number ~ ' ' ~ res.appartment.description : '–' }}</td>
<td>{{ res.reservationStatus ? res.reservationStatus.name : '–' }}</td>
<td>
{% if res.invoices is empty %}
<span class="text-muted">–</span>
{% else %}
{% for invoice in res.invoices %}
<a href="#"
data-action="click->customers#openModalAction"
data-url="{{ path('invoices.get.invoice', {id: invoice.id}) }}"
data-title="{{ 'invoice.details'|trans }}"
class="text-decoration-none {{ invoice.status == 1 ? 'text-danger' : (invoice.status == 2 ? 'text-success' : (invoice.status == 3 ? 'text-warning' : 'text-muted')) }}"
title="{{ ('invoice.status.' ~ (invoice.status == 1 ? 'unpaid' : invoice.status == 2 ? 'paid' : invoice.status == 3 ? 'prepaid' : 'canceled'))|trans }}">
{{ invoice.number ? invoice.number : '–' }}
</a>{% if not loop.last %}<br>{% endif %}
{% endfor %}
{% endif %}
</td>
<td>
<a href="#"
data-action="click->customers#openModalAction"
data-url="{{ path('reservations.get.reservation', {id: res.id}) }}"
data-title="{{ 'reservation.details'|trans }}"
class="btn btn-sm btn-outline-secondary py-0"
title="{{ 'reservation.details'|trans }}">
<i class="fas fa-external-link-alt"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
8 changes: 8 additions & 0 deletions translations/Customers/messages.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@
<source>customer.mandateReference.label</source>
<target>Mandatsreferenznr.</target>
</trans-unit>
<trans-unit id="customer.reservation.history">
<source>customer.reservation.history</source>
<target>Reservierungshistorie</target>
</trans-unit>
<trans-unit id="customer.reservation.history.empty">
<source>customer.reservation.history.empty</source>
<target>Keine Reservierungen vorhanden.</target>
</trans-unit>
</body>
</file>
</xliff>
2 changes: 2 additions & 0 deletions translations/Customers/messages.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ customer.accountIBAN.label: IBAN
customer.accountIBAN.hint: >-
The IBAN and mandate reference number must be filled in for the direct debit payment method.
customer.mandateReference.label: Mandate Reference Number
customer.reservation.history: "Reservation history"
customer.reservation.history.empty: "No reservations found."