orders booking api
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* book_from_ticket.php
|
||||
* --------------------------------------------------------------------------
|
||||
* Prenota una NUOVA lezione usando un ticket residuo di un ordine.
|
||||
* Basata su bookandgo.php, con prepared statement e controlli di sicurezza.
|
||||
*
|
||||
* Posizione: public/api/book_from_ticket.php
|
||||
* Metodo: POST
|
||||
* Auth: Bearer token (Sanctum)
|
||||
*
|
||||
* Body (JSON):
|
||||
* - order_id (int) -> idorderbook da cui pescare il ticket
|
||||
* - new_schedule_id (int) -> idserviceschedule dello slot scelto
|
||||
*
|
||||
* Logica:
|
||||
* 1. Verifica che l'ordine sia dell'utente e abbia ticket residui.
|
||||
* 2. Verifica che lo slot esista e sia compatibile col servizio dell'ordine.
|
||||
* 3. Verifica posto libero e non-già-prenotato.
|
||||
* 4. Inserisce bookingclass status 'pending' (come bookandgo.php).
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_bootstrap.php';
|
||||
$userId = (int) $user->id;
|
||||
|
||||
// ==========================================================================
|
||||
// INPUT
|
||||
// ==========================================================================
|
||||
$raw = file_get_contents('php://input');
|
||||
$json = json_decode($raw, true);
|
||||
if (is_array($json)) {
|
||||
$orderId = (int) ($json['order_id'] ?? 0);
|
||||
$newScheduleId = (int) ($json['new_schedule_id'] ?? 0);
|
||||
} else {
|
||||
$orderId = (int) ($_POST['order_id'] ?? 0);
|
||||
$newScheduleId = (int) ($_POST['new_schedule_id'] ?? 0);
|
||||
}
|
||||
|
||||
if ($orderId <= 0 || $newScheduleId <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Parametri mancanti']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 1) Ordine dell'utente + ticket residui
|
||||
// ==========================================================================
|
||||
$stmt = $db->prepare("
|
||||
SELECT idorderbook, iduser, idservice, nticket, expireon
|
||||
FROM orderbook
|
||||
WHERE idorderbook = :oid AND iduser = :uid
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':oid' => $orderId, ':uid' => $userId]);
|
||||
$order = $stmt->fetch();
|
||||
|
||||
if (!$order) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Ordine non trovato']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$orderServiceId = (int) $order['idservice'];
|
||||
$nticket = (int) ($order['nticket'] ?? 0);
|
||||
$expireon = $order['expireon'] ?? null;
|
||||
|
||||
// Scadenza ordine
|
||||
if ($expireon !== null && $expireon < date('Y-m-d')) {
|
||||
http_response_code(409);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Questo pacchetto è scaduto.',
|
||||
'code' => 'expired',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Calcolo ticket residui: nticket - (prenotazioni non cancellate su questo ordine)
|
||||
$stmt = $db->prepare("
|
||||
SELECT COUNT(*) AS n
|
||||
FROM bookingclass
|
||||
WHERE idorder = :oid AND iduser = :uid AND status != 'cancelled'
|
||||
");
|
||||
$stmt->execute([':oid' => $orderId, ':uid' => $userId]);
|
||||
$used = (int) ($stmt->fetch()['n'] ?? 0);
|
||||
$residui = $nticket - $used;
|
||||
|
||||
if ($residui <= 0) {
|
||||
http_response_code(409);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Non hai lezioni residue su questo pacchetto.',
|
||||
'code' => 'no_tickets',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 2) Slot esiste + compatibile col servizio dell'ordine
|
||||
// ==========================================================================
|
||||
$stmt = $db->prepare("
|
||||
SELECT ss.idserviceschedule, ss.idservice, ss.dateschedule, s.maxcapacity
|
||||
FROM serviceschedule ss
|
||||
LEFT JOIN service s ON ss.idservice = s.idservice
|
||||
WHERE ss.idserviceschedule = :sched
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':sched' => $newScheduleId]);
|
||||
$slot = $stmt->fetch();
|
||||
|
||||
if (!$slot) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Slot non trovato']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$newServiceId = (int) $slot['idservice'];
|
||||
$newTime = $slot['dateschedule'];
|
||||
$maxCap = (int) ($slot['maxcapacity'] ?? 0);
|
||||
|
||||
$compatible = [$orderServiceId];
|
||||
$cs = $db->prepare("SELECT idassociateservice FROM associateclass WHERE idmainservice = :sid");
|
||||
$cs->execute([':sid' => $orderServiceId]);
|
||||
foreach ($cs->fetchAll() as $r) {
|
||||
$compatible[] = (int) $r['idassociateservice'];
|
||||
}
|
||||
|
||||
if (!in_array($newServiceId, $compatible, true)) {
|
||||
http_response_code(409);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Lo slot scelto non è compatibile con questo pacchetto.',
|
||||
'code' => 'not_compatible',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 3) Posto libero + non già prenotato
|
||||
// ==========================================================================
|
||||
$stmt = $db->prepare("
|
||||
SELECT COUNT(*) AS n FROM bookingclass
|
||||
WHERE idserviceschedule = :sched AND status = 'booked'
|
||||
");
|
||||
$stmt->execute([':sched' => $newScheduleId]);
|
||||
$bookedCount = (int) ($stmt->fetch()['n'] ?? 0);
|
||||
|
||||
if ($maxCap > 0 && $bookedCount >= $maxCap) {
|
||||
http_response_code(409);
|
||||
echo json_encode(['success' => false, 'error' => 'La classe scelta è piena.', 'code' => 'full']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT COUNT(*) AS n FROM bookingclass
|
||||
WHERE idserviceschedule = :sched AND iduser = :uid AND status IN ('booked','pending')
|
||||
");
|
||||
$stmt->execute([':sched' => $newScheduleId, ':uid' => $userId]);
|
||||
if (((int) ($stmt->fetch()['n'] ?? 0)) > 0) {
|
||||
http_response_code(409);
|
||||
echo json_encode(['success' => false, 'error' => 'Sei già prenotato/a su questa classe.', 'code' => 'already_booked']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 4) Inserimento (status pending, come bookandgo.php)
|
||||
// ==========================================================================
|
||||
try {
|
||||
$ins = $db->prepare("
|
||||
INSERT INTO bookingclass
|
||||
(idserviceschedule, iduser, prevbookingstart, idprevserviceschedule,
|
||||
idservice, idorder, bookingstart, status)
|
||||
VALUES
|
||||
(:sched, :uid, '0', '0', :service, :order, :newstart, 'pending')
|
||||
");
|
||||
$ins->execute([
|
||||
':sched' => $newScheduleId,
|
||||
':uid' => $userId,
|
||||
':service' => $newServiceId,
|
||||
':order' => $orderId,
|
||||
':newstart' => $newTime,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'Errore durante la prenotazione']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// OUTPUT
|
||||
// ==========================================================================
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Prenotazione richiesta. In attesa di conferma.',
|
||||
'new_datetime' => $newTime,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* orders.php
|
||||
* --------------------------------------------------------------------------
|
||||
* Elenca gli ordini/pacchetti dell'utente con il calcolo dei ticket residui.
|
||||
* Logica di selectorder.php.
|
||||
*
|
||||
* Posizione: public/api/orders.php
|
||||
* Metodo: GET
|
||||
* Auth: Bearer token (Sanctum)
|
||||
*
|
||||
* Output: lista ordini con nticket, usati, residui, scadenza, se scaduto.
|
||||
* Utile per la schermata "Programma lezioni" (scelta del pacchetto).
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_bootstrap.php';
|
||||
$userId = (int) $user->id;
|
||||
|
||||
// Tutti gli ordini dell'utente + nome servizio
|
||||
$stmt = $db->prepare("
|
||||
SELECT ob.idorderbook, ob.idservice, ob.nticket, ob.expireon,
|
||||
s.servicename
|
||||
FROM orderbook ob
|
||||
LEFT JOIN service s ON ob.idservice = s.idservice
|
||||
WHERE ob.iduser = :uid
|
||||
ORDER BY ob.idorderbook DESC
|
||||
");
|
||||
$stmt->execute([':uid' => $userId]);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
// Per ogni ordine, calcola i ticket usati (prenotazioni non cancellate)
|
||||
$usedStmt = $db->prepare("
|
||||
SELECT COUNT(*) AS n
|
||||
FROM bookingclass
|
||||
WHERE idorder = :oid AND iduser = :uid AND status != 'cancelled'
|
||||
");
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$orders = [];
|
||||
|
||||
foreach ($rows as $r) {
|
||||
$orderId = (int) $r['idorderbook'];
|
||||
$nticket = (int) ($r['nticket'] ?? 0);
|
||||
$expireon = $r['expireon'] ?? null;
|
||||
|
||||
$usedStmt->execute([':oid' => $orderId, ':uid' => $userId]);
|
||||
$used = (int) ($usedStmt->fetch()['n'] ?? 0);
|
||||
|
||||
$residui = $nticket - $used;
|
||||
if ($residui < 0) {
|
||||
$residui = 0;
|
||||
}
|
||||
|
||||
$isExpired = ($expireon !== null && $expireon < $today);
|
||||
|
||||
$orders[] = [
|
||||
'order_id' => $orderId,
|
||||
'service_id' => (int) $r['idservice'],
|
||||
'service_name' => (string) ($r['servicename'] ?? ''),
|
||||
'tickets' => $nticket,
|
||||
'used' => $used,
|
||||
'remaining' => $residui,
|
||||
'expire_on' => $expireon,
|
||||
'is_expired' => $isExpired,
|
||||
// prenotabile se ha residui e non è scaduto
|
||||
'bookable' => ($residui > 0 && !$isExpired),
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'orders' => $orders,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* reschedule.php
|
||||
* --------------------------------------------------------------------------
|
||||
* Riprogramma una lezione esistente su un nuovo slot.
|
||||
* Fedele a rebookandgo.php, ma con prepared statement e controlli di sicurezza.
|
||||
*
|
||||
* Posizione: public/api/reschedule.php
|
||||
* Metodo: POST
|
||||
* Auth: Bearer token (Sanctum, gestito da _bootstrap.php)
|
||||
*
|
||||
* Body (JSON):
|
||||
* - booking_id (int) -> idbookingclass della lezione da spostare
|
||||
* - new_schedule_id (int) -> idserviceschedule del nuovo slot scelto
|
||||
*
|
||||
* Logica (come rebookandgo.php):
|
||||
* 1. Verifica che il booking sia dell'utente. Legge idorder, idservice vecchi.
|
||||
* 2. Controlla maxreschedule (reprogrammed < maxreschedule).
|
||||
* 3. Verifica che il nuovo slot esista e sia di un servizio COMPATIBILE.
|
||||
* 4. Verifica posto libero e che l'utente non sia già su quello slot.
|
||||
* 5. Cancella il vecchio booking, inserisce il nuovo con status 'pending',
|
||||
* salvando prevbookingstart / idprevserviceschedule.
|
||||
* 6. Incrementa orderbook.reprogrammed.
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_bootstrap.php';
|
||||
$userId = (int) $user->id;
|
||||
|
||||
// ==========================================================================
|
||||
// INPUT
|
||||
// ==========================================================================
|
||||
$raw = file_get_contents('php://input');
|
||||
$json = json_decode($raw, true);
|
||||
if (is_array($json)) {
|
||||
$bookingId = (int) ($json['booking_id'] ?? 0);
|
||||
$newScheduleId = (int) ($json['new_schedule_id'] ?? 0);
|
||||
} else {
|
||||
$bookingId = (int) ($_POST['booking_id'] ?? 0);
|
||||
$newScheduleId = (int) ($_POST['new_schedule_id'] ?? 0);
|
||||
}
|
||||
|
||||
if ($bookingId <= 0 || $newScheduleId <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Parametri mancanti']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 1) Vecchio booking (con ownership)
|
||||
// ==========================================================================
|
||||
$stmt = $db->prepare("
|
||||
SELECT bc.idbookingclass, bc.iduser, bc.idservice, bc.idorder,
|
||||
bc.bookingstart, bc.idserviceschedule
|
||||
FROM bookingclass bc
|
||||
WHERE bc.idbookingclass = :bid AND bc.iduser = :uid
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':bid' => $bookingId, ':uid' => $userId]);
|
||||
$old = $stmt->fetch();
|
||||
|
||||
if (!$old) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Prenotazione non trovata']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$oldServiceId = (int) $old['idservice'];
|
||||
$oldOrderId = (int) $old['idorder'];
|
||||
$oldSchedId = (int) $old['idserviceschedule'];
|
||||
$oldBookingStart = $old['bookingstart'];
|
||||
|
||||
// ==========================================================================
|
||||
// 2) Controllo maxreschedule sull'ordine
|
||||
// ==========================================================================
|
||||
$stmt = $db->prepare("
|
||||
SELECT maxreschedule, reprogrammed
|
||||
FROM orderbook
|
||||
WHERE idorderbook = :oid
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':oid' => $oldOrderId]);
|
||||
$order = $stmt->fetch();
|
||||
|
||||
if ($order) {
|
||||
$maxr = (int) ($order['maxreschedule'] ?? 0);
|
||||
$repr = (int) ($order['reprogrammed'] ?? 0);
|
||||
if ($repr >= $maxr) {
|
||||
http_response_code(409);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Hai raggiunto il numero massimo di riprogrammazioni per questo pacchetto.',
|
||||
'code' => 'max_reschedule',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 3) Nuovo slot: esiste? servizio compatibile?
|
||||
// ==========================================================================
|
||||
$stmt = $db->prepare("
|
||||
SELECT ss.idserviceschedule, ss.idservice, ss.dateschedule, s.maxcapacity
|
||||
FROM serviceschedule ss
|
||||
LEFT JOIN service s ON ss.idservice = s.idservice
|
||||
WHERE ss.idserviceschedule = :sched
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':sched' => $newScheduleId]);
|
||||
$slot = $stmt->fetch();
|
||||
|
||||
if (!$slot) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Slot non trovato']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$newServiceId = (int) $slot['idservice'];
|
||||
$newTime = $slot['dateschedule'];
|
||||
$maxCap = (int) ($slot['maxcapacity'] ?? 0);
|
||||
|
||||
// Servizi compatibili col vecchio servizio (associateclass) + se stesso
|
||||
$compatible = [$oldServiceId];
|
||||
$cs = $db->prepare("SELECT idassociateservice FROM associateclass WHERE idmainservice = :sid");
|
||||
$cs->execute([':sid' => $oldServiceId]);
|
||||
foreach ($cs->fetchAll() as $r) {
|
||||
$compatible[] = (int) $r['idassociateservice'];
|
||||
}
|
||||
|
||||
if (!in_array($newServiceId, $compatible, true)) {
|
||||
http_response_code(409);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Lo slot scelto non è compatibile con la tua lezione.',
|
||||
'code' => 'not_compatible',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 4) Posto libero + non già prenotato dall'utente su quello slot
|
||||
// ==========================================================================
|
||||
$stmt = $db->prepare("
|
||||
SELECT COUNT(*) AS n FROM bookingclass
|
||||
WHERE idserviceschedule = :sched AND status = 'booked'
|
||||
");
|
||||
$stmt->execute([':sched' => $newScheduleId]);
|
||||
$bookedCount = (int) ($stmt->fetch()['n'] ?? 0);
|
||||
|
||||
if ($maxCap > 0 && $bookedCount >= $maxCap) {
|
||||
http_response_code(409);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'La classe scelta è piena.',
|
||||
'code' => 'full',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT COUNT(*) AS n FROM bookingclass
|
||||
WHERE idserviceschedule = :sched AND iduser = :uid
|
||||
AND status IN ('booked','pending')
|
||||
");
|
||||
$stmt->execute([':sched' => $newScheduleId, ':uid' => $userId]);
|
||||
if (((int) ($stmt->fetch()['n'] ?? 0)) > 0) {
|
||||
http_response_code(409);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Sei già prenotato/a su questa classe.',
|
||||
'code' => 'already_booked',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 5) + 6) Transazione: cancella vecchio, inserisci nuovo, incrementa reprogrammed
|
||||
// ==========================================================================
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
|
||||
// Cancella il vecchio (con ownership)
|
||||
$del = $db->prepare("
|
||||
DELETE FROM bookingclass WHERE idbookingclass = :bid AND iduser = :uid LIMIT 1
|
||||
");
|
||||
$del->execute([':bid' => $bookingId, ':uid' => $userId]);
|
||||
|
||||
// Inserisci il nuovo (status pending)
|
||||
$ins = $db->prepare("
|
||||
INSERT INTO bookingclass
|
||||
(idserviceschedule, iduser, prevbookingstart, idprevserviceschedule,
|
||||
idservice, idorder, bookingstart, status)
|
||||
VALUES
|
||||
(:sched, :uid, :prevstart, :prevsched, :service, :order, :newstart, 'pending')
|
||||
");
|
||||
$ins->execute([
|
||||
':sched' => $newScheduleId,
|
||||
':uid' => $userId,
|
||||
':prevstart' => $oldBookingStart,
|
||||
':prevsched' => $oldSchedId,
|
||||
':service' => $newServiceId,
|
||||
':order' => $oldOrderId,
|
||||
':newstart' => $newTime,
|
||||
]);
|
||||
|
||||
// Incrementa reprogrammed
|
||||
$upd = $db->prepare("
|
||||
UPDATE orderbook SET reprogrammed = reprogrammed + 1 WHERE idorderbook = :oid
|
||||
");
|
||||
$upd->execute([':oid' => $oldOrderId]);
|
||||
|
||||
$db->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction()) {
|
||||
$db->rollBack();
|
||||
}
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'Errore durante la riprogrammazione']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// OUTPUT
|
||||
// ==========================================================================
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Riprogrammazione richiesta. In attesa di conferma.',
|
||||
'new_datetime' => $newTime,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
Reference in New Issue
Block a user