Guía 85 – Relacionar 2 tablas de forma muy visual y rápida

Este ejemplo también lo he iniciado para ayudar a Rubén.
Me explicó que estaba diseñando un sistema de Gestión de Tesorería y que tenía que conciliar las previsiones de pago con los pagos reales, de cara a realizar las previsiones de saldos de sus cuentas corrientes a medio plazo.

Él imaginó un sistema que por arrastre de registros de un panel (de previsión)  a otro (pagos reales), se fueran cancelando los registros de previsión.

Si no queda clara esta explicación, os resumo, es relacionar unos registros de una tabla con otros registros de otra tabla, pero a ser posible de forma muy visual.

Objetivo

De forma muy visual, sencilla y rápida, relacionar registros de la tabla de previsión con los registros de la tabla de pagos reales.

En este caso, es para una gestión de Tesorería, pero puede usarse para multitud de situaciones, como:

  • Albaranes y facturas.
  • Facturas y movimientos bancarios de pagos.
  • Alumnos y clases.
  • Alumnos y asignaturas optativas.
  • Etc.

DEMOs:

Solución Técnica

Al final se ha dado solución con una página DashBoard con 3 paneles:

  • (1) Previsiones sin conciliación
  • (2) Pagos reales sin conciliación
  • (3) Pagos reales conciliados (con o sin previsión)

El funcionamiento es seleccionar 1 o más registros de Previsión (1) y un registro de Pago Real (2) y pulsar el botón “Concile Data”. Esta es la conciliación general.

También existe la posibilidad de que el pago no se haya previsto y en este caso se selecciona 1 o varios registros de tabla (2) y se pulsa el botón “No Concile”.

En ambos casos, los registros seleccionados pasan al panel (3) y si queremos deshacer cualquiera de las operaciones anteriores lo que tenemos que hacer es seleccionar 1 o varios registros del panel (3) y cliquear el botón “Rollback”.

A los paneles se les ha añadido con funcionalidad JavaScript:

  • Resaltado del registro que el ratón está encima de él.
  • Seleccionar o deseleccionar el registro haciendo clic en cualquier parte del registro.
  • Validad cuántos check (registros seleccionados)  hay en el panel de cara a activar el botón de la acción.
  • Múltiples validaciones previas en JavaScript, antes de iniciar las acciones.
  • Notificaciones a través de SW Alert.
  • Para obtener los check he tenido que habilitar la opción de “Delete”, pero he ocultado los botones y he deshabilitado la ejecución de este código

En la acción de “Data Concile” se chequea registros seleccionados en paneles (1) y (2). Los registros seleccionados se capturan con el API de PHPRunner y este botón “dispara” la ejecución en el panel (2), que tiene un botón oculto “Select real” y la ejecución en el panel (1).

Otra de las características más reseñable es que mantenemos en variables globales de JavaScript la información de los paneles para operar en ellos desde los botones.

Paso a mostrar algunos códigos para que podáis identificar las características de ellos.

Evento “JavaScript Onload event” del panel “Forecast”:

$('span[data-itemtype="delete"]').hide(); // Oculta botón de DELETE

var table_forecast = '#form_grid_'+pageid; // Botón de recuperar id's de registros chequeados

// Selection check row, with clic into row record
$(table_forecast + " tr[id^=gridRow]").bind('click',function() {
  $("input[name^=selection]",this).click();
  });
// Para los casos de poner el ratón sobre una fila, añade y elimina una clase de CSS que fija el color de fondo sobre dicha fila.
$( table_forecast +' tbody tr').hover(function(){
  $(this).find('td').addClass('resaltar');
}, function(){
  $(this).find('td'). removeClass('resaltar');
});

var buttom_forecast = 'a[id^="select_forescat_"]'; // Define JQUERY BUTTOM

// Para los botones de 3 estados. Control
window.buttom_forecast = buttom_forecast;
window.pageObj_forecast = pageObj;
window.table_forecast = table_forecast;
window.pageid_forecast = pageid;

$(buttom_forecast).attr("disabled","disabled");	   // First Disable BUTTOM

// Control de algún registro seleccionado para activar el botón 
$( table_forecast +' input[type="checkbox"][name^="selection"').change( function(e) {
  var $target = $(e.target),
       selBoxes;
 
  if ( $target.attr('name') == 'selection[]') {
    selBoxes = pageObj.getSelBoxes( pageid );
    //$(buttom_forecast).toggle( selBoxes.length > 0 );
    if ( selBoxes.length > 0 ) {
      $(buttom_forecast).removeAttr('disabled');
      } else {
      $(buttom_forecast).attr("disabled","disabled");
    }
 }
});

En el botón de “Data Concile” en el evento “Client Before”:

// Para los botones de 3 estados. Control
buttom_real = window.buttom_real;
pageObj_real = window.pageObj_real;
table_real = window.table_real;
pageid_real = window.pageid_real;

// Para los botones de 3 estados. Control
buttom_forecast = window.buttom_forecast ;
pageObj_forecast = window.pageObj_forecast ;
table_forecast = window.table_forecast;
pageid_forecast = window.pageid_forecast;

