Gestión de Reservas

He recibido consultas de cómo resolver la gestión de reservas de habitaciones en un hotel o toldos en una playa.

En general, esta problemática de gestión de las reservas (diarias y no por horas) de un artículo (habitación, coche, moto, barco, vivienda, etc.) es muy habitual, por ello he desarrollado este ejemplo (y no aplicación) para que os pueda ayudar a desarrollar un sistema que tenga esta problemática.

Requisitos funcionales

He previsto que el ejemplo contemple los siguientes requisitos funcionales.

  • Se dispone de un inventario de productos (en este caso habitaciones) para reservar. Dicho inventario integra las características de las habitaciones.
  • En la Base de datos, se almacenan las fechas en las que las habitaciones están reservadas.
  • Asociada a la reserva, se relaciona, además de la habitación y las fechas, los datos del Cliente.
  • Se tiene que facilitar un calendario donde queden reflejadas todas las reservas.
  • Para buscar la disponibilidad de una habitación o para realizar una nueva reserva, existirá un formulario donde se describa las características de la habitación que se busca y las fechas que se desean. El sistema indicará las habitaciones disponibles según los criterios y un calendario de las ocupaciones de estas habitaciones, con el fin de que se ajusten las reservas y no se dejen huecos sin  que las habitaciones queden reservadas (optimización de los recursos).
  • El sistema se diseña para que se use por personal del Hotel, aunque disponga de posibilidades para extender el ejemplo y habilitarlo para el público en general.

Solución

DEMO: https://fhumanes.com/hotel

Usuario y password: admin/admin

El modelo de datos es muy simple

El ejemplo está desarrollado en PHPRunner 10.2, para que más usuarios puedan utilizarlo (no todos disponen de la versión 10.4). Utiliza los plugins Color y DateRange que podéis descargar desde plugines.

Utiliza la librería de JavaScript Fullcalendar que ya hemos usado en otras ocasiones y que es muy buena, pero en este caso hemos utilizado la funcionalidad de “Timeline”.

Paso a describir la funcionalidad del ejemplo

Aspecto del menú general con las opciones de gestión de los aspectos más importantes.

Información de las habitaciones, con sus datos más relevantes. Se ha puesto un color, que describe globalmente las características de la habitación.

Panel de información de la ocupación y disponibilidad de las habitaciones. Se puede observar que existe una agrupación de las habitaciones para mostrase en el calendario y que en este se puede navegar por meses, pudiendo mostrar las reservas por mes o por año.

Haciendo “clic” sobre la reserva se pasa a la visualización de todos los datos y a poder modificar la misma.

A nivel de desarrollo, esta es la pantalla con mayor codificación y novedades.

Se crea una pantalla sin que haya una tabla reflejo exacta por detrás y se construye dinámicamente el “SELECT” para ajustarse a los criterios de búsqueda.

Como resultado de la búsqueda muestra las habitaciones que cumplen los criterios para que se seleccione la que se desee y también muestra el calendario de las mismas, para optimizar los huecos.

Hay que hacer “clic” en el icono del “lápiz” para confirmar la reserva.

Una vez cliqueado en el “lápiz” se obtiene la pantalla de los datos de la reserva para completar con el dato del Cliente y confirmar la reserva.

Explico brevemente algunos códigos, para que veáis la simplicidad del ejemplo.

timeline.php .- Para mostrar el grid del calendario.

<?php
global $conn;
$language = $hours_a_day = $_SESSION['config'][array_search('CALENDAR_LANGUAGE', array_column($_SESSION['config'], 'name'))]['value'];
$firstDay = $hours_a_day = $_SESSION['config'][array_search('CALENDAR_FIRST_DAY', array_column($_SESSION['config'], 'name'))]['value']; // 1 = Lunes, 0=Domingo
$now = substr(now(),0,10);
if (isset($_SESSION['q_fromDate'])) { $now = substr($_SESSION['q_fromDate'],0,10); }
$str1 = <<<EOD
 <style>
    html, body {
      margin: 0;
      padding: 0;
      font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
      font-size: 12px; 
    }
    #calendar {
      max-width: 900px;
      max-height: 400px;
      margin: 40px auto;
    }
  </style>
<link href='fullcalendar/packages/core/main.css' rel='stylesheet' />
<link href='fullcalendar/packages-premium/timeline/main.css' rel='stylesheet' />
<link href='fullcalendar/packages-premium/resource-timeline/main.css' rel='stylesheet' />
<script src='fullcalendar/packages/core/main.js'></script>
<script src='fullcalendar/packages/interaction/main.js'></script>
<script src='fullcalendar/packages-premium/timeline/main.js'></script>
<script src='fullcalendar/packages-premium/resource-common/main.js'></script>
<script src='fullcalendar/packages-premium/resource-timeline/main.js'></script>
<script src='fullcalendar/packages/core/locales-all.js'></script>
<script src='fullcalendar/tooltip/tooltip.min.js'></script>
<script>
  var initialLocaleCode = '$language';
      var firstDay = '$firstDay'; 
   
  document.addEventListener('DOMContentLoaded', function() {
    var calendarEl = document.getElementById('calendar');
    var calendar = new FullCalendar.Calendar(calendarEl, {
      plugins: [ 'interaction', 'resourceTimeline' ],
      now: '$now',
       
      // timeZone: 'UTC+1', // the default 'local' (unnecessary to specify)
      locale: initialLocaleCode,
       
      editable: false,
      aspectRatio: 1.8,
      scrollTime: '00:00',
      header: {
        left: 'today prev,next',
        center: 'title',
        right: 'resourceTimelineMonth,resourceTimelineYear'
      },
      defaultView: 'resourceTimelineMonth',
      navLinks: true,
      resourceAreaWidth: '25%',
      resourceLabelText: 'Rooms',
      //
      eventClick: function(info) {
                        var booking = info.event.id;
                        var title = info.event.title;
                        var url = "bookings_view.php?editid1="+booking;
                        var header = '<h2 data-itemtype="view_header" data-itemid="view_header" data-pageid="10">'+'Booking: '+ title+'</h2>' ;
                        window.popup = Runner.displayPopup({
                    url: url,
                    width: 800,
                    height: 500,
                    header: header
               }); 
                  },
                  //
      resourceLabelText: 'Rooms',
      resources: { // you can also specify a plain string like 'json/resources.json'
        url: 'timeline_ajax_resource.php'},
      events: { // you can also specify a plain string like 'json/events-for-resources.json'
        url: 'timeline_ajax_event.php'},
    });
    calendar.render();
  });
