future lessons
This commit is contained in:
+450
-4
@@ -11,7 +11,212 @@ $stmt = $pdo->prepare("SELECT id, name FROM schools WHERE owner_id = ?");
|
||||
$stmt->execute([$iduserlogin]);
|
||||
$school = $stmt->fetch();
|
||||
if (!$school) die("Scuola non trovata.");
|
||||
$school_id = $school['id'];
|
||||
$school_id = (int)$school['id'];
|
||||
|
||||
|
||||
// === UTENTI DELLA SCUOLA (dropdown cercabile) ===
|
||||
$stmtUsers = $pdo->prepare("
|
||||
SELECT au.id, au.first_name, au.last_name, au.email
|
||||
FROM user_schools us
|
||||
JOIN auth_users au ON au.id = us.user_id
|
||||
WHERE us.school_id = ?
|
||||
AND us.status = 'active'
|
||||
ORDER BY au.first_name, au.last_name
|
||||
");
|
||||
$stmtUsers->execute([$school_id]);
|
||||
$schoolUsers = $stmtUsers->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// === PRODOTTI ATTIVI ===
|
||||
$stmtProd = $pdo->prepare("
|
||||
SELECT id, name, type
|
||||
FROM products
|
||||
WHERE school_id = ? AND status = 'active'
|
||||
ORDER BY name
|
||||
");
|
||||
$stmtProd->execute([$school_id]);
|
||||
$products = $stmtProd->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// === VARIAZIONI ATTIVE (per filtro lato JS) ===
|
||||
$variations = [];
|
||||
if (!empty($products)) {
|
||||
$prodIds = array_column($products, 'id');
|
||||
$in = implode(',', array_fill(0, count($prodIds), '?'));
|
||||
|
||||
$stmtVar = $pdo->prepare("
|
||||
SELECT id, product_id, name, price, duration_days, max_entries, max_recoveries
|
||||
FROM product_variations
|
||||
WHERE product_id IN ($in)
|
||||
AND status = 'active'
|
||||
ORDER BY product_id, name
|
||||
");
|
||||
$stmtVar->execute($prodIds);
|
||||
$variations = $stmtVar->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// === CLASSI ATTIVE ===
|
||||
$stmtClasses = $pdo->prepare("
|
||||
SELECT id, name
|
||||
FROM classes
|
||||
WHERE school_id = ? AND status = 'active'
|
||||
ORDER BY name
|
||||
");
|
||||
$stmtClasses->execute([$school_id]);
|
||||
$classes = $stmtClasses->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// === CLASS TYPES (filtrati lato JS per class_id) ===
|
||||
$stmtCT = $pdo->prepare("
|
||||
SELECT ct.id, ct.class_id, ct.level, ct.day_of_week
|
||||
FROM class_types ct
|
||||
JOIN classes c ON ct.class_id = c.id
|
||||
WHERE c.school_id = ?
|
||||
ORDER BY c.name, ct.day_of_week, ct.level
|
||||
");
|
||||
$stmtCT->execute([$school_id]);
|
||||
$classTypes = $stmtCT->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$feedback = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'add_order_manual') {
|
||||
$user_id = (int)($_POST['user_id'] ?? 0);
|
||||
$product_id = (int)($_POST['product_id'] ?? 0);
|
||||
$variation_id = (int)($_POST['variation_id'] ?? 0);
|
||||
$payment_method = trim($_POST['payment_method'] ?? '');
|
||||
$status = trim($_POST['status'] ?? 'completed');
|
||||
|
||||
$price = isset($_POST['price']) ? (float)str_replace(',', '.', $_POST['price']) : 0.0;
|
||||
$total_entries = ($_POST['total_entries'] ?? '') !== '' ? (int)$_POST['total_entries'] : null;
|
||||
$available_entries = ($_POST['available_entries'] ?? '') !== '' ? (int)$_POST['available_entries'] : null;
|
||||
|
||||
$activation_date = $_POST['activation_date'] ?? date('Y-m-d');
|
||||
$expiration_date = $_POST['expiration_date'] ?? null;
|
||||
$expiration_date = ($expiration_date === '') ? null : $expiration_date;
|
||||
|
||||
$class_id = ($_POST['class_id'] ?? '') !== '' ? (int)$_POST['class_id'] : null;
|
||||
$class_type_id = ($_POST['class_type_id'] ?? '') !== '' ? (int)$_POST['class_type_id'] : null;
|
||||
|
||||
// Validazioni base
|
||||
if ($user_id <= 0 || $product_id <= 0 || $payment_method === '') {
|
||||
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Compila utente, prodotto e metodo di pagamento.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
} else {
|
||||
// 1) utente appartiene alla scuola
|
||||
$stmt = $pdo->prepare("SELECT 1 FROM user_schools WHERE school_id = ? AND user_id = ? AND status='active' LIMIT 1");
|
||||
$stmt->execute([$school_id, $user_id]);
|
||||
if (!$stmt->fetchColumn()) {
|
||||
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Utente non associato alla scuola.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
} else {
|
||||
// 2) prodotto appartiene alla scuola
|
||||
$stmt = $pdo->prepare("SELECT id FROM products WHERE id=? AND school_id=? AND status='active' LIMIT 1");
|
||||
$stmt->execute([$product_id, $school_id]);
|
||||
if (!$stmt->fetchColumn()) {
|
||||
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Prodotto non valido.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
} else {
|
||||
// 3) se variation_id valorizzato, deve appartenere al prodotto
|
||||
$varMeta = null;
|
||||
if ($variation_id > 0) {
|
||||
$stmt = $pdo->prepare("SELECT id, price, duration_days, max_entries, max_recoveries FROM product_variations WHERE id=? AND product_id=? AND status='active' LIMIT 1");
|
||||
$stmt->execute([$variation_id, $product_id]);
|
||||
$varMeta = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$varMeta) {
|
||||
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Variazione non valida.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($feedback === '') {
|
||||
// 4) class / class_type se presenti devono essere coerenti e della scuola
|
||||
if ($class_id) {
|
||||
$stmt = $pdo->prepare("SELECT 1 FROM classes WHERE id=? AND school_id=? LIMIT 1");
|
||||
$stmt->execute([$class_id, $school_id]);
|
||||
if (!$stmt->fetchColumn()) {
|
||||
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Classe non valida.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
}
|
||||
}
|
||||
if ($class_type_id) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT 1
|
||||
FROM class_types ct
|
||||
JOIN classes c ON ct.class_id = c.id
|
||||
WHERE ct.id=? AND c.school_id=?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$class_type_id, $school_id]);
|
||||
if (!$stmt->fetchColumn()) {
|
||||
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Class type non valido.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($feedback === '') {
|
||||
// Autocomplete intelligente da variazione se non inserito
|
||||
if ($varMeta) {
|
||||
if ($price <= 0) $price = (float)$varMeta['price'];
|
||||
if ($total_entries === null && $varMeta['max_entries'] !== null) $total_entries = (int)$varMeta['max_entries'];
|
||||
if ($available_entries === null && $total_entries !== null) $available_entries = $total_entries;
|
||||
|
||||
if (!$expiration_date && !empty($varMeta['duration_days'])) {
|
||||
$d = new DateTime($activation_date);
|
||||
$d->modify('+' . (int)$varMeta['duration_days'] . ' days');
|
||||
$expiration_date = $d->format('Y-m-d');
|
||||
}
|
||||
$available_recoveries = ($varMeta['max_recoveries'] !== null) ? (int)$varMeta['max_recoveries'] : null;
|
||||
} else {
|
||||
$available_recoveries = null;
|
||||
}
|
||||
|
||||
// order_number progressivo per scuola
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$stmt = $pdo->prepare("SELECT COALESCE(MAX(order_number),0) + 1 FROM orders WHERE school_id=? FOR UPDATE");
|
||||
$stmt->execute([$school_id]);
|
||||
$nextOrderNumber = (int)$stmt->fetchColumn();
|
||||
|
||||
$stmtIns = $pdo->prepare("
|
||||
INSERT INTO orders
|
||||
(order_number, school_id, user_id, product_id, variation_id, class_id, class_type_id,
|
||||
created_at, payment_method, price, status,
|
||||
total_entries, available_entries, available_recoveries,
|
||||
expiration_date, activation_date)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?,
|
||||
NOW(), ?, ?, ?,
|
||||
?, ?, ?,
|
||||
?, ?)
|
||||
");
|
||||
|
||||
$stmtIns->execute([
|
||||
$nextOrderNumber,
|
||||
$school_id,
|
||||
$user_id,
|
||||
$product_id,
|
||||
($variation_id > 0 ? $variation_id : null),
|
||||
$class_id,
|
||||
$class_type_id,
|
||||
$payment_method,
|
||||
$price,
|
||||
$status,
|
||||
$total_entries,
|
||||
$available_entries,
|
||||
$available_recoveries,
|
||||
$expiration_date,
|
||||
$activation_date
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
$feedback = '<div class="alert alert-success alert-dismissible fade show">Ordine manuale inserito con successo!<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Errore inserimento ordine: ' . htmlspecialchars($e->getMessage()) . '<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recupera tutti gli ordini con tutti i dati necessari
|
||||
$stmt = $pdo->prepare("
|
||||
@@ -55,6 +260,7 @@ $orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<title>Ordini - <?php echo htmlspecialchars($school['name']); ?></title>
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<?php include('siteinfo.php'); ?>
|
||||
<link rel="stylesheet" href="assets/plugins/select2/css/select2.min.css">
|
||||
|
||||
<!-- DataTables CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css">
|
||||
@@ -77,13 +283,15 @@ $orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
Gestione Ordini
|
||||
<span class="badge bg-light text-dark ms-2"><?php echo count($orders); ?></span>
|
||||
</h4>
|
||||
<div>
|
||||
<button class="btn btn-light btn-sm" onclick="table.buttons().export()">
|
||||
Esporta
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-light btn-sm" data-bs-toggle="modal" data-bs-target="#manualOrderModal">
|
||||
+ Ordine manuale
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?php echo $feedback; ?>
|
||||
|
||||
<div class="card radius-15 shadow">
|
||||
<div class="card-body p-0">
|
||||
@@ -170,11 +378,154 @@ $orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="manualOrderModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="add_order_manual">
|
||||
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title">Inserisci ordine manuale</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Utente</label>
|
||||
<select name="user_id" class="form-select select2-user" required data-placeholder="Cerca per nome o email">
|
||||
<option value=""></option>
|
||||
<?php foreach ($schoolUsers as $u): ?>
|
||||
<option value="<?php echo (int)$u['id']; ?>">
|
||||
<?php
|
||||
$full = trim(($u['first_name'] ?? '') . ' ' . ($u['last_name'] ?? ''));
|
||||
echo htmlspecialchars($full . ' — ' . ($u['email'] ?? ''));
|
||||
?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Metodo pagamento</label>
|
||||
<select name="payment_method" class="form-select" required>
|
||||
<option value="">Seleziona...</option>
|
||||
<option value="cash">Cash</option>
|
||||
<option value="pos">POS</option>
|
||||
<option value="bank_transfer">Bonifico</option>
|
||||
<option value="manual">Manuale</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Prodotto</label>
|
||||
<select name="product_id" class="form-select select2-product" required data-placeholder="Seleziona prodotto">
|
||||
<option value=""></option>
|
||||
<?php foreach ($products as $p): ?>
|
||||
<option value="<?php echo (int)$p['id']; ?>">
|
||||
<?php echo htmlspecialchars($p['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Variazione</label>
|
||||
<select name="variation_id" class="form-select select2-variation" data-placeholder="(opzionale)">
|
||||
<option value=""></option>
|
||||
<?php foreach ($variations as $v): ?>
|
||||
<option
|
||||
value="<?php echo (int)$v['id']; ?>"
|
||||
data-product-id="<?php echo (int)$v['product_id']; ?>"
|
||||
data-price="<?php echo htmlspecialchars($v['price']); ?>"
|
||||
data-duration-days="<?php echo (int)($v['duration_days'] ?? 0); ?>"
|
||||
data-max-entries="<?php echo ($v['max_entries'] !== null ? (int)$v['max_entries'] : ''); ?>">
|
||||
<?php echo htmlspecialchars($v['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<small class="text-muted">Le variazioni verranno filtrate in base al prodotto.</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Prezzo (€)</label>
|
||||
<input type="text" name="price" class="form-control" placeholder="es. 49.90">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Stato</label>
|
||||
<select name="status" class="form-select">
|
||||
<option value="completed" selected>completed</option>
|
||||
<option value="pending">pending</option>
|
||||
<option value="cancelled">cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Attivazione</label>
|
||||
<input type="date" name="activation_date" class="form-control" value="<?php echo date('Y-m-d'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Ingressi totali</label>
|
||||
<input type="number" name="total_entries" class="form-control" min="0" placeholder="(vuoto = illimitati)">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Ingressi disponibili</label>
|
||||
<input type="number" name="available_entries" class="form-control" min="0" placeholder="(auto)">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Scadenza</label>
|
||||
<input type="date" name="expiration_date" class="form-control" placeholder="(auto se duration)">
|
||||
</div>
|
||||
|
||||
<?php if (isset($classes, $classTypes)): ?>
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Classe (opzionale)</label>
|
||||
<select name="class_id" class="form-select select2-class" data-placeholder="Tutte le classi">
|
||||
<option value=""></option>
|
||||
<?php foreach ($classes as $c): ?>
|
||||
<option value="<?php echo (int)$c['id']; ?>"><?php echo htmlspecialchars($c['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Class Type (opzionale)</label>
|
||||
<select name="class_type_id" class="form-select select2-classtype" data-placeholder="Qualsiasi">
|
||||
<option value=""></option>
|
||||
<?php foreach ($classTypes as $ct): ?>
|
||||
<option value="<?php echo (int)$ct['id']; ?>" data-class-id="<?php echo (int)$ct['class_id']; ?>">
|
||||
<?php echo htmlspecialchars(($ct['day_of_week'] ?? '') . ' - ' . ($ct['level'] ?? '')); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||
<button type="submit" class="btn btn-primary">Salva ordine</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
|
||||
<?php include('jsinclude.php'); ?>
|
||||
<script src="assets/plugins/select2/js/select2.min.js"></script>
|
||||
|
||||
<!-- DataTables + Plugin -->
|
||||
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
||||
@@ -222,6 +573,101 @@ $orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(function() {
|
||||
// Select2 dentro modale
|
||||
$('#manualOrderModal').on('shown.bs.modal', function() {
|
||||
const $m = $('#manualOrderModal');
|
||||
|
||||
function initSel(selector) {
|
||||
const $el = $m.find(selector);
|
||||
if (!$el.length) return;
|
||||
if ($el.hasClass('select2-hidden-accessible')) return;
|
||||
$el.select2({
|
||||
dropdownParent: $m,
|
||||
width: '100%',
|
||||
allowClear: true,
|
||||
placeholder: $el.data('placeholder') || 'Seleziona...'
|
||||
});
|
||||
}
|
||||
|
||||
initSel('.select2-user');
|
||||
initSel('.select2-product');
|
||||
initSel('.select2-variation');
|
||||
initSel('.select2-class');
|
||||
initSel('.select2-classtype');
|
||||
});
|
||||
|
||||
// Filtra variazioni in base al prodotto
|
||||
$(document).on('change', 'select[name="product_id"]', function() {
|
||||
const pid = $(this).val();
|
||||
const $var = $('#manualOrderModal').find('select[name="variation_id"]');
|
||||
|
||||
$var.val('').trigger('change');
|
||||
|
||||
$var.find('option').each(function() {
|
||||
const optPid = $(this).data('product-id');
|
||||
if (!optPid) return; // option vuota
|
||||
$(this).toggle(String(optPid) === String(pid));
|
||||
});
|
||||
});
|
||||
|
||||
// Quando selezioni una variazione: precompila prezzo + ingressi + scadenza
|
||||
$(document).on('change', '#manualOrderModal select[name="variation_id"]', function() {
|
||||
const $m = $('#manualOrderModal');
|
||||
const $opt = $(this).find('option:selected');
|
||||
|
||||
const price = $opt.data('price');
|
||||
const maxEntries = $opt.data('max-entries');
|
||||
const durationDays = parseInt($opt.data('duration-days') || 0, 10);
|
||||
|
||||
// Prezzo
|
||||
if (price !== undefined && price !== '') {
|
||||
$m.find('input[name="price"]').val(price);
|
||||
}
|
||||
|
||||
// Ingressi
|
||||
if (maxEntries !== undefined && maxEntries !== '') {
|
||||
$m.find('input[name="total_entries"]').val(maxEntries);
|
||||
$m.find('input[name="available_entries"]').val(maxEntries);
|
||||
}
|
||||
|
||||
// Scadenza (activation_date + durationDays)
|
||||
const act = $m.find('input[name="activation_date"]').val();
|
||||
if (durationDays > 0 && act) {
|
||||
const d = new Date(act);
|
||||
d.setDate(d.getDate() + durationDays);
|
||||
|
||||
const yyyy = d.getFullYear();
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
|
||||
$m.find('input[name="expiration_date"]').val(`${yyyy}-${mm}-${dd}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ricalcola scadenza se cambia activation_date (solo se variation con duration)
|
||||
$(document).on('change', 'input[name="activation_date"]', function() {
|
||||
$('select[name="variation_id"]').trigger('change');
|
||||
});
|
||||
|
||||
// Filtra class_types in base alla classe
|
||||
$(document).on('change', 'select[name="class_id"]', function() {
|
||||
const cid = $(this).val();
|
||||
const $ct = $('select[name="class_type_id"]');
|
||||
|
||||
$ct.val('').trigger('change');
|
||||
|
||||
$ct.find('option').each(function() {
|
||||
const optCid = $(this).data('class-id');
|
||||
if (!optCid) return;
|
||||
$(this).toggle(String(optCid) === String(cid));
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user