Files
ByteCenter/app/models/servicioModel.php
T
2026-08-04 19:24:03 -03:00

447 lines
14 KiB
PHP

<?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;
}
}