Files
yogibook_aury_new/public/admin_reprogram_booking.php
T
2026-10-14 15:02:51 +02:00

215 lines
8.8 KiB
PHP

<?php
/**
* Riprogrammazione di una prenotazione da parte della scuola.
*
* POST: id = idbookingclass
* idschedule = idserviceschedule della nuova lezione
* send_email = Y/N email all'allieva con la nuova data
* count_reprog = Y/N conta come riprogrammazione (orderbook.reprogrammed + 1)
*
* Sposta la prenotazione salvando la vecchia lezione in idprevserviceschedule / prevbookingstart.
* Controlla posti liberi e doppia prenotazione con le righe bloccate (FOR UPDATE).
*/
ob_start();
require_once('include/headscript.php');
function jsonOut(array $payload, int $code = 200): void
{
if (ob_get_length() !== false) ob_end_clean();
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonOut(['success' => false, 'message' => 'Metodo non consentito.'], 405);
}
$id = (int) ($_POST['id'] ?? 0);
$idSchedule = (int) ($_POST['idschedule'] ?? 0);
$sendEmail = ($_POST['send_email'] ?? 'N') === 'Y';
$countReprog = ($_POST['count_reprog'] ?? 'N') === 'Y';
$staffId = (int) $iduserlogin;
if ($id <= 0 || $idSchedule <= 0) {
jsonOut(['success' => false, 'message' => 'Seleziona la nuova lezione.'], 400);
}
$errors = [
'notfound' => 'Prenotazione non trovata (forse è stata cancellata).',
'notarget' => 'La lezione scelta non esiste più.',
'same' => 'Hai scelto la stessa lezione di adesso.',
'past' => 'La lezione scelta è già iniziata o passata.',
'already' => 'L\'allieva è già iscritta alla lezione scelta.',
'full' => 'La lezione scelta è piena.',
];
$pdo = DBHandlerSelect::getInstance()->getConnection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$bk = null;
$target = null;
$incremented = 'N';
try {
$pdo->beginTransaction();
// Prenotazione attuale (bloccata)
$st = $pdo->prepare("
SELECT bc.idbookingclass, bc.iduser, bc.idorder, bc.idserviceschedule, bc.bookingstart,
s.servicename AS old_servicename, ss.dateschedule AS old_date,
u.email, u.first_name
FROM bookingclass bc
LEFT JOIN service s ON s.idservice = bc.idservice
LEFT JOIN serviceschedule ss ON ss.idserviceschedule = bc.idserviceschedule
LEFT JOIN auth_users u ON u.id = bc.iduser
WHERE bc.idbookingclass = ?
FOR UPDATE
");
$st->execute([$id]);
$bk = $st->fetch(PDO::FETCH_ASSOC);
if (!$bk) throw new RuntimeException('notfound');
if ((int) $bk['idserviceschedule'] === $idSchedule) throw new RuntimeException('same');
// Nuova lezione (bloccata: evita due spostamenti contemporanei sull'ultimo posto)
$st = $pdo->prepare("
SELECT ss.idserviceschedule, ss.idservice, ss.dateschedule, s.servicename, s.maxcapacity
FROM serviceschedule ss
JOIN service s ON s.idservice = ss.idservice
WHERE ss.idserviceschedule = ?
FOR UPDATE
");
$st->execute([$idSchedule]);
$target = $st->fetch(PDO::FETCH_ASSOC);
if (!$target) throw new RuntimeException('notarget');
if (strtotime($target['dateschedule']) <= time()) throw new RuntimeException('past');
// L'allieva è già su quella lezione?
$st = $pdo->prepare("SELECT COUNT(*) FROM bookingclass WHERE idserviceschedule = ? AND iduser = ?");
$st->execute([$idSchedule, (int) $bk['iduser']]);
if ((int) $st->fetchColumn() > 0) throw new RuntimeException('already');
// Posti liberi (booked + pending)
if ($target['maxcapacity'] !== null) {
$st = $pdo->prepare("SELECT COUNT(*) FROM bookingclass WHERE idserviceschedule = ? AND status IN ('booked','pending')");
$st->execute([$idSchedule]);
if ((int) $st->fetchColumn() >= (int) $target['maxcapacity']) throw new RuntimeException('full');
}
// Spostamento: la vecchia lezione resta in idprevserviceschedule / prevbookingstart
$up = $pdo->prepare("
UPDATE bookingclass
SET idprevserviceschedule = :prevsched,
prevbookingstart = :prevstart,
idserviceschedule = :sched,
idservice = :svc,
bookingstart = :start,
is_reprogrammed = 'Y'
WHERE idbookingclass = :id
");
$up->execute([
':prevsched' => $bk['idserviceschedule'] !== null ? (int) $bk['idserviceschedule'] : null,
':prevstart' => $bk['bookingstart'],
':sched' => $idSchedule,
':svc' => (int) $target['idservice'],
':start' => $target['dateschedule'],
':id' => $id,
]);
// Conta come riprogrammazione
if ($countReprog && $bk['idorder'] !== null) {
$st = $pdo->prepare("UPDATE orderbook SET reprogrammed = reprogrammed + 1 WHERE idorderbook = ?");
$st->execute([(int) $bk['idorder']]);
if ($st->rowCount() === 1) $incremented = 'Y';
}
$pdo->commit();
} catch (PDOException $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
error_log("admin_reprogram_booking #{$id} -> {$idSchedule} (staff #{$staffId}): " . $e->getMessage());
jsonOut(['success' => false, 'message' => 'Errore durante la riprogrammazione. Riprova.'], 500);
} catch (RuntimeException $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
$code = $e->getMessage();
jsonOut(['success' => false, 'message' => $errors[$code] ?? 'Errore durante la riprogrammazione.'], 409);
}
/* -------------------------------------------------------------------------
* Email all'allieva (dopo il commit, solo se richiesta)
* ---------------------------------------------------------------------- */
$fmt = fn($d) => $d ? date('d/m/Y H:i', strtotime($d)) : '';
$emailNote = '';
if ($sendEmail) {
$to = trim((string) ($bk['email'] ?? ''));
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
$emailNote = ' Email non inviata: indirizzo dell\'allieva mancante o non valido.';
} else {
try {
require_once(__DIR__ . '/class/mailer.php');
$clean = fn($v) => trim((string) $v, "\" \t\n\r\0\x0B");
$enc = strtolower($clean($_ENV['MAIL_ENCRYPTION'] ?? 'ssl'));
$mail = new \PHPMailer\PHPMailer\PHPMailer(true);
$mail->isSMTP();
$mail->Host = $clean($_ENV['MAIL_HOST'] ?? '');
$mail->SMTPAuth = true;
$mail->Username = $_ENV['MAIL_USERNAME'] ?? '';
$mail->Password = $_ENV['MAIL_PASSWORD'] ?? '';
$mail->Port = (int) ($_ENV['MAIL_PORT'] ?? 465);
if (in_array($enc, ['tls', 'ssl'], true)) {
$mail->SMTPSecure = $enc;
}
$mail->Timeout = 20;
$mail->CharSet = 'UTF-8';
$fromName = $clean($_ENV['MAIL_FROM_NAME'] ?? 'YogiBook');
$mail->setFrom($clean($_ENV['MAIL_FROM_ADDRESS'] ?? ''), $fromName);
$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
$nome = trim((string) ($bk['first_name'] ?? ''));
$oldName = $bk['old_servicename'] ?: 'Lezione';
$oldDate = $fmt($bk['old_date'] ?: $bk['bookingstart']);
$newName = $target['servicename'] ?: 'Lezione';
$newDate = $fmt($target['dateschedule']);
$mail->addAddress($to, $nome);
$mail->isHTML(true);
$mail->Subject = 'Lezione riprogrammata - ' . $newName . ' ' . $newDate;
$mail->Body = "
<div style='font-family:Arial,sans-serif;font-size:14px;color:#222'>
<p>Ciao " . $h($nome !== '' ? $nome : 'Yogi') . ",</p>
<p>la tua lezione è stata <b>riprogrammata</b>.</p>
<p>
<b>Prima:</b> " . $h($oldName) . " &ndash; " . $h($oldDate) . "<br>
<b>Adesso:</b> " . $h($newName) . " &ndash; <b>" . $h($newDate) . "</b>
</p>
<p>Se hai domande, rispondi pure a questa email.</p>
<p>A presto,<br>" . $h($fromName) . "</p>
</div>";
$mail->AltBody = "Ciao " . ($nome !== '' ? $nome : 'Yogi') . ",\n"
. "la tua lezione è stata riprogrammata.\n"
. "Prima: {$oldName} - {$oldDate}\nAdesso: {$newName} - {$newDate}\n\n{$fromName}";
$mail->send();
$emailNote = ' Email inviata all\'allieva.';
} catch (Throwable $e) {
error_log("admin_reprogram_booking #{$id}: email non inviata: " . $e->getMessage());
$emailNote = ' Attenzione: la lezione è stata spostata ma l\'email non è partita.';
}
}
}
$reprogNote = $incremented === 'Y' ? ' Conteggiata come riprogrammazione.' : '';
jsonOut([
'success' => true,
'message' => 'Lezione spostata a ' . $fmt($target['dateschedule']) . '.' . $reprogNote . $emailNote,
]);