Gestión de Medios (actualizado)

Reserva de los recursos compartidos de una Empresa

Este ejemplo está dedicado a mis hijos (Lorena y Pablo) que en sus vidas laborables les surgió el problema de cómo compartir recursos (Medios) que la empresa pone para el servicio de sus trabajadores.

En la actualidad, con el auge del teletrabajo y trabajos fuera de la oficina, es más relevante la buena gestión de los recursos de las empresas, estas situaciones no son nuevas, pero en estos momentos se amplia las necesidades de los medios y la optimización de la gestión de los mismos.

Por ejemplo, una plaza de garaje asignada a una persona, cuando esta persona está de vacaciones o de viaje de trabajo, puede ofrecer dicha plaza a otro compañero que no la tiene asignada.

Para entender su funcionamiento es muy importante entender cuáles fueron los requisitos que he utilizado para hacer el desarrollo.

Requisitos del aplicativo:

  • La Empresa dispone de un inventario de todos los medios/recursos que está facilitando para sus trabajadores. Estos bienes tienen una clasificación que indica:
    • Clave de medio.
    • Tiempo máximo de reserva (no es igual reservar una sala de reuniones que una plaza de aparcamiento).
    • Hora inicial del día en el que se puede reservar.
    • Hora final del día en el que se puede reservar.
  • Todos los medios (salas de reuniones, proyectores, plazas de garaje, coches, despachos, escritorios, etc. ) tienen una persona de la organización como “propietario/gestor”.
  • Son los propietarios quienes indican las fechas en la que el medio va a poder ser compartida. Los medios que pudieran ser generales como una sala de reuniones, también tienen un «propietario» que define los periodos de disponibilidad.
  • Todas las consultas de disponibilidad se concretarán en un periodo determinado. Ahora el aplicativo tiene que muestra disponibilidad y reuniones desde hace 10 días y hasta 30 días más de la fecha actual. Estos son parámetros del CONFIG que se pueden cambiar sin ningún problema.
  • Todo el mundo puede ver los medios reservados y la disponibilidad de los mismos, pero sólo los propietarios del medio o de la reserva, pueden cambiar estos o cancelar la reserva.
  • Los administradores pueden hacer de todo, sin restricciones.

El aplicativo en modo DEMO está en https://fhumanes.com/reservations

Los usuarios de acceso que tiene son:

  • admin/admin – Administrador
  • user1/user1 – Usuario normal
  • user2/user2 – Usuario normal

Como siempre indico en estos ejemplos que hago, el fuente está para ser descargado y que cada uno lo adapte a su Empresa.

También, para aquellos que empezáis con PHPRunner, este ejemplo os puede ayudar a aprender muchas de las características de este excelente entorno de programación. No tiene mucho código y es muy fácil de leer.

Descripción técnica del desarrollo

Se ha desarrollado en PHPRunner 10.2 y Mysql.

El modelo de datos es bastante simple:

El único “secreto” que tiene, con respecto a otros desarrollos que tengo en el portal, es que cada vez que se hace una reserva se actualiza la tabla “available_block” que son los bloques de fecha en la que el medio está libre para poder ser reservado.

(1) Aunque la información no se presenta en los clásicos “LIST”, el aplicativo mantiene las características de los filtros y las funciones de búsquedas.

(2) Para mostrar los rangos de fecha se ha utilizado las librerías de javascript DayPilot, que ya he utilizado en otras ocasiones. Es una versión DEMO.

(3) En la disponibilidad y en las reservas, al posicionarse sobre ellas, facilita información en una popup y si se hace clic en ella muestra en una nueva ventana para hacer la reserva o modificarla.

El aplicativo está en inglés y en español.

A continuación dejaré algunos ejemplos del código fuente, para que apreciéis qué simple es la integración con las librerías de javascript, en este caso con Daypilot.

daypilot_availability.php  (gráfica para facilitar la disponibilidad de fechas en las reservas)

