Carpeta models
This commit is contained in:
@@ -0,0 +1,433 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class cajaModel {
|
||||||
|
|
||||||
|
// Variables privadas de la clase que se utilizan para armar los arrays de caja
|
||||||
|
|
||||||
|
private string $ventaKey = 'productosVenta';
|
||||||
|
private string $pedidoKey = 'productosPedido';
|
||||||
|
private string $servicioKey = 'insumosServicio';
|
||||||
|
private ?string $error = null;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
|
||||||
|
// Si no estan seteados, se crean los arrays vacios (para luego trabajar con los métodos)
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->ventaKey])) {
|
||||||
|
$_SESSION[$this->ventaKey] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->pedidoKey])) {
|
||||||
|
$_SESSION[$this->pedidoKey] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->servicioKey])) {
|
||||||
|
$_SESSION[$this->servicioKey] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MÉTODO PARA OBTENER ERRORES Y MANEJARLOS EN CONTROLLER
|
||||||
|
|
||||||
|
public function getError(): ?string {
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// <---------------------------------- MANEJO DE CAJA PARA VENTA ---------------------------------->
|
||||||
|
|
||||||
|
// AGREGAR PRODUCTO
|
||||||
|
|
||||||
|
public function agregarProducto(array $producto, int $cantidad): bool {
|
||||||
|
|
||||||
|
if ($cantidad <= 0 || empty($producto['id'])) {
|
||||||
|
$this->error = 'Datos inválidos del producto.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $producto['id'];
|
||||||
|
$stockDisponible = $producto['stock'];
|
||||||
|
|
||||||
|
// Si el producto ya se encuentra agregado
|
||||||
|
|
||||||
|
if (isset($_SESSION[$this->ventaKey][$id])) {
|
||||||
|
|
||||||
|
if ($cantidad > $stockDisponible) {
|
||||||
|
$this->error = 'La cantidad supera el stock disponible.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[$this->ventaKey][$id]['cantidad'] = $cantidad;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si aún no existe en el array continua la ejecución
|
||||||
|
|
||||||
|
if ($cantidad > $stockDisponible) {
|
||||||
|
$this->error = 'La cantidad supera el stock disponible.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cálculos
|
||||||
|
|
||||||
|
$descuentoTotal = $producto['descuento_producto'] + $producto['descuento_categoria'];
|
||||||
|
|
||||||
|
$precioFinal = ((100 - $descuentoTotal) * $producto['precio'] / 100); // PRECIO UNITARIO MENOS EL DESCUENTO
|
||||||
|
|
||||||
|
// Se guardan los datos en el array
|
||||||
|
|
||||||
|
$_SESSION[$this->ventaKey][$id] = [
|
||||||
|
'id_producto' => $id,
|
||||||
|
'marca' => $producto['marca'],
|
||||||
|
'modelo' => $producto['modelo'],
|
||||||
|
'precio' => $producto['precio'],
|
||||||
|
'descuento_total' => $descuentoTotal,
|
||||||
|
'cantidad' => $cantidad,
|
||||||
|
'stock' => $producto['stock'],
|
||||||
|
'precio_final' => $precioFinal
|
||||||
|
];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELIMINAR PRODUCTO
|
||||||
|
|
||||||
|
public function eliminarProducto(int $id): bool {
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->ventaKey][$id])) {
|
||||||
|
$this->error = 'El producto no existe en la caja.';
|
||||||
|
return false;
|
||||||
|
} else{
|
||||||
|
unset($_SESSION[$this->ventaKey][$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDITAR CANTIDAD DE PRODUCTO
|
||||||
|
|
||||||
|
public function editarCantidad(int $id, int $nuevaCantidad): bool {
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->ventaKey][$id])) {
|
||||||
|
$this->error = 'Producto inexistente en la caja.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nuevaCantidad <= 0) {
|
||||||
|
$this->error = 'La cantidad debe ser mayor a cero.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nuevaCantidad > $_SESSION[$this->ventaKey][$id]['stock']) {
|
||||||
|
$this->error = 'La cantidad supera el stock disponible.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[$this->ventaKey][$id]['cantidad'] = $nuevaCantidad;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PRODUCTOS
|
||||||
|
|
||||||
|
public function listarProductos(): array {
|
||||||
|
return $_SESSION[$this->ventaKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR SUBTOTAL
|
||||||
|
|
||||||
|
public function calcularSubtotal(): float {
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
foreach ($_SESSION[$this->ventaKey] as $item) {
|
||||||
|
|
||||||
|
$precio = (float) $item['precio'];
|
||||||
|
|
||||||
|
$total += $precio * $item['cantidad'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR PRECIO TOTAL
|
||||||
|
|
||||||
|
public function calcularPrecioTotal(): float {
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
foreach ($_SESSION[$this->ventaKey] as $item) {
|
||||||
|
|
||||||
|
$precio = (float) $item['precio'];
|
||||||
|
|
||||||
|
if (!empty($item['descuento_total'])) {
|
||||||
|
$precio -= ($item['descuento_total'] * $precio / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total += $precio * $item['cantidad'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR DESCUENTO TOTAL
|
||||||
|
|
||||||
|
public function calcularDescuentoTotal(): float {
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
foreach ($_SESSION[$this->ventaKey] as $item) {
|
||||||
|
|
||||||
|
$precio = (float) $item['precio'];
|
||||||
|
$descuento = 0;
|
||||||
|
|
||||||
|
if (!empty($item['descuento_total'])) {
|
||||||
|
$descuento += ($item['descuento_total'] * $precio / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total += $descuento * $item['cantidad'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VACIAR CAJA DE PRODUCTOS
|
||||||
|
|
||||||
|
public function vaciarCajaVenta(): bool {
|
||||||
|
$_SESSION[$this->ventaKey] = [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// <---------------------------------- MANEJO DE CAJA PARA PEDIDO ---------------------------------->
|
||||||
|
|
||||||
|
// AGREGAR PRODUCTO PARA PEDIDO
|
||||||
|
|
||||||
|
public function agregarProductoPedido(array $producto, float $costo, int $cantidad): bool {
|
||||||
|
|
||||||
|
if ($cantidad <= 0 || $costo <= 0 || empty($producto['id'])) {
|
||||||
|
$this->error = 'Datos inválidos del producto.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $producto['id'];
|
||||||
|
$ganancia = (float) ($producto['porcentaje_ganancia'] ?? 0);
|
||||||
|
|
||||||
|
$precio = (100 + $ganancia) * $costo / 100;
|
||||||
|
$stockfinal = $producto['stock'] + $cantidad;
|
||||||
|
|
||||||
|
// Si el producto se encuentra en el array
|
||||||
|
|
||||||
|
if (isset($_SESSION[$this->pedidoKey][$id])) {
|
||||||
|
$_SESSION[$this->pedidoKey][$id]['costo'] = $costo;
|
||||||
|
$_SESSION[$this->pedidoKey][$id]['precio'] = $precio;
|
||||||
|
$_SESSION[$this->pedidoKey][$id]['cantidad'] = $cantidad;
|
||||||
|
$_SESSION[$this->pedidoKey][$id]['stock_final'] = $stockfinal;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caso contrario se guarda en el arreglo
|
||||||
|
|
||||||
|
$_SESSION[$this->pedidoKey][$id] = [
|
||||||
|
'id_producto' => $id,
|
||||||
|
'marca' => $producto['marca'],
|
||||||
|
'modelo' => $producto['modelo'],
|
||||||
|
'costo' => $costo,
|
||||||
|
'precio' => $precio,
|
||||||
|
'cantidad' => $cantidad,
|
||||||
|
'stock' => $producto['stock'],
|
||||||
|
'stock_final' => $stockfinal,
|
||||||
|
'porcentaje_ganancia' => $ganancia
|
||||||
|
];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR TOTAL DE PEDIDO
|
||||||
|
|
||||||
|
public function calcularPrecioTotalPedido(): float {
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
if (empty($_SESSION[$this->pedidoKey])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($_SESSION[$this->pedidoKey] as $item) {
|
||||||
|
|
||||||
|
$costo = (float) $item['costo'];
|
||||||
|
|
||||||
|
$total += $costo * $item['cantidad'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PRODUCTOS PARA PEDIDO
|
||||||
|
|
||||||
|
public function listarProductosPedido(): array {
|
||||||
|
return $_SESSION[$this->pedidoKey] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELIMINAR PRODUCTO PARA PEDIDO DE LA CAJA
|
||||||
|
|
||||||
|
public function eliminarProductoPedido($id): bool {
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->pedidoKey][$id])) {
|
||||||
|
$this->error = 'El producto no existe en el pedido.';
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
unset($_SESSION[$this->pedidoKey][$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VACIAR CAJA DE PRODUCTOS PARA PEDIDO
|
||||||
|
|
||||||
|
public function vaciarCajaPedido(): bool {
|
||||||
|
$_SESSION[$this->pedidoKey] = [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDITAR DATOS DE PRODUCTO PARA PEDIDO
|
||||||
|
|
||||||
|
public function editarDatosProductoPedido(int $id, $nuevoCosto, $nuevaCantidad): bool {
|
||||||
|
|
||||||
|
if ($nuevoCosto <= 0 || $nuevaCantidad <= 0) {
|
||||||
|
$this->error = 'Costo o cantidad inválidos.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->pedidoKey][$id])) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$_SESSION[$this->pedidoKey][$id]['costo'] = $nuevoCosto;
|
||||||
|
$_SESSION[$this->pedidoKey][$id]['cantidad'] = $nuevaCantidad;
|
||||||
|
|
||||||
|
// Actualización de otro datos
|
||||||
|
|
||||||
|
$precio = (100 + $_SESSION[$this->pedidoKey][$id]['porcentaje_ganancia']) * $nuevoCosto / 100;
|
||||||
|
|
||||||
|
$stockfinal = $_SESSION[$this->pedidoKey][$id]['stock'] + $nuevaCantidad;
|
||||||
|
|
||||||
|
$_SESSION[$this->pedidoKey][$id]['precio'] = $precio;
|
||||||
|
$_SESSION[$this->pedidoKey][$id]['stock_final'] = $stockfinal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// <---------------------------------- MANEJO DE CAJA PARA SERVICIO ---------------------------------->
|
||||||
|
|
||||||
|
// LISTAR INSUMOS SERVICIO
|
||||||
|
|
||||||
|
public function listarInsumos(): array {
|
||||||
|
return $_SESSION[$this->servicioKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR EL TOTAL DEL SERVICIO
|
||||||
|
|
||||||
|
public function calcularPrecioTotalServicio(): float {
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
if (empty($_SESSION[$this->servicioKey])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($_SESSION[$this->servicioKey] as $item) {
|
||||||
|
|
||||||
|
$precio = (float) $item['precio'];
|
||||||
|
|
||||||
|
$total += $precio * $item['cantidad'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AGREGAR INSUMO A LA CAJA
|
||||||
|
|
||||||
|
public function agregarInsumo(array $insumo, int $cantidad): bool {
|
||||||
|
|
||||||
|
if ($cantidad <= 0 || empty($insumo['id'])) {
|
||||||
|
$this->error = 'Datos inválidos del insumo.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $insumo['id'];
|
||||||
|
$stockDisponible = $insumo['stock'];
|
||||||
|
|
||||||
|
// Si el insumo ya existe en el array
|
||||||
|
|
||||||
|
if (isset($_SESSION[$this->servicioKey][$id])) {
|
||||||
|
|
||||||
|
if ($cantidad > $stockDisponible) {
|
||||||
|
$this->error = 'La cantidad supera el stock disponible.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[$this->servicioKey][$id]['cantidad'] = $cantidad;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si no se encuentra en el array, lo inserta
|
||||||
|
|
||||||
|
if ($cantidad > $stockDisponible) {
|
||||||
|
$this->error = 'La cantidad supera el stock disponible.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[$this->servicioKey][$id] = [
|
||||||
|
'id_producto' => $id,
|
||||||
|
'marca' => $insumo['marca'],
|
||||||
|
'modelo' => $insumo['modelo'],
|
||||||
|
'precio' => $insumo['precio'],
|
||||||
|
'cantidad' => $cantidad,
|
||||||
|
'stock' => $insumo['stock'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VACIAR CAJA DE INSUMOS PARA SERVICIO
|
||||||
|
|
||||||
|
public function vaciarCajaServicio(): bool {
|
||||||
|
$_SESSION[$this->servicioKey] = [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELIMINAR INSUMO PARA SERVICIO DE LA CAJA
|
||||||
|
|
||||||
|
public function eliminarInsumoServicio($id): bool {
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->servicioKey][$id])) {
|
||||||
|
$this->error = 'El insumo no existe en el pedido.';
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
unset($_SESSION[$this->servicioKey][$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDITAR CANTIDAD DE INSUMO
|
||||||
|
|
||||||
|
public function editarCantidadInsumo(int $id, int $nuevaCantidad): bool {
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->servicioKey][$id])) {
|
||||||
|
$this->error = 'Insumo inexistente en la caja.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nuevaCantidad <= 0) {
|
||||||
|
$this->error = 'La cantidad debe ser mayor a cero.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nuevaCantidad > $_SESSION[$this->servicioKey][$id]['stock']) {
|
||||||
|
$this->error = 'La cantidad supera el stock disponible.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[$this->servicioKey][$id]['cantidad'] = $nuevaCantidad;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class clienteModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR CLIENTES
|
||||||
|
|
||||||
|
public function listarClientes(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre, apellido, telefono, email FROM clientes ORDER BY id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREAR CLIENTE
|
||||||
|
|
||||||
|
public function crearCliente(array $data): bool {
|
||||||
|
|
||||||
|
$sql = "INSERT INTO clientes (nombre, apellido, telefono, email, cuit, cond_iva) VALUES (:nombre, :apellido, :telefono, :email, :cuit, :cond_iva)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
':nombre' => $data['nombre'],
|
||||||
|
':apellido' => $data['apellido'],
|
||||||
|
':telefono' => $data['telefono'],
|
||||||
|
':email' => $data['email'],
|
||||||
|
':cuit' => $data['cuit'],
|
||||||
|
':cond_iva' => $data['cond_iva']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER CLIENTE POR ID
|
||||||
|
|
||||||
|
public function obtenerPorId(int $id): ?array { // El ? lo uso para que pueda devolver NULL
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre, apellido, telefono, email, cuit, cond_iva FROM clientes WHERE (id = :id) LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]); // Le asigna al parámetro nombrado el valor de $id
|
||||||
|
|
||||||
|
$cliente = $stmt->fetch(PDO::FETCH_ASSOC); // Devuelve un array asociativo
|
||||||
|
|
||||||
|
return $cliente ?: null; // Verifica si el array no esta vacío y lo devuelve, sino devuelve null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACTUALIZAR DATOS DE CLIENTE
|
||||||
|
|
||||||
|
public function actualizarCliente(int $id, array $data): bool {
|
||||||
|
|
||||||
|
if (empty($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$campos = [];
|
||||||
|
$params = []; // Se utiliza para evitar inyección SQL usando parámetros nombrados de PDO
|
||||||
|
|
||||||
|
foreach ($data as $campo => $valor) {
|
||||||
|
$campos[] = "$campo = :$campo"; // Arma el string relacionando $campo con los parámetros nombrados (se usa en la consulta)
|
||||||
|
$params[":$campo"] = $valor; // Arma array relacionando el parámetro nombrado con el valor (se usa en la consulta)
|
||||||
|
}
|
||||||
|
|
||||||
|
$params[':id'] = $id;
|
||||||
|
|
||||||
|
$sql = "UPDATE clientes SET " . implode(', ', $campos) . " WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute($params); // Relaciona los nombrados con los valores
|
||||||
|
}
|
||||||
|
|
||||||
|
// VERIFICAR EXISTENCIA DE CLIENTE (al crear)
|
||||||
|
|
||||||
|
public function verificarExistencia(array $datosCliente, ?int $idExcluir = null): bool {
|
||||||
|
|
||||||
|
$sql = "SELECT 1 FROM clientes WHERE ((nombre = :nombre AND apellido = :apellido AND email = :email) OR (email = :email) OR (cuit = :cuit))";
|
||||||
|
|
||||||
|
if ($idExcluir !== null) {
|
||||||
|
$sql .= " AND id <> :id_excluir";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
':nombre' => $datosCliente['nombre'],
|
||||||
|
':apellido' => $datosCliente['apellido'],
|
||||||
|
':email' => $datosCliente['email'],
|
||||||
|
':cuit' => $datosCliente['cuit']
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($idExcluir !== null) {
|
||||||
|
$params[':id_excluir'] = $idExcluir;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
return (bool) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class configuracionModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER CONFIGRACIÓN
|
||||||
|
|
||||||
|
public function obtenerConfiguracion() {
|
||||||
|
|
||||||
|
$sql = "SELECT nombre, direccion, ciudad, codigo_postal, provincia, telefono, email, cond_iva, cuit, iva, inicio_act,
|
||||||
|
hora_apertura_maniana, hora_cierre_maniana, hora_apertura_tarde, hora_cierre_tarde, mail_automatico, logo_ruta, cargo_cancelacion_servicio,
|
||||||
|
email_emisor, contrasenia_emisor
|
||||||
|
FROM configuracion WHERE id = 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACTUALIZAR CONFIGURACIÓN
|
||||||
|
|
||||||
|
public function actualizarConfiguracion(array $datosEmpresa): bool {
|
||||||
|
|
||||||
|
$sql = "UPDATE configuracion
|
||||||
|
SET nombre = :nombre,
|
||||||
|
direccion = :direccion,
|
||||||
|
ciudad = :ciudad,
|
||||||
|
codigo_postal = :codigo_postal,
|
||||||
|
provincia = :provincia,
|
||||||
|
telefono = :telefono,
|
||||||
|
email = :email,
|
||||||
|
cond_iva = :cond_iva,
|
||||||
|
cuit = :cuit,
|
||||||
|
iva = :iva,
|
||||||
|
hora_apertura_maniana = :hora_apertura_maniana,
|
||||||
|
hora_cierre_maniana = :hora_cierre_maniana,
|
||||||
|
hora_apertura_tarde = :hora_apertura_tarde,
|
||||||
|
hora_cierre_tarde = :hora_cierre_tarde,
|
||||||
|
mail_automatico = :mail_automatico,
|
||||||
|
cargo_cancelacion_servicio = :cargo_cancelacion_servicio,
|
||||||
|
email_emisor = :email_emisor,
|
||||||
|
contrasenia_emisor = :contrasenia_emisor
|
||||||
|
WHERE id = 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return (bool) $stmt->execute([
|
||||||
|
'nombre' => $datosEmpresa['nombre'],
|
||||||
|
'direccion' => $datosEmpresa['direccion'],
|
||||||
|
'ciudad' => $datosEmpresa['ciudad'],
|
||||||
|
'codigo_postal' => $datosEmpresa['codigo_postal'],
|
||||||
|
'provincia' => $datosEmpresa['provincia'],
|
||||||
|
'telefono' => $datosEmpresa['telefono'],
|
||||||
|
'email' => $datosEmpresa['email'],
|
||||||
|
'cond_iva' => $datosEmpresa['cond_iva'],
|
||||||
|
'cuit' => $datosEmpresa['cuit'],
|
||||||
|
'iva' => $datosEmpresa['iva'],
|
||||||
|
'hora_apertura_maniana' => $datosEmpresa['hora_apertura_maniana'],
|
||||||
|
'hora_cierre_maniana' => $datosEmpresa['hora_cierre_maniana'],
|
||||||
|
'hora_apertura_tarde' => $datosEmpresa['hora_apertura_tarde'],
|
||||||
|
'hora_cierre_tarde' => $datosEmpresa['hora_cierre_tarde'],
|
||||||
|
'mail_automatico' => $datosEmpresa['mail_automatico'],
|
||||||
|
'cargo_cancelacion_servicio' => $datosEmpresa['cargo_cancelacion_servicio'],
|
||||||
|
'email_emisor' => $datosEmpresa['email_emisor'],
|
||||||
|
'contrasenia_emisor' => $datosEmpresa['contrasenia_emisor']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER EL MONTO AL CANCELAR EL SERVICIO
|
||||||
|
|
||||||
|
public function obtenerCargoCancelacion(): float {
|
||||||
|
|
||||||
|
$sql = "SELECT cargo_cancelacion_servicio FROM configuracion WHERE id = 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return isset($row['cargo_cancelacion_servicio']) ? (float) $row['cargo_cancelacion_servicio'] : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER DATOS NECESARIOS PARA EL ENVIO DE EMAIL
|
||||||
|
|
||||||
|
public function obtenerDatosEnvioMail(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT direccion, ciudad, telefono, email, hora_apertura_maniana, hora_cierre_maniana, hora_apertura_tarde, hora_cierre_tarde, email_emisor, contrasenia_emisor, mail_automatico, cargo_cancelacion_servicio FROM configuracion
|
||||||
|
WHERE id = 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER FECHA DE ULTIMO BACKUP
|
||||||
|
|
||||||
|
public function obtenerFechaUltimoBackup(): ?string {
|
||||||
|
|
||||||
|
$backupDir = __DIR__ . '/../backups/';
|
||||||
|
|
||||||
|
$archivos = glob($backupDir . '*.sql');
|
||||||
|
|
||||||
|
if (!$archivos) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordenar por fecha de modificación (más reciente primero)
|
||||||
|
|
||||||
|
usort($archivos, function ($a, $b) {
|
||||||
|
return filemtime($b) - filemtime($a);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tomar el archivo más reciente
|
||||||
|
|
||||||
|
$ultimoArchivo = $archivos[0];
|
||||||
|
|
||||||
|
// Obtener fecha desde filemtime
|
||||||
|
|
||||||
|
$timestamp = filemtime($ultimoArchivo);
|
||||||
|
|
||||||
|
// Formatear a Y-m-d
|
||||||
|
|
||||||
|
return date('Y-m-d', $timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class informeModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER BALANCE ANUAL
|
||||||
|
|
||||||
|
public function obtenerBalanceAnual($anio) {
|
||||||
|
|
||||||
|
$sql = "WITH meses AS (
|
||||||
|
SELECT generate_series(1,12) AS mes
|
||||||
|
),
|
||||||
|
|
||||||
|
-- INGRESOS DE VENTAS
|
||||||
|
|
||||||
|
ingresos_ventas AS (
|
||||||
|
SELECT mes, SUM(total) AS total FROM (
|
||||||
|
SELECT
|
||||||
|
EXTRACT(MONTH FROM v.fecha)::int AS mes,
|
||||||
|
SUM(v.total) AS total
|
||||||
|
FROM ventas v
|
||||||
|
WHERE EXTRACT(YEAR FROM v.fecha)::int = :anio
|
||||||
|
GROUP BY mes
|
||||||
|
|
||||||
|
) sub GROUP BY mes
|
||||||
|
),
|
||||||
|
|
||||||
|
-- INGRESOS DE SERVICIOS
|
||||||
|
|
||||||
|
ingresos_servicios AS (
|
||||||
|
SELECT mes, SUM(total) AS total FROM (
|
||||||
|
SELECT
|
||||||
|
EXTRACT(MONTH FROM s.fecha_terminado)::int AS mes,
|
||||||
|
SUM(s.total) AS total
|
||||||
|
FROM servicios s
|
||||||
|
WHERE EXTRACT(YEAR FROM s.fecha_terminado)::int = :anio
|
||||||
|
GROUP BY mes
|
||||||
|
) sub GROUP BY mes
|
||||||
|
),
|
||||||
|
|
||||||
|
-- COSTOS DE PEDIDOS
|
||||||
|
|
||||||
|
costos_pedidos AS (
|
||||||
|
SELECT mes, SUM(total) AS total FROM (
|
||||||
|
SELECT
|
||||||
|
EXTRACT(MONTH FROM p.fecha)::int AS mes,
|
||||||
|
SUM(p.total) AS total
|
||||||
|
FROM pedidos p
|
||||||
|
WHERE EXTRACT(YEAR FROM p.fecha)::int = :anio
|
||||||
|
GROUP BY mes
|
||||||
|
) sub GROUP BY mes
|
||||||
|
)
|
||||||
|
|
||||||
|
-- SUMAS Y CALCULO DE UTILIDAD
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
m.mes,
|
||||||
|
COALESCE(SUM(iv.total),0) AS ventas,
|
||||||
|
COALESCE(SUM(isv.total),0) AS servicios,
|
||||||
|
COALESCE(SUM(cp.total),0) AS costos,
|
||||||
|
COALESCE(SUM(iv.total),0)
|
||||||
|
+ COALESCE(SUM(isv.total),0)
|
||||||
|
- COALESCE(SUM(cp.total),0) AS utilidad
|
||||||
|
FROM meses m
|
||||||
|
LEFT JOIN ingresos_ventas iv ON (iv.mes = m.mes)
|
||||||
|
LEFT JOIN ingresos_servicios isv ON (isv.mes = m.mes)
|
||||||
|
LEFT JOIN costos_pedidos cp ON (cp.mes = m.mes)
|
||||||
|
GROUP BY m.mes
|
||||||
|
ORDER BY m.mes";
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->bindValue(':anio', $anio, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$resultados = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$meses = [];
|
||||||
|
$ventas = [];
|
||||||
|
$servicios = [];
|
||||||
|
$costos = [];
|
||||||
|
$utilidades = [];
|
||||||
|
|
||||||
|
$totalVentas = 0;
|
||||||
|
$totalServicios = 0;
|
||||||
|
$totalCostos = 0;
|
||||||
|
$totalUtilidad = 0;
|
||||||
|
|
||||||
|
$mejorMes = '';
|
||||||
|
$maxUtilidad = null;
|
||||||
|
|
||||||
|
// Calculo de totales
|
||||||
|
|
||||||
|
foreach ($resultados as $row) {
|
||||||
|
|
||||||
|
$meses[] = (int) $row['mes'];
|
||||||
|
$ventas[] = (float) $row['ventas'];
|
||||||
|
$servicios[] = (float) $row['servicios'];
|
||||||
|
$costos[] = (float) $row['costos'];
|
||||||
|
$utilidades[] = (float) $row['utilidad'];
|
||||||
|
|
||||||
|
$totalVentas += $row['ventas'];
|
||||||
|
$totalServicios += $row['servicios'];
|
||||||
|
$totalCostos += $row['costos'];
|
||||||
|
$totalUtilidad += $row['utilidad'];
|
||||||
|
|
||||||
|
if ($maxUtilidad === null || $row['utilidad'] > $maxUtilidad) {
|
||||||
|
$maxUtilidad = $row['utilidad'];
|
||||||
|
$mejorMes = $row['mes'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapeo de nombres de mes
|
||||||
|
|
||||||
|
$nombresMeses = [
|
||||||
|
1=>'Enero',2=>'Febrero',3=>'Marzo',4=>'Abril',
|
||||||
|
5=>'Mayo',6=>'Junio',7=>'Julio',8=>'Agosto',
|
||||||
|
9=>'Septiembre',10=>'Octubre',11=>'Noviembre',12=>'Diciembre'
|
||||||
|
];
|
||||||
|
|
||||||
|
$mejorMesNombre = $nombresMeses[$mejorMes] ?? '';
|
||||||
|
|
||||||
|
// Retorno de array completo
|
||||||
|
|
||||||
|
return [
|
||||||
|
'meses' => $meses,
|
||||||
|
'ventas' => $ventas,
|
||||||
|
'servicios' => $servicios,
|
||||||
|
'costos' => $costos,
|
||||||
|
'utilidad' => $utilidades,
|
||||||
|
'kpis' => [
|
||||||
|
'totalIngresos' => $totalVentas + $totalServicios,
|
||||||
|
'totalCostos' => $totalCostos,
|
||||||
|
'resultado' => $totalUtilidad,
|
||||||
|
'mejorMes' => $mejorMesNombre
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
|
||||||
|
// Log interno
|
||||||
|
|
||||||
|
error_log($e->getMessage());
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER MONTOS A COBRAR
|
||||||
|
|
||||||
|
public function obtenerACobrar($anio) {
|
||||||
|
|
||||||
|
$sql = "WITH meses AS (
|
||||||
|
SELECT generate_series(1,12) AS mes
|
||||||
|
),
|
||||||
|
|
||||||
|
-- SERVICIOS PENDIENTES
|
||||||
|
|
||||||
|
servicios_pendientes AS (
|
||||||
|
SELECT
|
||||||
|
EXTRACT(MONTH FROM s.fecha_registro)::int AS mes,
|
||||||
|
SUM(s.total) AS total
|
||||||
|
FROM servicios s
|
||||||
|
WHERE s.estado = 'pendiente - enviado'
|
||||||
|
AND EXTRACT(YEAR FROM s.fecha_registro)::int = :anio
|
||||||
|
GROUP BY mes
|
||||||
|
)
|
||||||
|
|
||||||
|
-- RESULTADO FINAL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
m.mes,
|
||||||
|
COALESCE(sp.total,0) AS servicios
|
||||||
|
FROM meses m
|
||||||
|
LEFT JOIN servicios_pendientes sp ON (sp.mes = m.mes)
|
||||||
|
ORDER BY m.mes";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->bindValue(':anio', $anio, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$resultados = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$meses = $totales = [];
|
||||||
|
|
||||||
|
foreach ($resultados as $row) {
|
||||||
|
$meses[] = (int) $row['mes'];
|
||||||
|
$totales[] = (float) $row['servicios'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'meses' => $meses,
|
||||||
|
'totales' => $totales,
|
||||||
|
'kpis' => [
|
||||||
|
'totalACobrar' => array_sum($totales)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
|
||||||
|
// Log interno
|
||||||
|
|
||||||
|
error_log($e->getMessage());
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class logInModel {
|
||||||
|
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
// ESTABLECER CONECCIÓN CON BASE DE DATOS
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection(); // Guardamos el objeto PDO en $db
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUSCAR USUARIO
|
||||||
|
|
||||||
|
public function buscarPorUsuario($usuario) {
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM usuarios WHERE (usuario = :usuario) AND (usuarios.activo = true) LIMIT 1"; // LIMIT 1, más que nada por seguridad u optimizacón. :usuario es reemplazado luego con el valor real
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql); // Uso de prepare para evitar SQL Injection
|
||||||
|
|
||||||
|
$stmt->bindParam(':usuario', $usuario, PDO::PARAM_STR); // Asocia la variable $usuario con el placeholder :usuario en la consulta.
|
||||||
|
|
||||||
|
$stmt->execute(); // Ejecuta la consulta ya preparada con los parámetros vinculados
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC); // Obtiene la primera fila del resultado como un array asociativo
|
||||||
|
}
|
||||||
|
|
||||||
|
// INVALIDAR POR USUARIO
|
||||||
|
|
||||||
|
public function invalidarPorUsuario(int $id): bool {
|
||||||
|
|
||||||
|
$sql = "UPDATE recuperacion_cuenta
|
||||||
|
SET usado = 1
|
||||||
|
WHERE (id_usuario = :id_usuario)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([':id_usuario' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREAR RECUPERACIÓN
|
||||||
|
|
||||||
|
public function crear(array $data): bool {
|
||||||
|
|
||||||
|
$sql = "INSERT INTO recuperacion_cuenta (id_usuario, codigo, expira_a, usado) VALUES (:id_usuario, :codigo, :expira_a, 0)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
':id_usuario' => $data['id_usuario'],
|
||||||
|
':codigo' => $data['codigo'],
|
||||||
|
':expira_a' => $data['expira_a']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER ÚLTIMO CÓDIGO VÁLIDO
|
||||||
|
|
||||||
|
public function obtenerCodigoValido(int $idUsuario): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, codigo, expira_a, usado FROM recuperacion_cuenta
|
||||||
|
WHERE (id_usuario = :id_usuario) AND (usado = 0)
|
||||||
|
ORDER BY id DESC LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id_usuario' => $idUsuario]);
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARCAR COMO USADO
|
||||||
|
|
||||||
|
public function marcarComoUsado(int $id): bool {
|
||||||
|
|
||||||
|
$sql = "UPDATE recuperacion_cuenta
|
||||||
|
SET usado = 1
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([':id' => $id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class pedidoModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
private ?string $error = null;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// MÉTODO PARA OBTENER ERRORES E INFORMARLOS A TRAVÉZ DEL CONTROLLER
|
||||||
|
|
||||||
|
public function getError(): ?string {
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GUARDAR PEDIDO (retorna false o el ID del pedido generado)
|
||||||
|
|
||||||
|
public function guardarPedido(array $productosFinales, array $proveedor, array $vendedor, float $total): int|false {
|
||||||
|
|
||||||
|
// Validaciones (ya verifica el controller también)
|
||||||
|
|
||||||
|
if (empty($productosFinales)) {
|
||||||
|
$this->error = 'El pedido no tiene productos.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($proveedor['id'])) {
|
||||||
|
$this->error = 'Proveedor inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($vendedor['id'])) {
|
||||||
|
$this->error = 'No se pudo vincular al usuario.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($total <= 0) {
|
||||||
|
$this->error = 'Total de pedido inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($productosFinales as $p) {
|
||||||
|
if (empty($p['id_producto']) || empty($p['cantidad']) || empty($p['costo']) || empty($p['precio'])) {
|
||||||
|
$this->error = 'Producto inválido en el pedido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($p['cantidad'] <= 0 || $p['costo'] < 0) {
|
||||||
|
$this->error = 'Cantidad o costo inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
// 1. INSERT PEDIDO
|
||||||
|
|
||||||
|
$sqlPedido = "
|
||||||
|
INSERT INTO pedidos (total, id_proveedor, id_usuario)
|
||||||
|
VALUES (:total, :id_proveedor, :id_usuario)
|
||||||
|
RETURNING id
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtPedido = $this->db->prepare($sqlPedido);
|
||||||
|
|
||||||
|
$stmtPedido->execute([
|
||||||
|
':total' => $total,
|
||||||
|
':id_proveedor' => $proveedor['id'],
|
||||||
|
':id_usuario' => $vendedor['id']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$idPedido = $stmtPedido->fetchColumn();
|
||||||
|
|
||||||
|
if (!$idPedido) {
|
||||||
|
$this->error = 'No se pudo crear el pedido.';
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. INSERT DETALLE (LOOP)
|
||||||
|
|
||||||
|
$sqlDetalle = "
|
||||||
|
INSERT INTO pedido_detalle
|
||||||
|
(precio_unitario, cantidad, subtotal, id_pedido, id_producto)
|
||||||
|
VALUES
|
||||||
|
(:precio_unitario, :cantidad, :subtotal, :id_pedido, :id_producto)
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtDetalle = $this->db->prepare($sqlDetalle);
|
||||||
|
|
||||||
|
foreach ($productosFinales as $p) {
|
||||||
|
|
||||||
|
$subtotal = $p['costo'] * $p['cantidad'];
|
||||||
|
|
||||||
|
if (!$stmtDetalle->execute([
|
||||||
|
':precio_unitario' => $p['costo'],
|
||||||
|
':cantidad' => $p['cantidad'],
|
||||||
|
':subtotal' => $subtotal,
|
||||||
|
':id_pedido' => $idPedido,
|
||||||
|
':id_producto' => $p['id_producto'],
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
$this->error = 'Error al guardar el detalle del pedido.';
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. ACTUALIZAR DATOS DE PRODUCTO
|
||||||
|
|
||||||
|
$sqlStock = "
|
||||||
|
UPDATE productos
|
||||||
|
SET
|
||||||
|
stock = COALESCE(stock, 0) + :cantidad,
|
||||||
|
precio = :precio
|
||||||
|
WHERE id = :id_producto
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtStock = $this->db->prepare($sqlStock);
|
||||||
|
|
||||||
|
foreach ($productosFinales as $p) {
|
||||||
|
|
||||||
|
if (!$stmtStock->execute([
|
||||||
|
':cantidad' => $p['cantidad'],
|
||||||
|
':precio' => $p['precio'],
|
||||||
|
':id_producto' => $p['id_producto'],
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
$this->error = 'Error al actualizar el producto.';
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. COMMIT
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return $idPedido;
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
|
||||||
|
// Log interno
|
||||||
|
|
||||||
|
error_log($e->getMessage());
|
||||||
|
|
||||||
|
$this->error = 'Error interno al guardar el pedido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PEDIDOS
|
||||||
|
|
||||||
|
public function listarPedidos(int $anio): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.fecha, p.total, pe.razon_social, u.nombre AS vendedor_nombre, u.apellido AS vendedor_apellido FROM pedidos AS p
|
||||||
|
INNER JOIN proveedores AS pe ON (p.id_proveedor = pe.id)
|
||||||
|
INNER JOIN usuarios AS u ON (p.id_usuario = u.id)
|
||||||
|
WHERE EXTRACT(YEAR FROM p.fecha) = :anio
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':anio' => $anio]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER DETALLE DE PEDIDO
|
||||||
|
|
||||||
|
public function getDetallePedido(int $idPedido): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id AS id_producto, p.marca, p.modelo, pd.precio_unitario, pd.cantidad, pd.subtotal FROM pedido_detalle AS pd
|
||||||
|
INNER JOIN productos AS p ON (pd.id_producto = p.id)
|
||||||
|
WHERE (pd.id_pedido = :id_pedido)
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id_pedido' => $idPedido]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER PREVIEW DE CODIGO DE REMITO (no se utiliza más)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
public function getNumeroRemitoPreview(): int {
|
||||||
|
|
||||||
|
$sql = "SELECT nextval('remito_preview_seq')";
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return (int) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// OBTENER COSTO DE PRODUCTO
|
||||||
|
|
||||||
|
public function obtenerCosto(int $idProducto): ?float {
|
||||||
|
|
||||||
|
$sql = "SELECT pd.precio_unitario FROM pedido_detalle AS pd
|
||||||
|
INNER JOIN productos AS p ON (pd.id_producto = p.id)
|
||||||
|
WHERE (p.id = :idProducto)
|
||||||
|
AND pd.id_pedido = (
|
||||||
|
SELECT MAX(pd2.id_pedido)
|
||||||
|
FROM pedido_detalle AS pd2
|
||||||
|
WHERE pd2.id_producto = :idProducto2
|
||||||
|
)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':idProducto' => $idProducto,
|
||||||
|
':idProducto2' => $idProducto
|
||||||
|
]);
|
||||||
|
|
||||||
|
$resultado = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
return $resultado !== false ? (float) $resultado : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER COSTOS DE PRODUCTOS EN BASE A UNA CATEGORIA
|
||||||
|
|
||||||
|
public function obtenerCostosPorCategoria(int $idCategoria): array {
|
||||||
|
|
||||||
|
// Uso de LATERAL en LEFT JOIN -> permite traer el costo mas reciente (actúa como un foreach, ejecutando la subconsulta por cada producto)
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
p.id AS id_producto, pd.precio_unitario AS costo FROM productos AS p
|
||||||
|
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT pd.precio_unitario FROM pedido_detalle AS pd
|
||||||
|
INNER JOIN pedidos AS pe ON (pe.id = pd.id_pedido)
|
||||||
|
WHERE (pd.id_producto = p.id)
|
||||||
|
ORDER BY pe.fecha DESC, pd.id DESC LIMIT 1
|
||||||
|
) pd ON true
|
||||||
|
|
||||||
|
WHERE (p.id_categoria = :idCategoria)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':idCategoria' => $idCategoria]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class presupuestoModel {
|
||||||
|
|
||||||
|
private string $presupuestoKey = 'productosPresupuesto';
|
||||||
|
private ?string $error = null;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->presupuestoKey])) {
|
||||||
|
$_SESSION[$this->presupuestoKey] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MÉTODO PARA OBTENER ERRORES E INFORMAR A TRAVEZ DEL CONTROLLER
|
||||||
|
|
||||||
|
public function getError(): ?string {
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AGREGAR PRODUCTO
|
||||||
|
|
||||||
|
public function agregarProducto(array $producto, int $cantidad): bool {
|
||||||
|
|
||||||
|
if ($cantidad <= 0 || empty($producto['id'])) {
|
||||||
|
$this->error = 'Datos inválidos del producto.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $producto['id'];
|
||||||
|
|
||||||
|
$cantidadActual = $_SESSION[$this->presupuestoKey][$id]['cantidad'] ?? 0;
|
||||||
|
$stockDisponible = $producto['stock'];
|
||||||
|
|
||||||
|
if (($cantidadActual + $cantidad) > $stockDisponible) {
|
||||||
|
$this->error = 'La cantidad supera el stock disponible.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$descuentoTotal = $producto['descuento_producto'] + $producto['descuento_categoria'];
|
||||||
|
|
||||||
|
$precioFinal = ((100 - $descuentoTotal) * $producto['precio'] / 100); // PRECIO UNITARIO MENOS EL DESCUENTO
|
||||||
|
|
||||||
|
if (isset($_SESSION[$this->presupuestoKey][$id])) {
|
||||||
|
$_SESSION[$this->presupuestoKey][$id]['cantidad'] = $cantidad;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[$this->presupuestoKey][$id] = [
|
||||||
|
'id_producto' => $id,
|
||||||
|
'marca' => $producto['marca'],
|
||||||
|
'modelo' => $producto['modelo'],
|
||||||
|
'precio' => $producto['precio'],
|
||||||
|
'descuento_total' => $descuentoTotal,
|
||||||
|
'cantidad' => $cantidad,
|
||||||
|
'stock' => $producto['stock'],
|
||||||
|
'precio_final' => $precioFinal
|
||||||
|
];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PRODUCTOS
|
||||||
|
|
||||||
|
public function listarProductos(): array {
|
||||||
|
return $_SESSION[$this->presupuestoKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELIMINAR PRODUCTO
|
||||||
|
|
||||||
|
public function eliminarProducto(int $id): bool {
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->presupuestoKey][$id])) {
|
||||||
|
$this->error = 'El producto no existe en la caja.';
|
||||||
|
return false;
|
||||||
|
} else{
|
||||||
|
unset($_SESSION[$this->presupuestoKey][$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDITAR CANTIDAD DE PRODUCTO
|
||||||
|
|
||||||
|
public function editarCantidad(int $id, int $nuevaCantidad): bool {
|
||||||
|
|
||||||
|
if (!isset($_SESSION[$this->presupuestoKey][$id])) {
|
||||||
|
$this->error = 'Producto inexistente en la caja.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nuevaCantidad <= 0) {
|
||||||
|
$this->error = 'La cantidad debe ser mayor a cero.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nuevaCantidad > $_SESSION[$this->presupuestoKey][$id]['stock']) {
|
||||||
|
$this->error = 'La cantidad supera el stock disponible.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[$this->presupuestoKey][$id]['cantidad'] = $nuevaCantidad;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR SUBTOTAL
|
||||||
|
|
||||||
|
public function calcularSubtotal(): float {
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
foreach ($_SESSION[$this->presupuestoKey] as $item) {
|
||||||
|
|
||||||
|
$precio = (float) $item['precio'];
|
||||||
|
|
||||||
|
$total += $precio * $item['cantidad'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR PRECIO TOTAL
|
||||||
|
|
||||||
|
public function calcularPrecioTotal(): float {
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
foreach ($_SESSION[$this->presupuestoKey] as $item) {
|
||||||
|
|
||||||
|
$precio = (float) $item['precio'];
|
||||||
|
|
||||||
|
if (!empty($item['descuento_total'])) {
|
||||||
|
$precio -= ($item['descuento_total'] * $precio / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total += $precio * $item['cantidad'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR DESCUENTO TOTAL
|
||||||
|
|
||||||
|
public function calcularDescuentoTotal(): float {
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
foreach ($_SESSION[$this->presupuestoKey] as $item) {
|
||||||
|
|
||||||
|
$precio = (float) $item['precio'];
|
||||||
|
$descuento = 0;
|
||||||
|
|
||||||
|
if (!empty($item['descuento_total'])) {
|
||||||
|
$descuento += ($item['descuento_total'] * $precio / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total += $descuento * $item['cantidad'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VACIAR CAJA DE PRODUCTOS
|
||||||
|
|
||||||
|
public function vaciarCajaPresupuesto(): bool {
|
||||||
|
$_SESSION[$this->presupuestoKey] = [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR TOTAL PROVISORIO APLICANDO DESCUENTO Y RECARGO EN BASE A TIPO Y FORMA DE PAGO
|
||||||
|
|
||||||
|
public function actualizarDatosProvisorio($descuentoPresupuesto, $recargoPresupuesto, $subtotal, $descuento): ?array {
|
||||||
|
|
||||||
|
// El descuento propio del presupuesto se calcula en base al subtotal con los descuento de productos aplicados
|
||||||
|
|
||||||
|
$descuentoProvisorio = ($descuentoPresupuesto * ($subtotal - $descuento) / 100) + $descuento; // Sumamos los dos descuentos
|
||||||
|
|
||||||
|
// Calculo el recargo teniendo en cuenta el subtotal (descuentos de productos ya aplicados)
|
||||||
|
|
||||||
|
$recargoProvisorio = ($recargoPresupuesto * ($subtotal - $descuento) / 100);
|
||||||
|
|
||||||
|
$totalProvisorio = $subtotal + $recargoProvisorio - $descuentoProvisorio;
|
||||||
|
|
||||||
|
return $_SESSION['prov_presupuesto'] = [
|
||||||
|
'recargo' => $recargoProvisorio,
|
||||||
|
'descuento' => $descuentoProvisorio,
|
||||||
|
'total' => $totalProvisorio
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class productoModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PRODUCTOS
|
||||||
|
|
||||||
|
public function listarProductos(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.precio, p.stock, (COALESCE(p.descuento, 0) + COALESCE(c.descuento, 0)) AS descuento_total, c.nombre AS categoria, t.nombre AS tipo
|
||||||
|
FROM productos AS p
|
||||||
|
INNER JOIN categorias AS c ON (p.id_categoria = c.id)
|
||||||
|
INNER JOIN tipos AS t ON (p.id_tipo = t.id)
|
||||||
|
WHERE (p.estado IS TRUE)
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PRODUCTOS INACTIVOS
|
||||||
|
|
||||||
|
public function listarProductosInactivos(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.precio, c.nombre AS categoria, t.nombre AS tipo
|
||||||
|
FROM productos AS p
|
||||||
|
INNER JOIN categorias AS c ON (p.id_categoria = c.id)
|
||||||
|
INNER JOIN tipos AS t ON (p.id_tipo = t.id)
|
||||||
|
WHERE (p.estado IS FALSE)
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PRODUCTOS PARA VENTA
|
||||||
|
|
||||||
|
public function listarProductosVenta(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.precio, CAST(p.precio - ((COALESCE(p.descuento, 0) + COALESCE(c.descuento, 0)) * p.precio / 100) AS DECIMAL(10,2)) AS precio_final, p.stock,
|
||||||
|
(COALESCE(p.descuento, 0) + COALESCE(c.descuento, 0)) AS descuento_total
|
||||||
|
FROM productos AS p
|
||||||
|
INNER JOIN categorias AS c ON (p.id_categoria = c.id)
|
||||||
|
INNER JOIN tipos AS t ON (p.id_tipo = t.id)
|
||||||
|
WHERE (p.estado IS TRUE) AND (p.id_tipo = 1) AND (p.precio IS NOT NULL) AND (p.stock <> 0)
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PORDUCTOS PARA PEDIDO
|
||||||
|
|
||||||
|
public function listarProductosPedido(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.stock FROM productos AS p
|
||||||
|
INNER JOIN tipos AS t ON (p.id_tipo = t.id)
|
||||||
|
WHERE (p.estado IS TRUE)
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR INSUMOS PARA SERVICIO
|
||||||
|
|
||||||
|
public function listarInsumosServicio(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.precio, p.stock
|
||||||
|
FROM productos AS p
|
||||||
|
INNER JOIN categorias AS c ON (p.id_categoria = c.id)
|
||||||
|
INNER JOIN tipos AS t ON (p.id_tipo = t.id)
|
||||||
|
WHERE (p.estado IS TRUE) AND (p.id_tipo = 2) AND (p.precio IS NOT NULL) AND (p.stock <> 0)
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREAR PRODUCTO
|
||||||
|
|
||||||
|
public function crearProducto(array $data): bool {
|
||||||
|
|
||||||
|
$sql = "INSERT INTO productos (marca, modelo, precio, stock, descuento, estado, id_categoria, id_tipo)
|
||||||
|
VALUES (:marca, :modelo, :precio, :stock, :descuento, :estado, :id_categoria, :id_tipo)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
':marca' => $data['marca'],
|
||||||
|
':modelo' => $data['modelo'],
|
||||||
|
':precio' => $data['precio'],
|
||||||
|
':stock' => $data['stock'],
|
||||||
|
':descuento' => $data['descuento'],
|
||||||
|
':estado' => 1, // Cuando un producto se crea, automáticamente se setea el estado en true, es decir está activo
|
||||||
|
':id_categoria' => $data['id_categoria'],
|
||||||
|
':id_tipo' => $data['id_tipo']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER PRODUCTO MEDIANTE ID
|
||||||
|
|
||||||
|
public function obtenerPorId(int $id): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.precio, p.stock, p.descuento, p.id_categoria, p.id_tipo, pd.id_pedido AS id_pedido FROM productos AS p
|
||||||
|
LEFT JOIN pedido_detalle AS pd ON (p.id = pd.id_producto)
|
||||||
|
WHERE p.id = :id
|
||||||
|
ORDER BY pd.id_pedido DESC LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
$producto = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $producto ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER PRODUCTO POR ID PARA CAJA
|
||||||
|
|
||||||
|
public function obtenerPorIdParaCaja(int $id): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.precio, COALESCE(p.descuento, 0) AS descuento_producto, COALESCE(c.descuento, 0) AS descuento_categoria, p.stock
|
||||||
|
FROM productos AS p
|
||||||
|
LEFT JOIN categorias AS c ON (c.id = p.id_categoria)
|
||||||
|
WHERE (p.id = :id) AND (p.estado IS TRUE) AND (p.id_tipo = 1) LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER PRODUCTOS DE PEDIDO POR ID PARA CAJA
|
||||||
|
|
||||||
|
public function obtenerPorIdParaCajaPedido(int $id): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.stock, COALESCE(c.porcentaje_ganancia, 0) AS porcentaje_ganancia
|
||||||
|
FROM productos AS p
|
||||||
|
LEFT JOIN categorias AS c ON (c.id = p.id_categoria)
|
||||||
|
WHERE (p.id = :id) AND (p.estado IS TRUE) LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER INSUMO DE SERVICIO POR ID PARA CAJA
|
||||||
|
|
||||||
|
public function obtenerPorIdParaCajaServicio(int $id): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.marca, p.modelo, p.precio, p.stock
|
||||||
|
FROM productos AS p
|
||||||
|
LEFT JOIN categorias AS c ON (c.id = p.id_categoria)
|
||||||
|
WHERE (p.id = :id) AND (p.estado IS TRUE) AND (p.id_tipo = 2) LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDITAR PRODUCTO
|
||||||
|
|
||||||
|
public function actualizarProducto(int $id, array $data): bool {
|
||||||
|
|
||||||
|
if (empty($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$campos = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($data as $campo => $valor) {
|
||||||
|
$campos[] = "$campo = :$campo";
|
||||||
|
$params[":$campo"] = $valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
$params[':id'] = $id;
|
||||||
|
|
||||||
|
$sql = "UPDATE productos SET " . implode(', ', $campos) . " WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACTUALIZAR ESTADO (ACTIVO / INACTIVO)
|
||||||
|
|
||||||
|
public function actualizarEstado(int $idProducto, bool $nuevoEstado): bool {
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
// Actualizamaos datos del producto
|
||||||
|
|
||||||
|
$sql1 = "UPDATE productos
|
||||||
|
SET estado = :estado,
|
||||||
|
precio = NULL,
|
||||||
|
stock = NULL,
|
||||||
|
descuento = NULL
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt1 = $this->db->prepare($sql1);
|
||||||
|
|
||||||
|
$stmt1->bindValue(':estado', $nuevoEstado, PDO::PARAM_BOOL);
|
||||||
|
|
||||||
|
$stmt1->bindValue(':id', $idProducto, PDO::PARAM_INT);
|
||||||
|
|
||||||
|
$stmt1->execute();
|
||||||
|
|
||||||
|
// Actualizamos los stock y cantidades de pedido_detalle
|
||||||
|
|
||||||
|
$sql2 = "UPDATE pedido_detalle
|
||||||
|
SET cantidad_vendida = cantidad
|
||||||
|
WHERE id_producto = :id";
|
||||||
|
|
||||||
|
$stmt2 = $this->db->prepare($sql2);
|
||||||
|
|
||||||
|
$stmt2->bindValue(':id', $idProducto, PDO::PARAM_INT);
|
||||||
|
|
||||||
|
$stmt2->execute();
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
|
||||||
|
// Log interno
|
||||||
|
|
||||||
|
error_log($e->getMessage());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR CATEGORIAS
|
||||||
|
|
||||||
|
public function listarCategorias(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre, porcentaje_ganancia, descuento FROM categorias ORDER BY nombre ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VERIFICAR SI EXISTE UNA CATEGORIA
|
||||||
|
|
||||||
|
public function existeCategoria(int $id): bool {
|
||||||
|
|
||||||
|
$sql = "SELECT 1 FROM categorias WHERE id = :id LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
return (bool) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER CATEGORIA POR ID
|
||||||
|
|
||||||
|
public function obtenerPorIdCategoria(int $id): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre, porcentaje_ganancia, descuento FROM categorias WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
$categoria = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $categoria ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREAR CATEGORIA
|
||||||
|
|
||||||
|
public function crearCategoria(array $data): bool {
|
||||||
|
|
||||||
|
$sql = "INSERT INTO categorias (nombre, porcentaje_ganancia, descuento) VALUES (:nombre, :porcentaje_ganancia, :descuento)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
':nombre' => $data['nombre'],
|
||||||
|
':porcentaje_ganancia' => $data['porcentaje_ganancia'],
|
||||||
|
':descuento' => $data['descuento']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDITAR CATEGORIA
|
||||||
|
|
||||||
|
public function actualizarCategoria(int $id, array $data): bool {
|
||||||
|
|
||||||
|
if (empty($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$campos = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($data as $campo => $valor) {
|
||||||
|
$campos[] = "$campo = :$campo";
|
||||||
|
$params[":$campo"] = $valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
$params[':id'] = $id;
|
||||||
|
|
||||||
|
$sql = "UPDATE categorias SET " . implode(', ', $campos) . " WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACTUALIZAR PRECIOS DE PRODUCTOS AL EDITAR LA CATEGORIA
|
||||||
|
|
||||||
|
public function actualizarPreciosPorCategoria(int $idCategoria, array $data, array $costosProductos): bool {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
// Actualizamos la categoría
|
||||||
|
|
||||||
|
$ok = $this->actualizarCategoria($idCategoria, $data);
|
||||||
|
|
||||||
|
if (!$ok) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtenemos el nuevo porcentaje de ganancia
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
SELECT porcentaje_ganancia
|
||||||
|
FROM categorias
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $idCategoria]);
|
||||||
|
|
||||||
|
$categoria = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$categoria) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$porcentaje_ganancia = (float) $categoria['porcentaje_ganancia'];
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
UPDATE productos
|
||||||
|
SET precio = :precio
|
||||||
|
WHERE (id = :id) AND (id_categoria = :idCategoria)
|
||||||
|
");
|
||||||
|
|
||||||
|
foreach ($costosProductos as $c) {
|
||||||
|
|
||||||
|
// Validaciones defensivas
|
||||||
|
|
||||||
|
if (empty($c['id_producto']) || $c['costo'] === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$costo = (float) $c['costo'];
|
||||||
|
|
||||||
|
// Fórmula correcta
|
||||||
|
|
||||||
|
$nuevoPrecio = (100 + $porcentaje_ganancia) * $costo / 100;
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':precio' => $nuevoPrecio,
|
||||||
|
':id' => $c['id_producto'],
|
||||||
|
':idCategoria' => $idCategoria
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
|
||||||
|
// Log interno
|
||||||
|
|
||||||
|
error_log($e->getMessage());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR TIPOS DE PRODUCTOS
|
||||||
|
|
||||||
|
public function listarTipos(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre FROM tipos ORDER BY id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VERIFICAR SI EXISTE EL TIPO DE PRODUCTO
|
||||||
|
|
||||||
|
public function existeTipo(int $id): bool {
|
||||||
|
|
||||||
|
$sql = "SELECT 1 FROM tipos WHERE id = :id LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
return (bool) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER TIPO POR ID
|
||||||
|
|
||||||
|
public function obtenerPorIdTipo(int $tipo): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre FROM tipos WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$tipo = $stmt->execute();
|
||||||
|
|
||||||
|
return $tipo ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER DESCUENTOS DE PRODUCTOS EN BASE A UNA CATEGORIA
|
||||||
|
|
||||||
|
public function obtenerDescuento(int $idCategoria): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT productos.id, productos.descuento FROM productos
|
||||||
|
INNER JOIN categorias ON (categorias.id = productos.id_categoria)
|
||||||
|
WHERE (categorias.id = :idCategoria)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':idCategoria' => $idCategoria]);
|
||||||
|
|
||||||
|
$resultado = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return !empty($resultado) ? $resultado : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BÚSQUEDA EN INPUT DE DESCRIPCIÓN DEL EQUIPO (MÓDULO SERVICIOS)
|
||||||
|
|
||||||
|
public function busquedaParaServicio(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT marca, modelo FROM productos WHERE (id_tipo = 1)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class proveedorModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR PROVEEDORES
|
||||||
|
|
||||||
|
public function listarProveedores(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, razon_social, telefono, email from proveedores ORDER BY id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREAR PROVEEDOR
|
||||||
|
|
||||||
|
public function crearProveedor(array $data): bool {
|
||||||
|
|
||||||
|
$sql = "INSERT INTO proveedores (razon_social, telefono, email, cuit, cond_iva) VALUES (:razon_social, :telefono, :email, :cuit, :cond_iva)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
':razon_social' => $data['razon_social'],
|
||||||
|
':telefono' => $data['telefono'],
|
||||||
|
':email' => $data['email'],
|
||||||
|
':cuit' => $data['cuit'],
|
||||||
|
':cond_iva' => $data['cond_iva']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUSCAR PROVEEDOR POR ID
|
||||||
|
|
||||||
|
public function obtenerPorId(int $id): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, razon_social, telefono, email, cuit, cond_iva FROM proveedores WHERE id = :id LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
$proveedor = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $proveedor ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDITAR PROVEEDOR
|
||||||
|
|
||||||
|
public function actualizarProveedor(int $id, array $data): bool {
|
||||||
|
|
||||||
|
if (empty($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$campos = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($data as $campo => $valor) {
|
||||||
|
$campos[] = "$campo = :$campo";
|
||||||
|
$params[":$campo"] = $valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
$params[':id'] = $id;
|
||||||
|
|
||||||
|
$sql = "UPDATE proveedores SET " . implode(', ', $campos) . " WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER ID DE PROVEEDOR CORRESPONDIENTE A UN ID DE PEDIDO
|
||||||
|
|
||||||
|
public function obtenerIDProveedorPorPedido(int $idPedido): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id, p.razon_social, p.telefono, p.email FROM proveedores AS p
|
||||||
|
INNER JOIN pedidos AS pe ON (p.id = pe.id_proveedor)
|
||||||
|
WHERE (pe.id = :id_pedido) LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id_pedido' => $idPedido]);
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VERIFICAR EXISTENCIA DE PROVEEDOR
|
||||||
|
|
||||||
|
public function verificarExistencia(array $datosProveedor, ?int $idExcluir = null): bool {
|
||||||
|
|
||||||
|
$sql = "SELECT 1 FROM proveedores WHERE (razon_social = :razon_social OR email = :email OR cuit = :cuit)";
|
||||||
|
|
||||||
|
if ($idExcluir !== null) {
|
||||||
|
$sql .= " AND id <> :id_excluir";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
':razon_social' => $datosProveedor['razon_social'],
|
||||||
|
':email' => $datosProveedor['email'],
|
||||||
|
':cuit' => $datosProveedor['cuit']
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($idExcluir !== null) {
|
||||||
|
$params[':id_excluir'] = $idExcluir;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
return (bool) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,447 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class servicioModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
private ?string $error = null;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// MÉTODO PARA OBTENER ERRORES Y MANEJARLOS EN EL CONTROLLER
|
||||||
|
|
||||||
|
public function getError(): ?string {
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GUARDADO INCIAL DEL SERVICIO
|
||||||
|
|
||||||
|
public function guardarServicio(int $numero, array $datosServicio, array $cliente, array $vendedor): bool {
|
||||||
|
|
||||||
|
if (!isset($numero)) {
|
||||||
|
$this->error = 'No se pudo obtener el código de servicio.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($datosServicio)) {
|
||||||
|
$this->error = 'El servicio no posee datos.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "INSERT INTO servicios
|
||||||
|
(id, equipo, contrasenia_equipo, problema, observacion, cuenta, contrasenia_cuenta, id_cliente, id_usuario)
|
||||||
|
VALUES
|
||||||
|
(:id, :equipo, :contrasenia_equipo, :problema, :observacion, :cuenta, :contrasenia_cuenta, :id_cliente, :id_usuario)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return (bool) $stmt->execute([
|
||||||
|
':id' => $numero,
|
||||||
|
':equipo' => $datosServicio['descripcion_equipo'],
|
||||||
|
':contrasenia_equipo' => $datosServicio['contrasenia_equipo'] ?? null,
|
||||||
|
':problema' => $datosServicio['descripcion_problema'],
|
||||||
|
':observacion' => ($datosServicio['observacion'] ?? '') !== '' ? $datosServicio['observacion'] : null,
|
||||||
|
':cuenta' => ($datosServicio['cuenta'] ?? '') !== '' ? $datosServicio['cuenta'] : null,
|
||||||
|
':contrasenia_cuenta' => $datosServicio['contrasenia_cuenta'] ?? null,
|
||||||
|
':id_cliente' => $cliente['id'],
|
||||||
|
':id_usuario' => $vendedor['id']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER PREVIEW DE CODIGO DE SERVICIO
|
||||||
|
|
||||||
|
public function getNumeroServicioPreview(): int {
|
||||||
|
|
||||||
|
$sql = "SELECT nextval('servicio_preview_seq')";
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return (int) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR SERVICIOS
|
||||||
|
|
||||||
|
public function listarServicios(int $anio): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT s.id, s.fecha_registro, s.fecha_terminado, s.equipo, s.estado, c.nombre AS cliente_nombre, c.apellido AS cliente_apellido, u.nombre AS vendedor_nombre, u.apellido AS vendedor_apellido FROM servicios AS s
|
||||||
|
INNER JOIN clientes AS c ON (s.id_cliente = c.id)
|
||||||
|
INNER JOIN usuarios AS u ON (s.id_usuario = u.id)
|
||||||
|
WHERE EXTRACT(YEAR FROM s.fecha_registro) = :anio
|
||||||
|
ORDER BY s.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':anio' => $anio]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER DETALLE DEL SERVICIO
|
||||||
|
|
||||||
|
public function getDetalleServicio(int $idServicio): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, equipo, contrasenia_equipo, problema, observacion, cuenta, contrasenia_cuenta, detalle_realizado, mano_obra, total, estado FROM servicios
|
||||||
|
WHERE (id = :id)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $idServicio]);
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER DETALLE DE INSUMOS UTILIZADOS EN SERVICIO
|
||||||
|
|
||||||
|
public function getDetalleInsumos(int $idServicio): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id AS id_insumo, p.marca, p.modelo, p.stock, sd.precio_unitario, sd.cantidad, sd.subtotal, sd.id_pedido FROM servicio_detalle AS sd
|
||||||
|
INNER JOIN productos AS p ON (sd.id_producto = p.id)
|
||||||
|
WHERE (sd.id_servicio = :id_servicio)
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id_servicio' => $idServicio]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACTUALIZAR SERVICIO
|
||||||
|
|
||||||
|
public function actualizarServicio(array $datosServicio, array $insumos): bool {
|
||||||
|
|
||||||
|
// Validaciones (ya verifica el controller también)
|
||||||
|
|
||||||
|
if (empty($datosServicio) || $datosServicio['id_servicio'] <= 0) {
|
||||||
|
$this->error = 'Datos de servicio inválidos.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($datosServicio['mano_obra']) || $datosServicio['mano_obra'] < 0) {
|
||||||
|
$this->error = 'Mano de obra inválida.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($datosServicio['total_final'] < 0) {
|
||||||
|
$this->error = 'Total inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($insumos as $i) {
|
||||||
|
if (!empty($insumos) && (empty($i['id_producto']) || empty($i['precio']) || empty($i['cantidad']))) {
|
||||||
|
$this->error = 'Producto inválido en el pedido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($insumos) && ($i['cantidad'] <= 0 || $i['precio'] < 0)) {
|
||||||
|
$this->error = 'Cantidad o precio inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($insumos) && $i['stock'] < 0) {
|
||||||
|
$this->error = 'Ocurrió un error en el stock.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
// 1. ACTUALIZAR EL SERVICIO
|
||||||
|
|
||||||
|
$sqlServicio = "UPDATE servicios
|
||||||
|
SET detalle_realizado = :detalle_realizado,
|
||||||
|
mano_obra = :mano_obra,
|
||||||
|
total = :total,
|
||||||
|
estado = 'pendiente - enviado' -- SE ACTUALIZA EL ESTADO DEL SERVICIO A (PENDIENTE - ENVIADO)
|
||||||
|
WHERE id = :id
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtServicio = $this->db->prepare($sqlServicio);
|
||||||
|
|
||||||
|
$stmtServicio->execute([
|
||||||
|
':detalle_realizado' => $datosServicio['detalle_realizado'],
|
||||||
|
':mano_obra' => $datosServicio['mano_obra'],
|
||||||
|
':total' => $datosServicio['total_final'],
|
||||||
|
':id' => $datosServicio['id_servicio'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 2. PREPARAR QUERIES AUXILIARES
|
||||||
|
|
||||||
|
// Pedidos disponibles (FIFO)
|
||||||
|
|
||||||
|
$sqlPedidos = "SELECT id_pedido, (cantidad - cantidad_vendida) AS disponible FROM pedido_detalle
|
||||||
|
WHERE id_producto = :id_producto
|
||||||
|
AND (cantidad - cantidad_vendida) > 0
|
||||||
|
ORDER BY id_pedido ASC
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtPedidos = $this->db->prepare($sqlPedidos);
|
||||||
|
|
||||||
|
// Insert Detalle Servicio
|
||||||
|
|
||||||
|
$sqlDetalle = "INSERT INTO servicio_detalle (precio_unitario, cantidad, subtotal, id_servicio, id_producto, id_pedido) VALUES
|
||||||
|
(:precio_unitario, :cantidad, :subtotal, :id_servicio, :id_producto, :id_pedido)
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtDetalle = $this->db->prepare($sqlDetalle);
|
||||||
|
|
||||||
|
// Update Pedido Detalle
|
||||||
|
|
||||||
|
$sqlUpdatePedido = "UPDATE pedido_detalle
|
||||||
|
SET cantidad_vendida = cantidad_vendida + :cantidad
|
||||||
|
WHERE id_pedido = :id_pedido
|
||||||
|
AND id_producto = :id_producto
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtUpdatePedido = $this->db->prepare($sqlUpdatePedido);
|
||||||
|
|
||||||
|
// Update stock del producto
|
||||||
|
|
||||||
|
$sqlStock = "UPDATE productos
|
||||||
|
SET stock = stock - :cantidad
|
||||||
|
WHERE id = :id_producto
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtStock = $this->db->prepare($sqlStock);
|
||||||
|
|
||||||
|
// 3. PROCESAR DETALLE + CONSUMO STOCK (FIFO)
|
||||||
|
|
||||||
|
if (!empty($insumos)) {
|
||||||
|
|
||||||
|
foreach ($insumos as $p) {
|
||||||
|
|
||||||
|
$cantidadRestante = $p['cantidad'];
|
||||||
|
|
||||||
|
$stmtPedidos->execute([
|
||||||
|
':id_producto' => $p['id_producto']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pedidos = $stmtPedidos->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (empty($pedidos)) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
$this->error = "No hay stock disponible para el insumo ID {$p['id_producto']}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($pedidos as $pedido) {
|
||||||
|
|
||||||
|
if ($cantidadRestante <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aVender = min($cantidadRestante, $pedido['disponible']);
|
||||||
|
$subtotal = $p['precio'] * $aVender;
|
||||||
|
|
||||||
|
// Insert venta_detalle
|
||||||
|
|
||||||
|
$stmtDetalle->execute([
|
||||||
|
':precio_unitario' => $p['precio'],
|
||||||
|
':cantidad' => $aVender,
|
||||||
|
':subtotal' => $subtotal,
|
||||||
|
':id_servicio' => $datosServicio['id_servicio'],
|
||||||
|
':id_producto' => $p['id_producto'],
|
||||||
|
':id_pedido' => $pedido['id_pedido'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update pedido_detalle
|
||||||
|
|
||||||
|
$stmtUpdatePedido->execute([
|
||||||
|
':cantidad' => $aVender,
|
||||||
|
':id_pedido' => $pedido['id_pedido'],
|
||||||
|
':id_producto' => $p['id_producto'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update stock producto
|
||||||
|
|
||||||
|
$stmtStock->execute([
|
||||||
|
':cantidad' => $aVender,
|
||||||
|
':id_producto' => $p['id_producto'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cantidadRestante -= $aVender;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VALIDACIÓN IMPORTANTE DE STOCK
|
||||||
|
|
||||||
|
if ($cantidadRestante > 0) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
$this->error = "Stock insuficiente para el insumo ID {$p['id_producto']}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. COMMIT
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
|
||||||
|
// Log interno
|
||||||
|
|
||||||
|
error_log($e->getMessage());
|
||||||
|
|
||||||
|
$this->error = 'Error interno al actualizar el servicio.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FINALIZAR SERVICIO
|
||||||
|
|
||||||
|
public function finalizarServicio(int $idServicio): bool {
|
||||||
|
|
||||||
|
if ($idServicio <= 0) {
|
||||||
|
$this->error = 'Servicio inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"UPDATE servicios
|
||||||
|
SET estado = 'finalizado',
|
||||||
|
fecha_terminado = CURRENT_DATE
|
||||||
|
WHERE id = :id"
|
||||||
|
);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
':id' => $idServicio
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->error = 'Error interno al finalizar el servicio.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CANCELAR SERVICIO
|
||||||
|
|
||||||
|
public function cancelarServicio(int $idServicio, float $cargoCancelacion): bool {
|
||||||
|
|
||||||
|
if ($idServicio <= 0) {
|
||||||
|
$this->error = 'Servicio inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
// 1. OBTENER INSUMOS DEL SERVICIO
|
||||||
|
|
||||||
|
$sqlDetalle = "SELECT id_producto, cantidad, id_pedido FROM servicio_detalle WHERE id_servicio = :id";
|
||||||
|
|
||||||
|
$stmtDetalle = $this->db->prepare($sqlDetalle);
|
||||||
|
|
||||||
|
$stmtDetalle->execute([
|
||||||
|
':id' => $idServicio
|
||||||
|
]);
|
||||||
|
|
||||||
|
$insumos = $stmtDetalle->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 2. PREPARAR QUERIES
|
||||||
|
|
||||||
|
$sqlStock = "UPDATE productos
|
||||||
|
SET stock = stock + :cantidad
|
||||||
|
WHERE id = :id_producto";
|
||||||
|
|
||||||
|
$stmtStock = $this->db->prepare($sqlStock);
|
||||||
|
|
||||||
|
$sqlPedido = "UPDATE pedido_detalle
|
||||||
|
SET cantidad_vendida = cantidad_vendida - :cantidad
|
||||||
|
WHERE id_pedido = :id_pedido
|
||||||
|
AND id_producto = :id_producto";
|
||||||
|
|
||||||
|
$stmtPedido = $this->db->prepare($sqlPedido);
|
||||||
|
|
||||||
|
// 3. REVERTIR INVENTARIO
|
||||||
|
|
||||||
|
if (!empty($insumo)) {
|
||||||
|
|
||||||
|
foreach ($insumos as $i) {
|
||||||
|
|
||||||
|
// devolver stock
|
||||||
|
|
||||||
|
$stmtStock->execute([
|
||||||
|
':cantidad' => $i['cantidad'],
|
||||||
|
':id_producto' => $i['id_producto']
|
||||||
|
]);
|
||||||
|
|
||||||
|
// revertir FIFO
|
||||||
|
|
||||||
|
$stmtPedido->execute([
|
||||||
|
':cantidad' => $i['cantidad'],
|
||||||
|
':id_pedido' => $i['id_pedido'],
|
||||||
|
':id_producto' => $i['id_producto']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. ACTUALIZAR ESTADO DEL SERVICIO
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"UPDATE servicios
|
||||||
|
SET estado = 'cancelado',
|
||||||
|
total = :total,
|
||||||
|
fecha_terminado = CURRENT_DATE
|
||||||
|
WHERE id = :id"
|
||||||
|
);
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $idServicio,
|
||||||
|
':total' => $cargoCancelacion
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
|
||||||
|
// Log interno
|
||||||
|
|
||||||
|
error_log($e->getMessage());
|
||||||
|
|
||||||
|
$this->error = 'Error al cancelar el servicio.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER CLIENTE DEL SERVICIO
|
||||||
|
|
||||||
|
public function getClienteServicio(int $idServicio): array {
|
||||||
|
|
||||||
|
$sql = "SELECT c.nombre, c.apellido, c.email FROM clientes AS c
|
||||||
|
INNER JOIN servicios AS s ON (s.id_cliente = c.id)
|
||||||
|
WHERE (s.id = :id)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $idServicio,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER ESTADO DE SERVICIO
|
||||||
|
|
||||||
|
public function getEstadoServicio(int $idServicio): bool {
|
||||||
|
|
||||||
|
$sql = "SELECT estado FROM servicios
|
||||||
|
WHERE (servicios.id = :id) AND (servicios.estado = 'pendiente') LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $idServicio]);
|
||||||
|
|
||||||
|
return $stmt->fetch() !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class usuarioModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACTUALIZAR USUARIO
|
||||||
|
|
||||||
|
public function actualizarUsuario(int $id, array $data): bool {
|
||||||
|
|
||||||
|
if (empty($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$campos = [];
|
||||||
|
$params = []; // Se utiliza para evitar inyección SQL usando parámetros nombrados de PDO
|
||||||
|
|
||||||
|
foreach ($data as $campo => $valor) {
|
||||||
|
$campos[] = "$campo = :$campo"; // Arma el string relacionando $campo con los parámetros nombrados (se usa en la consulta)
|
||||||
|
$params[":$campo"] = $valor; // Arma array relacionando el parámetro nombrado con el valor (se usa en la consulta)
|
||||||
|
}
|
||||||
|
|
||||||
|
$params[':id'] = $id;
|
||||||
|
|
||||||
|
$sql = "UPDATE usuarios SET " . implode(', ', $campos) . " WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute($params); // Relaciona los nombrados con los valores
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER USUARIO POR ID
|
||||||
|
|
||||||
|
public function obtenerPorId(int $id): ?array { // El ? lo uso para que pueda devolver NULL
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre, apellido, email, telefono, usuario, rol FROM usuarios WHERE id = :id LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id' => $id]); // Le asigna al parámetro nombrado el valor de $id
|
||||||
|
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC); // Devuelve un array asociativo
|
||||||
|
|
||||||
|
return $user ?: null; // Verifica si el array no esta vacío y lo devuelve, sino devuelve null
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTAR USUARIOS
|
||||||
|
|
||||||
|
public function listarUsuarios(): array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre, apellido, email, telefono, usuario, rol FROM usuarios WHERE (activo = true) ORDER BY id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREAR USUARIO
|
||||||
|
|
||||||
|
public function crearUsuario(array $data): bool {
|
||||||
|
|
||||||
|
$sql = "INSERT INTO usuarios (nombre, apellido, email, telefono, usuario, password, rol) VALUES (:nombre, :apellido, :email, :telefono, :usuario, :password, :rol)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
':nombre' => $data['nombre'],
|
||||||
|
':apellido' => $data['apellido'],
|
||||||
|
':email' => $data['email'],
|
||||||
|
':telefono' => $data['telefono'],
|
||||||
|
':usuario' => $data['usuario'],
|
||||||
|
':password' => $data['password'],
|
||||||
|
':rol' => $data['rol']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VERIFICAR EXISTENCIA DE USUARIO
|
||||||
|
|
||||||
|
public function verificarExistencia(array $datosUsuario, ?int $idExcluir = null): bool {
|
||||||
|
|
||||||
|
$sql = "SELECT 1 FROM usuarios WHERE ((nombre = :nombre AND apellido = :apellido AND email = :email) OR (usuario = :usuario) OR (email = :email))";
|
||||||
|
|
||||||
|
if ($idExcluir !== null) {
|
||||||
|
$sql .= " AND id <> :id_excluir";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
':nombre' => $datosUsuario['nombre'],
|
||||||
|
':apellido' => $datosUsuario['apellido'],
|
||||||
|
':usuario' => $datosUsuario['usuario'],
|
||||||
|
':email' => $datosUsuario['email']
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($idExcluir !== null) {
|
||||||
|
$params[':id_excluir'] = $idExcluir;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
return (bool) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELIMINAR USUARIO (BAJA LÓGICA)
|
||||||
|
|
||||||
|
public function eliminarUsuario(int $id): bool {
|
||||||
|
|
||||||
|
$sql = "UPDATE usuarios
|
||||||
|
SET activo = false
|
||||||
|
WHERE (usuarios.id = :id)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([':id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER USUARIO POR MAIL
|
||||||
|
|
||||||
|
public function obtenerPorEmail ($email): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT id, nombre, apellido, email, telefono, usuario, rol FROM usuarios WHERE email = :email LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':email' => $email]);
|
||||||
|
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $user ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESTABLECER CONTRASENIA
|
||||||
|
|
||||||
|
public function actualizarContrasenia(int $id, string $password): bool {
|
||||||
|
|
||||||
|
$sql = "UPDATE usuarios
|
||||||
|
SET password = :password
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
':password' => $password,
|
||||||
|
':id' => $id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../database/database.php';
|
||||||
|
|
||||||
|
class ventaModel {
|
||||||
|
|
||||||
|
private PDO $db;
|
||||||
|
private ?string $error = null;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// MÉTODO PARA OBTENER ERRORES Y MANEJARLOS A TRAVÉZ DEL CONTROLLER
|
||||||
|
|
||||||
|
public function getError(): ?string {
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULAR TOTAL PROVISORIO APLICANDO DESCUENTO Y RECARGO EN BASE A TIPO Y FORMA DE PAGO
|
||||||
|
|
||||||
|
public function actualizarDatosProvisorio($descuentoPresupuesto, $recargoPresupuesto, $subtotal, $descuento): ?array {
|
||||||
|
|
||||||
|
// El descuento propio de la venta se calcula en base al subtotal con los descuento de productos aplicados
|
||||||
|
|
||||||
|
$descuentoProvisorio = ($descuentoPresupuesto * ($subtotal - $descuento) / 100) + $descuento; // Sumamos los dos descuentos
|
||||||
|
|
||||||
|
// Calculo el recargo teniendo en cuenta el subtotal (descuentos de productos ya aplicados)
|
||||||
|
|
||||||
|
$recargoProvisorio = ($recargoPresupuesto * ($subtotal - $descuento) / 100);
|
||||||
|
|
||||||
|
$totalProvisorio = $subtotal + $recargoProvisorio - $descuentoProvisorio;
|
||||||
|
|
||||||
|
return $_SESSION['prov_venta'] = [
|
||||||
|
'recargo' => $recargoProvisorio,
|
||||||
|
'descuento' => $descuentoProvisorio,
|
||||||
|
'total' => $totalProvisorio
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// FUNCION DE GUARDADO DE VENTA
|
||||||
|
|
||||||
|
public function guardarVenta(array $productosFinales, array $vendedor, array $cliente, array $datosExtraVenta, array $datosProvisorios): int|false {
|
||||||
|
|
||||||
|
// Validaciones (ya verifica el controller también)
|
||||||
|
|
||||||
|
if (empty($productosFinales)) {
|
||||||
|
$this->error = 'El pedido no tiene productos.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($vendedor['id'])) {
|
||||||
|
$this->error = 'No se pudo vincular al usuario.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($cliente['id'])) {
|
||||||
|
$this->error = 'Cliente inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($datosExtraVenta['descuento'] < 0 || $datosExtraVenta['descuento'] > 100) {
|
||||||
|
$this->error = 'El descuento debe estar entre 0 y 100%';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($datosExtraVenta['recargo'] < 0 || $datosExtraVenta['recargo'] > 100) {
|
||||||
|
$this->error = 'El recargo debe estar entre 0 y 100%';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($datosProvisorios['total'] <= 0) {
|
||||||
|
$this->error = 'Total de pedido inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($productosFinales as $p) {
|
||||||
|
if (empty($p['id_producto']) || empty($p['precio']) || empty($p['cantidad']) || empty($p['precio_final'])) {
|
||||||
|
$this->error = 'Producto inválido en el pedido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($p['cantidad'] <= 0 || $p['precio'] < 0 || $p['precio_final'] < 0) {
|
||||||
|
$this->error = 'Cantidad o precio inválido.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($p['stock'] < 0) {
|
||||||
|
$this->error = 'Ocurrió un error en el stock.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
// 1. INSERT VENTA
|
||||||
|
|
||||||
|
$sqlVenta = "INSERT INTO ventas (total, forma_pago, tipo_pago, descuento_pct, recargo_pct, id_cliente, id_usuario)
|
||||||
|
VALUES (:total, :forma_pago, :tipo_pago, :descuento_pct, :recargo_pct, :id_cliente, :id_usuario)
|
||||||
|
RETURNING id
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtVenta = $this->db->prepare($sqlVenta);
|
||||||
|
|
||||||
|
$stmtVenta->execute([
|
||||||
|
':total' => $datosProvisorios['total'],
|
||||||
|
':forma_pago' => $datosExtraVenta['forma_pago'],
|
||||||
|
':tipo_pago' => $datosExtraVenta['tipo_pago'],
|
||||||
|
':descuento_pct' => $datosExtraVenta['descuento'],
|
||||||
|
':recargo_pct' => $datosExtraVenta['recargo'],
|
||||||
|
':id_cliente' => $cliente['id'],
|
||||||
|
':id_usuario' => $vendedor['id']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$idVenta = $stmtVenta->fetchColumn();
|
||||||
|
|
||||||
|
if (!$idVenta) {
|
||||||
|
$this->error = 'No se pudo crear la venta.';
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. PREPARAR QUERIES AUXILIARES
|
||||||
|
|
||||||
|
// Pedidos disponibles (FIFO)
|
||||||
|
|
||||||
|
$sqlPedidos = "SELECT id_pedido, (cantidad - cantidad_vendida) AS disponible FROM pedido_detalle
|
||||||
|
WHERE id_producto = :id_producto
|
||||||
|
AND (cantidad - cantidad_vendida) > 0
|
||||||
|
ORDER BY id_pedido ASC
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtPedidos = $this->db->prepare($sqlPedidos);
|
||||||
|
|
||||||
|
// Insert Detalle Venta
|
||||||
|
|
||||||
|
$sqlDetalle = "INSERT INTO venta_detalle (precio_unitario, cantidad, subtotal, descuento_pct, id_venta, id_producto, id_pedido) VALUES
|
||||||
|
(:precio_unitario, :cantidad, :subtotal, :descuento_pct, :id_venta, :id_producto, :id_pedido)
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtDetalle = $this->db->prepare($sqlDetalle);
|
||||||
|
|
||||||
|
// Update Pedido Detalle
|
||||||
|
|
||||||
|
$sqlUpdatePedido = "UPDATE pedido_detalle
|
||||||
|
SET cantidad_vendida = cantidad_vendida + :cantidad
|
||||||
|
WHERE id_pedido = :id_pedido
|
||||||
|
AND id_producto = :id_producto
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtUpdatePedido = $this->db->prepare($sqlUpdatePedido);
|
||||||
|
|
||||||
|
// Update stock del producto
|
||||||
|
|
||||||
|
$sqlStock = "UPDATE productos
|
||||||
|
SET stock = stock - :cantidad
|
||||||
|
WHERE id = :id_producto
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmtStock = $this->db->prepare($sqlStock);
|
||||||
|
|
||||||
|
// 3. PROCESAR DETALLE + CONSUMO STOCK (FIFO)
|
||||||
|
|
||||||
|
foreach ($productosFinales as $p) {
|
||||||
|
|
||||||
|
$cantidadRestante = $p['cantidad'];
|
||||||
|
|
||||||
|
$stmtPedidos->execute([
|
||||||
|
':id_producto' => $p['id_producto']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pedidos = $stmtPedidos->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (empty($pedidos)) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
$this->error = "No hay stock disponible para el producto ID {$p['id_producto']}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($pedidos as $pedido) {
|
||||||
|
|
||||||
|
if ($cantidadRestante <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aVender = min($cantidadRestante, $pedido['disponible']);
|
||||||
|
$subtotal = $p['precio_final'] * $aVender;
|
||||||
|
|
||||||
|
// Insert venta_detalle
|
||||||
|
|
||||||
|
$stmtDetalle->execute([
|
||||||
|
':precio_unitario' => $p['precio'],
|
||||||
|
':cantidad' => $aVender,
|
||||||
|
':subtotal' => $subtotal,
|
||||||
|
':descuento_pct' => $p['descuento_total'],
|
||||||
|
':id_venta' => $idVenta,
|
||||||
|
':id_producto' => $p['id_producto'],
|
||||||
|
':id_pedido' => $pedido['id_pedido'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update pedido_detalle
|
||||||
|
|
||||||
|
$stmtUpdatePedido->execute([
|
||||||
|
':cantidad' => $aVender,
|
||||||
|
':id_pedido' => $pedido['id_pedido'],
|
||||||
|
':id_producto' => $p['id_producto'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update stock producto
|
||||||
|
|
||||||
|
$stmtStock->execute([
|
||||||
|
':cantidad' => $aVender,
|
||||||
|
':id_producto' => $p['id_producto'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cantidadRestante -= $aVender;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VALIDACIÓN IMPORTANTE DE STOCK
|
||||||
|
|
||||||
|
if ($cantidadRestante > 0) {
|
||||||
|
$this->error = "Stock insuficiente para el producto ID {$p['id_producto']}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. COMMIT
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return $idVenta;
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
|
||||||
|
// Log interno
|
||||||
|
|
||||||
|
error_log($e->getMessage());
|
||||||
|
|
||||||
|
$this->error = 'Error interno al guardar la venta.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER PREVIEW DE CODIGO DE VENTA (no implementado)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
public function getNumeroVentaPreview(): int {
|
||||||
|
|
||||||
|
$sql = "SELECT nextval('venta_preview_seq')";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return (int) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// LISTAR VENTAS
|
||||||
|
|
||||||
|
public function listarVentas(int $anio): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT v.id, v.fecha, v.total, v.forma_pago, v.tipo_pago, v.descuento_pct, v.recargo_pct, c.nombre AS cliente_nombre, c.apellido AS cliente_apellido, u.nombre AS vendedor_nombre, u.apellido AS vendedor_apellido FROM ventas AS v
|
||||||
|
INNER JOIN clientes AS c ON (v.id_cliente = c.id)
|
||||||
|
INNER JOIN usuarios AS u ON (v.id_usuario = u.id)
|
||||||
|
WHERE EXTRACT(YEAR FROM v.fecha) = :anio
|
||||||
|
ORDER BY v.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute(['anio' => $anio]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBTENER DETALLE DE VENTA
|
||||||
|
|
||||||
|
public function getDetalleVenta(int $idVenta): ?array {
|
||||||
|
|
||||||
|
$sql = "SELECT p.id AS id_producto, p.marca, p.modelo, vd.precio_unitario, vd.cantidad, vd.subtotal, vd.descuento_pct, vd.id_pedido FROM venta_detalle AS vd
|
||||||
|
INNER JOIN productos AS p ON (vd.id_producto = p.id)
|
||||||
|
WHERE (vd.id_venta = :id_venta)
|
||||||
|
ORDER BY p.id ASC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([':id_venta' => $idVenta]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user