Mini sistema de facturación - Confección de la factura

La página principal para la confección de la factura se encuentra en el archivo 'facturacion.php'. Inmediatamente ingresamos a la página se muestra una interfaz visual similar a:

facturación en PHP

Se muestra el número de factura a emitir, la fecha de emisión de la factura, el control select para buscar el cliente a quien se le facturará. En la parte inferior disponemos de dos botones, uno para agregar productos y otro para finalizar la factura.

facturacion.php
<!doctype html>
<html>

<head>
  <title>Facturación</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="css/bootstrap.min.css">
  <script src="js/jquery.min.js"></script>
  <script src="js/popper.min.js"></script>
  <script src="js/bootstrap.min.js"></script>
</head>

<body>

  <?php
  require("conexion.php");
  $con = retornarConexion();
  $consulta = mysqli_query($con, "insert into facturas() values ()")
    or die(mysqli_error($con));
  $codigofactura = mysqli_insert_id($con);
  ?>


  <div class="container">
    <div class="row mt-4">
      <div class="col-md">

        <div class="form-group row">
          <label for="CodigoFactura" class="col-lg-3 col-form-label">Número de factura:</label>
          <div class="col-lg-3">
            <input type="text" disabled class="form-control" id="CodigoFactura" value="<?php echo $codigofactura; ?>">
          </div>
        </div>


        <div class="form-group row">
          <label for="Fecha" class="col-lg-3 col-form-label">Fecha de emisión:</label>
          <div class="col-lg-3">
            <input type="date" class="form-control" id="Fecha">
          </div>
        </div>

        <div class="form-group row">
          <label for="CodigoCliente" class="col-lg-3 col-form-label">Cliente:</label>
          <div class="col-lg-3">
            <select class="form-control" id="CodigoCliente">
              <?php
              $consulta = mysqli_query($con, "select codigo, nombre from clientes")
                or die(mysqli_error($con));

              $clientes = mysqli_fetch_all($consulta, MYSQLI_ASSOC);

              echo "<option value='0'>Seleccionar Cliente</option>";
              foreach ($clientes as $cli) {
                echo "<option value='" . $cli['codigo'] . "'>" . $cli['nombre'] . "</option>";
              }
              ?>
            </select>
          </div>
        </div>


      </div>
    </div>


    <div class="row mt-4">
      <div class="col-md">
        <table class="table table-striped">
          <thead>
            <tr>
              <th>Código de Artículo</th>
              <th>Descripción</th>
              <th class="text-right">Cantidad</th>
              <th class="text-right">Precio Unitario</th>
              <th class="text-right">Total</th>
              <th class="text-right"></th>
            </tr>
          </thead>
          <tbody id="DetalleFactura">

          </tbody>
        </table>
        <button type="button" id="btnAgregarProducto" class="btn btn-success">Agregar Producto</button>
        <button type="button" id="btnTerminarFactura" class="btn btn-success">Terminar Factura</button>
      </div>
    </div>

  </div>



  <!-- ModalProducto(Agregar) -->
  <div class="modal fade" id="ModalProducto" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">

          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">

          <div class="form-group">
            <label>Producto:</label>
            <select class="form-control" id="CodigoProducto">
              <?php
              $consulta = mysqli_query($con, "select codigo, descripcion, precio from productos")
                or die(mysqli_error($con));

              $productos = mysqli_fetch_all($consulta, MYSQLI_ASSOC);
              foreach ($productos as $pro) {
                echo "<option value='" . $pro['codigo'] . "'>" . $pro['descripcion'] . '  ($' . $pro['precio'] . ")</option>";
              }
              ?>
            </select>
          </div>

          <div class="form-row">
            <div class="form-group col-md-12">
              <label>Cantidad:</label>
              <input type="number" id="Cantidad" class="form-control" placeholder="" min="1">
            </div>
          </div>


        </div>
        <div class="modal-footer">
          <button type="button" id="btnConfirmarAgregarProducto" class="btn btn-success">Agregar a la factura</button>
          <button type="button" data-dismiss="modal" class="btn btn-success">Cancelar</button>
        </div>
      </div>
    </div>
  </div>


  <!-- ModalFinFactura -->
  <div class="modal fade" id="ModalFinFactura" tabindex="-1" role="dialog">
    <div class="modal-dialog" style="max-width: 600px" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h1>Acciones</h1>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-footer">
          <button type="button" id="btnConfirmarFactura" class="btn btn-success">Confirmar Factura</button>
          <button type="button" id="btnConfirmarImprimirFactura" class="btn btn-success">Confirmar e Imprimir Factura</button>
          <button type="button" id="btnConfirmarDescartarFactura" class="btn btn-success">Descartar la Factura</button>
        </div>
      </div>
    </div>
  </div>


  <!-- ModalConfirmarBorrar -->
  <div class="modal fade" id="ModalConfirmarBorrar" tabindex="-1" role="dialog">
    <div class="modal-dialog" style="max-width: 600px" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h1>¿Realmente quiere borrarlo?</h1>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-footer">
          <button type="button" id="btnConfirmarBorrado" class="btn btn-success">Confirmar</button>
          <button type="button" data-dismiss="modal" class="btn btn-success">Cancelar</button>
        </div>
      </div>
    </div>
  </div>


  <script>
    document.addEventListener('DOMContentLoaded', function() {

      var producto;
      var cliente;

      document.getElementById('Fecha').valueAsDate = new Date();

      //Boton que muestra el diálogo de agregar producto
      $('#btnAgregarProducto').click(function() {
        LimpiarFormulario();
        $("#Cantidad").val("1");
        $("#ModalProducto").modal();
      });

      //Boton que agrega el producto al detalle
      $('#btnConfirmarAgregarProducto').click(function() {
        RecolectarDatosFormulario();
        $("#ModalProducto").modal('hide');
        if ($("#Cantidad").val() == "") { //Controlamos que no esté vacío la cantidad de productos
          alert('no puede estar vacío la cantidad de productos.');
          return;
        }
        EnviarInformacionProducto("agregar");
      });

      //Boton terminar factura
      $('#btnTerminarFactura').click(function() {
        $("#ModalFinFactura").modal();
      });

      //Boton confirmar factura
      $('#btnConfirmarFactura').click(function() {
        if ($('#CodigoCliente').val() == 0) {
          alert('Debe seleccionar un cliente');
          return;
        }
        RecolectarDatosCliente();
        EnviarInformacionFactura("confirmarfactura");
      });

      //Boton que descarta la factura generada borrando tanto en la tabla de facturas como detallefactura
      $('#btnConfirmarDescartarFactura').click(function() {
        RecolectarDatosCliente();
        EnviarInformacionFactura("confirmardescartarfactura");
      });

      //Boton confirmar factura y ademas genera pdf
      $('#btnConfirmarImprimirFactura').click(function() {
        if ($('#CodigoCliente').val() == 0) {
          alert('Debe seleccionar un cliente');
          return;
        }
        RecolectarDatosCliente();
        EnviarInformacionFacturaImprimir("confirmarfactura");
      });

      function RecolectarDatosFormulario() {
        producto = {
          codigoproducto: $('#CodigoProducto').val(),
          cantidad: $('#Cantidad').val()
        };
      }

      function RecolectarDatosCliente() {
        cliente = {
          codigocliente: $('#CodigoCliente').val(),
          fecha: $('#Fecha').val()
        };
      }

      //Funciones AJAX para enviar y recuperar datos del servidor
      //******************************************************* 
      function EnviarInformacionProducto(accion) {
        $.ajax({
          type: 'POST',
          url: 'procesar.php?accion=' + accion + '&codigofactura=' + <?php echo $codigofactura ?>,
          data: producto,
          success: function(msg) {
            RecuperarDetalle();
          },
          error: function() {
            alert("Hay un error ..");
          }
        });
      }

      function EnviarInformacionFactura(accion) {
        $.ajax({
          type: 'POST',
          url: 'procesar.php?accion=' + accion + '&codigofactura=' + <?php echo $codigofactura ?>,
          data: cliente,
          success: function(msg) {
            window.location = 'index.php';
          },
          error: function() {
            alert("Hay un error ..");
          }
        });
      }

      function EnviarInformacionFacturaImprimir(accion) {
        $.ajax({
          type: 'POST',
          url: 'procesar.php?accion=' + accion + '&codigofactura=' + <?php echo $codigofactura ?>,
          data: cliente,
          success: function(msg) {
            window.open('pdffactura.php?' + '&codigofactura=' + <?php echo $codigofactura ?>, '_blank');
            window.location = 'index.php';
          },
          error: function() {
            alert("Hay un error ..");
          }
        });
      }


      function LimpiarFormulario() {
        $('#Cantidad').val('');
      }



    });

    //Se ejecuta cuando se presiona un boton de borrar un item del detalle
    var cod;

    function borrarItem(coddetalle) {
      cod = coddetalle;
      $("#ModalConfirmarBorrar").modal();
    }

    $('#btnConfirmarBorrado').click(function() {
      $("#ModalConfirmarBorrar").modal('hide');
      $.ajax({
        type: 'POST',
        url: 'borrarproductodetalle.php?codigo=' + cod,
        success: function(msg) {
          RecuperarDetalle();
        },
        error: function() {
          alert("Hay un error ..");
        }
      });
    });




    function RecuperarDetalle() {
      $.ajax({
        type: 'GET',
        url: 'recuperardetalle.php?codigofactura=' + <?php echo $codigofactura ?>,
        success: function(datos) {
          document.getElementById('DetalleFactura').innerHTML = datos;
        },
        error: function() {
          alert("Hay un error ..");
        }

      });

    }
  </script>