<?php
$V_idavailability = $_SESSION['idavailability'];
$date_range_evaluations_min = $_SESSION['config'][array_search('DATE_RANGE_EVALUATIONS_MIN', array_column($_SESSION['config'], 'name'))][value];
$date_range_evaluations_max = $_SESSION['config'][array_search('DATE_RANGE_EVALUATIONS_MAX', array_column($_SESSION['config'], 'name'))][value];
// $V_now = strtotime(now());
$date = new DateTime(now());
$V_now = strtotime($date->format('d-m-Y H:i:s')."$date_range_evaluations_min days");
$V_now  = date("Y-m-d",$V_now)."T00:00:01";
$V_tot_day = $date_range_evaluations_max - $date_range_evaluations_min;
if ($_SESSION['language'] == 'Spanish') {
    $language = 'es-es'; // Spanish
            $range_row = 20;
            $formatDate = "dd/MM/yyyy HH:mm"; 
                $formatDatePHP = "d/m/Y H:i"; 
            $startTx = 'Inicio';
            $endTx = 'Fin';
} else {
    $language = 'en-us'; // English
            $range_row = 40;
            $formatDate = "MM/dd/yyyy HH:mm";  
                $formatDatePHP = "m/d/Y H:i"; 
            $startTx = 'Start';
            $endTx = 'End';
}
$strSQL = "SELECT `idavailability`, `media_idmedia`, `startDate`, `endDate` FROM availability where `idavailability` = $V_idavailability
";
$rsSQL = db_query($strSQL,$conn);
$data1 = db_fetch_array($rsSQL);
$V_idmedia = $data1['media_idmedia'];
$strSQL = "SELECT `idmedia`, `type_media_idtype_media`, `code` FROM media where idmedia = $V_idmedia
";
$rsSQL = db_query($strSQL,$conn);
$data2 = db_fetch_array($rsSQL);
$V_idtype_media = $data2['type_media_idtype_media'];
$strSQL = "SELECT `idtype_media`, `code`, `color`, `maxTimeReservation`, `startTimeDay`, `endTimeDay` FROM type_media where idtype_media = $V_idtype_media
";
$rsSQL = db_query($strSQL,$conn);
$data3 = db_fetch_array($rsSQL);
$V_idtype_media = $data2['type_media_idtype_media'];
$V_titleResource = $data3['code'].' -max:'.substr($data3['maxTimeReservation'],0,5).'<br>('.substr($data3['startTimeDay'],0,5).' - '.substr($data3['endTimeDay'],0,5).')';
$V_color = $data3['color'];
$V_event = '';
$strSQL = "SELECT `idavailable_block`, `availability_idavailability`, `media_idmedia`, `startDate`, `endDate` FROM available_block where availability_idavailability = $V_idavailability
";
$rsSQL = db_query($strSQL,$conn);
while ($data4 = db_fetch_array($rsSQL)) {
    $V_start = date($formatDatePHP,strtotime($data4['startDate']));
    $V_end = date($formatDatePHP,strtotime($data4['endDate']));
    $V_event .= "
{
      \"start\": \"".$data4['startDate']."\",
      \"end\": \"".$data4['endDate']."\",
      \"id\": \"".$data4['idavailable_block']."\",
      \"resource\": \"available\",
      \"text\": \"(".$V_start.' - '.$V_end.")\",
      \"barColor\": \"$V_color\"
    },";
}
if (strlen($V_event) <> 0) {
    $V_event = substr($V_event, 0, -1); // rest last comma
}
// ---------------------------------------------- Template of javascript ---------------------------------
$html = <<<EOT
<!-- daypilot libraries -->
<script src="daypilot/js/daypilot-all.min.js?v=2019.4.4160" type="text/javascript"></script>
<div id="dp"></div>
<script>
  var dp = new DayPilot.Scheduler("dp", {
    cellWidthSpec: "Fixed",
    cellWidth: $range_row,
    autoScroll: "Disabled",
    locale: "$language",
    timeHeaders: [{"groupBy":"Day"},{"groupBy":"Hour"}],
    scale: "Hour",
    startDate: "$V_now",
    days: $V_tot_day,
    // days: DayPilot.Date.today().daysInMonth(),
    // startDate: DayPilot.Date.today().firstDayOfMonth(),
    showNonBusiness: false,
    businessBeginsHour: 8,
    businessEndsHour: 21,
    eventHeight: 30,
    eventMovingStartEndEnabled: true,
    timeRangeSelectedHandling: "Disabled",
    eventMoveHandling: "Disabled",
    eventResizeHandling: "Disabled",
    eventDeleteHandling: "Disabled",
    eventClickHandling: "Disabled",
    eventHoverHandling: "Bubble",
    treeEnabled: false,
  });
