210 lines
8.0 KiB
PHP
210 lines
8.0 KiB
PHP
<?php
|
|
/**
|
|
* POST /modules/turnero/api/ocupar_lugar.php
|
|
*
|
|
* Gestiona la ocupación exclusiva de un puesto (recepción o muestras).
|
|
* Un puesto solo puede tener UN usuario activo.
|
|
*
|
|
* Actions:
|
|
* claim → reclamar el puesto (o renovar si ya es mío)
|
|
* ping → renovar heartbeat (cada 60 s)
|
|
* release → liberar el puesto
|
|
* solicitar → usuario B pide transferencia al ocupante A
|
|
* check_solicitud → A consulta si hay una solicitud pendiente
|
|
* denegar → A rechaza la solicitud
|
|
* aceptar → A acepta y libera el puesto
|
|
* check_transfer → B consulta si el puesto quedó libre o fue denegado
|
|
* disponibles → lista puestos del mismo tipo sin ocupante activo
|
|
*/
|
|
|
|
require_once __DIR__ . '/_helpers.php';
|
|
requireMethod('POST');
|
|
|
|
$datos = inputJson();
|
|
$lugarId = (int)($datos['lugar_id'] ?? 0);
|
|
$action = $datos['action'] ?? 'claim';
|
|
|
|
if ($lugarId <= 0) jsonError('lugar_id inválido.');
|
|
|
|
$validActions = ['claim','ping','release','solicitar','check_solicitud','denegar','aceptar','check_transfer','disponibles'];
|
|
if (!in_array($action, $validActions, true)) jsonError('action inválida.');
|
|
|
|
$pdo = db();
|
|
$userId = (int)adminId();
|
|
$nombre = $_SESSION['admin_user']['full_name']
|
|
?? $_SESSION['admin_user']['username']
|
|
?? 'Usuario';
|
|
|
|
$timeout = 'NOW() - INTERVAL 2 MINUTE';
|
|
|
|
/* ── release ─────────────────────────────────────────────────── */
|
|
if ($action === 'release') {
|
|
$pdo->prepare(
|
|
"DELETE FROM turnero_lugar_ocupacion WHERE lugar_id = ? AND user_id = ?"
|
|
)->execute([$lugarId, $userId]);
|
|
jsonOk(['liberado' => true]);
|
|
}
|
|
|
|
/* ── ping ────────────────────────────────────────────────────── */
|
|
if ($action === 'ping') {
|
|
$pdo->prepare(
|
|
"UPDATE turnero_lugar_ocupacion SET ultimo_ping = NOW()
|
|
WHERE lugar_id = ? AND user_id = ?"
|
|
)->execute([$lugarId, $userId]);
|
|
jsonOk(['ok' => true]);
|
|
}
|
|
|
|
/* ── aceptar (ocupante A acepta la solicitud y libera) ───────── */
|
|
if ($action === 'aceptar') {
|
|
$pdo->prepare(
|
|
"DELETE FROM turnero_lugar_ocupacion WHERE lugar_id = ? AND user_id = ?"
|
|
)->execute([$lugarId, $userId]);
|
|
jsonOk(['liberado' => true]);
|
|
}
|
|
|
|
/* ── denegar (ocupante A rechaza) ────────────────────────────── */
|
|
if ($action === 'denegar') {
|
|
$pdo->prepare(
|
|
"UPDATE turnero_lugar_ocupacion
|
|
SET solicitud_resultado = 'denegada'
|
|
WHERE lugar_id = ? AND user_id = ?"
|
|
)->execute([$lugarId, $userId]);
|
|
jsonOk(['ok' => true]);
|
|
}
|
|
|
|
/* ── check_solicitud (A consulta si hay solicitud pendiente) ─── */
|
|
if ($action === 'check_solicitud') {
|
|
$st = $pdo->prepare(
|
|
"SELECT solicitud_user_id, solicitud_nombre, solicitud_at, solicitud_resultado
|
|
FROM turnero_lugar_ocupacion
|
|
WHERE lugar_id = ? AND user_id = ?"
|
|
);
|
|
$st->execute([$lugarId, $userId]);
|
|
$row = $st->fetch(PDO::FETCH_ASSOC);
|
|
if (!$row || $row['solicitud_resultado'] !== 'pendiente') {
|
|
jsonOk(['hay_solicitud' => false]);
|
|
}
|
|
jsonOk([
|
|
'hay_solicitud' => true,
|
|
'solicitante' => $row['solicitud_nombre'],
|
|
'solicitud_at' => $row['solicitud_at'],
|
|
]);
|
|
}
|
|
|
|
/* ── check_transfer (B consulta si el puesto quedó libre) ───── */
|
|
if ($action === 'check_transfer') {
|
|
$st = $pdo->prepare(
|
|
"SELECT user_id, solicitud_user_id, solicitud_resultado
|
|
FROM turnero_lugar_ocupacion WHERE lugar_id = ?"
|
|
);
|
|
$st->execute([$lugarId]);
|
|
$row = $st->fetch(PDO::FETCH_ASSOC);
|
|
if (!$row) {
|
|
jsonOk(['disponible' => true]);
|
|
}
|
|
if ($row['solicitud_resultado'] === 'denegada') {
|
|
jsonOk(['disponible' => false, 'denegado' => true]);
|
|
}
|
|
jsonOk(['disponible' => false, 'denegado' => false]);
|
|
}
|
|
|
|
/* ── solicitar (B pide transferencia) ───────────────────────── */
|
|
if ($action === 'solicitar') {
|
|
// Limpiar expirados primero
|
|
$pdo->prepare(
|
|
"DELETE FROM turnero_lugar_ocupacion WHERE ultimo_ping < $timeout"
|
|
)->execute();
|
|
|
|
$st = $pdo->prepare(
|
|
"SELECT user_id, user_nombre, solicitud_at, solicitud_user_id
|
|
FROM turnero_lugar_ocupacion WHERE lugar_id = ?"
|
|
);
|
|
$st->execute([$lugarId]);
|
|
$ocupado = $st->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$ocupado || (int)$ocupado['user_id'] === $userId) {
|
|
// Ya libre o soy yo — reclamar directamente
|
|
$pdo->prepare(
|
|
"INSERT INTO turnero_lugar_ocupacion (lugar_id, user_id, user_nombre, ultimo_ping)
|
|
VALUES (?, ?, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE user_id=?, user_nombre=?, ultimo_ping=NOW(),
|
|
solicitud_user_id=NULL, solicitud_nombre=NULL, solicitud_at=NULL, solicitud_resultado=NULL"
|
|
)->execute([$lugarId, $userId, $nombre, $userId, $nombre]);
|
|
jsonOk(['transferido' => true]);
|
|
}
|
|
|
|
// Guardar solicitud (o renovarla si ya existía de este mismo B)
|
|
$pdo->prepare(
|
|
"UPDATE turnero_lugar_ocupacion
|
|
SET solicitud_user_id = ?,
|
|
solicitud_nombre = ?,
|
|
solicitud_at = NOW(),
|
|
solicitud_resultado = 'pendiente'
|
|
WHERE lugar_id = ?"
|
|
)->execute([$userId, $nombre, $lugarId]);
|
|
|
|
jsonOk(['solicitado' => true, 'ocupado_por' => $ocupado['user_nombre']]);
|
|
}
|
|
|
|
/* ── disponibles (puestos del mismo tipo sin ocupante) ───────── */
|
|
if ($action === 'disponibles') {
|
|
$stLugar = $pdo->prepare("SELECT tipo FROM turnero_lugares WHERE id = ?");
|
|
$stLugar->execute([$lugarId]);
|
|
$lugar = $stLugar->fetch(PDO::FETCH_ASSOC);
|
|
if (!$lugar) jsonError('Lugar no encontrado.', 404);
|
|
|
|
$stDisp = $pdo->prepare(
|
|
"SELECT l.id, l.nombre
|
|
FROM turnero_lugares l
|
|
WHERE l.tipo = ? AND l.activo = 1 AND l.id != ?
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM turnero_lugar_ocupacion o
|
|
WHERE o.lugar_id = l.id
|
|
AND o.ultimo_ping >= $timeout
|
|
)
|
|
ORDER BY l.sort_order ASC"
|
|
);
|
|
$stDisp->execute([$lugar['tipo'], $lugarId]);
|
|
jsonOk(['disponibles' => $stDisp->fetchAll(PDO::FETCH_ASSOC)]);
|
|
}
|
|
|
|
/* ── claim ───────────────────────────────────────────────────── */
|
|
// Limpiar ocupaciones expiradas
|
|
$pdo->prepare(
|
|
"DELETE FROM turnero_lugar_ocupacion WHERE ultimo_ping < $timeout"
|
|
)->execute();
|
|
|
|
// Verificar si hay un ocupante
|
|
$st = $pdo->prepare(
|
|
"SELECT user_id, user_nombre, solicitud_user_id, solicitud_at, solicitud_resultado
|
|
FROM turnero_lugar_ocupacion WHERE lugar_id = ?"
|
|
);
|
|
$st->execute([$lugarId]);
|
|
$ocupado = $st->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($ocupado && (int)$ocupado['user_id'] !== $userId) {
|
|
// Si hay una solicitud de este usuario pendiente hace más de 6s → auto-liberar
|
|
$esMiSolicitud = (int)($ocupado['solicitud_user_id'] ?? 0) === $userId;
|
|
$solicitudVieja = $ocupado['solicitud_at'] && (strtotime($ocupado['solicitud_at']) < time() - 6);
|
|
if ($esMiSolicitud && $solicitudVieja) {
|
|
$pdo->prepare("DELETE FROM turnero_lugar_ocupacion WHERE lugar_id = ?")
|
|
->execute([$lugarId]);
|
|
$ocupado = null; // caer al INSERT/UPDATE de abajo
|
|
} else {
|
|
jsonOk([
|
|
'disponible' => false,
|
|
'ocupado_por' => $ocupado['user_nombre'],
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Reclamar o renovar
|
|
$pdo->prepare(
|
|
"INSERT INTO turnero_lugar_ocupacion (lugar_id, user_id, user_nombre, ultimo_ping)
|
|
VALUES (?, ?, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE user_id=?, user_nombre=?, ultimo_ping=NOW(),
|
|
solicitud_user_id=NULL, solicitud_nombre=NULL, solicitud_at=NULL, solicitud_resultado=NULL"
|
|
)->execute([$lugarId, $userId, $nombre, $userId, $nombre]);
|
|
|
|
jsonOk(['disponible' => true]);
|