</body>

</html>

Cuando se carga la página se genera en forma automática el número de factura:

  <?php
  require("conexion.php");
  $con = retornarConexion();
  $consulta = mysqli_query($con, "insert into facturas() values ()")
    or die(mysqli_error($con));
  $codigofactura = mysqli_insert_id($con);
  ?>

Ejecutamos un comando SQL insert sin indicar valores, luego se genera y carga solo el campo 'codigo', dejando para después la actualización de los campos 'fecha' y 'codigocliente'.

Mostramos el código de factura en un control input desactivo, con el objetivo que el usuario no lo pueda modificar:

            <input type="text" disabled class="form-control" id="CodigoFactura" value="<?php echo $codigofactura; ?>">

Mediante un control HTML 'select' mostramos todos los clientes para que el usuario lo pueda seleccionar:

        <div class="form-group row">
          <label for="CodigoCliente" class="col-lg-3 col-form-label">Cliente:</label>
          <div class="col-lg-3">
            <select class="form-control" id="CodigoCliente">
              <?php
              $consulta = mysqli_query($con, "select codigo, nombre from clientes")
                or die(mysqli_error($con));

              $clientes = mysqli_fetch_all($consulta, MYSQLI_ASSOC);

              echo "<option value='0'>Seleccionar Cliente</option>";
              foreach ($clientes as $cli) {
                echo "<option value='" . $cli['codigo'] . "'>" . $cli['nombre'] . "</option>";
              }
              ?>
            </select>
          </div>
        </div>