// bubble, with async loading
dp.bubble = new DayPilot.Bubble({
    cssClassPrefix: "bubble_default",
     
    onLoad: function(args) {
        var ev = args.source;
        args.async = true;  // notify manually using .loaded()
        // simulating slow server-side load
        setTimeout(function() {
            args.html = "<div style='font-weight:bold'>" + ev.text() + "</div><div>$startTx: " + ev.start().toString("$formatDate ") + "</div><div>$endTx: " + ev.end().toString("$formatDate ") + "</div><div>Id: " + ev.id() + "</div>";
            args.loaded();
        }, 50);
    }
});
   
   dp.resources = [ { "name" : "$V_titleResource", "id" : "available" }]; 
   dp.events.list = [$V_event];
  dp.init();
</script>
EOT;
echo $html;
?>

media_availability.php (gráfico que representa el LIST de la disponibilidad de los medios)

<?php
// Mostrar, en gráfico de Daypilot, los recursos disponibles y los tiempos en que están disponibles en el rango de estudio.
// Show, in Daypilot chart, the resources available and the times they are available in the study range.
$V_limitDateMin = $_SESSION['limitDateMin'];
$V_limitDateMax = $_SESSION['limitDateMax'];
$V_availability_where = $_SESSION['availability_where'];
$V_label_head_reservations = GetCustomLabel ("ADD_RESERVATIONS");
$date_range_evaluations_min = $_SESSION['config'][array_search('DATE_RANGE_EVALUATIONS_MIN', array_column($_SESSION['config'], 'name'))][value];
$date_range_evaluations_max = $_SESSION['config'][array_search('DATE_RANGE_EVALUATIONS_MAX', array_column($_SESSION['config'], 'name'))][value];
$date = new DateTime(now());
$V_now = strtotime($date->format('d-m-Y H:i:s')."$date_range_evaluations_min days");
$V_now  = date("Y-m-d",$V_now)."T00:00:01";
$V_tot_day = $date_range_evaluations_max - $date_range_evaluations_min;
if ($_SESSION['language'] == 'Spanish') {
    $language = 'es-es'; // Spanish
    $range_row = 20;
    $formatDate = "dd/MM/yyyy HH:mm"; 
    $formatDatePHP = "d/m/Y H:i"; 
    $startTx = 'Inicio';
    $endTx = 'Fin';
} else {
    $language = 'en-us'; // English
    $range_row = 40;
    $formatDate = "MM/dd/yyyy HH:mm";  
    $formatDatePHP = "m/d/Y H:i"; 
    $startTx = 'Start';
    $endTx = 'End';
}
$V_resources = '';
$V_event = '';
$strSQL = "
SELECT distinct
media.idmedia,
-- media.type_media_idtype_media,
media.code,
media.title,
media.description,
media.owner_id,
-- type_media.code,
-- type_media.title,
type_media.color,
users.login,
users.username
FROM media
INNER JOIN availability ON media.idmedia = availability.media_idmedia
INNER JOIN type_media ON media.type_media_idtype_media = type_media.idtype_media
INNER JOIN users ON media.owner_id = users.user_id
where
availability.startDate <= '$V_limitDateMax'
and availability.endDate >= '$V_limitDateMin'
";
if ($V_availability_where <> ''){ // Filter?
    $strSQL .= "and ".$V_availability_where ;
}
$strSQL .= " order by media.code ";
$rsSQL = db_query($strSQL,$conn);
while ($data1 = db_fetch_array($rsSQL)) {
    $V_resources .= "{ \"name\" : \""."<b>".$data1['code']."</b>"." - ".$data1['title']."\", \"id\" : \"".$data1['code']."\"}, \n";
    $V_idmedia = $data1['idmedia'];
    $strSQL2 = "
SELECT
availability.idavailability,
available_block.idavailable_block idavailable_block,
available_block.startDate startDate,
available_block.endDate endDate
FROM availability
INNER JOIN available_block ON (availability.idavailability = available_block.availability_idavailability and availability.media_idmedia = $V_idmedia)
WHERE
availability.startDate <= '$V_limitDateMax'
and availability.endDate >= '$V_limitDateMin' ";
    $rsSQL2 = db_query($strSQL2,$conn);
    while ($data2 = db_fetch_array($rsSQL2)) {   
       $V_start = date($formatDatePHP,strtotime($data2['startDate']));
       $V_end = date($formatDatePHP,strtotime($data2['endDate']));
       $V_color = $data1['color'];
       $V_event .= "
{
      \"start\": \"".$data2['startDate']."\",
      \"end\": \"".$data2['endDate']."\",
      \"id\": \"".$data2['idavailable_block']."\",
      \"resource\": \"".$data1['code']."\",
      \"text\": \"(".$V_start.' - '.$V_end.")\",
      \"barColor\": \"$V_color\"
    },";
}
}
if (strlen($V_resources) <> 0) {
    $V_resources = substr($V_resources, 0, -1); // rest last comma
}
if (strlen($V_event) <> 0) {
    $V_event = substr($V_event, 0, -1); // rest last comma
}
// ---------------------------------------------- Template of javascript ---------------------------------
$html = <<<EOT
<!-- daypilot libraries -->
<script src="daypilot/js/daypilot-all.min.js?v=2019.4.4160" type="text/javascript"></script>
<div id="dp"></div>
<script>
  var dp = new DayPilot.Scheduler("dp", {
    cellWidthSpec: "Fixed",
    cellWidth: $range_row,
    autoScroll: "Disabled",
    locale: "$language",
    timeHeaders: [{"groupBy":"Day"},{"groupBy":"Hour"}],
    scale: "Hour",
    startDate: "$V_now",
    days: $V_tot_day,
    // days: DayPilot.Date.today().daysInMonth(),
    // startDate: DayPilot.Date.today().firstDayOfMonth(),
    showNonBusiness: false,
    businessBeginsHour: 8,
    businessEndsHour: 21,
    eventHeight: 30,
    eventMovingStartEndEnabled: true,
    timeRangeSelectedHandling: "Disabled",
    eventMoveHandling: "Disabled",
    eventResizeHandling: "Disabled",
    eventDeleteHandling: "Disabled",
    eventClickHandling: "Enabled",
    eventHoverHandling: "Bubble",
    treeEnabled: false,
  });
