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]
- 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.