Carpeta models
This commit is contained in:
@@ -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