// bubble, with async loading
dp.bubble = new DayPilot.Bubble({   
    onLoad: function(args) {
        var ev = args.source;
        args.async = true;  // notify manually using .loaded()
        // simulating slow server-side load
        setTimeout(function() {
            args.html = "<div style='font-weight:bold'>" + ev.resource() + "</div><div>$startTx: " + ev.start().toString("$formatDate ") + "</div><div>$endTx: " + ev.end().toString("$formatDate ") + "</div><div>Id: " + ev.id() + "</div>";
            args.loaded();
        }, 50);
    }
}); 
dp.onEventClicked = function(args) {
  console.log('Select: '+args.e.text());
  var block = args.e.id();
  var url = "v_reservations_add.php?idBlock="+block;
  var header = '<h2 data-itemtype="view_header" data-itemid="view_header" data-pageid="10">'+'$V_label_head_reservations '+'</h2>' ;
  window.popup = Runner.displayPopup({
            url: url,
            width: 800,
            height: 550,
            header: header
    });
  };  
         
   dp.resources = [$V_resources]; 
   dp.events.list = [$V_event];
  dp.init();
</script>
EOT;
echo $html;
?>

Reservations_view_cancellation.php (gráfico que representa el LIST de las reservas que existe, la consulta y si procede, la cancelación de la misma)

 <?php
