diff --git a/docker/test-frontdesk.php b/docker/test-frontdesk.php new file mode 100644 index 000000000..d9e77d745 --- /dev/null +++ b/docker/test-frontdesk.php @@ -0,0 +1,120 @@ +install(), 'module installed'); +} +check((bool)Tab::getIdFromClassName('AdminFrontDesk'), 'menu "Rezeption" exists'); +$idProfile = (int)Db::getInstance()->getValue('SELECT `id_profile` FROM `'._DB_PREFIX_.'profile_lang` WHERE `name` = \'Rezeption\''); +check($idProfile > 0, 'profile "Rezeption" exists'); +$visible = Db::getInstance()->executeS('SELECT t.`class_name` FROM `'._DB_PREFIX_.'access` a JOIN `'._DB_PREFIX_.'tab` t ON t.`id_tab` = a.`id_tab` WHERE a.`id_profile` = '.$idProfile.' AND a.`view` = 1'); +$visible = array_column($visible, 'class_name'); +check(in_array('AdminFrontDesk', $visible, true) && in_array('AdminOccupancyPlan', $visible, true), 'profile sees Rezeption and Belegungsplan'); +check(!in_array('AdminModules', $visible, true) && !in_array('AdminProducts', $visible, true) && !in_array('AdminEmployees', $visible, true), 'profile does not see modules, products, employees'); +check(count($visible) <= 15, 'profile sees only '.count($visible).' pages'); + +$cbEnabled = Configuration::get('CASPOSBRIDGE_ENABLED'); +Configuration::updateValue('CASPOSBRIDGE_ENABLED', 0); + +$db = Db::getInstance(); +$rooms = $db->executeS('SELECT `id`, `id_product`, `room_num` FROM `'._DB_PREFIX_.'htl_room_information` ORDER BY `id` DESC LIMIT 5'); +$d = function ($n) { return date('Y-m-d', strtotime(($n >= 0 ? '+' : '').$n.' day')); }; +$plan = array( + array($rooms[0], 0, 3, 1, 'Erika', 'Mustermann', 2, 0), // arrives today + array($rooms[1], 0, 1, 1, 'Hans', 'Schmidt', 1, 0), // arrives today + array($rooms[2], -3, 0, 2, 'Gisela', 'Meier', 2, 1), // leaves today + array($rooms[3], -1, 4, 2, 'Peter', 'Wagner', 2, 0), // in house + array($rooms[4], -1, 2, 1, 'Karl', 'Spät', 1, 0), // overdue +); +$ids = array(); +$customers = array(); +foreach ($plan as $p) { + list($room, $from, $to, $status, $fn, $ln, $ad, $ch) = $p; + $c = new Customer(); + $c->firstname = $fn; + $c->lastname = $ln; + $c->email = 'fd-test-'.uniqid().'@hotel.test'; + $c->passwd = Tools::passwdGen(16); + $c->add(); + $customers[] = (int)$c->id; + $db->insert('htl_booking_detail', array( + 'id_product' => (int)$room['id_product'], 'id_order' => 0, 'id_order_detail' => 1, 'id_cart' => 1, 'id_room' => (int)$room['id'], + 'id_hotel' => 1, 'id_customer' => (int)$c->id, 'booking_type' => 1, 'id_status' => $status, + 'check_in' => $status === 2 ? $d($from).' 15:10:00' : '0000-00-00 00:00:00', 'check_out' => '0000-00-00 00:00:00', + 'planned_check_out' => $d($to).' 11:00:00', 'date_from' => $d($from).' 00:00:00', 'date_to' => $d($to).' 00:00:00', + 'total_price_tax_excl' => 100, 'total_price_tax_incl' => 107, 'total_paid_amount' => 0, 'is_back_order' => 0, + 'hotel_name' => 'Test', 'room_type_name' => 'Doppelzimmer', 'room_num' => $room['room_num'], 'adults' => $ad, 'children' => $ch, + 'city' => 'Annweiler', 'country' => 'Deutschland', 'phone' => '0', 'email' => $c->email, + 'check_in_time' => '15:00', 'check_out_time' => '11:00', 'date_add' => date('Y-m-d H:i:s'), 'date_upd' => date('Y-m-d H:i:s'), + )); + $ids[] = (int)$db->Insert_ID(); +} +$mine = function (array $rows) use ($ids) { + return array_values(array_filter($rows, function ($r) use ($ids) { return in_array((int)$r['id'], $ids, true); })); +}; + +try { + $s = new FrontDeskService(); + $today = $d(0); + check(count($mine($s->arrivals($today))) === 2, 'arrivals today: 2'); + check(count($mine($s->departures($today))) === 1, 'departures today: 1'); + check(count($mine($s->inHouse($today))) === 1, 'in house: 1'); + check(count($mine($s->overdue($today))) === 1, 'overdue: 1'); + + list($ok, $msg) = $s->act($ids[0], 'checkin'); + check($ok && strpos($msg, 'Erika Mustermann') !== false, 'check-in: '.$msg); + check((int)$db->getValue('SELECT id_status FROM `'._DB_PREFIX_.'htl_booking_detail` WHERE id = '.$ids[0]) === 2, 'status is checked in'); + list($ok) = $s->act($ids[0], 'checkin'); + check(!$ok, 'second check-in is refused'); + list($ok, $msg) = $s->act($ids[0], 'undo_checkin'); + check($ok && (int)$db->getValue('SELECT id_status FROM `'._DB_PREFIX_.'htl_booking_detail` WHERE id = '.$ids[0]) === 1, 'undo check-in: '.$msg); + list($ok, $msg) = $s->act($ids[2], 'checkout'); + check($ok, 'check-out without payment in QloApps (paid at POS): '.$msg); + list($ok, $msg) = $s->act($ids[2], 'undo_checkout'); + check($ok, 'undo check-out: '.$msg); + check($db->getValue('SELECT COUNT(*) FROM `'._DB_PREFIX_.'htl_booking_status_history` WHERE id_htl_booking = '.$ids[0]) >= 2, 'status history written'); + + $s->act($ids[1], 'checkin'); + $page = new FrontDeskPage($s, $today, 'index.php?controller=AdminFrontDesk&token=x', array('planUrl' => '#plan', 'reserveUrl' => '#reserve')); + $html = $page->render(array('ok' => true, 'msg' => 'Hans Schmidt (Zimmer '.$rooms[1]['room_num'].') ist eingecheckt.', 'undo' => array($ids[1], 'undo_checkin'))); + check(strpos($html, 'Einchecken') !== false && strpos($html, 'Auschecken') !== false, 'big check-in and check-out buttons'); + check(strpos($html, 'Rückgängig') !== false, 'undo button after an action'); + check(strpos($html, 'Noch nicht angekommen') !== false, 'overdue section shown'); + $future = (new FrontDeskPage($s, $d(1), 'x', array()))->render(); + check(strpos($future, 'name="fd_action"') === false, 'no action buttons on other days'); + if ($preview) { + file_put_contents($preview, 'Rezeption Vorschau' + .''.$html.''); + echo "Preview: $preview\n"; + } +} catch (Throwable $e) { + check(false, 'unexpected '.get_class($e).': '.$e->getMessage().' @ '.basename($e->getFile()).':'.$e->getLine()); +} finally { + $db->delete('htl_booking_status_history', '`id_htl_booking` IN ('.implode(',', $ids).')'); + $db->delete('htl_booking_detail', '`id` IN ('.implode(',', $ids).')'); + foreach ($customers as $idc) { + (new Customer($idc))->delete(); + } + Configuration::updateValue('CASPOSBRIDGE_ENABLED', $cbEnabled); +} +echo $fail ? "$fail check(s) FAILED\n" : "All checks passed\n"; +exit($fail ? 1 : 0); diff --git a/modules/frontdesk/classes/FrontDeskPage.php b/modules/frontdesk/classes/FrontDeskPage.php new file mode 100644 index 000000000..bf4cdb7da --- /dev/null +++ b/modules/frontdesk/classes/FrontDeskPage.php @@ -0,0 +1,198 @@ +service = $service; + $this->day = $day; + $this->today = date('Y-m-d'); + $this->actionUrl = $actionUrl; + $this->links = $links; + } + + protected static function e($s) + { + return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); + } + + public static function longDate($day) + { + $wd = array('Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'); + $mo = array(1 => 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'); + $t = strtotime($day); + return $wd[(int)date('w', $t)].', '.(int)date('j', $t).'. '.$mo[(int)date('n', $t)].' '.date('Y', $t); + } + + /** + * @param array|null $flash ['ok' => bool, 'msg' => string, 'undo' => [id, action]] + */ + public function render($flash = null) + { + $isToday = $this->day === $this->today; + $prev = date('Y-m-d', strtotime($this->day.' -1 day')); + $next = date('Y-m-d', strtotime($this->day.' +1 day')); + $dayUrl = function ($d) { + return $this->actionUrl.'&day='.$d; + }; + + $h = '
'; + $h .= '

Rezeption

'.self::e(self::longDate($this->day)) + .($isToday ? ' Heute' : '').'

' + .'
'; + + if ($flash) { + $h .= '
' + .''.($flash['ok'] ? '✓ ' : '✗ ').self::e($flash['msg']).''; + if (!empty($flash['undo'])) { + $h .= $this->button($flash['undo'][0], $flash['undo'][1], 'Rückgängig', 'fd-btn-light'); + } + $h .= '
'; + } + + $h .= '
'; + if (!empty($this->links['planUrl'])) { + $h .= 'Belegungsplan'; + } + if (!empty($this->links['reserveUrl'])) { + $h .= 'Neue Reservierung'; + } + $h .= '
'; + + if (!$isToday) { + $h .= '

Sie sehen einen anderen Tag. Ein- und Auschecken geht nur heute.

'; + } + + $overdue = $isToday ? $this->service->overdue($this->day) : array(); + if ($overdue) { + $h .= $this->section('Noch nicht angekommen', 'Diese Gäste hätten schon anreisen sollen.', $overdue, 'arrive', $isToday, 'fd-warn'); + } + $h .= $this->section('Anreise', 'Gäste, die heute ankommen.', $this->service->arrivals($this->day), 'arrive', $isToday); + $h .= $this->section('Abreise', 'Gäste, die heute abreisen. Bezahlt wird an der Kasse.', $this->service->departures($this->day), 'depart', $isToday); + $h .= $this->section('Im Haus', 'Gäste, die über Nacht bleiben.', $this->service->inHouse($this->day), 'stay', $isToday); + $h .= '
'.self::css(); + return $h; + } + + protected function section($title, $sub, array $rows, $kind, $canAct, $extra = '') + { + $h = '

'.self::e($title).' '.count($rows).'

'.self::e($sub).'

'; + if (!$rows) { + return $h.'

Niemand.

'; + } + $h .= ''; + } + + protected function card(array $r, $kind, $canAct) + { + $name = trim($r['firstname'].' '.$r['lastname']) ?: 'Buchung #'.(int)$r['id_order']; + $nights = (int)round((strtotime($r['date_to']) - strtotime($r['date_from'])) / 86400); + $people = (int)$r['adults'].' '.((int)$r['adults'] === 1 ? 'Erwachsener' : 'Erwachsene') + .((int)$r['children'] ? ', '.(int)$r['children'].' '.((int)$r['children'] === 1 ? 'Kind' : 'Kinder') : ''); + $facts = $people.' · '.$nights.' '.($nights === 1 ? 'Nacht' : 'Nächte').' · '.date('d.m.', strtotime($r['date_from'])).'–'.date('d.m.', strtotime($r['date_to'])) + .' · '.$r['room_type_name'].($r['source_name'] ? ' · '.$r['source_name'] : ''); + $status = (int)$r['id_status']; + + $action = ''; + if ($kind === 'arrive') { + if ($status === FrontDeskService::ASSIGNED) { + $action = $canAct ? $this->button($r['id'], 'checkin', 'Einchecken', 'fd-btn-go') : 'erwartet'; + } else { + $action = '✓ Eingecheckt'.($r['check_in'] && $r['check_in'] !== '0000-00-00 00:00:00' ? ' '.date('H:i', strtotime($r['check_in'])) : '').''; + } + } elseif ($kind === 'depart') { + if ($status === FrontDeskService::CHECKED_IN) { + $action = $canAct ? $this->button($r['id'], 'checkout', 'Auschecken', 'fd-btn-out') : 'reist ab'; + } else { + $action = '✓ Ausgecheckt'.($r['check_out'] && $r['check_out'] !== '0000-00-00 00:00:00' ? ' '.date('H:i', strtotime($r['check_out'])) : '').''; + } + } else { + $action = $canAct ? $this->button($r['id'], 'checkout', 'Früher abreisen', 'fd-btn-light') : ''; + } + + $pos = ''; + if (isset($r['pos_done'])) { + if ((int)$r['pos_failed']) { + $pos = 'Kasse: Fehler – bitte Kassenanbindung prüfen'; + } elseif ((int)$r['pos_done']) { + $pos = 'Kasse: '.(int)$r['pos_done'].' '.((int)$r['pos_done'] === 1 ? 'Nacht' : 'Nächte').' auf Tisch '.self::e($r['room_num']).''; + } + } + $details = !empty($this->links['orderUrl']) && (int)$r['id_order'] + ? 'Details' : ''; + + return '
  • '.self::e($r['room_num']).'
    ' + .'
    '.self::e($name).'
    '.self::e($facts).'
    '.$pos.$details.'
    ' + .'
    '.$action.'
  • '; + } + + protected function button($idBooking, $action, $label, $class) + { + return '
    ' + .'' + .'' + .'
    '; + } + + public static function css() + { + return ''; + } +} diff --git a/modules/frontdesk/classes/FrontDeskService.php b/modules/frontdesk/classes/FrontDeskService.php new file mode 100644 index 000000000..9dee920c3 --- /dev/null +++ b/modules/frontdesk/classes/FrontDeskService.php @@ -0,0 +1,121 @@ +idLang = $idLang ?: (int)Configuration::get('PS_LANG_DEFAULT'); + } + + protected function select($where, $order) + { + $hasCaspos = (bool)Db::getInstance()->getValue('SELECT COUNT(*) FROM information_schema.tables + WHERE table_schema = DATABASE() AND table_name = \''._DB_PREFIX_.'casposbridge_job\''); + $sql = 'SELECT bd.`id`, bd.`id_order`, bd.`id_room`, bd.`room_num`, bd.`room_type_name`, bd.`id_status`, + bd.`adults`, bd.`children`, DATE(bd.`date_from`) AS date_from, DATE(bd.`date_to`) AS date_to, + bd.`check_in`, bd.`check_out`, c.`firstname`, c.`lastname`, c.`email`, o.`reference`, + srcl.`name` AS source_name, src.`code` AS source_code' + .($hasCaspos ? ', (SELECT COUNT(*) FROM `'._DB_PREFIX_.'casposbridge_job` j WHERE j.`id_htl_booking` = bd.`id` AND j.`status` = \'done\') AS pos_done, + (SELECT COUNT(*) FROM `'._DB_PREFIX_.'casposbridge_job` j WHERE j.`id_htl_booking` = bd.`id` AND j.`status` = \'failed\') AS pos_failed' : '').' + FROM `'._DB_PREFIX_.'htl_booking_detail` bd + LEFT JOIN `'._DB_PREFIX_.'customer` c ON c.`id_customer` = bd.`id_customer` + LEFT JOIN `'._DB_PREFIX_.'orders` o ON o.`id_order` = bd.`id_order` + LEFT JOIN `'._DB_PREFIX_.'source` src ON src.`id_source` = o.`id_source` + LEFT JOIN `'._DB_PREFIX_.'source_lang` srcl ON srcl.`id_source` = src.`id_source` AND srcl.`id_lang` = '.(int)$this->idLang.' + WHERE bd.`is_back_order` = 0 AND '.$where + .(method_exists('OrderReturn', 'getRefundedBookingIdsSubquery') ? ' AND bd.`id` NOT IN ('.OrderReturn::getRefundedBookingIdsSubquery().')' : '') + .' ORDER BY '.$order; + return (array)Db::getInstance()->executeS($sql); + } + + /** Guests arriving on $day: still to check in, and those already checked in that day. */ + public function arrivals($day) + { + return $this->select('DATE(bd.`date_from`) = \''.pSQL($day).'\' AND bd.`id_status` IN ('.self::ASSIGNED.', '.self::CHECKED_IN.')', + 'bd.`id_status`, bd.`room_num`'); + } + + /** Guests due to leave on $day (still in house) and those already checked out that day. */ + public function departures($day) + { + return $this->select('DATE(bd.`date_to`) = \''.pSQL($day).'\' AND bd.`id_status` IN ('.self::CHECKED_IN.', '.self::CHECKED_OUT.')', + 'FIELD(bd.`id_status`, '.self::CHECKED_IN.', '.self::CHECKED_OUT.'), bd.`room_num`'); + } + + /** Guests in house who neither arrive nor leave on $day. */ + public function inHouse($day) + { + return $this->select('bd.`id_status` = '.self::CHECKED_IN.' AND DATE(bd.`date_from`) < \''.pSQL($day).'\' AND DATE(bd.`date_to`) > \''.pSQL($day).'\'', + 'bd.`room_num`'); + } + + /** Overdue: should have arrived before $day but are still only booked. */ + public function overdue($day) + { + return $this->select('bd.`id_status` = '.self::ASSIGNED.' AND DATE(bd.`date_from`) < \''.pSQL($day).'\' AND DATE(bd.`date_to`) > \''.pSQL($day).'\'', + 'bd.`date_from`, bd.`room_num`'); + } + + /** + * @param string $action checkin | checkout | undo_checkin | undo_checkout + * @return array [bool ok, string message] + */ + public function act($idBooking, $action, $idEmployee = 0) + { + $b = new HotelBookingDetail((int)$idBooking); + if (!Validate::isLoadedObject($b)) { + return array(false, 'Buchung nicht gefunden.'); + } + $guest = trim((string)Db::getInstance()->getValue('SELECT CONCAT(`firstname`, " ", `lastname`) FROM `'._DB_PREFIX_.'customer` WHERE `id_customer` = '.(int)$b->id_customer)); + $who = ($guest ?: 'Gast').' (Zimmer '.$b->room_num.')'; + $now = date('Y-m-d H:i:s'); + $params = array('id_employee' => (int)$idEmployee, 'remark' => 'Rezeption'); + + switch ($action) { + case 'checkin': + if ((int)$b->id_status !== self::ASSIGNED) { + return array(false, $who.' ist nicht im Status „gebucht“.'); + } + $b->check_in = $now; + $b->check_out = ''; + $ok = $b->changeStatus(self::CHECKED_IN, $params); + return array($ok, $ok ? $who.' ist eingecheckt.' : 'Einchecken hat nicht geklappt.'); + case 'checkout': + if ((int)$b->id_status !== self::CHECKED_IN) { + return array(false, $who.' ist nicht eingecheckt.'); + } + $b->check_out = $now; + // billing happens at the POS (CASPOS); the QloApps payment state is not checked here + $ok = $b->changeStatus(self::CHECKED_OUT, $params); + return array($ok, $ok ? $who.' ist ausgecheckt.' : 'Auschecken hat nicht geklappt.'); + case 'undo_checkin': + if ((int)$b->id_status !== self::CHECKED_IN) { + return array(false, $who.' ist nicht eingecheckt.'); + } + $b->check_in = ''; + $b->check_out = ''; + $ok = $b->changeStatus(self::ASSIGNED, $params); + return array($ok, $ok ? 'Einchecken von '.$who.' zurückgenommen.' : 'Zurücknehmen hat nicht geklappt.'); + case 'undo_checkout': + if ((int)$b->id_status !== self::CHECKED_OUT) { + return array(false, $who.' ist nicht ausgecheckt.'); + } + $b->check_out = ''; + $ok = $b->changeStatus(self::CHECKED_IN, $params); + return array($ok, $ok ? 'Auschecken von '.$who.' zurückgenommen.' : 'Zurücknehmen hat nicht geklappt.'); + } + return array(false, 'Unbekannte Aktion.'); + } +} diff --git a/modules/frontdesk/classes/index.php b/modules/frontdesk/classes/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/frontdesk/classes/index.php @@ -0,0 +1,32 @@ +bootstrap = true; + $this->display = 'view'; + parent::__construct(); + $this->meta_title = 'Rezeption'; + } + + public function initToolbar() + { + parent::initToolbar(); + $this->toolbar_btn = array(); + } + + public function initPageHeaderToolbar() + { + parent::initPageHeaderToolbar(); + $this->page_header_toolbar_btn = array(); + } + + protected function day() + { + $day = Tools::getValue('day'); + return $day && Validate::isDate($day) ? date('Y-m-d', strtotime($day)) : date('Y-m-d'); + } + + public function postProcess() + { + if (!Tools::isSubmit('submitFrontDesk')) { + return parent::postProcess(); + } + if (!$this->tabAccess['edit']) { + $this->errors[] = 'Keine Berechtigung.'; + return; + } + $id = (int)Tools::getValue('id_booking'); + $action = Tools::getValue('fd_action'); + $service = new FrontDeskService((int)$this->context->language->id); + list($ok, $msg) = $service->act($id, $action, (int)$this->context->employee->id); + $undo = array('checkin' => 'undo_checkin', 'checkout' => 'undo_checkout'); + $flash = array('ok' => (bool)$ok, 'msg' => $msg, 'undo' => $ok && isset($undo[$action]) ? array($id, $undo[$action]) : null); + $this->context->cookie->frontdesk_flash = json_encode($flash); + $this->context->cookie->write(); + // Post/Redirect/Get: reloading the page must not repeat the action + Tools::redirectAdmin(self::$currentIndex.'&token='.$this->token.'&day='.$this->day()); + } + + public function renderView() + { + $flash = null; + if (!empty($this->context->cookie->frontdesk_flash)) { + $flash = json_decode($this->context->cookie->frontdesk_flash, true); + unset($this->context->cookie->frontdesk_flash); + $this->context->cookie->write(); + } + $link = $this->context->link; + $page = new FrontDeskPage( + new FrontDeskService((int)$this->context->language->id), + $this->day(), + self::$currentIndex.'&token='.$this->token, + array( + 'planUrl' => Tab::getIdFromClassName('AdminOccupancyPlan') ? $link->getAdminLink('AdminOccupancyPlan') : null, + 'reserveUrl' => $link->getAdminLink('AdminHotelRoomsBooking'), + 'orderUrl' => function ($idOrder) use ($link) { + return $link->getAdminLink('AdminOrders').'&id_order='.(int)$idOrder.'&vieworder'; + }, + ) + ); + return $page->render($flash); + } +} diff --git a/modules/frontdesk/controllers/admin/index.php b/modules/frontdesk/controllers/admin/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/frontdesk/controllers/admin/index.php @@ -0,0 +1,32 @@ + edit rights) */ + const PROFILE_TABS = array( + 'AdminFrontDesk' => true, + 'AdminOccupancyPlan' => false, + 'AdminHotelReservationSystemManagement' => false, // parent menu of the two above + 'AdminHotelRoomsBooking' => true, // "Neue Reservierung" + 'AdminParentOrders' => false, + 'AdminOrders' => true, // order created from Book Now, details link + 'AdminGuestRegistration' => true, // Meldeschein / guest registration card + 'AdminCarts' => true, // Book Now builds the booking in a cart + 'AdminParentCustomer' => false, + 'AdminCustomers' => true, // create the guest during a booking + 'AdminAddresses' => true, + ); + + public function __construct() + { + $this->name = 'frontdesk'; + $this->tab = 'administration'; + $this->version = '1.0.0'; + $this->author = 'Zum alten Wasserrad'; + $this->need_instance = 0; + $this->bootstrap = true; + parent::__construct(); + + $this->displayName = 'Rezeption'; + $this->description = 'Einfache Tagesansicht für die Rezeption: Anreise, Abreise, Im Haus, Ein- und Auschecken mit großen Knöpfen.'; + $this->ps_versions_compliancy = array('min' => '1.6', 'max' => '1.6.99.99'); + } + + public function install() + { + return parent::install() && $this->installTab() && $this->installProfile(); + } + + public function uninstall() + { + $idTab = (int)Tab::getIdFromClassName('AdminFrontDesk'); + if ($idTab) { + (new Tab($idTab))->delete(); + } + return parent::uninstall(); + } + + protected function installTab() + { + if (Tab::getIdFromClassName('AdminFrontDesk')) { + return true; + } + $tab = new Tab(); + $tab->class_name = 'AdminFrontDesk'; + $tab->module = $this->name; + $tab->id_parent = (int)Tab::getIdFromClassName('AdminHotelReservationSystemManagement'); + foreach (Language::getLanguages(false) as $lang) { + $tab->name[$lang['id_lang']] = 'Rezeption'; + } + $tab->add(); // returns initAccess(), false on CLI; the tab exists anyway + if (!$tab->id) { + return false; + } + // first entry of its menu group + Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'tab` SET `position` = `position` + 1 WHERE `id_parent` = '.(int)$tab->id_parent.' AND `id_tab` <> '.(int)$tab->id); + Db::getInstance()->update('tab', array('position' => 0), '`id_tab` = '.(int)$tab->id); + return true; + } + + /** Profile "Rezeption" with view/edit only on the pages the front desk needs. */ + public function installProfile() + { + $idProfile = (int)Db::getInstance()->getValue('SELECT `id_profile` FROM `'._DB_PREFIX_.'profile_lang` WHERE `name` = \'Rezeption\''); + if (!$idProfile) { + $profile = new Profile(); + foreach (Language::getLanguages(false) as $lang) { + $profile->name[$lang['id_lang']] = 'Rezeption'; + } + if (!$profile->add()) { + return false; + } + $idProfile = (int)$profile->id; + } + // start from "no access" for every page, then open the listed ones + Db::getInstance()->delete('access', '`id_profile` = '.$idProfile); + $rows = array(); + foreach (Db::getInstance()->executeS('SELECT `id_tab`, `class_name` FROM `'._DB_PREFIX_.'tab`') as $t) { + $allowed = array_key_exists($t['class_name'], self::PROFILE_TABS); + $edit = $allowed && self::PROFILE_TABS[$t['class_name']]; + $rows[] = array('id_profile' => $idProfile, 'id_tab' => (int)$t['id_tab'], 'view' => (int)$allowed, + 'add' => (int)$edit, 'edit' => (int)$edit, 'delete' => 0, 'kpi' => 0); + } + return Db::getInstance()->insert('access', $rows); + } + + public function getContent() + { + Tools::redirectAdmin($this->context->link->getAdminLink('AdminFrontDesk')); + } +} diff --git a/modules/frontdesk/index.php b/modules/frontdesk/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/frontdesk/index.php @@ -0,0 +1,32 @@ +