</script>
  <div id='calendar'></div>
EOD;
echo $str1;
?>

 

timeline_ajax_resource.php .- Para facilita el inventario de las habitaciones. Cuando es en la fase de búsqueda, sólo se entrega la relación de las habitaciones resultado de la consulta.

<?php
require_once("include/dbcommon.php"); // DataBase PHPRunner
// Require our Event class and datetime utilities
require 'fullcalendar/php/utils.php';
$language = $_SESSION['language'];
$search = '';
If ( isset($_SESSION['q_IN']) && $_SESSION['q_IN'] <> '' ) {  // Are you coming from Room Search?
      $search = ' and rooms.idrooms IN ('.$_SESSION['q_IN'].') ';
}
$sql = "
SELECT idrooms, code, color, catalog_code 
FROM rooms
join (
SELECT catalog_num, catalog_code
FROM catalog
join super_catalog on ( super_catalog_idsuper_catalog = idsuper_catalog
AND super_code = 'TYPE_ROOM')
where language = '$language'
) T2
where catalog_num = type $search
order by catalog_code, code
";
$resql = db_query($sql,$conn);
// Accumulate an output array of event data arrays.
// $output_arrays = array();
$str = '[';
$group = '';
while ($data = db_fetch_array($resql)){
  
    If ($group <> $data['catalog_code']) {
        if ($group <> '' ) {
            $str = substr($str,0,strlen($str)-1); // erase last ","
            $str .= '  ] },';
        }
        $str .= '{ "id": "'.$data['catalog_code'].'", "title": "'.$data['catalog_code'].'", "children": [';
        $group = $data['catalog_code'];
         
    } 
    $str .= '{ "id": "'.$data['code'].'", "title": "'.$data['code'].'", "eventColor": "'.$data['color'].'" },';
     
}
$str = substr($str,0,strlen($str)-1); // erase last ","
$str .= '  ] } ]';
echo $str;
// Send JSON to the client.
// $str = json_encode($output_arrays);
// echo json_encode($output_arrays);
?>

 

timeline_ajax_event.php . Facilita al grid del calendario las reservas del periodo y si es el de búsqueda, de las habitaciones que han salido según los criterios facilitados.

<?php
require_once("include/dbcommon.php"); // DataBase PHPRunner
// Require our Event class and datetime utilities
require 'fullcalendar/php/utils.php';
$language = $_SESSION['language'];
$search = '';
If ( isset($_SESSION['q_IN']) && $_SESSION['q_IN'] <> '' ) {  // Are you coming from Room Search?
      $search = ' and rooms.idrooms IN ('.$_SESSION['q_IN'].') ';
}
$sql = "
SELECT idrooms, code, color, catalog_code 
FROM rooms
join (
SELECT catalog_num, catalog_code
FROM catalog
join super_catalog on ( super_catalog_idsuper_catalog = idsuper_catalog
AND super_code = 'TYPE_ROOM')
where language = '$language'
) T2
where catalog_num = type $search
order by catalog_code, code
";
$resql = db_query($sql,$conn);
// Accumulate an output array of event data arrays.
// $output_arrays = array();
$str = '[';
$group = '';
while ($data = db_fetch_array($resql)){
  
    If ($group <> $data['catalog_code']) {
        if ($group <> '' ) {
            $str = substr($str,0,strlen($str)-1); // erase last ","
            $str .= '  ] },';
        }
        $str .= '{ "id": "'.$data['catalog_code'].'", "title": "'.$data['catalog_code'].'", "children": [';
        $group = $data['catalog_code'];
         
    } 
    $str .= '{ "id": "'.$data['code'].'", "title": "'.$data['code'].'", "eventColor": "'.$data['color'].'" },';
     
}
$str = substr($str,0,strlen($str)-1); // erase last ","
$str .= '  ] } ]';
echo $str;
// Send JSON to the client.
// $str = json_encode($output_arrays);
// echo json_encode($output_arrays);
?>

Como siempre, podréis contactar conmigo, para cualquier necesidad en [email protected]

También, como es habitual, os dejo todos los ficheros que necesitáis para que instaléis el ejemplo en vuestros equipos.

Adjuntos

Archivo Tamaño de archivo Descargas
zip Mysql WorkBench, Modelo de datos 13 KB 661
zip Backup de la base de datos 3 KB 758
zip PHPRunner 10.2 - actualizado 10/03/2022 1 MB 922

Blog personal para facilitar soporte gratuito a usuarios de React y PHPRunner