//  Mostrar, en gráfico de Daypilot, los recursos reservador y los tiempos en que están reservados en el rango de estudio.
//  Show, in Daypilot chart, the reserve resources and the times they are reserved in the study range.
$V_limitDateMin = $_SESSION['limitDateMin'];
$V_limitDateMax = $_SESSION['limitDateMax'];
$V_reservations_where = $_SESSION['reservations_where'];
$V_label_head_reservations = GetCustomLabel ("VIEW_RESERVATIONS");
$date_range_evaluations_min = $_SESSION['config'][array_search('DATE_RANGE_EVALUATIONS_MIN', array_column($_SESSION['config'], 'name'))][value];
$date_range_evaluations_max = $_SESSION['config'][array_search('DATE_RANGE_EVALUATIONS_MAX', array_column($_SESSION['config'], 'name'))][value];
$date = new DateTime(now());
$V_now = strtotime($date->format('d-m-Y H:i:s')."$date_range_evaluations_min days");
$V_now  = date("Y-m-d",$V_now)."T00:00:01";
$V_tot_day = $date_range_evaluations_max - $date_range_evaluations_min;
if ($_SESSION['language'] == 'Spanish') {
    $language = 'es-es'; // Spanish
    $range_row = 20;
    $formatDate = "dd/MM/yyyy HH:mm"; 
    $formatDatePHP = "d/m/Y H:i"; 
    $startTx = 'Inicio';
    $endTx = 'Fin';
    $userTx = 'Usuario';
} else {
    $language = 'en-us'; // English
    $range_row = 40;
    $formatDate = "MM/dd/yyyy HH:mm";  
    $formatDatePHP = "m/d/Y H:i"; 
    $startTx = 'Start';
    $endTx = 'End';
    $userTx = 'User';
}
$V_resources = '';
$V_event = '';
$strSQL = "
SELECT distinct
media.idmedia,
-- media.type_media_idtype_media,
media.code,
media.title,
media.description,
media.owner_id,
-- type_media.code,
-- type_media.title,
type_media.color
FROM media
INNER JOIN reservations ON media.idmedia = reservations.media_idmedia
INNER JOIN type_media ON media.type_media_idtype_media = type_media.idtype_media
where
reservations.startDate <= '$V_limitDateMax'
and reservations.endDate >= '$V_limitDateMin'
";
if ($V_reservations_where <> ''){ // Filter?
    $strSQL .= "and ".$V_reservations_where ;
}
$strSQL .= " order by media.code ";
$rsSQL = db_query($strSQL,$conn);
while ($data1 = db_fetch_array($rsSQL)) {
    $V_resources .= "{ \"name\" : \""."<b>".$data1['code']."</b>"." - ".$data1['title']."\", \"id\" : \"".$data1['code']."\"}, \n";
    $V_idmedia = $data1['idmedia'];
    $strSQL2 = "
SELECT
reservations.idreservations,
-- reservations.media_idmedia,
-- reservations.users_user_id,
reservations.startDate startDate,
reservations.endDate endDate,
users.user_id,
users.login,
users.username
FROM reservations
INNER JOIN users ON (reservations.users_user_id = users.user_id)
WHERE
reservations.media_idmedia = $V_idmedia
and reservations.startDate <= '$V_limitDateMax'
and reservations.endDate >= '$V_limitDateMin' ";
    $rsSQL2 = db_query($strSQL2,$conn);
    while ($data2 = db_fetch_array($rsSQL2)) {   
       $V_start = date($formatDatePHP,strtotime($data2['startDate']));
       $V_end = date($formatDatePHP,strtotime($data2['endDate']));
       $V_color = $data1['color'];
       $V_event .= "
{
      \"start\": \"".$data2['startDate']."\",
      \"end\": \"".$data2['endDate']."\",
      \"id\": \"".$data2['idreservations']."\",
      \"resource\": \"".$data1['code']."\",
      \"text\": \"".$data2['login']."\",
      \"user\": \"".$data2['login'].' - '.$data2['username']."\",          
      \"barColor\": \"$V_color\"
    },";
}
}
if (strlen($V_resources) <> 0) {
    $V_resources = substr($V_resources, 0, -1); // rest last comma
}
if (strlen($V_event) <> 0) {
    $V_event = substr($V_event, 0, -1); // rest last comma
}
// ---------------------------------------------- Template of javascript ---------------------------------
$html = <<<EOT
<!-- daypilot libraries -->
<script src="daypilot/js/daypilot-all.min.js?v=2019.4.4160" type="text/javascript"></script>
<div id="dp"></div>
<script>
  var dp = new DayPilot.Scheduler("dp", {
    cellWidthSpec: "Fixed",
    cellWidth: $range_row,
    autoScroll: "Disabled",
    locale: "$language",
    timeHeaders: [{"groupBy":"Day"},{"groupBy":"Hour"}],
    scale: "Hour",
    startDate: "$V_now",
    days: $V_tot_day,
    // days: DayPilot.Date.today().daysInMonth(),
    // startDate: DayPilot.Date.today().firstDayOfMonth(),
    showNonBusiness: false,
    businessBeginsHour: 8,
    businessEndsHour: 21,
    eventHeight: 30,
    eventMovingStartEndEnabled: true,
    timeRangeSelectedHandling: "Disabled",
    eventMoveHandling: "Disabled",
    eventResizeHandling: "Disabled",
    eventDeleteHandling: "Disabled",
    eventClickHandling: "Enabled",
    eventHoverHandling: "Bubble",
    treeEnabled: false,
  });
