Files
yogibook_aury_new/public/class/BookingRules.php
T

192 lines
7.0 KiB
PHP

<?php
/**
* BookingRules
* Regole di prenotazione in un unico posto, sempre limitate alla scuola corrente.
* Usata da: selectorder, bookingpanel, bookingpanelorder, bookandgo, rebookandgo,
* delete_lesson e (in seguito) dalle API dell'app.
*/
if (!class_exists('DBHandlerSelect')) {
require_once __DIR__ . '/db-functions.php';
}
class BookingRules
{
/* ================================================================== */
/* ORDINI / TICKET */
/* ================================================================== */
/** Ordine dell'utente nella scuola corrente, con nome servizio (null se non suo) */
public static function order(int $idorder, int $userId): ?array
{
$st = self::pdo()->prepare(
"SELECT ob.*, s.servicename
FROM orderbook ob
LEFT JOIN service s ON s.idservice = ob.idservice
WHERE ob.idorderbook = ? AND ob.iduser = ? AND ob.idschool = ?"
);
$st->execute([$idorder, $userId, SchoolContext::id()]);
return $st->fetch() ?: null;
}
/** Tutti gli ordini dell'utente nella scuola corrente */
public static function userOrders(int $userId): array
{
$st = self::pdo()->prepare(
"SELECT ob.*, s.servicename
FROM orderbook ob
LEFT JOIN service s ON s.idservice = ob.idservice
WHERE ob.iduser = ? AND ob.idschool = ?
ORDER BY ob.order_date_created DESC, ob.idorderbook DESC"
);
$st->execute([$userId, SchoolContext::id()]);
return $st->fetchAll();
}
/** Ticket totali dell'ordine: nticket, o quantityclass se nticket è vuoto */
public static function ticketsOf(array $order): int
{
$n = (int) ($order['nticket'] ?? 0);
return $n > 0 ? $n : (int) ($order['quantityclass'] ?? 0);
}
/** Ticket usati: prenotazioni non annullate e non omaggio collegate all'ordine */
public static function usedTickets(int $idorder): int
{
$st = self::pdo()->prepare(
"SELECT COUNT(*) FROM bookingclass
WHERE idorder = ? AND idschool = ?
AND status <> 'cancelled' AND is_gift = 'N'"
);
$st->execute([$idorder, SchoolContext::id()]);
return (int) $st->fetchColumn();
}
public static function remaining(array $order): int
{
return max(0, self::ticketsOf($order) - self::usedTickets((int) $order['idorderbook']));
}
public static function isExpired(array $order): bool
{
return !empty($order['expireon']) && $order['expireon'] < date('Y-m-d');
}
/** Si può ancora riprogrammare con questo ordine? (gli omaggi, senza ordine, no) */
public static function canReprogram(?array $order): bool
{
if (!$order) {
return false;
}
return (int) ($order['reprogrammed'] ?? 0) < (int) ($order['maxreschedule'] ?? 0);
}
/* ================================================================== */
/* CLASSI / POSTI */
/* ================================================================== */
/** Servizi prenotabili con un ordine: il servizio + le classi associate */
public static function allowedServices(int $idservice): array
{
$st = self::pdo()->prepare(
"SELECT idassociateservice FROM associateclass WHERE idmainservice = ? AND idschool = ?"
);
$st->execute([$idservice, SchoolContext::id()]);
$ids = array_map('intval', $st->fetchAll(PDO::FETCH_COLUMN));
array_unshift($ids, $idservice);
return array_values(array_unique($ids));
}
/** Lezione in calendario della scuola corrente, con dati del servizio */
public static function schedule(int $idschedule): ?array
{
$st = self::pdo()->prepare(
"SELECT ss.*, s.servicename, s.maxcapacity, s.colorclass, s.idteacher
FROM serviceschedule ss
JOIN service s ON s.idservice = ss.idservice
WHERE ss.idserviceschedule = ? AND ss.idschool = ?"
);
$st->execute([$idschedule, SchoolContext::id()]);
return $st->fetch() ?: null;
}
/** Posti occupati (prenotate + in attesa di conferma) */
public static function occupied(int $idschedule): int
{
$st = self::pdo()->prepare(
"SELECT COUNT(*) FROM bookingclass
WHERE idserviceschedule = ? AND idschool = ? AND status IN ('booked','pending')"
);
$st->execute([$idschedule, SchoolContext::id()]);
return (int) $st->fetchColumn();
}
/** L'utente ha già una prenotazione attiva (anche in coda) in questa lezione? */
public static function userHasBooking(int $idschedule, int $userId): bool
{
$st = self::pdo()->prepare(
"SELECT 1 FROM bookingclass
WHERE idserviceschedule = ? AND iduser = ? AND idschool = ? AND status <> 'cancelled'
LIMIT 1"
);
$st->execute([$idschedule, $userId, SchoolContext::id()]);
return (bool) $st->fetchColumn();
}
public static function waitingListEnabled(): bool
{
return SchoolSettings::get('waitinglist_enabled', 'N') === 'Y';
}
/**
* Stato di una nuova prenotazione da parte dell'allievo:
* 'pending' c'è posto (lo staff conferma)
* 'waiting' classe piena, lista d'attesa attiva
* 'full' classe piena, lista d'attesa disattivata
*/
public static function decideStatus(array $schedule): string
{
$max = (int) ($schedule['maxcapacity'] ?? 0);
if ($max <= 0 || self::occupied((int) $schedule['idserviceschedule']) < $max) {
return 'pending';
}
return self::waitingListEnabled() ? 'waiting' : 'full';
}
/* ================================================================== */
/* CANCELLAZIONE */
/* ================================================================== */
/**
* L'allievo può ancora cancellare/riprogrammare la lezione?
* Regola attuale (Seregno), da rendere configurabile per scuola:
* - lezione passata: no
* - lezione in un altro giorno: sì
* - lezione di oggi entro le 17:00: entro le 00:01 di oggi
* - lezione di oggi dopo le 17:00: entro le 12:00 di oggi
*/
public static function canCancel(string $dateschedule): bool
{
$now = new DateTime();
$class = new DateTime($dateschedule);
if ($class <= $now) {
return false;
}
if ($class->format('Y-m-d') !== $now->format('Y-m-d')) {
return true;
}
$h = (int) $class->format('H');
$m = (int) $class->format('i');
$before1700 = $h < 17 || ($h === 17 && $m === 0);
$deadline = new DateTime($class->format('Y-m-d') . ($before1700 ? ' 00:01:00' : ' 12:00:00'));
return $now <= $deadline;
}
private static function pdo(): PDO
{
return DBHandlerSelect::getInstance()->getConnection();
}
}