-
Notifications
You must be signed in to change notification settings - Fork 17
Verwendung Operations Report Templates
This page documents operations-specific template data. General editor behavior is documented here:
Operations report templates generate PDF output (e.g. housekeeping, frontdesk, meals, management summaries) from the selected date range, subsidiary, and occupancy filters.
-
filters(array) -
rangeView(array) -
dayViews(array, alias ofrangeView.dayViews) -
reservations(array, alias ofrangeView.reservations) -
occupancyLabels(array) -
statusLabels(array) -
invoiceStatusLabels(array) -
simple(array) -
statistics(array, optional; only added if template text containsstatistics.) -
touristTax(array, optional; only added if template text containstouristTax)
simple keeps complex logic in PHP and provides template-safe, flat data for visual editing (data-repeat, data-if).
-
simple.meta.periodLabel(string,d.m.Yord.m.Y - d.m.Y) -
simple.meta.generatedAt(string,d.m.Y H:i) -
simple.meta.subsidiaryName(string|null, concrete subsidiary name when a subsidiary is selected) -
simple.meta.subsidiaryAllLabelKey(translation key; default:housekeeping.subsidiary.all) -
simple.meta.occupancyTypeLabelKeys(string[]) -
simple.meta.dayKey(string,Y-m-d)
Shared day list for week-like tables.
simple.days[]:
-
day.date(DateTimeImmutable) -
day.dateKey(string,Y-m-d) -
day.label(string,d.m.)
Normalized reservation rows reused across reports.
simple.reservations[]:
-
row.roomNumber(string) -
row.roomLabel(string) -
row.guest(string) -
row.persons(int|string) -
row.start(DateTimeImmutable) -
row.end(DateTimeImmutable) -
row.period(string) -
row.frontdeskSectionKey(string|null) -
row.invoiceStatusLabelKey(string|null) -
row.apartmentId(int|null)
Report-specific shaped data based on the shared structures above.
-
simple.views.housekeepingDay.date(DateTimeImmutable) -
simple.views.housekeepingDay.rows[]with: -
row.date(string,Y-m-d) -
row.room(string) -
row.occupancyLabelKey(string) -
row.guests(int|string) -
row.reservationSummary(string) -
row.statusLabelKey(string|null) -
row.assignedTo(string) -
row.note(string)
simple.views.housekeepingWeek.rows[]-
row.apartmentLabel(string) -
row.cells[]with: -
cell.occupancyLabelKey(string) -
cell.statusLabelKey(string) -
cell.guests(string)
simple.views.frontdesk.sections[]-
section.titleKey(translation key) -
section.rows[]with: -
row.guest(string) -
row.persons(int|string) -
row.room(string) -
row.period(string) -
row.invoiceStatusLabelKey(string|null)
simple.views.mealsChecklist.rows[]-
row.room(string) -
row.guest(string) -
row.cells(string[])
-
filters.start(DateTimeImmutable) -
filters.end(DateTimeImmutable) -
filters.subsidiary(Subsidiary|null) -
filters.occupancyTypes(string[]) -
filters.template(Template, export only) -
filters.subsidiaryId(string, e.g."all")
Built by HousekeepingViewService::buildRangeView():
-
rangeView.start(DateTimeImmutable) -
rangeView.end(DateTimeImmutable) -
rangeView.days(DateTimeImmutable[]) -
rangeView.apartments(Appartment[]) -
rangeView.reservations(Reservation[]) -
rangeView.dayViews(array<string, DayView>)
DayView rows include:
-
row.apartment(Appartment) -
row.occupancyType(FREE|STAYOVER|ARRIVAL|DEPARTURE|TURNOVER) -
row.guestCount(int|null) -
row.reservationSummary(string|null) -
row.status(RoomDayStatus|null) -
row.apartmentReservations(Reservation[])
-
occupancyLabels: occupancy type value -> translation key -
statusLabels: housekeeping status value -> translation key -
invoiceStatusLabels: invoice status value -> translation key
Only present when template text contains statistics..
-
statistics.range.start(DateTimeImmutable) -
statistics.range.end(DateTimeImmutable) -
statistics.countryNames(array<string, string>, locale-resolved ISO-alpha-2 → country name) -
statistics.months[]: -
entry.year(int) -
entry.month(int) -
entry.metrics(array) -
entry.warnings(string[])
entry.metrics.tourism includes (in addition to the country/state buckets):
-
metrics.tourism.overnights_by_guest_category(array<int, int>, keyed byGuestCategory.id→ overnight count) -
metrics.tourism.overnights_by_country_and_category(array<string, array<int, float>>, keyed by ISO country code → category id → overnight count; fractional because category counts are split proportionally across linked customers' addresses)
Only present when template text contains touristTax. Live-calculated via TouristTaxService::calculateForReservation() — the same source of truth as the invoice positions and the booking journal. If the hotelier changes a tariff or report-group setting, the next report render shows the new values automatically.
-
touristTax.range.start(DateTimeImmutable) -
touristTax.range.end(DateTimeImmutable) -
touristTax.guestCategories[]— active guest categories for the selected subsidiary: -
category.id(int) -
category.name(string) -
category.statisticalGroup(string:ADULT|CHILD|INFANT|OTHER) -
category.isCountedInOccupancy(bool) -
category.sortOrder(int) -
touristTax.months[]— one entry per month inside the reporting range: -
entry.year(int) -
entry.month(int) -
entry.taxes— keyed byTouristTax.id, only contains taxes that are active and valid within this month (sorted bysortOrder): -
tax.taxId(int) -
tax.name(string) -
tax.sortOrder(int) -
tax.appliesOnlyToAdult(bool) — when true, only categories withstatisticalGroup === ADULTcontribute -
tax.calculationMode(string:per_night_flat|percent_per_room) -
tax.percentageRate(float|null) — only for percent modes -
tax.percentageBase(string|null:net|gross) — only for percent modes -
tax.totalNights(int) — sum across all groups -
tax.totalAmount(float) — money total -
tax.groups[]— one row per report group (multipleTouristTaxRateentries sharing the samereportGrouptext are merged here; rates without areportGroupfall back to one row perGuestCategory): -
group.label(string) —reportGroupif set, otherwise the category name -
group.reportGroup(string|null) — raw report-group key, useful for debugging -
group.pricePerNight(float) -
group.pricePerNightConsistent(bool) —falsewhen merged rates have differing tariffs (signals a config issue) -
group.totalNights(int) -
group.totalCount(int) -
group.totalAmount(float) -
group.categoryIds(int[]) — whichGuestCategory.ids ended up in this group
<div class="header">
<div style="text-align:center; color:#666; font-size:11px;">
[[ simple.meta.periodLabel ]]
<span data-repeat="simple.meta.occupancyTypeLabelKeys" data-repeat-as="type">
| [[ occupancyLabels[type]|trans({}, 'Housekeeping') ]]
</span>
| [[ simple.meta.subsidiaryName ?: (simple.meta.subsidiaryAllLabelKey|trans({}, 'Housekeeping')) ]]
| [[ simple.meta.generatedAt ]]
</div>
</div><table>
<tr>
<th>Room</th>
<th data-repeat="simple.days" data-repeat-as="day">[[ day.label ]]</th>
</tr>
<tr data-repeat="simple.views.housekeepingWeek.rows" data-repeat-as="row">
<td>[[ row.apartmentLabel ]]</td>
<td data-repeat="row.cells" data-repeat-as="cell">
[[ cell.occupancyLabelKey ? (cell.occupancyLabelKey|trans({}, 'Housekeeping')) : '' ]]
</td>
</tr>
</table><div data-repeat="simple.views.frontdesk.sections" data-repeat-as="section">
<h3>[[ section.titleKey|trans ]]</h3>
<table>
<tr data-repeat="section.rows" data-repeat-as="row">
<td>[[ row.guest ]]</td>
<td>[[ row.room ]]</td>
<td>[[ row.period ]]</td>
<td>[[ row.invoiceStatusLabelKey ? (row.invoiceStatusLabelKey|trans) : '-' ]]</td>
</tr>
<tr data-if="section.rows is empty"><td colspan="4">-</td></tr>
</table>
</div>Pulls the taxes that are active and valid for the selected month, then renders one
section per tax with its report-group rows. pricePerNightConsistent is a soft warning
when multiple rates were merged into the same report group but carry different tariffs.
[% set selectedYear = filters.start|date('Y') %]
[% set selectedMonth = filters.start|date('n') %]
[% set currentTaxMonth = touristTax.months|filter(m => m.year == selectedYear and m.month == selectedMonth)|first %]
[% set currentTaxes = currentTaxMonth ? currentTaxMonth.taxes : [] %]
<div data-repeat="currentTaxes" data-repeat-as="tax">
<h3>[[ tax.name ]]</h3>
<table>
<thead>
<tr>
<th>Nights</th>
<th>Guests</th>
<th>Rate per night</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr data-repeat="tax.groups" data-repeat-as="group">
<td>[[ group.totalNights ]]</td>
<td>
[[ group.label ]]
<span data-if="not group.pricePerNightConsistent">⚠ inconsistent rates</span>
</td>
<td>[[ group.pricePerNight|number_format(2, '.', '') ]]</td>
<td>[[ group.totalAmount|number_format(2, '.', '') ]]</td>
</tr>
<tr data-if="tax.groups is empty"><td colspan="4">No bookings.</td></tr>
<tr data-if="tax.groups is not empty">
<td>[[ tax.totalNights ]]</td>
<td colspan="2">Total</td>
<td>[[ tax.totalAmount|number_format(2, '.', '') ]]</td>
</tr>
</tbody>
</table>
</div>
<div data-if="currentTaxes is empty">No active tourist tax for the selected month.</div>Uses the snapshot bucket statistics.months[].metrics.tourism.overnights_by_country_and_category
together with the locale-resolved country names. Values are fractional because category counts
are split proportionally across the linked customers' countries; round in the template as needed.
[% set currentStats = statistics.months|filter(m => m.year == selectedYear and m.month == selectedMonth)|first %]
[% set countryAndCategory = currentStats.metrics.tourism.overnights_by_country_and_category ?? {} %]
<table>
<thead>
<tr>
<th>Country</th>
<th data-repeat="touristTax.guestCategories" data-repeat-as="category">[[ category.name ]]</th>
</tr>
</thead>
<tbody>
<tr data-repeat="countryAndCategory" data-repeat-key="countryCode" data-repeat-as="rowData">
<td>[[ statistics.countryNames[countryCode|upper] ?? countryCode ]]</td>
<td data-repeat="touristTax.guestCategories" data-repeat-as="category">
[[ (rowData[category.id] ?? 0)|round(0) ]]
</td>
</tr>
</tbody>
</table>