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:

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:

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:

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:

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:

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);