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 = "
Ciao " . $h($nome !== '' ? $nome : 'Yogi') . ",
la tua lezione è stata riprogrammata.
Prima: " . $h($oldName) . " – " . $h($oldDate) . "
Adesso: " . $h($newName) . " – " . $h($newDate) . "
Se hai domande, rispondi pure a questa email.
A presto,
" . $h($fromName) . "