New back-office page "Hotel Reservation System > Belegungsplan": - rooms grouped by room type as rows, 14-62 days as columns, weekends and today highlighted, sticky room column, daily occupancy footer - bookings as bars (booked / checked in / checked out / no-show) with guest name, guests, tooltip with dates, order reference and price; click opens the order; stays outside the view are cut - booking source badge from orders.id_source (W website, T phone, B Booking.com, E Expedia ...; portals highlighted), so channel-manager bookings show up with their source - blocked periods (htl_room_disable_dates) hatched with reason - CASPOS badge: nights already posted by casposbridge, "!" on failures - reserve: click a free cell (first night) and a later one (last night) in the same row -> action bar -> "Book Now" opens with dates, hotel and room type prefilled; keyboard: Enter/Space, Escape - docker/test-occupancyplan.php: 15 checks with throw-away bookings, optional HTML preview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
4.2 KiB
PHP
80 lines
4.2 KiB
PHP
<?php
|
|
|
|
require_once _PS_MODULE_DIR_.'occupancyplan/classes/OccupancyPlanRenderer.php';
|
|
|
|
class AdminOccupancyPlanController extends ModuleAdminController
|
|
{
|
|
public function __construct()
|
|
{
|
|
$this->bootstrap = true;
|
|
$this->display = 'view';
|
|
parent::__construct();
|
|
$this->meta_title = $this->l('Belegungsplan');
|
|
}
|
|
|
|
public function initToolbar()
|
|
{
|
|
parent::initToolbar();
|
|
unset($this->toolbar_btn['new']);
|
|
}
|
|
|
|
public function setMedia()
|
|
{
|
|
parent::setMedia();
|
|
$this->addJS(_MODULE_DIR_.'occupancyplan/views/js/occupancyplan.js');
|
|
}
|
|
|
|
public function renderView()
|
|
{
|
|
$days = (int)Tools::getValue('days', 21);
|
|
$from = Tools::getValue('from');
|
|
if (!$from || !Validate::isDate($from)) {
|
|
$from = date('Y-m-d', strtotime('-2 day'));
|
|
}
|
|
$idHotel = (int)Tools::getValue('id_hotel');
|
|
$plan = new OccupancyPlanRenderer($from, $days, $idHotel, (int)$this->context->language->id);
|
|
|
|
$base = self::$currentIndex.'&token='.$this->token;
|
|
$nav = function ($shift) use ($from, $days, $idHotel, $base) {
|
|
return $base.'&from='.date('Y-m-d', strtotime($from.' '.$shift)).'&days='.(int)$days.($idHotel ? '&id_hotel='.$idHotel : '');
|
|
};
|
|
$link = $this->context->link;
|
|
$orderLink = function ($idOrder) use ($link) {
|
|
return $link->getAdminLink('AdminOrders').'&id_order='.(int)$idOrder.'&vieworder';
|
|
};
|
|
|
|
$hotels = Db::getInstance()->executeS('SELECT hbi.`id`, hbil.`hotel_name` FROM `'._DB_PREFIX_.'htl_branch_info` hbi
|
|
LEFT JOIN `'._DB_PREFIX_.'htl_branch_info_lang` hbil ON hbil.`id` = hbi.`id` AND hbil.`id_lang` = '.(int)$this->context->language->id.'
|
|
WHERE hbi.`active` = 1');
|
|
$e = function ($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); };
|
|
|
|
$form = '<form method="get" action="index.php" class="form-inline op-nav">'
|
|
.'<input type="hidden" name="controller" value="AdminOccupancyPlan"><input type="hidden" name="token" value="'.$e($this->token).'">'
|
|
.'<a class="btn btn-default" href="'.$e($nav('-7 day')).'"><i class="icon-chevron-left"></i> '.$this->l('Woche').'</a> '
|
|
.'<a class="btn btn-default" href="'.$e($base.'&days='.(int)$days.($idHotel ? '&id_hotel='.$idHotel : '')).'">'.$this->l('Heute').'</a> '
|
|
.'<a class="btn btn-default" href="'.$e($nav('+7 day')).'">'.$this->l('Woche').' <i class="icon-chevron-right"></i></a> '
|
|
.'<label for="op-from">'.$this->l('ab').'</label> <input id="op-from" type="date" class="form-control" name="from" value="'.$e($from).'"> '
|
|
.'<label for="op-days">'.$this->l('Tage').'</label> <select id="op-days" class="form-control" name="days">';
|
|
foreach (array(14, 21, 31, 45, 62) as $d) {
|
|
$form .= '<option value="'.$d.'"'.($d == $days ? ' selected' : '').'>'.$d.'</option>';
|
|
}
|
|
$form .= '</select> ';
|
|
if (count($hotels) > 1) {
|
|
$form .= '<select class="form-control" name="id_hotel" aria-label="'.$e($this->l('Hotel')).'"><option value="0">'.$this->l('Alle Hotels').'</option>';
|
|
foreach ($hotels as $h) {
|
|
$form .= '<option value="'.(int)$h['id'].'"'.($h['id'] == $idHotel ? ' selected' : '').'>'.$e($h['hotel_name']).'</option>';
|
|
}
|
|
$form .= '</select> ';
|
|
}
|
|
$form .= '<button class="btn btn-primary" type="submit">'.$this->l('Anzeigen').'</button></form>';
|
|
|
|
// "Book Now" with dates/hotel/room type taken over from the selection (see views/js/occupancyplan.js)
|
|
$reserveUrl = $link->getAdminLink('AdminHotelRoomsBooking');
|
|
$hint = '<p class="help-block">'.$this->l('Reservieren: freie Zelle anklicken (erste Nacht), dann die letzte Nacht in derselben Zeile anklicken.').'</p>';
|
|
|
|
return '<div class="panel" data-reserve-url="'.$e($reserveUrl).'"><div class="panel-heading"><i class="icon-calendar"></i> '.$this->l('Belegungsplan').'</div>'
|
|
.$form.$hint.'<style>.op-nav{margin-bottom:12px;display:flex;flex-wrap:wrap;gap:6px;align-items:center}</style>'
|
|
.$plan->render($orderLink).'</div>';
|
|
}
|
|
}
|