447 lines
14 KiB
PHP
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;
|
|
}
|
|
} |