LinuxParty
¿Que hemos usado para el proyecto?
-
Anexgrid: para páginar los registros.
-
jQuery UI: basicamente por el tema de autocomplete, el cual me parece bueno y lo he usado bastante tiempo.
-
Arquitectura MVC: ya que es la arquitectura que uso en todos mis proyectos.
Nuestro objeto facturador (Javascript)
Creamos un objeto llamado "facturador" en javascript que implementaba las reglas de negocio necesaria para manipular el DOM y comenzar a facturar, agregando detalle, calculando el monto por cada línea ingresada, el sub total, total y el IVA (IGV en otros países). Este ha sido modificado, ya que ahora los productos que se ingresan los elegimos desde la base de datos usando el autocomplete de jquery ui, adicionalmente, tambíen usamos el autocomplete para seleccionar un cliente.
Nuestro objeto es el siguiente:
var facturador = { detalle: { igv: 0, total: 0, subtotal: 0, cliente_id: 0, items: [] }, /* Encargado de agregar un producto a nuestra colección */ registrar: function(item) { var existe = false; item.total = (item.cantidad * item.precio); this.detalle.items.forEach(function(x){ if(x.producto_id === item.producto_id) { x.cantidad += item.cantidad; x.total += item.total; existe = true; } }); if(!existe) { this.detalle.items.push(item); } this.refrescar(); },
/* Encargado de actualizar el precio/cantidad de un producto */ actualizar: function(id, row) { /* Capturamos la fila actual para buscar los controles por sus nombres */ row = $(row).closest('.list-group-item'); /* Buscamos la columna que queremos actualizar */ $(this.detalle.items).each(function(indice, fila){ if(indice == id) { /* Agregamos un nuevo objeto para reemplazar al anterior */ facturador.detalle.items[indice] = { producto_id: row.find("input[name='producto_id']").val(), producto: row.find("input[name='producto']").val(), cantidad: row.find("input[name='cantidad']").val(), precio: row.find("input[name='precio']").val(), }; facturador.detalle.items[indice].total = facturador.detalle.items[indice].precio * facturador.detalle.items[indice].cantidad; return false; } }) this.refrescar(); }, /* Encargado de retirar el producto seleccionado */ retirar: function(id) { /* Declaramos un ID para cada fila */ $(this.detalle.items).each(function(indice, fila){ if(indice == id) { facturador.detalle.items.splice(id, 1); return false; } }) this.refrescar(); }, /* Refresca todo los productos elegidos */ refrescar: function() { this.detalle.total = 0; /* Declaramos un id y calculamos el total */ $(this.detalle.items).each(function(indice, fila){ facturador.detalle.items[indice].id = indice; facturador.detalle.total += fila.total; }) /* Calculamos el subtotal e IGV */ this.detalle.igv = (this.detalle.total * 0.18).toFixed(2); // 18 % El IGV y damos formato a 2 deciamles this.detalle.subtotal = (this.detalle.total - this.detalle.igv).toFixed(2); // Total - IGV y formato a 2 decimales this.detalle.total = this.detalle.total.toFixed(2); var template = $.templates("#facturador-detalle-template"); var htmlOutput = template.render(this.detalle); $("#facturador-detalle").html(htmlOutput); } }; $(document).ready(function(){ $("#btn-agregar").click(function(){ var producto_id = $("#producto_id"), producto = $("#producto"), cantidad = $("#cantidad"), precio = $("#precio"); // Validaciones if(producto_id.val() === '0') { alert('Debe seleccionar un producto'); return; } if(!isNumber(cantidad.val())) { alert('Debe ingresar una cantidad válida'); return; } else if( parseInt(cantidad.val()) <= 0 ) { alert('Debe ingresar una cantidad válida'); return; } facturador.registrar({ producto_id: parseInt(producto_id.val()), producto: producto.val(), cantidad: parseFloat(cantidad.val()), precio: parseFloat(precio.val()), }); producto_id.val('0'); producto.val(''); cantidad.val(''); precio.val(''); }) $("#frm-comprobante").submit(function(){ var form = $(this); if(facturador.detalle.cliente_id == 0) { alert('Debe agregar un cliente'); } else if(facturador.detalle.items.length == 0) { alert('Debe agregar por lo menos un detalle al comprobante'); }else { $.ajax({ dataType: 'JSON', type: 'POST', url: form.attr('action'), data: facturador.detalle, success: function (r) { if(r) window.location.href = '?c=Comprobante'; }, error: function(jqXHR, textStatus, errorThrown){ console.log(errorThrown + ' ' + textStatus); } }); } return false; }) /* Autocomplete de cliente, jquery UI */ $("#cliente").autocomplete({ dataType: 'JSON', source: function (request, response) { jQuery.ajax({ url: '?c=Comprobante&a=ClienteBuscar', type: "post", dataType: "json", data: { criterio: request.term }, success: function (data) { response($.map(data, function (item) { return { id: item.id, value: item.Nombre, direccion: item.Direccion, ruc: item.RUC, } })) } }) }, select: function (e, ui) { $("#cliente_id").val(ui.item.id); $("#direccion").val(ui.item.direccion); $("#ruc").val(ui.item.ruc); $(this).blur(); facturador.detalle.cliente_id = ui.item.id; } }) /* Autocomplete de producto, jquery UI */ $("#producto").autocomplete({ dataType: 'JSON', source: function (request, response) { jQuery.ajax({ url: '?c=Comprobante&a=ProductoBuscar', type: "post", dataType: "json", data: { criterio: request.term }, success: function (data) { response($.map(data, function (item) { return { id: item.id, value: item.Nombre, precio: item.Precio } })) } }) }, select: function (e, ui) { $("#producto_id").val(ui.item.id); $("#precio").val(ui.item.precio); $("#cantidad").focus(); } }) }) function isNumber(n) { return !isNaN(parseFloat(n)) && isFinite(n); }
Detalle del producto, IVA, sub total y total (JSRender)
Nuestro template ha sufrido cambios tambíen quedando de la siguiente manera.
<script id="facturador-detalle-template" type="text/x-jsrender" src=""> {{for items}} <li class="list-group-item"> <div class="row"> <div class="col-xs-7"> <div class="input-group"> <span class="input-group-btn"> <button type="button" class="btn btn-danger form-control"> <i class="glyphicon glyphicon-minus"></i> </button> </span> <input name="producto_id" type="hidden" value="{{:producto_id}}" /> <input disabled name="producto" class="form-control" type="text" placeholder="Nombre del producto" value="{{:producto}}" /> </div> </div> <div class="col-xs-1"> <input name="cantidad" class="form-control" type="text" placeholder="Cantidad" value="{{:cantidad}}" /> </div> <div class="col-xs-2"> <div class="input-group"> <span class="input-group-addon"> <input name="precio" class="form-control" type="text" placeholder="Precio" value="{{:precio}}" /> </div> </div> <div class="col-xs-2"> <div class="input-group"> <span class="input-group-addon">S/.</span> <input name="precio" class="form-control" type="text" readonly value="{{:total}}" /> <span class="input-group-btn"> <button type="button" class="btn btn-success form-control" class="btn-retirar"> <i class="glyphicon glyphicon-refresh"></i> </button> </span> </div> </div> </div> </li> {{else}} <li class="text-center list-group-item">No se han agregado productos al detalle</li> {{/for}} <li class="list-group-item"> <div class="row text-right"> <div class="col-xs-10 text-right"> Sub Total </div> <div class="col-xs-2"> <b>{{:subtotal}}</b> </div> </div> </li> <li class="list-group-item"> <div class="row text-right"> <div class="col-xs-10 text-right"> IGV (18%) </div> <div class="col-xs-2"> <b>{{:igv}}</b> </div> </div> </li> <li class="list-group-item"> <div class="row text-right"> <div class="col-xs-10 text-right"> Total </div> <div class="col-xs-2"> <b>{{:total}}</b> </div> </div> </li> </script>
Enviando la información del facturador al servidor mediante AJAX
Esto es muy simple, en nuestro ejemplo hemos usado AJAX, y si se fijaron, nuestro objeto facturador, tiene una propiedad llamada detalle, este detalle es lo que debemos enviar al servidor mediante una petición AJAX.
Revicemos la propeidad detalle:
detalle: { igv: 0, total: 0, subtotal: 0, cliente_id: 0, items: [] },
Dentro del detalle, la propiedad items va a guardar todos los productos que vayamos agregando al comprobante.
Entonces, como mencione, debemos pasar el detalle de nuestro facturador al servidor, lo que hice fue agregar un formulario dentro del facturador e implementar un botón del tipo submit, para luego, mediante jQuery declarar el evento SUBMIT y mandar toda la información mediante ajax. Veamos el código:
$("#frm-comprobante").submit(function(){ var form = $(this); if(facturador.detalle.cliente_id == 0) { alert('Debe agregar un cliente'); } else if(facturador.detalle.items.length == 0) { alert('Debe agregar por lo menos un detalle al comprobante'); }else { $.ajax({ dataType: 'JSON', type: 'POST', url: form.attr('action'), data: facturador.detalle, success: function (r) { if(r) window.location.href = '?c=Comprobante'; }, error: function(jqXHR, textStatus, errorThrown){ console.log(errorThrown + ' ' + textStatus); } }); } return false; })
Registrando la factura en al base de datos
Nuestra petición AJAX envía toda la información al servidor, y desde ahí solo debemos hacer los respectivos INSERTS. Nuestro controlador espera lo siguiente:
public function Guardar() { print_r(json_encode( $this->model->Registrar( $_POST ) )); }
Luego nuestro modelo se encarga de registrar dicha información en la base de datos.
public function Registrar($comprobante)
{
try
{
/* Registramos el comprobante */
$sql = "INSERT INTO comprobante(Cliente_id, IGV, SubTotal, Total) VALUES (?, ?, ?, ?);";
$this->pdo->prepare($sql)
->execute(
array(
$comprobante['cliente_id'],
$comprobante['igv'],
$comprobante['subtotal'],
$comprobante['total']
));
/* El ultimo ID que se ha generado */
$comprobante_id = $this->pdo->lastInsertId();
/* Recorremos el detalle para insertar */
foreach($comprobante['items'] as $d)
{
$sql = "INSERT INTO comprobante_detalle (Comprobante_id,Producto_id,Cantidad,PrecioUnitario,Total)
VALUES (?, ?, ?, ?, ?)";
$this->pdo->prepare($sql)
->execute(
array(
$comprobante_id,
$d['producto_id'],
$d['cantidad'],
$d['precio'],
$d['total']
));
}
return true;
}
catch (Exception $e)
{
return false;
}
}
Conclusión
La idea de este proyecto es trabajar todo desde el lado del cliente, es decir, agregar detalle a la factura, buscar un cliente/producto, calcular los montos. Toda esta responsabilidad la tiene ahora nuestro amigo javascript obteniendo un performance bastante alto (porque evitamos hacer las reglas de negocio que mencione en la base de datos) y nuestro servidor solo va a estar preparado para recibir el guardar del comprobante, es decir a PHP solo le interesa que le enviemos la factura lista para guardar, nada más .
PD 1: obviamente faltan validar varias cosas, ya eso es tarea de ustedes.
PD 2: si tienen dudas por favor comenten y no olviden compartir y valorar la publicación.
PD 3: disponemos de un software de venta en el siguiente enlace, el cual podría interesarte.
¡ Actualización !
- Había un BUG en las consultas hacia la base de datos, como nadie me notificó especificamente cual era el error tuve que bajar el proyecto para revisarlo y al final lo encontré. Por eso salia error cargando la data.
- He actualizado el script de la base de datos para que tenga data por defecto
- Cliente por defecto: Eduardo
- Productos por defecto:
- Guitarra eléctrica
- Amplicifador para guitarra eléctrica
Todo debería funcionar bien ahora.
Adjuntos

-
Tecnología
- Así revolucionarán los carritos inteligentes la experiencia en supermercados españoles
- 🛠️ Automatiza tu infraestructura: Cómo instalar, configurar y desplegar Ansible en Linux
- InventWood está a punto de producir en masa madera más resistente que el acero.
- Las baterías de papel que prometen transformar la tecnología: ligeras, flexibles y ecológicas
- Instalar, configurar e Integrar un Servidor Linux en Active Directory
- Cómo instalar y configurar un servidor NAS en Red Hat / AlmaLinux
- La 'pintura solar' que desarrolla Mercedes-Benz podría revolucionar la carga de vehículos eléctricos
- El sistema operativo Raspberry Pi ahora habilita Wayland de forma predeterminada en todos los modelos de Raspberry Pi
- Las ventas mundiales de vehículos eléctricos aumentaron un 30,5 % en septiembre
- Cómo un ingeniero desenmascaró una red internacional de robo de bicicletas - Por sus amigos de Facebook
- Un traje espacial inspirado en las dunas recicla la orina y la convierte en agua potable
- Alemania tiene demasiados paneles solares y eso ha hecho que los precios de la energía sean negativos.
Comentarios