diff --git a/public/api/api_teacher_add_booking.php b/public/api/api_teacher_add_booking.php new file mode 100644 index 00000000..519e0aed --- /dev/null +++ b/public/api/api_teacher_add_booking.php @@ -0,0 +1,250 @@ + classe destinazione + * idservice= servizio della classe + * bookingstart= data/ora lezione (dateschedule) + * bkmode= + * user_id= utente esistente (0 se nuovo) + * name, surname, email per creare l'utente se user_id=0 + * idorder= solo per 'scala' + * new_expiry= solo per 'nuovo' + * + * Regole identiche alla webapp: + * - status sempre 'booked' + * - omaggio: idorder NULL, is_gift='Y' + * - nuovo: crea orderbook (1 ticket, maxreschedule 0), is_gift='N' + * - scala: aggancia ordine esistente (verifica proprietà + scadenza) + * - idorder mai 0 (FK): NULL o id reale + * - crea utente se user_id=0 (riusa email esistente) + * - anti-duplicato: stesso utente già in questa classe + * -------------------------------------------------------------------------- + */ + +require_once __DIR__ . '/_bootstrap.php'; + +// Gate staff +$roleId = (int) $user->role_id; +if (!in_array($roleId, [1, 3], true)) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden']); + exit; +} + +// --- Input --- +$name = trim($_POST['name'] ?? ''); +$surname = trim($_POST['surname'] ?? ''); +$email = trim($_POST['email'] ?? ''); +$userid = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0; +$idserviceschedule = isset($_POST['idserviceschedule']) ? (int) $_POST['idserviceschedule'] : 0; +$idservice = isset($_POST['idservice']) ? (int) $_POST['idservice'] : 0; +$bookingstart = trim($_POST['bookingstart'] ?? ''); +$bkmode = trim($_POST['bkmode'] ?? 'scala'); // scala | omaggio | nuovo +$idorderRaw = $_POST['idorder'] ?? ''; +$newExpiry = trim($_POST['new_expiry'] ?? ''); + +$status = 'booked'; // inserimento da insegnante: sempre booked + +// --- Validazione minima --- +if ($idserviceschedule <= 0 || $idservice <= 0) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Dati mancanti (classe/servizio)']); + exit; +} +// Se nuovo utente, servono almeno nome e cognome +if ($userid <= 0 && ($name === '' || $surname === '')) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Nome e cognome obbligatori per un nuovo utente']); + exit; +} + +try { + $db->beginTransaction(); + + // ------------------------------------------------------------------ + // 1) Risolvi / crea utente + // ------------------------------------------------------------------ + if ($userid <= 0) { + if ($email !== '') { + $check = $db->prepare("SELECT id FROM auth_users WHERE email = :email LIMIT 1"); + $check->execute([':email' => $email]); + $existing = $check->fetchColumn(); + if ($existing) { + $userid = (int) $existing; + } + } + + if ($userid <= 0) { + $randomPassword = bin2hex(random_bytes(8)); + $hashed = password_hash($randomPassword, PASSWORD_BCRYPT); + + $insUser = $db->prepare( + "INSERT INTO auth_users + (first_name, last_name, email, password, role_id, status, created_at, avatar) + VALUES + (:first, :last, :email, :password, :role, :status, :created, :avatar)" + ); + $insUser->execute([ + ':first' => $name, + ':last' => $surname, + ':email' => $email !== '' ? $email : null, + ':password' => $hashed, + ':role' => 2, + ':status' => 'Active', + ':created' => date('Y-m-d H:i:s'), + ':avatar' => 'mediationb.png', + ]); + $userid = (int) $db->lastInsertId(); + } + } + + // ------------------------------------------------------------------ + // 2) Determina idorder e is_gift in base alla modalità + // ------------------------------------------------------------------ + $idorder = null; + $isGift = 'N'; + + if ($bkmode === 'omaggio') { + $idorder = null; + $isGift = 'Y'; + } elseif ($bkmode === 'nuovo') { + if ($newExpiry === '') { + throw new RuntimeException('Scadenza mancante per il nuovo ordine.'); + } + + $uStmt = $db->prepare( + "SELECT first_name, last_name, email FROM auth_users WHERE id = :uid LIMIT 1" + ); + $uStmt->execute([':uid' => $userid]); + $uRow = $uStmt->fetch(PDO::FETCH_ASSOC) ?: []; + $uEmail = $uRow['email'] ?? ($email !== '' ? $email : null); + $uFirst = $uRow['first_name'] ?? $name; + $uLast = $uRow['last_name'] ?? $surname; + + $sStmt = $db->prepare( + "SELECT servicename FROM service WHERE idservice = :sid LIMIT 1" + ); + $sStmt->execute([':sid' => $idservice]); + $serviceName = (string) ($sStmt->fetchColumn() ?: ''); + + $firstLessonDate = null; + if ($bookingstart !== '') { + try { + $firstLessonDate = (new DateTime($bookingstart))->format('Y-m-d'); + } catch (Throwable $e) { + $firstLessonDate = null; + } + } + + $insOrder = $db->prepare( + "INSERT INTO orderbook + (iduser, idservice, nticket, quantity, quantityclass, + expireon, maxreschedule, reprogrammed, status, + order_billing_email, cod, product_name, + first_name, last_name, first_lesson_date, order_date_created) + VALUES + (:uid, :service, 1, 1, 1, + :expire, 0, 0, 'booked', + :bemail, :cod, :pname, + :first, :last, :firstlesson, :created)" + ); + $insOrder->execute([ + ':uid' => $userid, + ':service' => $idservice, + ':expire' => $newExpiry, + ':bemail' => $uEmail, + ':cod' => $serviceName, + ':pname' => $serviceName, + ':first' => $uFirst, + ':last' => $uLast, + ':firstlesson' => $firstLessonDate, + ':created' => date('Y-m-d'), + ]); + $idorder = (int) $db->lastInsertId(); + $isGift = 'N'; + } else { + // 'scala' + $idorder = ($idorderRaw !== '' && (int) $idorderRaw > 0) ? (int) $idorderRaw : null; + + if ($idorder !== null) { + $chk = $db->prepare( + "SELECT expireon FROM orderbook + WHERE idorderbook = :oid AND iduser = :uid LIMIT 1" + ); + $chk->execute([':oid' => $idorder, ':uid' => $userid]); + $row = $chk->fetch(PDO::FETCH_ASSOC); + if (!$row) { + throw new RuntimeException('Ordine non valido per questo utente.'); + } + $exp = $row['expireon'] ?? null; + if ($exp !== null && $exp < date('Y-m-d')) { + throw new RuntimeException('Il pacchetto selezionato è scaduto.'); + } + } else { + throw new RuntimeException('Nessun pacchetto selezionato.'); + } + $isGift = 'N'; + } + + // ------------------------------------------------------------------ + // 2b) Anti-duplicato + // ------------------------------------------------------------------ + $dup = $db->prepare( + "SELECT COUNT(*) FROM bookingclass + WHERE iduser = :uid + AND idserviceschedule = :idschedule + AND status != 'cancelled'" + ); + $dup->execute([':uid' => $userid, ':idschedule' => $idserviceschedule]); + if ((int) $dup->fetchColumn() > 0) { + $db->rollBack(); + http_response_code(409); + echo json_encode(['success' => false, 'message' => 'Questo utente è già prenotato in questa classe.']); + exit; + } + + // ------------------------------------------------------------------ + // 3) Inserimento prenotazione + // ------------------------------------------------------------------ + $insBooking = $db->prepare( + "INSERT INTO bookingclass + (iduser, idserviceschedule, status, idorder, idservice, bookingstart, is_gift) + VALUES + (:iduser, :idschedule, :status, :idorder, :idservice, :bookingstart, :isgift)" + ); + $insBooking->execute([ + ':iduser' => $userid, + ':idschedule' => $idserviceschedule, + ':status' => $status, + ':idorder' => $idorder, + ':idservice' => $idservice, + ':bookingstart' => $bookingstart !== '' ? $bookingstart : null, + ':isgift' => $isGift, + ]); + + $db->commit(); + + echo json_encode([ + 'success' => true, + 'user_id' => $userid, + 'is_gift' => $isGift, + 'idorder' => $idorder, + ], JSON_UNESCAPED_UNICODE); +} catch (Throwable $ex) { + if ($db->inTransaction()) { + $db->rollBack(); + } + error_log('api_teacher_add_booking error: ' . $ex->getMessage()); + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Errore inserimento: ' . $ex->getMessage()]); +} diff --git a/public/api/api_teacher_search_users.php b/public/api/api_teacher_search_users.php new file mode 100644 index 00000000..a3106764 --- /dev/null +++ b/public/api/api_teacher_search_users.php @@ -0,0 +1,75 @@ +&last= (min 2 caratteri in almeno uno) + * -------------------------------------------------------------------------- + */ + +require_once __DIR__ . '/_bootstrap.php'; + +// Gate staff +$roleId = (int) $user->role_id; +if (!in_array($roleId, [1, 3], true)) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden']); + exit; +} + +$firstName = trim($_GET['first'] ?? ''); +$lastName = trim($_GET['last'] ?? ''); + +// Serve almeno 2 caratteri in uno dei due campi +if (mb_strlen($firstName) < 2 && mb_strlen($lastName) < 2) { + echo json_encode(['success' => true, 'results' => []], JSON_UNESCAPED_UNICODE); + exit; +} + +try { + $conditions = []; + $params = []; + + if (mb_strlen($firstName) >= 2) { + $conditions[] = "first_name LIKE :fn"; + $params[':fn'] = '%' . $firstName . '%'; + } + if (mb_strlen($lastName) >= 2) { + $conditions[] = "last_name LIKE :ln"; + $params[':ln'] = '%' . $lastName . '%'; + } + + $where = implode(' OR ', $conditions); + + $sql = "SELECT id, first_name, last_name, email + FROM auth_users + WHERE $where + ORDER BY last_name, first_name + LIMIT 15"; + + $stmt = $db->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $results = []; + foreach ($rows as $r) { + $results[] = [ + 'id' => (int) $r['id'], + 'first_name' => (string) ($r['first_name'] ?? ''), + 'last_name' => (string) ($r['last_name'] ?? ''), + 'email' => (string) ($r['email'] ?? ''), + ]; + } + + echo json_encode(['success' => true, 'results' => $results], JSON_UNESCAPED_UNICODE); +} catch (Throwable $ex) { + error_log('api_teacher_search_users error: ' . $ex->getMessage()); + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Errore ricerca']); +} diff --git a/public/api/api_teacher_user_orders.php b/public/api/api_teacher_user_orders.php new file mode 100644 index 00000000..21b9ee38 --- /dev/null +++ b/public/api/api_teacher_user_orders.php @@ -0,0 +1,81 @@ + + * + * Residui = ticket - (prenotazioni non cancellate e NON omaggio). + * -------------------------------------------------------------------------- + */ + +require_once __DIR__ . '/_bootstrap.php'; + +// Gate staff +$roleId = (int) $user->role_id; +if (!in_array($roleId, [1, 3], true)) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden']); + exit; +} + +$userId = isset($_GET['user_id']) ? (int) $_GET['user_id'] : 0; +if ($userId <= 0) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Utente non valido', 'orders' => []]); + exit; +} + +try { + $today = date('Y-m-d'); + + $sql = "SELECT ob.idorderbook, ob.idservice, ob.nticket, ob.quantityclass, + ob.expireon, ob.maxreschedule, ob.reprogrammed, + s.servicename + FROM orderbook ob + LEFT JOIN service s ON ob.idservice = s.idservice + WHERE ob.iduser = :uid + AND (ob.expireon IS NULL OR ob.expireon >= :today) + ORDER BY ob.idorderbook DESC"; + $stmt = $db->prepare($sql); + $stmt->execute([':uid' => $userId, ':today' => $today]); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // conta le prenotazioni che consumano ticket (non cancellate, non omaggio) + $usedStmt = $db->prepare( + "SELECT COUNT(*) AS n + FROM bookingclass + WHERE idorder = :oid + AND status != 'cancelled' + AND is_gift = 'N'" + ); + + $orders = []; + foreach ($rows as $o) { + $orderId = (int) $o['idorderbook']; + + $tickets = (int) (($o['quantityclass'] !== null && $o['quantityclass'] !== '') + ? $o['quantityclass'] + : ($o['nticket'] ?? 0)); + + $usedStmt->execute([':oid' => $orderId]); + $used = (int) ($usedStmt->fetch(PDO::FETCH_ASSOC)['n'] ?? 0); + + $remaining = $tickets - $used; + if ($remaining < 0) { + $remaining = 0; + } + + $orders[] = [ + 'idorderbook' => $orderId, + 'service_name' => (string) ($o['servicename'] ?? ''), + 'tickets' => $tickets, + 'used' => $used, + 'remaining' => $remaining, + 'expireon' => $o['expireon'] ?? \ No newline at end of file