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
-
Programación
- Thomas E. Kurtz, coinventor de BASIC, muere a los 96 años
- Profesor de informática del MIT prueba el impacto de la IA en la formación de programadores
- Lanzamiento del IDE de código abierto Qt Creator 14 con soporte para complementos basados en Lua
- Plantillas para Joomla - Episodio 1: Plantillas, marcos y clubes o no...
- Este es el mejor libro que he visto para aprender a programar en Python en castellano desde cero, gratis y online
- ¿Deberían los niños seguir aprendiendo a programar en la era de la IA?
- La 'obsolescencia' de VBScript confirmada por Microsoft y su eventual eliminación de Windows
- El Gran Debate: ¿Deberían los Modelos de Inteligencia Artificial Ser de Código Abierto?
- El lenguaje de programación BASIC cumple 60 años
- El CEO de Nvidia dice que los niños no deberían aprender a programar
- 40 años de Turbo Pascal: recuerdos del dinosaurio codificador que revolucionó los IDE
- Los lenguajes de programación más populares y dónde aprenderlos.
- Top 5 de los principales lenguajes de programación para desarrollar aplicaciones de escritorio Linux
- Qt Creator 12 lanzado con complementos de grabación de pantalla y Explorador de compiladores
- 10 sitios web para practicar problemas de programación
Comentarios