305 lines
18 KiB
PHP
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>
|