LinuxParty

NUESTRO SITIO necesita la publicidad para costear hosting y el dominio. Por favor considera deshabilitar tu AdBlock en nuestro sitio. También puedes hacernos una donación entrando en linuxparty.es, en la columna de la derecha.
Inicio desactivadoInicio desactivadoInicio desactivadoInicio desactivadoInicio desactivado
 

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

Pin It

Comentarios  

0 # daavid andrade orell 23-11-2016 20:49
hola tengo una duda. Me a gustado mucho el proyecto y lo quiero acoplar a un proyecto que estoy haciendo el problema que tengo es que a la base de datos le e agregado mas campos como apellido y teléfono pero a la hara de hacer la consulta no me muestra estos datos ya modifique el (Javascript) con los demas datos y no me da el resultado no se si me puede dar una idea
Responder | Responder con una citación | Citar
0 # David Reyes 29-12-2016 00:09
La parte de registrar los productos y el cliente
Responder | Responder con una citación | Citar

Escribir un comentario


Código de seguridad
Refescar



Redes:



 

Suscribete / Newsletter

Suscribete a nuestras Newsletter y periódicamente recibirás un resumen de las noticias publicadas.

Donar a LinuxParty

Probablemente te niegues, pero.. ¿Podrías ayudarnos con una donación?


Tutorial de Linux

Filtro por Categorías