Cuando se presiona el botón 'Agregar producto' se activa el diálogo donde debemos seleccionar el producto y la cantidad del mismo:

facturación en PHP

El evento del botón es:

      $('#btnAgregarProducto').click(function() {
        LimpiarFormulario();
        $("#Cantidad").val("1");
        $("#ModalProducto").modal();
      });

Llamamos a la funcón de 'LimpiarFormulario' para borrar datos de selecciones previas, fijamos la cantidad de productos en uno y hacemos visible el diálogo.

Cuando presionamos el botón del diálogo 'Agregar a la factura' se dispara el evento:

      $('#btnConfirmarAgregarProducto').click(function() {
        RecolectarDatosFormulario();
        $("#ModalProducto").modal('hide');
        if ($("#Cantidad").val() == "") { //Controlamos que no esté vacío la cantidad de productos
          alert('no puede estar vacío la cantidad de productos.');
          return;
        }
        EnviarInformacionProducto("agregar");
      });

Recuperamos los datos del formulario, ocultamos el diálogo, controlamos que haya cargado un valor en la cantidad de productos y llamamos finalmente a la función 'EnviarInformaciónProducto' pasando el parámetro de "agregar".

La función de EnviarInformacionProducto se comunica mediante AJAX al servidor para que se agregue el producto seleccionado:

      function EnviarInformacionProducto(accion) {
        $.ajax({
          type: 'POST',
          url: 'procesar.php?accion=' + accion + '&codigofactura=' + <?php echo $codigofactura ?>,
          data: producto,
          success: function(msg) {
            RecuperarDetalle();
          },
          error: function() {
            alert("Hay un error ..");
          }
        });
      }

