Mini sistema de facturación - CRUD de productos

El algorimo implementado para permitir agregar, borrar y modificar productos es similar a las otras dos tablas de 'categorias' y 'clientes', con la salvedad que cuando agregamos o modificamos productos debemos mostrar en un control HTML de tipo 'select' las 'categorias' disponibles en su interior.

De forma similar hemos creado una subcarpeta llamada 'productos' donde almacenamos los dos archivos: 'administración.html' y 'procesar.php':

facturación en PHP

administracion.html
<!doctype html>
<html>

<head>
  <title>Administración de categorías</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>
  <div class="container">
    <h1>Administracion de Productos</h1>
    <table class="table table-striped">
      <thead>
        <tr>
          <th>Código</th>
          <th>Descripcion</th>
          <th>Categoria</th>
          <th class="text-right">Precio</th>
          <th class="text-right">Edición</th>
        </tr>
      </thead>
      <tbody id="datos">
      </tbody>
    </table>
    <button type="button" id="btnAgregar" class="btn btn-success">Agregar</button>
    <hr>
    <button type="button" id="btnFinalizar" class="btn btn-success">Finalizar</button>
  </div>

  <!-- Modal(Agregar, modificar y borrar) -->
  <div class="modal fade" id="ModalEditar" 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">
          <input type="hidden" id="Codigo" name="Codigo">
          <div class="form-row">
            <div class="form-group col-md-12">
              <label>Descripción:</label>
              <input type="text" id="Descripcion" class="form-control" placeholder="">
            </div>
          </div>
          <div class="form-row">
            <div class="form-group col-md-12">
              <label>Precio:</label>
              <input type="number" id="Precio" class="form-control" placeholder="">
            </div>
          </div>

          <div class="form-group">
            <label>Categoría:</label>
            <select class="form-control" id="CodigoCategoria">
            </select>
          </div>

        </div>
        <div class="modal-footer">
          <button type="button" id="btnConfirmarAgregar" class="btn btn-success">Agregar</button>
          <button type="button" id="btnModificar" class="btn btn-success">Modificar</button>
          <button type="button" id="btnBorrar" class="btn btn-success">Borrar</button>
          <button type="button" data-dismiss="modal" class="btn btn-success">Cancelar</button>
        </div>
      </div>
    </div>
  </div>

  <!-- ModalConfirmarCancelar -->
  <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;
      MostrarProductos();
      CargarCategorias();

      //Boton que vuelve a la página principal
      $('#btnFinalizar').click(function () {
        window.location = '../index.php';
      });

      //Boton que muestra el diálogo de agregar
      $('#btnAgregar').click(function () {
        LimpiarFormulario();
        $('#btnConfirmarAgregar').prop("disabled", false);
        $('#btnConfirmarAgregar').show();
        $('#btnModificar').hide();
        $('#btnBorrar').hide();
        $("#ModalEditar").modal();
      });


      //Botones que permiten agregar, borrar y modificar una fila de la tabla.
      $('#btnConfirmarAgregar').click(function () {
        RecolectarDatosFormulario();
        if (!EntradaFormularioCorrecto())
          return;
        $("#ModalEditar").modal('hide');
        EnviarInformacion("agregar");
      });

      $('#btnBorrar').click(function () {
        $("#ModalEditar").modal('hide');
        $("#ModalConfirmarBorrar").modal();
      });

      $('#btnConfirmarBorrado').click(function () {
        $("#ModalConfirmarBorrar").modal('hide');
        RecolectarDatosFormulario();
        $("#ModalEditar").modal('hide');
        EnviarInformacion("borrar");
      });

      $('#btnModificar').click(function () {
        RecolectarDatosFormulario();
        if (!EntradaFormularioCorrecto())
          return;
        $("#ModalEditar").modal('hide');
        EnviarInformacion("modificar");
      });
      //******************************************************* 

      function MostrarProductos() {
        $.ajax({
          type: 'GET',
          url: 'procesar.php?accion=listar',
          success: function (productos) {
            let filas = '';
            for (let producto of productos) {
              filas += '<tr><td>' + producto.codigo + '</td><td>' + producto.descripcion + '</td><td>' + producto.descripcioncategoria + '</td><td>' + producto.precio + '</td>';
              filas += '<td><a class="btn btn-primary botoneditar" role="button" href="#" data-codigo="' + producto.codigo + '">Edita?</a> </td></tr>';
            }
            $('#datos').html(filas);
            //Boton que muestra el diálogo de modificar y borrar
            $('.botoneditar').click(function () {
              $('#Codigo').val($(this).get(0).dataset.codigo);
              RecolectarDatosFormulario();
              RecuperarProducto("recuperar");
              $('#btnConfirmarAgregar').hide();
              $('#btnModificar').show();
              $('#btnBorrar').show();
              $("#ModalEditar").modal();
            });

          },
          error: function () {
            alert("Hay un error ..");
          }
        });
      }

      function CargarCategorias() {
        $.ajax({
          type: 'GET',
          url: 'procesar.php?accion=listarcategorias',
          success: function (categorias) {
            let filas = '';
            for (let categoria of categorias) {
              filas += '<option value="' + categoria.codigo + '">' + categoria.descripcion + '</option>';
            }
            $('#CodigoCategoria').html(filas);
          },
          error: function () {
            alert("Hay un error ..");
          }
        });
      }



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

      function RecuperarProducto(accion) {
        $.ajax({
          type: 'POST',
          url: 'procesar.php?accion=' + accion,
          data: producto,
          success: function (datos) {
            $('#Descripcion').val(datos[0]['descripcion']);
            $('#Precio').val(datos[0]['precio']);
            selectItemByValue(document.getElementById('CodigoCategoria'), datos[0]['codigocategoria']);
          },
          error: function () {
            alert("Hay un error ..");
          }
        });
      }
      //******************************************************* 

      function RecolectarDatosFormulario() {
        producto = {
          codigo: $('#Codigo').val(),
          descripcion: $('#Descripcion').val(),
          precio: $('#Precio').val(),
          codigocategoria: $('#CodigoCategoria').val()
        };
      }

      function LimpiarFormulario() {
        $('#Codigo').val('');
        $('#Descripcion').val('');
        $('#Precio').val('');
        $('#CodigoCategoria').val('');
      }

      function EntradaFormularioCorrecto() {
        if (producto['descripcion'] == '') {
          alert("No Puede estar vacía la descripción");
          return false;
        }
        if (producto['precio'] == '') {
          alert("No Puede estar vacío el precio");
          return false;
        }
        return true;
      }

      // otras funciones
      // Selecciona un determinado elemento de un control HTML 'select'
      function selectItemByValue(elmnt, value) {
        for (var i = 0; i < elmnt.options.length; i++) {
          if (elmnt.options[i].value == value)
            elmnt.selectedIndex = i;
        }
      }

    });    
  </script>

