Luego de mucho tiempo al fin traigo una versión 2 del sistema de ventas publicado anteriormente. No es un sistema de ventas como tal, pues es un simple ejemplo, pero no lo puedo llamar de otro modo.
En ese post prometí que haría otro tutorial pero siguiendo el patrón MVC y qué mejor que hacerlo con CodeIgniter (para repasar, ya que justo ahora tengo que hacer un proyecto usándolo)
Lo que trae esta versión no es nada diferente en cuanto a su uso, sino a su programación.
Te invito a probar Sublime POS 3, un punto de venta evolutivo, gratuito y que funciona en la nube.
Si eres de los que prefieren ver un vídeo, mira la demo y la explicación en YT:
Se hicieron muchos cambios. En primera porque siempre hay formas de mejorar el software. Lo que hicimos fue cambiar el tema CSS, además de ser más indicativos en cuanto a mensajes y avisos gracias a los datos temporales en sesión de CodeIgniter.
Por otro lado, se mejoró la base de datos. Se usa una tabla transaccional para los productos vendidos, eliminando la redundancia. Y para las ventas, ahora el total se calcula dependiendo de los productos vendidos.
También se agregó un apartado en donde se ven los detalles de una venta.
He puesto esfuerzo en que sea compatible con las versiones más antiguas de PHP. Por ejemplo, no utilicé funciones de tipado fuerte (así les llamo yo) ni definiciones cortas de arreglos.
Esta vez he alojado el código en GitHub. Es un repositorio público, tal vez le dé seguimiento más tarde, quién sabe. Pero aquí el link, déjale una estrella si te gusta 😉
https://github.com/parzibyte/ventasci
Se llama ventasci porque quiere decir “ventas con CodeIgniter” es decir, la ci es de CodeIgniter.
Puedes clonarlo si sabes manejar GitHub, o descargarlo como ZIP. Da igual, cualquiera puede usarlo y descargarlo; me he esforzado en hacerlo lo más expresivo posible.
En este post explicaré algunas cosas importantes, pero obviamente no pondré todo el código y configuraciones.
Ya deberías saber de PHP y CodeIgniter. Si quieres una pequeña introducción a CodeIgniter haz click aquí.
Recuerda ver el sistema que creamos anteriormente, pues en ese nos basaremos y tal vez nos saltemos cosas básicas.
Por cierto, lee sobre los controladores y modelos, pues explicaré todo suponiendo que ya tienes una base.
Si quieres, explora todos los tutoriales de CodeIgniter que tengo publicados.
Vamos a crear modelos, controladores y vistas para cada cosa. Pero antes de ello configuraremos nuestra base de datos.
Vamos a usar PDO con MySQL, omitiré explicar la cadena de conexión y esas cosas, pues ya lo he hecho anteriormente.
La configuración de la base de datos queda así:
#<?php
$db['default'] = array(
'dsn' => 'mysql:host=localhost;dbname=ventas',
'hostname' => 'localhost',
'username' => 'root',
'password' => '',
'database' => 'ventas',
'dbdriver' => 'pdo',
'dbprefix' => '',
'pconnect' => FALSE,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8',
'dbcollat' => 'utf8_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
El usuario es root y la contraseña es una string vacía. Si quieres ver todo el archivo, recuerda que está en GitHub.
La base de datos se llama ventas. El esquema, que puedes encontrar igualmente en el repositorio es el siguiente:
CREATE TABLE productos(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
codigo VARCHAR(255) NOT NULL,
descripcion VARCHAR(255) NOT NULL,
precioVenta DECIMAL(5, 2) NOT NULL,
precioCompra DECIMAL(5, 2) NOT NULL,
existencia DECIMAL(5, 2) NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
CREATE TABLE ventas(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
fecha DATETIME NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
CREATE TABLE productos_vendidos(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
id_producto BIGINT UNSIGNED NOT NULL,
cantidad BIGINT UNSIGNED NOT NULL,
precio DECIMAL(5, 2) NOT NULL,
id_venta BIGINT UNSIGNED NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(id_producto) REFERENCES productos(id) ON DELETE CASCADE,
FOREIGN KEY(id_venta) REFERENCES ventas(id) ON DELETE CASCADE
) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
Si cambias el nombre de algo, recuerda cambiarlo en el código. Pongo aquí el esquema como imagen porque se ve bonito.
Comenzamos con los productos. Vamos a registrarlos para después poder eliminarlos.
Definimos nuestro modelo, y definimos también las funciones que tendrá. Es un simple CRUD:
<?php
class ProductoModel extends CI_Model{
public $id;
public $codigo;
public $descripcion;
public $precioVenta;
public $precioCompra;
public $existencia;
public function __construct(){
$this->load->database();
}
public function nuevo($codigo, $descripcion, $precioVenta, $precioCompra, $existencia){
$this->codigo = $codigo;
$this->descripcion = $descripcion;
$this->precioVenta = $precioVenta;
$this->precioCompra = $precioCompra;
$this->existencia = $existencia;
return $this->db->insert('productos', $this);
}
public function guardarCambios($id, $codigo, $descripcion, $precioVenta, $precioCompra, $existencia){
$this->id = $id;
$this->codigo = $codigo;
$this->descripcion = $descripcion;
$this->precioVenta = $precioVenta;
$this->precioCompra = $precioCompra;
$this->existencia = $existencia;
return $this->db->update('productos', $this, array("id" => $id));
}
public function todos(){
return $this->db->get("productos")->result();
}
public function eliminar($id){
return $this->db->delete("productos", array("id" => $id));
}
public function uno($id){
return $this->db->get_where("productos", array("id" => $id))->row();
}
public function porCodigoDeBarras($codigoDeBarras){
return $this->db->get_where("productos", array("codigo" => $codigoDeBarras))->row();
}
}
?>
El método uno
obtiene un producto a través de su ID; ojo, su ID, no su código de barras. Para obtenerlo por código de barras usamos el método porCodigoDeBarras
.
De ahí, el de eliminar
elimina, el de todos devuelve todos los productos (falta una paginación pero eso ya es otra cosa), el que se llama nuevo
inserta un nuevo producto y finalmente el de guardarCambios
se encarga de realizar un update o actualización.
Este es el pegamento que se encarga de realizar acciones dependiendo de la URL. Para que esto funcione cargamos el helper url, como explico en un post que dejé al inicio.
El código es este:
<?php
class Productos extends CI_Controller{
public function __construct(){
parent::__construct();
$this->load->model("ProductoModel");
$this->load->library('session');
}
public function agregar(){
$this->load->view("encabezado");
$this->load->view("productos/agregar");
$this->load->view("pie");
}
public function guardarCambios(){
$resultado = $this->ProductoModel->guardarCambios(
$this->input->post("id"),
$this->input->post("codigo"),
$this->input->post("descripcion"),
$this->input->post("precioVenta"),
$this->input->post("precioCompra"),
$this->input->post("existencia")
);
if($resultado){
$mensaje = "Producto actualizado correctamente";
$clase = "success";
}else{
$mensaje = "Error al actualizar el producto";
$clase = "danger";
}
$this->session->set_flashdata(array(
"mensaje" => $mensaje,
"clase" => $clase,
));
redirect("productos/");
}
public function editar($id){
$producto = $this->ProductoModel->uno($id);
if(null === $producto){
$this->session->set_flashdata(array(
"mensaje" => "El producto que quieres editar no existe",
"clase" => "danger",
));
redirect("productos/");
}
$this->load->view("encabezado");
$this->load->view("productos/editar", array("producto" => $producto));
$this->load->view("pie");
}
public function eliminar($id){
$resultado = $this->ProductoModel->eliminar($id);
if($resultado){
$mensaje = "Producto eliminado correctamente";
$clase = "success";
}else{
$mensaje = "Error al eliminar el producto";
$clase = "danger";
}
$this->session->set_flashdata(array(
"mensaje" => $mensaje,
"clase" => $clase,
));
redirect("productos/");
}
public function index(){
$this->load->view("encabezado");
$this->load->view("productos/listar", array(
"productos" => $this->ProductoModel->todos()
));
$this->load->view("pie");
}
public function guardar(){
$resultado = $this->ProductoModel->nuevo(
$this->input->post("codigo"),
$this->input->post("descripcion"),
$this->input->post("precioVenta"),
$this->input->post("precioCompra"),
$this->input->post("existencia")
);
if($resultado){
$mensaje = "Producto guardado correctamente";
$clase = "success";
}else{
$mensaje = "Error al guardar el producto";
$clase = "danger";
}
$this->session->set_flashdata(array(
"mensaje" => $mensaje,
"clase" => $clase,
));
redirect("productos/agregar");
}
}
?>
Lo que hace es igualmente un crud, pero renderiza vistas. Las vistas tienen un if dentro de ellas para ver si hay datos en la sesión, dependiendo de ello muestran una alerta, ya veremos más adelante. Recordemos que las alertas son gracias al framework css Bootstrap.
En el método guardar usamos los datos que nos enviaron a través del formulario accediendo a $this->input->post("clave");
Aquí tenemos a la vista que lista los productos:
<div class="col-xs-12">
<h1>Productos</h1>
<?php if(!empty($this->session->flashdata())): ?>
<div class="alert alert-<?php echo $this->session->flashdata('clase')?>">
<?php echo $this->session->flashdata('mensaje') ?>
</div>
<?php endif; ?>
<div>
<a class="btn btn-success" href="<?php echo base_url() ?>index.php/productos/agregar">Nuevo <i class="fa fa-plus"></i></a>
</div>
<br>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Código</th>
<th>Descripción</th>
<th>Precio de compra</th>
<th>Precio de venta</th>
<th>Ganancia</th>
<th>Existencia</th>
<th>Editar</th>
<th>Eliminar</th>
</tr>
</thead>
<tbody>
<?php foreach($productos as $producto){ ?>
<tr>
<td><?php echo $producto->id ?></td>
<td><?php echo $producto->codigo ?></td>
<td><?php echo $producto->descripcion ?></td>
<td><?php echo $producto->precioCompra ?></td>
<td><?php echo $producto->precioVenta ?></td>
<td><?php echo $producto->precioVenta - $producto->precioCompra ?></td>
<td><?php echo $producto->existencia ?></td>
<td><a class="btn btn-warning" href="<?php echo base_url() ."index.php/productos/editar/" . $producto->id ?>"><i class="fa fa-edit"></i></a></td>
<td><a class="btn btn-danger" href="<?php echo base_url() ."index.php/productos/eliminar/" . $producto->id ?>"><i class="fa fa-trash"></i></a></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
Como vemos, mostramos una alerta si hay datos en la sesión. De ahí es una simple tabla con enlaces para editar y eliminar, la cual se dibuja gracias a un arreglo de productos traídos por el controlador.
Si queremos insertar un nuevo producto, se dibuja esta vista o view de CodeIgniter. Es un simple formulario que manejamos en el controlador.
<div class="col-xs-12">
<h1>Nuevo producto</h1>
<?php if(!empty($this->session->flashdata())): ?>
<div class="alert alert-<?php echo $this->session->flashdata('clase')?>">
<?php echo $this->session->flashdata('mensaje') ?>
</div>
<?php endif; ?>
<form method="post" action="<?php echo base_url() ?>index.php/productos/guardar">
<label for="codigo">Código de barras:</label>
<input class="form-control" name="codigo" required type="text" id="codigo" placeholder="Escribe el código">
<label for="descripcion">Descripción:</label>
<textarea required id="descripcion" name="descripcion" cols="30" rows="5" class="form-control"></textarea>
<label for="precioVenta">Precio de venta:</label>
<input class="form-control" name="precioVenta" required type="number" id="precioVenta" placeholder="Precio de venta">
<label for="precioCompra">Precio de compra:</label>
<input class="form-control" name="precioCompra" required type="number" id="precioCompra" placeholder="Precio de compra">
<label for="existencia">Existencia:</label>
<input class="form-control" name="existencia" required type="number" id="existencia" placeholder="Cantidad o existencia">
<br><br><input class="btn btn-info" type="submit" value="Guardar">
</form>
</div>
Igualmente si hay un mensaje que mostrar, lo muestra.
Finalmente, para editar es el mismo formulario pero con un input oculto de tipo hidden
para mandar el id del producto.
Para rellenar el formulario usamos el producto que nos pasa el controlador. La vista se ve así, el código queda abajo.
Este es el código que hace a la vista de arriba:
<div class="col-xs-12">
<h1>Editar producto</h1>
<form method="post" action="<?php echo base_url() ?>index.php/productos/guardarCambios">
<input name="id" type="hidden" value="<?php echo $producto->id ?>">
<label for="codigo">Código de barras:</label>
<input value="<?php echo $producto->codigo ?>" class="form-control" name="codigo" required type="text" id="codigo" placeholder="Escribe el código">
<label for="descripcion">Descripción:</label>
<textarea required id="descripcion" name="descripcion" cols="30" rows="5" class="form-control"><?php echo $producto->descripcion ?></textarea>
<label for="precioVenta">Precio de venta:</label>
<input value="<?php echo $producto->precioVenta ?>" class="form-control" name="precioVenta" required type="number" id="precioVenta" placeholder="Precio de venta">
<label for="precioCompra">Precio de compra:</label>
<input value="<?php echo $producto->precioCompra ?>" class="form-control" name="precioCompra" required type="number" id="precioCompra" placeholder="Precio de compra">
<label for="existencia">Existencia:</label>
<input value="<?php echo $producto->existencia ?>" class="form-control" name="existencia" required type="number" id="existencia" placeholder="Cantidad o existencia">
<br><br><input class="btn btn-info" type="submit" value="Guardar">
</form>
</div>
Este es el segundo apartado, aquí es en donde agregamos productos al carrito de compras. El carrito persiste en la sesión, así no importa si refrescan la página. Usamos igualmente los métodos de CodeIgniter para interactuar con el arreglo superglobal $_SESSION
así como para iniciar sesiones.
No hay modelo para vender, únicamente un controlador. Se usa el modelo de Ventas, explicado abajo.
En este caso este controlador gestiona el carrito de compras. Se comunica con un modelo únicamente al realizar la venta, y solamente para eso, de ahí, la responsabilidad es toda suya.
<?php
class Vender extends CI_Controller{
public function __construct(){
parent::__construct();
$this->load->library('session');
}
public function index(){
if(!$this->session->has_userdata("carrito"))
$this->session->set_userdata("carrito", array());
$carrito = $this->session->carrito;
$this->load->view("encabezado");
$this->load->view("vender/vender", array(
"carrito" => $carrito,
));
$this->load->view("pie");
}
public function quitarDelCarrito($indice){
$carrito = $this->session->carrito;
array_splice($carrito, $indice, 1);
$this->session->set_userdata("carrito", $carrito);
redirect("vender/");
}
public function cancelarVenta(){
$this->vaciarCarrito();
$this->session->set_flashdata(array(
"mensaje" => "Venta cancelada correctamente",
"clase" => "success",
));
redirect("vender/");
}
private function vaciarCarrito(){
$this->session->set_userdata("carrito", array());
}
public function terminarVenta(){
$carrito = $this->session->carrito;
# Primero ver si hay algo en el carrito, si no, indicarlo
if(count($carrito) < 1){
$this->session->set_flashdata(array(
"mensaje" => "Para vender, primero tienes que agregar productos al carrito",
"clase" => "warning",
));
redirect("vender/");
}
$this->load->model("VentaModel");
$resultado = $this->VentaModel->nueva($carrito);
if($resultado){
$this->vaciarCarrito();
$this->session->set_flashdata(array(
"mensaje" => "Venta realizada correctamente",
"clase" => "success",
));
redirect("vender/");
}else{
$this->session->set_flashdata(array(
"mensaje" => "Error realizando la venta, intente de nuevo",
"clase" => "danger",
));
redirect("vender/");
}
}
private function agregarAlCarrito($producto){
$carrito = $this->session->carrito;
$producto->cantidad = 1;
$producto->total = $producto->cantidad * $producto->precioVenta;
array_push($carrito, $producto);
$this->session->set_userdata("carrito", $carrito);
}
private function obtenerIndiceSiExiste($codigo){
$carrito = $this->session->carrito;
$conteo = count($carrito);
for($indice = 0; $indice < $conteo; $indice++){
if($carrito[$indice]->codigo === $codigo) return $indice;
}
return -1;
}
private function aumentarCantidad($indice){
$carrito = $this->session->carrito;
$producto = $carrito[$indice];
$producto->cantidad++;
$producto->total = $producto->cantidad * $producto->precioVenta;
$carrito[$indice] = $producto;
$this->session->set_userdata("carrito", $carrito);
}
public function agregar(){
$codigoDeBarras = $this->input->post("codigo");
$indice = $this->obtenerIndiceSiExiste($codigoDeBarras);
# Si el producto ya estaba en el carrito
if($indice !== -1){
# Simplemente le aumentamos la cantidad
$this->aumentarCantidad($indice);
}else{
#Si no, es uno nuevo
$this->load->model("ProductoModel");
$producto = $this->ProductoModel->porCodigoDeBarras($codigoDeBarras);
# Pero puede que no exista un producto con ese código
if(null === $producto){
$this->session->set_flashdata(array(
"mensaje" => "No existe un producto registrado con el código de barras que se proporcionó",
"clase" => "warning",
));
# O que no tenga existencia
}else if($producto->existencia < 1){
$this->session->set_flashdata(array(
"mensaje" => "No hay suficiente existencia del producto",
"clase" => "warning",
));
}else{
# Y caso de que sí exista y la existencia sea suficiente
$this->agregarAlCarrito($producto);
}
}
# Al final, en cualquier caso redireccionamos, ya sea con o sin mensajes
redirect("vender/");
}
}
?>
Usamos a los métodos de sesiones. Aquí una explicación:
has_userdata
para ver si ya hay carrito, si no, lo ponemos vacío.$this->session->carrito;
set_userdata
para guardar el carrito, es decir, el arreglo de productos después de haberlo modificado. Ya sea que hayamos agregado o quitado uno, y de igual manera si hemos aumentado la cantidad.Vale la pena mencionar que para ver si el producto ya está en el carrito usamos una simple búsqueda secuencial en nuestro arreglo de objetos. Y redireccionamos a vender la mayoría de veces, cambiando los mensajes que se muestran.
Para vaciar el carro simplemente definimos un nuevo arreglo en la sesión; bueno, un nuevo arreglo vacío.
En la vista sólo tenemos un formulario y una tabla. La tabla dibuja los productos que ya existen, y el formulario es uno en donde leemos el código de barras, el cual es enviado al controlador y comparado con el carrito o con la base de datos.
Cada que agregamos un producto, se redirige al mismo lugar y se da la ilusión de que no hubo redirección. También tenemos la opción de quitar del carrito, en donde simplemente pasamos el índice.
<?php
$granTotal = 0;
?>
<div class="col-xs-12">
<h1>Vender</h1>
<?php if(!empty($this->session->flashdata())): ?>
<div class="alert alert-<?php echo $this->session->flashdata('clase')?>">
<?php echo $this->session->flashdata('mensaje') ?>
</div>
<?php endif; ?>
<br>
<form method="post" action="<?php echo base_url() ?>index.php/vender/agregar">
<label for="codigo">Código de barras:</label>
<input autocomplete="off" autofocus class="form-control" name="codigo" required type="text" id="codigo" placeholder="Escribe el código">
</form>
<br><br>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Código</th>
<th>Descripción</th>
<th>Precio de venta</th>
<th>Cantidad</th>
<th>Total</th>
<th>Quitar</th>
</tr>
</thead>
<tbody>
<?php foreach($carrito as $indice => $producto){
$granTotal += $producto->total;
?>
<tr>
<td><?php echo $producto->id ?></td>
<td><?php echo $producto->codigo ?></td>
<td><?php echo $producto->descripcion ?></td>
<td><?php echo $producto->precioVenta ?></td>
<td><?php echo $producto->cantidad ?></td>
<td><?php echo $producto->total ?></td>
<td><a class="btn btn-danger" href="<?php echo base_url() . "index.php/vender/quitarDelCarrito/" . $indice?>"><i class="fa fa-trash"></i></a></td>
</tr>
<?php } ?>
</tbody>
</table>
<h3>Total: <?php echo $granTotal; ?></h3>
<input name="total" type="hidden" value="<?php echo $granTotal;?>">
<a href="<?php echo base_url() ?>index.php/vender/terminarVenta" class="btn btn-success">Terminar venta</a>
<a href="<?php echo base_url() ?>index.php/vender/cancelarVenta" class="btn btn-danger">Cancelar venta</a>
</div>
Puede que sea una mala práctica, pero no encontré una manera más óptima de calcular el total.
Podríamos calcularlo desde el controlador, pero sería recorrer dos veces el arreglo: una en el controlador y otra en la vista; y como de por sí estamos recorriéndolo en la vista, aprovechamos y vamos sumando en cada iteración.
Este modelo se encarga de registrar la venta, pero también de otras cosas relacionadas con las ventas. Aquí lo explicaré, porque tiene que ver tanto con vender y con ventas. Pondré únicamente el método:
#<?php
public function nueva($productosVendidos){
# Primero registramos la venta
$detalleDeVenta = array("fecha" => date("Y-m-d H:i:s"));
$this->db->insert("ventas", $detalleDeVenta);
# Ahora tomamos su ID
# Ver: https://parzibyte.me/blog/2018/03/20/ultimo-id-insertado-codeigniter/
$idVenta = $this->db->insert_id();
# Recorrer el carrito
foreach($productosVendidos as $producto){
# El producto que insertamos es diferente al del carrito, sólo necesitamos algunas cosas:
$detalleDeProductoVendido = array(
"id_producto" => $producto->id,
"cantidad" => $producto->cantidad,
"precio" => $producto->precioVenta,
"id_venta" => $idVenta,
);
$this->db->insert("productos_vendidos", $detalleDeProductoVendido);
}
return true;
}
El método se llama nueva, ya que pertenece al modelo VentaModel. Lo que hacemos es insertar una venta con la fecha de hoy. La insertamos y más tarde obtenemos el último id insertado, luego recorremos el arreglo de productos y vamos insertando producto a producto en nuestra tabla transaccional.
Ya para terminar, veamos cómo funcionan las ventas. Lo que tenemos que hacer es cancelar una venta, listarlas a todas o ver el detalle de una.
El modelo completo es el siguiente, ya explicamos arriba cómo se inserta una venta. De ahí, el único método un poco complejo es en donde se obtiene el detalle de una venta, porque se llaman a dos funciones privadas del modelo.
En los demás casos hacemos algunos inner joins con where, cosas muy interesantes.
<?php
class Ventas extends CI_Controller{
public function __construct(){
parent::__construct();
$this->load->model("VentaModel");
$this->load->library("session");
}
public function index(){
$ventasRealizadas = $this->VentaModel->todas();
$datos = array("ventas" => $ventasRealizadas);
$this->load->view("encabezado");
$this->load->view("ventas/todas", $datos);
$this->load->view("pie");
}
public function detalle($id){
$detallesDeVenta = $this->VentaModel->porId($id);
# Por si no existe la venta
if($detallesDeVenta->detalles === null){
$this->session->set_flashdata(array(
"mensaje" => "Los detalles de la venta no se pueden ver porque no existe una venta con ese ID",
"clase" => "warning",
));
redirect("ventas/");
}
$datos = array("venta" => $detallesDeVenta);
$this->load->view("encabezado");
$this->load->view("ventas/detalle", $datos);
$this->load->view("pie");
}
public function eliminar($id){
$resultado = $this->VentaModel->eliminar($id);
if($resultado){
$mensaje = "Venta eliminada";
$clase = "success";
}else{
$mensaje = "Error al eliminar la venta";
$clase = "warning";
}
$this->session->set_flashdata(array(
"mensaje" => $mensaje,
"clase" => $clase,
));
redirect("ventas/");
}
}
?>
Para eliminar ventas hacemos lo mismo que en los otros modelos, y como existen relaciones, no necesitamos eliminar los productos vendidos porque el proceso se hace automáticamente cuando eliminamos la venta.
Este controlador redirige si queremos ver el detalle de una venta que no existe, y manda mensajes a la interfaz. Por defecto listará todas las ventas, únicamente las ventas, no sus detalles.
<?php
class Ventas extends CI_Controller{
public function __construct(){
parent::__construct();
$this->load->model("VentaModel");
$this->load->library("session");
}
public function index(){
$ventasRealizadas = $this->VentaModel->todas();
$datos = array("ventas" => $ventasRealizadas);
$this->load->view("encabezado");
$this->load->view("ventas/todas", $datos);
$this->load->view("pie");
}
public function detalle($id){
$detallesDeVenta = $this->VentaModel->porId($id);
# Por si no existe la venta
if($detallesDeVenta->detalles === null){
$this->session->set_flashdata(array(
"mensaje" => "Los detalles de la venta no se pueden ver porque no existe una venta con ese ID",
"clase" => "warning",
));
redirect("ventas/");
}
$datos = array("venta" => $detallesDeVenta);
$this->load->view("encabezado");
$this->load->view("ventas/detalle", $datos);
$this->load->view("pie");
}
public function eliminar($id){
$resultado = $this->VentaModel->eliminar($id);
if($resultado){
$mensaje = "Venta eliminada";
$clase = "success";
}else{
$mensaje = "Error al eliminar la venta";
$clase = "warning";
}
$this->session->set_flashdata(array(
"mensaje" => $mensaje,
"clase" => $clase,
));
redirect("ventas/");
}
}
?>
Cargamos la librería de la sesión en el constructor porque en el método index
renderizamos una vista que muestra un mensaje si hay datos en la sesión; y si no la hubiéramos cargado habría errores. El método detalle
trae los detalles de una venta a través del modelo, recibe un id como argumento.
El lugar en donde listamos todas las ventas es una simple tabla. Esa tabla tiene un botón que permite ver los detalles de la misma, o eliminarla. Para obtener el total de la venta usamos la función SUM
de MySQL, y agrupamos. Eso tiene que ver más con bases de datos que con CodeIgniter o PHP.
La vista en donde se muestran es la siguiente:
<div class="col-xs-12">
<h1>Ventas</h1>
<?php if(!empty($this->session->flashdata())): ?>
<div class="alert alert-<?php echo $this->session->flashdata('clase')?>">
<?php echo $this->session->flashdata('mensaje') ?>
</div>
<?php endif; ?>
<div>
<a class="btn btn-success" href="<?php echo base_url() ?>index.php/vender">Nueva <i class="fa fa-plus"></i></a>
</div>
<br>
<table class="table table-bordered">
<thead>
<tr>
<th>Número</th>
<th>Fecha</th>
<th>Total</th>
<th>Detalles</th>
<th>Eliminar</th>
</tr>
</thead>
<tbody>
<?php foreach($ventas as $venta){ ?>
<tr>
<td><?php echo $venta->id ?></td>
<td><?php echo $venta->fecha ?></td>
<td><?php echo $venta->total ?></td>
<td><a class="btn btn-info" href="<?php echo base_url() . "index.php/ventas/detalle/" . $venta->id?>"><i class="fa fa-info"></i></a></td>
<td><a class="btn btn-danger" href="<?php echo base_url() . "index.php/ventas/eliminar/" . $venta->id?>"><i class="fa fa-trash"></i></a></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
La variable $ventas
es pasada por el controlador.
Finalmente aquí está la vista de cuando vemos el detalle de una venta. En grande se muestra el total y la fecha, y más abajo la lista de los productos vendidos.
<div class="col-xs-12">
<a class="btn btn-info" href="<?php echo base_url() ?>/index.php/ventas">Ver todas las ventas</a>
<h1>Detalles de la venta #<?php echo $venta->detalles->id; ?></h1>
<h4>Total: <span class="label label-success"><?php echo $venta->detalles->total ?></span></h4>
<h4>Fecha: <span class="label label-primary"><?php echo $venta->detalles->fecha ?></span></h4>
<h2>Productos vendidos</h2>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Código</th>
<th>Descripción</th>
<th>Cantidad</th>
<th>Precio</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<?php foreach($venta->productos as $producto){ ?>
<tr>
<td><?php echo $producto->codigo ?></td>
<td><?php echo $producto->descripcion ?></td>
<td><?php echo $producto->cantidad ?></td>
<td><?php echo $producto->precio ?></td>
<td><?php echo $producto->cantidad * $producto->precio ?></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
Por cierto, ponemos un simple enlace que lleva al listado de todas las ventas. Fácil y sencillo.
Espero que algún día pueda seguir con este sistema, no me gusta mucho pero queda bien a modo de ejercicio. Recuerda que arriba dejé el código fuente en GitHub.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.
Ver comentarios
Que tal amigo mi nombre es Carlos Antonio Rojas Gonzales de la Universidad Politécnica de Pachuca
En la materia mantenimiento de software de la carreraing en software realice tanto retroalimentacion de tu proyecto como mantenimiento, me gustaria saber si esta interesado en ver mi trabajo y de ser asi podria contactarme
Hola, me encanto tu explicación. me interesa saber si cobras por las tareas o exámenes de programación
Hola. Gracias por sus comentarios.
Así es, todo trabajo tiene un costo
Saludos!
hola, hay una observación, a la hora de hacer una venta, no llega a descontar la cantidad de producto en el almacén.
se llego a solucionar?
saludos
Hola, primero que todo comentarte que es muy bueno y dinámico la plataforma, lo otro como pudiste solucionar que descuente la cantidad del articulo según el stock??
Gracias por sus comentarios; lo arreglaré en cuanto pueda :)
hola, disculpe me sale un error a la hora de ejecutar el código, nose que estoy haciendo mal
[Running] php "c:\xampp\htdocs\ventasci-master\index.php"
"php" no se reconoce como un comando interno o externo,
programa o archivo por lotes ejecutable.
[Done] exited with code=1 in 0.461 seconds
sale la interfas pero solo vender, cuando selecciono productos o ventas me sale:
A Database Error Occurred
SQLSTATE[HY000] [1049] Unknown database 'ventas'
Filename: C:/xampp/htdocs/ventasci/system/database/drivers/pdo/pdo_driver.php
Line Number: 142
Claro, basta con leer el mensaje de error: no existe la base de datos "ventas" recuerda que tienes que crearla, en el repositorio está el esquema para que veas cómo es la creación
Saludos
Hola, me parece que estás ejecutando el archivo desde la línea de comandos, y no desde un servidor web.
Te recomiendo instalar XAMPP: https://parzibyte.me/blog/2017/12/11/configurar-instalar-php-7-apache-server-mysql-windows/
Después de eso, descarga y extrae el proyecto; colócalo en C:\xampp\htdocs
Ahora ve a localhost/ventasci y debería funcionar
Saludos cordiales
Amigo como haria el detalle de compra con solo el uso de php ?
Hola, si quieres hacerlo sin framework te recomiendo que le des un vistazo a este: https://parzibyte.me/blog/2018/03/13/pequeno-sistema-ventas-php/
Para el detalle sería una consulta a la base de datos, crear una vista y listo.
También puedo hacerlo por ti si gustas, solo mira mi página de contrataciones y ayuda
Saludos
me gusta la forma en la que realizaste el punto de venta, muy sencillo y práctico, tengo una pregunta... como se puede adaptar tu código para que al finalizar la venta, se genere e imprima un ticket o recibo..?
Hola. Al final de todo, el código es PHP (CodeIgniter es un framework, así de simple) por lo que puedes tomar este ejemplo: https://parzibyte.me/blog/2017/09/10/imprimir-ticket-en-impresora-termica-php/
Lo que se necesita es cargar el autoload (si quieres, con un
require_once
oinclude_once
) y de ahí trabajar normalmente en tu modelo.