Si pasamos la acción 'agregar' luego el archivo 'procesar.php' se ejecuta:

<?php

header('Content-Type: application/json');
require("conexion.php");

$conexion = retornarConexion();

switch ($_GET['accion']) {
    case 'agregar':
        //Recuperamos el precio del producto
        $respuesta = mysqli_query($conexion, "select precio from productos where codigo=".$_POST['codigoproducto']);
        $reg=mysqli_fetch_array($respuesta);

        $respuesta = mysqli_query($conexion, "insert into detallefactura(codigofactura,codigoproducto,cantidad,precio) values ($_GET[codigofactura],$_POST[codigoproducto],$_POST[cantidad],$reg[precio])");
        echo json_encode($respuesta);
        break;

    case 'confirmarfactura':
        $respuesta = mysqli_query($conexion, "update facturas set
                                                fecha='$_POST[fecha]',
                                                codigocliente=$_POST[codigocliente]
                                              where codigo=$_GET[codigofactura]");
        echo json_encode($respuesta);        
        break;
    case 'confirmardescartarfactura':
        $respuesta = mysqli_query($conexion, "delete from facturas where codigo=$_GET[codigofactura]");
        $respuesta = mysqli_query($conexion, "delete from detallefactura where codigofactura=$_GET[codigofactura]");
        echo json_encode($respuesta);        

}

?>

Inmediatamente el servidor nos informa que el producto fue agregado se ejectua la función 'RecuperarDetalle':

    function RecuperarDetalle() {
      $.ajax({
        type: 'GET',
        url: 'recuperardetalle.php?codigofactura=' + <?php echo $codigofactura ?>,
        success: function(datos) {
          document.getElementById('DetalleFactura').innerHTML = datos;
        },
        error: function() {
          alert("Hay un error ..");
        }

      });

    }

La función 'RecuperarDetalle' ejecuta el algoritmo contenido en el archivo 'recuperardetalle.php':

<?php 
  require("conexion.php");

  $conexion = retornarConexion();
  
  $datos = mysqli_query($conexion, "select pro.codigo as codigo,
                                            descripcion,
                                            round(deta.precio,2) as precio,
                                            cantidad,
                                            round(deta.precio*cantidad,2) as preciototal,
                                            deta.codigo as coddetalle
                                        from detallefactura as deta
                                        join productos as pro on pro.codigo=deta.codigoproducto
                                        where codigofactura=$_GET[codigofactura]") or die(mysqli_error($conexion));

  $resultado = mysqli_fetch_all($datos, MYSQLI_ASSOC);
  $pago=0;
  foreach ($resultado as $fila) {
      echo "<tr>";
      echo "<td>$fila[codigo]</td>";
      echo "<td>$fila[descripcion]</td>";      
      echo "<td class=\"text-right\">$fila[cantidad]</td>";            
      echo "<td class=\"text-right\">$fila[precio]</td>";
      echo "<td class=\"text-right\">$fila[preciototal]</td>";
      echo '<td class="text-right"><a class="btn btn-primary" onclick="borrarItem('.$fila['coddetalle'].')" role="button" href="#" id="'.$fila['coddetalle'].'">Borra?</a></td>';
      echo "</tr>";      
      $pago=$pago+$fila['preciototal'];
  }
  echo "<tr>";
  echo "<td></td>";
  echo "<td></td>";      
  echo "<td></td>";            
  echo "<td class=\"text-right\"><strong>Importe total</strong></td>";              
  echo "<td class=\"text-right\"><strong>".number_format(round($pago,2),2,'.','')."</strong></td>";
  echo "<td></td>";            
  echo "</tr>";

?>

La función recuperar detalle modifica la tabla HTML donde se muestran todos los productos seleccionados hasta ese momento:

        success: function(datos) {
          document.getElementById('DetalleFactura').innerHTML = datos;
        },

En pantalla podemos ver todos los productos seleccionados y sus cantidades:

facturación en PHP

En cualquier momento podemos eliminar un producto de la factura presionando el botón 'Borra?', en dicha situación se dispara la función:

    //Se ejecuta cuando se presiona un boton de borrar un item del detalle
    var cod;

    function borrarItem(coddetalle) {
      cod = coddetalle;
      $("#ModalConfirmarBorrar").modal();
    }

Se nos muestra un diálogo para confirma su eliminación:

facturación en PHP

En caso de confirma su eliminación se ejecuta la función:

    $('#btnConfirmarBorrado').click(function() {
      $("#ModalConfirmarBorrar").modal('hide');
      $.ajax({
        type: 'POST',
        url: 'borrarproductodetalle.php?codigo=' + cod,
        success: function(msg) {
          RecuperarDetalle();
        },
        error: function() {
          alert("Hay un error ..");
        }
      });
    });

Donde se informa al servidor el código del detalle de factura a eliminar, dicha acción la ejecuta la página 'borrarproductodetalle.php':

<?php
header('Content-Type: application/json');
require("conexion.php");

$conexion = retornarConexion();

$respuesta = mysqli_query($conexion, "delete from detallefactura where codigo=".$_GET['codigo']);
echo json_encode($respuesta);
?>

Cuando se presiona el botón 'Terminar factura' se muestra un diálogo que nos permite:

facturación en PHP

Confirmar factura:

      $('#btnConfirmarFactura').click(function() {
        if ($('#CodigoCliente').val() == 0) {
          alert('Debe seleccionar un cliente');
          return;
        }
        RecolectarDatosCliente();
        EnviarInformacionFactura("confirmarfactura");
      });

Donde se comunica con el servidor mediante AJAX para dejar firme la factura generada:

    case 'confirmarfactura':
        $respuesta = mysqli_query($conexion, "update facturas set
                                                fecha='$_POST[fecha]',
                                                codigocliente=$_POST[codigocliente]
                                              where codigo=$_GET[codigofactura]");
        echo json_encode($respuesta);        
        break;

Confirmar e imprimir factura (similar al primer botón, con la salvedad que se agrega la llamada a la generación del PDF de la factura):

      $('#btnConfirmarImprimirFactura').click(function() {
        if ($('#CodigoCliente').val() == 0) {
          alert('Debe seleccionar un cliente');
          return;
        }
        RecolectarDatosCliente();
        EnviarInformacionFacturaImprimir("confirmarfactura");
      });

En la función 'EnviarInformacionFacturaImprimir' se llama a la página que genera el PDF:

      function EnviarInformacionFacturaImprimir(accion) {
        $.ajax({
          type: 'POST',
          url: 'procesar.php?accion=' + accion + '&codigofactura=' + ,
          data: cliente,
          success: function(msg) {
            window.open('pdffactura.php?' + '&codigofactura=' + , '_blank');
            window.location = 'index.php';
          },
          error: function() {
            alert("Hay un error ..");
          }
        });
      }

Por último el botón de 'Descartar la factura' dispara el evento:

      $('#btnConfirmarDescartarFactura').click(function() {
        RecolectarDatosCliente();
        EnviarInformacionFactura("confirmardescartarfactura");
      });

Se comunica con el servidor para que borre la factura actual y todos los productos añadidos hasta el momento:

    case 'confirmardescartarfactura':
        $respuesta = mysqli_query($conexion, "delete from facturas where codigo=$_GET[codigofactura]");
        $respuesta = mysqli_query($conexion, "delete from detallefactura where codigofactura=$_GET[codigofactura]");
        echo json_encode($respuesta);