2025-12-25 21:12:29 +01:00

673 lines
34 KiB
PHP

<?php
include('include/headscript.php');
if (!isset($iduserlogin)) die("Errore: utente non loggato.");
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
// Recupera la scuola
$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 = (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("
SELECT
o.id,
o.order_number,
o.created_at,
o.price,
o.status,
o.payment_method,
o.total_entries,
o.available_entries,
o.expiration_date,
u.first_name,
u.last_name,
u.email,
p.name AS product_name,
pv.name AS variation_name,
c.name AS class_name,
ct.level,
ct.day_of_week
FROM orders o
JOIN auth_users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id
LEFT JOIN product_variations pv ON o.variation_id = pv.id
LEFT JOIN classes c ON o.class_id = c.id
LEFT JOIN class_types ct ON o.class_type_id = ct.id
WHERE o.school_id = ?
ORDER BY o.created_at DESC
");
$stmt->execute([$school_id]);
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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">
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.2/css/buttons.bootstrap5.min.css">
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="container-fluid px-1">
<div class="card radius-15 shadow-lg mb-4">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h4 class="mb-0">
Gestione Ordini
<span class="badge bg-light text-dark ms-2"><?php echo count($orders); ?></span>
</h4>
<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">
<div class="table-responsive">
<table id="ordersTable" class="table table-striped table-hover" style="width:100%">
<thead class="table-dark">
<tr>
<th>Data</th>
<th>Ordine #</th>
<th>Cliente</th>
<th>Email</th>
<th>Prodotto</th>
<th>Variazione</th>
<th>Prezzo</th>
<th>Ingressi</th>
<th>Scadenza</th>
<th>Stato</th>
<th>Pagamento</th>
<th>Lezione</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($orders as $o): ?>
<tr>
<td data-order="<?php echo $o['created_at']; ?>">
<strong><?php echo date('d/m/Y', strtotime($o['created_at'])); ?></strong><br>
<small class="text-muted"><?php echo date('H:i', strtotime($o['created_at'])); ?></small>
</td>
<td><strong>#<?php echo $o['order_number']; ?></strong></td>
<td><?php echo htmlspecialchars($o['first_name'] . ' ' . $o['last_name']); ?></td>
<td>
<a href="mailto:<?php echo htmlspecialchars($o['email']); ?>">
<?php echo htmlspecialchars($o['email']); ?>
</a>
</td>
<td><?php echo htmlspecialchars($o['product_name']); ?></td>
<td><?php echo htmlspecialchars($o['variation_name'] ?: '-'); ?></td>
<td><strong>€<?php echo number_format($o['price'], 2); ?></strong></td>
<td>
<?php echo $o['total_entries'] ? $o['available_entries'] . '/' . $o['total_entries'] : 'Illimitati'; ?>
</td>
<td>
<?php if ($o['expiration_date']): ?>
<span class="text-danger fw-bold"><?php echo date('d/m/Y', strtotime($o['expiration_date'])); ?></span>
<?php else: ?>
<em>Nessuna</em>
<?php endif; ?>
</td>
<td>
<span class="badge <?php echo $o['status'] == 'completed' ? 'bg-success' : ($o['status'] == 'pending' ? 'bg-warning' : 'bg-secondary'); ?>">
<?php echo ucfirst($o['status']); ?>
</span>
</td>
<td>
<span class="badge bg-info"><?php echo strtoupper($o['payment_method']); ?></span>
</td>
<td>
<?php if ($o['class_name']): ?>
<small>
<strong><?php echo htmlspecialchars($o['class_name']); ?></strong><br>
<?php echo ucfirst($o['level'] ?? ''); ?>
(<?php echo ucfirst($o['day_of_week'] ?? 'qualsiasi'); ?>)
</small>
<?php else: ?>
<em>Tutte le classi</em>
<?php endif; ?>
</td>
<td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm btn-outline-primary">Dettaglio</button>
<button type="button" class="btn btn-sm btn-outline-success">Prenotazioni</button>
<button type="button" class="btn btn-sm btn-outline-warning">Email</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</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>
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.5.0/js/responsive.bootstrap5.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.bootstrap5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
<script>
$(document).ready(function() {
$('#ordersTable').DataTable({
language: {
url: "//cdn.datatables.net/plug-ins/1.10.25/i18n/Italian.json"
},
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
order: [
[0, 'desc']
],
responsive: true,
dom: 'Bfrtip',
buttons: [{
extend: 'excelHtml5',
text: 'Esporta Excel',
className: 'btn btn-success btn-sm'
},
{
extend: 'csvHtml5',
text: 'Esporta CSV',
className: 'btn btn-info btn-sm'
}
],
columnDefs: [{
targets: '_all',
className: 'text-center'
},
{
targets: 12,
orderable: false
}
]
});
});
</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>