</body>

</html>

El segundo archivo que tenemos en la carpeta 'productos':

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

$conexion = retornarConexion();

switch ($_GET['accion']) {
    case 'listar':
        $respuesta = mysqli_query($conexion, "select 
                                                 pro.codigo as codigo,
                                                 pro.descripcion descripcion,
                                                 cat.descripcion descripcioncategoria,
                                                 precio
                                              from productos as pro
                                              join categorias as cat on cat.codigo=pro.codigocategoria");
        $resultado = mysqli_fetch_all($respuesta, MYSQLI_ASSOC);
        echo json_encode($resultado);
        break;
    case 'agregar':
        $respuesta = mysqli_query($conexion, "insert into productos(descripcion,precio,codigocategoria) values ('$_POST[descripcion]',$_POST[precio],$_POST[codigocategoria])");
        echo json_encode($respuesta);
        break;
    case 'recuperar':
        $respuesta = mysqli_query($conexion, "select codigo, descripcion, precio, codigocategoria from productos where codigo=$_POST[codigo]");
        $resultado = mysqli_fetch_all($respuesta, MYSQLI_ASSOC);
        echo json_encode($resultado);
        break;
    case 'borrar':
        $respuesta = mysqli_query($conexion, "delete from productos where codigo=" . $_POST['codigo']);
        echo json_encode($respuesta);
        break;
    case 'modificar':
        $respuesta = mysqli_query($conexion, "update productos set descripcion='$_POST[descripcion]', precio=$_REQUEST[precio], codigocategoria=$_POST[codigocategoria] where codigo=$_POST[codigo]");
        echo json_encode($respuesta);
        break;
    case 'listarcategorias':
        $respuesta = mysqli_query($conexion, "select codigo, descripcion from categorias");
        $resultado = mysqli_fetch_all($respuesta, MYSQLI_ASSOC);
        echo json_encode($resultado);
        break;
}

?>

La ejecución de la página nos genera una interfaz visual similar a:

facturación en PHP

Con un diálogo que se muestra cuando presionamos el botón de "Agregar":

facturación en PHP

o "Editar":

facturación en PHP

Listado de productos

El archivo 'administracion.html' mediante AJAX recupera todos los productos para ser mostrados en una tabla HTML:

      function MostrarProductos() {
        $.ajax({
          type: 'GET',
          url: 'procesar.php?accion=listar',
          success: function (productos) {
            let filas = '';
            for (let producto of productos) {
              filas += '<tr><td>' + producto.codigo + '</td><td>' + producto.descripcion + '</td><td>' + producto.descripcioncategoria + '</td><td>' + producto.precio + '</td>';
              filas += '<td><a class="btn btn-primary botoneditar" role="button" href="#" data-codigo="' + producto.codigo + '">Edita?</a> </td></tr>';
            }
            $('#datos').html(filas);
            //Boton que muestra el diálogo de modificar y borrar
            $('.botoneditar').click(function () {
              $('#Codigo').val($(this).get(0).dataset.codigo);
              RecolectarDatosFormulario();
              RecuperarProducto("recuperar");
              $('#btnConfirmarAgregar').hide();
              $('#btnModificar').show();
              $('#btnBorrar').show();
              $("#ModalEditar").modal();
            });

          },
          error: function () {
            alert("Hay un error ..");
          }
        });
      }

La función es llamada inmediatamente luego de ser cargada la página HTML por completo:

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

      var producto;
      MostrarProductos();
      CargarCategorias();

Lo nuevo es que llamamos a la función 'CargarCategorias' para que se muestre en el control 'select' del diálogo todas las categorías de productos.

      function CargarCategorias() {
        $.ajax({
          type: 'GET',
          url: 'procesar.php?accion=listarcategorias',
          success: function (categorias) {
            let filas = '';
            for (let categoria of categorias) {
              filas += '<option value="' + categoria.codigo + '">' + categoria.descripcion + '</option>';
            }
            $('#CodigoCategoria').html(filas);
          },
          error: function () {
            alert("Hay un error ..");
          }
        });
      }

Agregar producto

Cuando se presiona el botón "Agregar" se dispara el evento click siguiente, donde borramos los datos del formulario HTML, hacemos visible el botón de confirmar y ocultamos los botones de borrar y editar. Finalmente llamamos a la función 'modal' para que se haga visible el diálogo:

      //Boton que muestra el diálogo de agregar
      $('#btnAgregar').click(function () {
        LimpiarFormulario();
        $('#btnConfirmarAgregar').prop("disabled", false);
        $('#btnConfirmarAgregar').show();
        $('#btnModificar').hide();
        $('#btnBorrar').hide();
        $("#ModalEditar").modal();
      });

Una vez que el operador ingresa los datos del producto y selecciona la categoría del mismo, presiona el botón de 'Confirmar' que dispara la función donde se recuperan los datos del diálogo, se controla que la descripción no esté vacía y finalmente se llama a la función 'EnviarInformación' para que se agregue una fila en la tabla 'productos':

      $('#btnConfirmarAgregar').click(function () {
        RecolectarDatosFormulario();
        if (!EntradaFormularioCorrecto())
          return;
        $("#ModalEditar").modal('hide');
        EnviarInformacion("agregar");
      });

La función que recupera los datos del formulario crea un objeto llamado 'productos' (variable global definida previamente):

      function RecolectarDatosFormulario() {
        producto = {
          codigo: $('#Codigo').val(),
          descripcion: $('#Descripcion').val(),
          precio: $('#Precio').val(),
          codigocategoria: $('#CodigoCategoria').val()
        };
      }

La función 'EnviarInformacion' mediante AJAX envía los datos del formulario almacenados en la variable 'productos', además luego llamamos a la función 'MostrarCategorias' para que se actualice la tabla HTML con el nuevo producto ingresado:

      function EnviarInformacion(accion) {
        $.ajax({
          type: 'POST',
          url: 'procesar.php?accion=' + accion,
          data: producto,
          success: function (msg) {
            MostrarProductos();
          },
          error: function () {
            alert("Hay un error ..");
          }
        });
      }

El archivo 'procesar.php' recibe por parámetro 'GET' la acción 'agregar' y por POST los datos del producto a insertar:

    case 'agregar':
        $respuesta = mysqli_query($conexion, "insert into productos(descripcion,precio,codigocategoria) values ('$_POST[descripcion]',$_POST[precio],$_POST[codigocategoria])");
        echo json_encode($respuesta);
        break;

Editar producto

Cuando se presiona el botón 'Editar' de un producto se dispara la función:

            $('.botoneditar').click(function () {
              $('#Codigo').val($(this).get(0).dataset.codigo);
              RecolectarDatosFormulario();
              RecuperarProducto("recuperar");
              $('#btnConfirmarAgregar').hide();
              $('#btnModificar').show();
              $('#btnBorrar').show();
              $("#ModalEditar").modal();
            });

Recuperamos el código del producto almacenado en 'data-codigo' en el HTML mediante la sintaxis:

              $('#Codigo').val($(this).get(0).dataset.codigo);

Mediante AJAX recuperamos los datos del servidor del producto seleccionado:

              RecuperarProducto("recuperar");

La función 'RecuperarProducto' debe mostrar en el control HTML 'select' la categoría en particular que pertenece dicho producto:

      function RecuperarProducto(accion) {
        $.ajax({
          type: 'POST',
          url: 'procesar.php?accion=' + accion,
          data: producto,
          success: function (datos) {
            $('#Descripcion').val(datos[0]['descripcion']);
            $('#Precio').val(datos[0]['precio']);
            selectItemByValue(document.getElementById('CodigoCategoria'), datos[0]['codigocategoria']);
          },
          error: function () {
            alert("Hay un error ..");
          }
        });
      }

Implementamos una función llamada 'selectItemByValue' que tiene por objetivo que se muestre una determinada opción dentro de un control HTML 'select':

      function selectItemByValue(elmnt, value) {
        for (var i = 0; i < elmnt.options.length; i++) {
          if (elmnt.options[i].value == value)
            elmnt.selectedIndex = i;
        }
      }

El archivo 'accion.php' retorna los datos del producto a editar:

    case 'recuperar':
        $respuesta = mysqli_query($conexion, "select codigo, descripcion, precio, codigocategoria from productos where codigo=$_POST[codigo]");
        $resultado = mysqli_fetch_all($respuesta, MYSQLI_ASSOC);
        echo json_encode($resultado);
        break;

Borrar producto

Una vez que el operador presionó el botón de 'Editar' tiene las opciones de borrar o modificar el producto. Si presiona el botón de 'Borrar' se ejecuta la función:

      $('#btnBorrar').click(function () {
        $("#ModalEditar").modal('hide');
        $("#ModalConfirmarBorrar").modal();
      });

Se procede a ocultar el diálogo actual y activar un nuevo diálogo que permita confirma el borrado del producto, en caso afirmativo se ejecuta:

      $('#btnConfirmarBorrado').click(function () {
        $("#ModalConfirmarBorrar").modal('hide');
        RecolectarDatosFormulario();
        $("#ModalEditar").modal('hide');
        EnviarInformacion("borrar");
      });

En ésta función nos comunicamos con el servidor mediante AJAX, donde se procede a eliminar la fila de la tabla:

    case 'borrar':
        $respuesta = mysqli_query($conexion, "delete from productos where codigo=" . $_POST['codigo']);
        echo json_encode($respuesta);
        break;

Modificar producto

Si presiona en cambio el botón 'Modificar' en el diálogo que se ha mostrado al presionar el botón 'editar', se ejecuta:


      $('#btnModificar').click(function () {
        RecolectarDatosFormulario();
        if (!EntradaFormularioCorrecto())
          return;
        $("#ModalEditar").modal('hide');
        EnviarInformacion("modificar");
      });

Donde recolectamos los datos del diálogo, verificamos llamando a la función 'EntradaFormularioCorrecto' que se hayan cargado los datos y en caso afirmativo ocultamos el diálogo y procedemos a comunicarnos con el servidor llamando a 'EnviarInformacion("modificar")'.

En el servidor se ejecuta el comando SQL 'update':

    case 'modificar':
        $respuesta = mysqli_query($conexion, "update productos set descripcion='$_POST[descripcion]', precio=$_REQUEST[precio], codigocategoria=$_POST[codigocategoria] where codigo=$_POST[codigo]");
        echo json_encode($respuesta);
        break;