Files
yogibook_aury_new/public/package_rules.php
T
2026-10-14 10:21:55 +02:00

305 lines
18 KiB
PHP

<?php
require_once('include/headscript.php');
require_once('include/csrf.php');
SchoolContext::requireSchoolAdmin();
/**
* SCUOLA - Regole dei pacchetti a numero di lezioni (tabella expiryparameter).
* Per ogni numero di lezioni: dopo quante lezioni in calendario scade il pacchetto
* e quanti recuperi sono consentiti. Usate da calcolo scadenza / propagazione,
* ordini manuali e WooCommerce, e dai prodotti con "Recuperi" lasciato vuoto.
*/
$pdo = DBHandlerSelect::getInstance()->getConnection();
function e($value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function back(string $msg, string $type = 'msg'): void
{
header('Location: package_rules.php?' . $type . '=' . urlencode($msg));
exit();
}
/** Valori consigliati per una scuola che parte da zero */
const PR_DEFAULTS = [[4, 5, 1], [6, 8, 2], [8, 10, 2], [10, 12, 2], [12, 15, 3]];
/* ---------------------------------------------------------------- AZIONI */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
yb_csrf_check();
$action = (string) ($_POST['action'] ?? '');
$id = (int) ($_POST['id'] ?? 0);
$int = function (string $k, int $min, int $max): ?int {
$v = trim((string) ($_POST[$k] ?? ''));
return (ctype_digit($v) && (int) $v >= $min && (int) $v <= $max) ? (int) $v : null;
};
try {
if ($action === 'save') {
$qty = $int('quantityclass', 1, 200);
$exp = $int('expirylessons', 1, 400);
$res = $int('reschedulenumber', 0, 99);
if ($qty === null || $exp === null || $res === null) {
back('Controlla i valori: numeri interi, lezioni almeno 1.', 'err');
}
if ($exp < $qty) {
back('La scadenza (' . $exp . ' lezioni) non può essere minore delle lezioni del pacchetto (' . $qty . ').', 'err');
}
$st = $pdo->prepare("SELECT 1 FROM expiryparameter WHERE idschool = ? AND quantityclass = ? AND idexpiryparameter <> ? LIMIT 1");
$st->execute([(int) $idschool, $qty, $id]);
if ($st->fetchColumn()) {
back('Esiste già una regola per i pacchetti da ' . $qty . ' lezioni.', 'err');
}
if ($id > 0) {
$st = $pdo->prepare("UPDATE expiryparameter SET quantityclass = ?, expirylessons = ?, reschedulenumber = ? WHERE idexpiryparameter = ? AND idschool = ?");
$st->execute([$qty, $exp, $res, $id, (int) $idschool]);
back('Regola per ' . $qty . ' lezioni aggiornata.');
}
$pdo->prepare("INSERT INTO expiryparameter (quantityclass, expirylessons, reschedulenumber, idschool) VALUES (?, ?, ?, ?)")
->execute([$qty, $exp, $res, (int) $idschool]);
back('Regola per ' . $qty . ' lezioni aggiunta.');
}
if ($action === 'delete' && $id > 0) {
$pdo->prepare("DELETE FROM expiryparameter WHERE idexpiryparameter = ? AND idschool = ?")->execute([$id, (int) $idschool]);
back('Regola eliminata.');
}
if ($action === 'defaults') {
$st = $pdo->prepare("SELECT COUNT(*) FROM expiryparameter WHERE idschool = ?");
$st->execute([(int) $idschool]);
if ((int) $st->fetchColumn() > 0) {
back('Ci sono già delle regole: i valori consigliati si caricano solo su una tabella vuota.', 'err');
}
$ins = $pdo->prepare("INSERT INTO expiryparameter (quantityclass, expirylessons, reschedulenumber, idschool) VALUES (?, ?, ?, ?)");
foreach (PR_DEFAULTS as [$q, $x, $r]) {
$ins->execute([$q, $x, $r, (int) $idschool]);
}
back('Valori consigliati caricati: modificali come preferisci.');
}
} catch (PDOException $ex) {
error_log('[package_rules] ' . $ex->getMessage());
back('Errore database, operazione non eseguita.', 'err');
}
back('Azione non valida.', 'err');
}
/* ------------------------------------------------------------------ DATI */
$st = $pdo->prepare("SELECT * FROM expiryparameter WHERE idschool = ? ORDER BY quantityclass, idexpiryparameter");
$st->execute([(int) $idschool]);
$rules = $st->fetchAll(PDO::FETCH_ASSOC);
// Quanti ordini usano ogni numero di lezioni (per sapere cosa si tocca)
$st = $pdo->prepare("SELECT quantityclass, COUNT(*) AS n FROM orderbook WHERE idschool = ? AND quantityclass IS NOT NULL GROUP BY quantityclass");
$st->execute([(int) $idschool]);
$usage = [];
foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $u) {
$usage[(int) $u['quantityclass']] = (int) $u['n'];
}
$missing = array_diff(array_keys($usage), array_map(fn($r) => (int) $r['quantityclass'], $rules));
sort($missing);
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8" />
<title>YogiBook - Regole pacchetti</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="assets/images/favicon.ico">
<link href="assets/css/bootstrap.min.css" id="bootstrap-style" rel="stylesheet" type="text/css" />
<link href="assets/css/icons.min.css" rel="stylesheet" type="text/css" />
<link href="assets/css/app.min.css" id="app-style" rel="stylesheet" type="text/css" />
<link href="assets/css/yb-billing.css" rel="stylesheet" type="text/css" />
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
.pr-row input {
max-width: 110px;
padding: 7px 10px;
}
.pr-row .warn-cell {
color: #b03a2e;
font-weight: 700;
font-size: 12px;
}
.pr-new {
background: #F4FAFA;
}
.pr-example {
font-size: 13px;
color: #4b5563;
}
</style>
</head>
<body>
<div id="layout-wrapper">
<header id="page-topbar" class="isvertical-topbar">
<div class="navbar-header">
<div class="d-flex">
<?php include('include/logoarea.php'); ?>
<button type="button" class="btn btn-sm px-3 font-size-24 header-item waves-effect vertical-menu-btn"><i class="bx bx-menu align-middle"></i></button>
<div class="page-title-box align-self-center d-none d-md-block">
<h4 class="page-title mb-0">Regole pacchetti</h4>
</div>
</div>
<div class="d-flex">
<?php include('include/schoolswitcher.php'); ?><?php include('include/languageselection.php'); ?>
<?php include('include/profiletopbar.php'); ?>
</div>
</div>
</header>
<?php include('include/sidebar.php'); ?>
<header class="ishorizontal-topbar">
<div class="navbar-header">
<div class="d-flex"></div>
</div>
<div class="topnav">
<div class="container-fluid">
<nav class="navbar navbar-light navbar-expand-lg topnav-menu"></nav>
</div>
</div>
</header>
<div class="main-content">
<div class="page-content">
<div class="container-fluid">
<div class="row">
<div class="col-xl-9">
<?php if (!empty($_GET['msg'])) : ?><div class="alert alert-success"><?php echo e($_GET['msg']); ?></div><?php endif; ?>
<?php if (!empty($_GET['err'])) : ?><div class="alert alert-danger"><?php echo e($_GET['err']); ?></div><?php endif; ?>
<div class="card admin-card">
<div class="card-body">
<div class="section-title"><span class="disc"></span>Scadenza e recuperi dei pacchetti</div>
<p class="section-lead">
Per ogni pacchetto a numero di lezioni: dopo quante lezioni in calendario della sua classe scade, e quanti recuperi sono consentiti.
Valgono per la propagazione degli ordini, per gli ordini inseriti a mano e per i prodotti del negozio con "Recuperi" lasciato vuoto.
</p>
<div class="info-box info">
<i class="bx bx-bulb"></i>
<div class="pr-example">
<strong>Esempio:</strong> pacchetto da <strong>8</strong> lezioni, scade dopo <strong>10</strong> lezioni, <strong>2</strong> recuperi.
Se la classe è il martedì, il pacchetto vale per le prossime 10 lezioni del martedì (2 settimane di margine per le assenze) e l'allieva può spostare fino a 2 lezioni.
I giorni di chiusura non contano.
</div>
</div>
<?php if ($missing) : ?>
<div class="info-box warn">
<i class="bx bx-error"></i>
<div>Ci sono ordini con un numero di lezioni senza regola: <strong><?php echo e(implode(', ', $missing)); ?></strong>. Per questi la scadenza non si calcola da sola: aggiungi una regola o inseriscila a mano alla propagazione.</div>
</div>
<?php endif; ?>
<div class="table-responsive">
<table class="table yb-table align-middle">
<thead>
<tr>
<th>Lezioni del pacchetto</th>
<th>Scade dopo (lezioni)</th>
<th>Recuperi consentiti</th>
<th>Margine</th>
<th>Ordini</th>
<th class="text-end">Azioni</th>
</tr>
</thead>
<tbody>
<?php if (!$rules) : ?>
<tr>
<td colspan="6">
<div class="empty-state">
<i class="bx bx-list-ol"></i>
Nessuna regola ancora.
<form method="post" action="package_rules.php" class="mt-3">
<?php echo yb_csrf_field(); ?>
<input type="hidden" name="action" value="defaults">
<button type="submit" class="btn btn-yb"><i class="bx bx-magic-wand"></i> Carica i valori consigliati</button>
</form>
<div class="hint">4 → 5 · 6 → 8 · 8 → 10 · 10 → 12 · 12 → 15, poi li modifichi.</div>
</div>
</td>
</tr>
<?php endif; ?>
<?php foreach ($rules as $r) :
$rid = (int) $r['idexpiryparameter'];
$q = (int) $r['quantityclass'];
$x = (int) $r['expirylessons'];
$bad = $x < $q; ?>
<tr class="pr-row">
<td>
<form method="post" action="package_rules.php" id="f-<?php echo $rid; ?>">
<?php echo yb_csrf_field(); ?>
<input type="hidden" name="action" value="save">
<input type="hidden" name="id" value="<?php echo $rid; ?>">
</form>
<input form="f-<?php echo $rid; ?>" type="number" min="1" max="200" name="quantityclass" class="form-control" value="<?php echo $q; ?>" aria-label="Lezioni del pacchetto">
</td>
<td><input form="f-<?php echo $rid; ?>" type="number" min="1" max="400" name="expirylessons" class="form-control" value="<?php echo $x; ?>" aria-label="Scade dopo"></td>
<td><input form="f-<?php echo $rid; ?>" type="number" min="0" max="99" name="reschedulenumber" class="form-control" value="<?php echo (int) $r['reschedulenumber']; ?>" aria-label="Recuperi consentiti"></td>
<td>
<?php if ($bad) : ?>
<span class="warn-cell"><i class="bx bx-error"></i> scade prima di finire le lezioni</span>
<?php else : ?>
<?php echo $x - $q; ?> <?php echo $x - $q === 1 ? 'lezione' : 'lezioni'; ?>
<?php endif; ?>
</td>
<td><?php echo $usage[$q] ?? 0; ?></td>
<td class="text-end">
<button type="submit" form="f-<?php echo $rid; ?>" class="btn-act ok"><i class="bx bx-save"></i> Salva</button>
<form method="post" action="package_rules.php" class="d-inline confirm-form" data-msg="Eliminare la regola per <?php echo $q; ?> lezioni?" data-text="Gli ordini già propagati non cambiano. I nuovi ordini da <?php echo $q; ?> lezioni non avranno più scadenza automatica." data-ok="Elimina" data-color="#e74c3c">
<?php echo yb_csrf_field(); ?>
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?php echo $rid; ?>">
<button type="submit" class="btn-act danger" aria-label="Elimina regola"><i class="bx bx-trash"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
<!-- Nuova regola -->
<tr class="pr-row pr-new">
<td>
<form method="post" action="package_rules.php" id="f-new">
<?php echo yb_csrf_field(); ?>
<input type="hidden" name="action" value="save">
<input type="hidden" name="id" value="0">
</form>
<input form="f-new" type="number" min="1" max="200" name="quantityclass" class="form-control" placeholder="es. 16" required aria-label="Lezioni del nuovo pacchetto">
</td>
<td><input form="f-new" type="number" min="1" max="400" name="expirylessons" class="form-control" placeholder="es. 20" required aria-label="Scade dopo"></td>
<td><input form="f-new" type="number" min="0" max="99" name="reschedulenumber" class="form-control" placeholder="es. 3" required aria-label="Recuperi consentiti"></td>
<td colspan="2" class="hint">Nuova regola</td>
<td class="text-end"><button type="submit" form="f-new" class="btn-act ok"><i class="bx bx-plus"></i> Aggiungi</button></td>
</tr>
</tbody>
</table>
</div>
<div class="hint">Le modifiche valgono per i prossimi calcoli: gli ordini già propagati mantengono scadenza e recuperi che hanno (li cambi da Ordini).</div>
</div>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
</div>
</div>
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="assets/libs/metismenujs/metismenujs.min.js"></script>
<script src="assets/libs/simplebar/simplebar.min.js"></script>
<script src="assets/libs/eva-icons/eva.min.js"></script>
<script src="assets/js/app.js"></script>
<script src="assets/js/yb-confirm.js"></script>
</body>
</html>