// Control de check de FORECAST
var selBoxes_forecast = 0;
$(table_forecast +' input[type="checkbox"][name^="selection"').each(function() {
  if (  $(this).attr('name') == 'selection[]') {
    selBoxes_forecast = pageObj_forecast.getSelBoxes( pageid_forecast );
 }
});
count_forecast = selBoxes_forecast.length;   // Registros seleccionados en REAL

if ( count_forecast == 0 ) {
  Swal.fire({
     icon: 'error',
     title: 'It is necessary to select Forecast record',
     text: 'You can one or more select a Forecast record !!!',
     footer: ''
    })
  return false;
  }
// Control de check de REAL
var selBoxes_real = 0;
$(table_real +' input[type="checkbox"][name^="selection"').each(function() {
  if (  $(this).attr('name') == 'selection[]') {
    selBoxes_real = pageObj_real.getSelBoxes( pageid_real );
 }
});
count_real = selBoxes_real.length;   // Registros seleccionados en REAL

if ( count_real != 1 ) {
  Swal.fire({
     icon: 'error',
     title: 'It is necessary to select Real record',
     text: 'You can only select a Real record !!!',
     footer: ''
    })
  return false;
  } 

$(buttom_real).click(); // Disparar botón de regisdtros de Real

Swal.fire({
   icon: 'info',
   title: 'The conciliation process begins',
   text: '',
   timer: 2000,
   timerProgressBar: true,
   toast: true,
   position: "top-start",
   footer: ''
  })

console.log('Forecast: '+Date.now());

Evento “Server”:

$result["txt"] = 'Registros seleccionados: ';
$row_array = array();
while($record = $button->getNextSelectedRecord())
{
  $row_array[] = $record["id_reconcile_forecast"];
}
$result["txt"] .= implode(',',$row_array);
$result["error"] = '';

if (!isset($_SESSION['row_real'])) { // No hay registros seleccionado de REAL
    $result["error"] = "ERROR: Not found record in table 'REAL'";
} else {
  include "MyCode/concile.php";
  unset($_SESSION['row_real']);
}

Los código de actualización de base de datos están en ficheros PHP en el directorio “MyCode”. En este caso el código “concile.php” es:

<?php
$id_real = $_SESSION['row_real']; 
$ids = implode(",", $row_array); 
$rs = DB::Query("SELECT * FROM reconcile_forecast WHERE id_reconcile_forecast in ($ids)");
while( $data_r = $rs->fetchAssoc() )
{
    $data = array();
    $keyvalues = array();
    $data["reconcile_real_id"] = $id_real;
    $keyvalues["id_reconcile_forecast"] = $data_r["id_reconcile_forecast"];
    DB::Update("reconcile_forecast", $data, $keyvalues );
}
$data = array();
$keyvalues = array();
$data["swich_reconcile"] = "1";
$keyvalues["id_reconcile_real"] = $id_real;
DB::Update("reconcile_real", $data, $keyvalues );

En botón “Data Concile”, evento “Client After”, tiene:

// Put your code here.
var message = result["txt"] + " !!!";
ajax.setMessage(message);

var error = result["error"];

buttom_forecast = window.buttom_forecast;

if ( error != '') { // Process with error
  console.log('Error: '+ error);
  timeout = setTimeout(repetButtom, 1000); // Wait 1 seg and repet action

/*
  Swal.fire({
     icon: 'error',
     title: error,
     text: 'The process has been aborted !!!',
     footer: ''
    })
*/
} else {
//Refresh panels
var page = Runner.dashboard.getElementPage("p_real_panel");
page.reload({a:'reload'});
var page = Runner.dashboard.getElementPage("p_concile_panel");
page.reload({a:'reload'});
var page = Runner.dashboard.getElementPage("p_forecast_panel");
page.reload({a:'reload'});
}

function repetButtom() {
  	$(buttom_forecast).click(); // Disparar botón de Forecast (-repetición por error en sincronismo
  console.log('Repet in Forecast: '+Date.now());
}

Creo que este ejemplo es bastante bueno para aquellos que se inician en JavaScript, tanto en el API de PHPRunner, como en JQUERY.

Yo he disfrutado mucho con la solución. Espero que a vosotros también os guste. Es un ejemplo que se sale del funcionamiento típico de PHPRunner, pero como veis, es 100% código PHPRunner.

Os dejo los fuentes, para que lo descarguéis y probéis es vuestros equipos y como siempre, para cualquier duda o lo que sea, comunicar conmigo a través de mi email [email protected]

Actualización 1/07/2024
Se ha hecho corrección en:

  • Cuando se produce error en sincronismo del orden de ejecución de los botones en ambos paneles en botón “Data Concile”, el proceso se resuelve automáticamente volviéndose a ejecutar, sin la intervención del usuario
  • En los botones del panel “REAL” se elimina el prefijo de la tabla, para que estos botones puedan estar dentro o fuera de la tabla, en dicho panel.
Ampliación 3/07/2024
Se ha ampliado la funcionalidad incluyendo un formulario de filtro sobre los paneles del DashBoard.

Esta nueva versión la he llamado “concile2”.
El formulario es una página ADD que se crea con una tabla ficticia. Creo que es una excelente alternativa  para filtrar las páginas de este tipo.

Adjuntos

Archivo Tamaño de archivo Descargas
zip reconcile - PHPR10.91 + backup db - Actualización: 01/07/2024 85 KB 150

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