// bubble, with async loading
dp.bubble = new DayPilot.Bubble({   
    onLoad: function(args) {
        var ev = args.source;
        args.async = true;  // notify manually using .loaded()
        // simulating slow server-side load
        setTimeout(function() {
            args.html = "<div style='font-weight:bold'>" + ev.resource() + "</div><div>$userTx: " + ev.text() + "</div><div>$startTx: " + ev.start().toString("$formatDate ") + "</div><div>$endTx: " + ev.end().toString("$formatDate ") + "</div><div>Id: " + ev.id() + "</div>";
            args.loaded();
        }, 50);
    }
}); 
dp.onEventClicked = function(args) {
  console.log('Select: '+args.e.text());
  var reservation = args.e.id();
  var url = "v_reservations_view.php?editid1="+reservation;
  var header = '<h2 data-itemtype="view_header" data-itemid="view_header" data-pageid="10">'+'$V_label_head_reservations '+'</h2>' ;
  window.popup = Runner.displayPopup({
            url: url,
            width: 800,
            height: 550,
            header: header
    });
  };    
         
   dp.resources = [$V_resources]; 
   dp.events.list = [$V_event];
  dp.init();
</script>
EOT;
echo $html;
?>

Para cualquier duda o lo que necesitéis, poneros en contacto conmigo a través de email [email protected]

Os dejo los fuentes y copia de la Base de Datos, para que lo podáis instalar en vuestros equipos.

 

 

 

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