sommario update
This commit is contained in:
+68
-25
@@ -26,6 +26,7 @@ $SOGLIE = [
|
||||
'classi_deboli' => ['verde' => 0, 'giallo' => 2], // n. classi sotto il 40% di riempimento
|
||||
'trend' => ['verde' => -5, 'giallo' => -20], // variazione % rispetto al periodo prima
|
||||
'soglia_debole' => 40, // sotto questo riempimento una classe è "debole"
|
||||
'giorni_rinnovo' => 30, // giorni concessi dopo la scadenza per rinnovare
|
||||
];
|
||||
|
||||
if (!function_exists('e')) {
|
||||
@@ -98,7 +99,7 @@ $periodi = [
|
||||
'180' => 'Ultimi 6 mesi',
|
||||
'365' => 'Ultimi 12 mesi',
|
||||
];
|
||||
$giorni = isset($periodi[$_GET['p'] ?? '']) ? (int) $_GET['p'] : 30;
|
||||
$giorni = isset($periodi[$_GET['p'] ?? '']) ? (int) $_GET['p'] : 90;
|
||||
|
||||
$oggi = new DateTime('today');
|
||||
$fine = (clone $oggi)->modify('+1 day'); // escluso
|
||||
@@ -112,7 +113,7 @@ $now = date('Y-m-d H:i:s');
|
||||
* Tutte le metriche di un periodo [$d1, $d2).
|
||||
* Le lezioni considerano solo quelle già svolte (fino ad adesso).
|
||||
*/
|
||||
function metriche(PDO $pdo, string $d1, string $d2, string $now, int $sogliaDebole): array
|
||||
function metriche(PDO $pdo, string $d1, string $d2, string $now, int $sogliaDebole, int $grazia = 30): array
|
||||
{
|
||||
$m = [];
|
||||
$fineLezioni = min($d2, $now);
|
||||
@@ -194,14 +195,22 @@ function metriche(PDO $pdo, string $d1, string $d2, string $now, int $sogliaDebo
|
||||
$m['last_minute_pct'] = null;
|
||||
}
|
||||
|
||||
// --- Riacquisto: chi ha un pacchetto scaduto nel periodo, ha comprato di nuovo?
|
||||
// --- Rinnovo: chi ha finito un pacchetto ne ha comprato un altro?
|
||||
// Contano i pacchetti scaduti da almeno $grazia giorni (prima non è ancora "mancato rinnovo"),
|
||||
// quindi la finestra è spostata indietro di $grazia giorni. Così un pacchetto da 12 lezioni
|
||||
// (circa 3 mesi) viene valutato solo quando è davvero finito, qualunque sia il periodo scelto.
|
||||
// Rinnovato = un nuovo ordine dopo quello scaduto, entro $grazia giorni dalla scadenza.
|
||||
$g1 = date('Y-m-d', strtotime(substr($d1, 0, 10) . " -{$grazia} days"));
|
||||
$g2 = date('Y-m-d', strtotime(substr($d2, 0, 10) . " -{$grazia} days"));
|
||||
$scad = q($pdo, "SELECT o.iduser,
|
||||
MAX(EXISTS(SELECT 1 FROM orderbook o2
|
||||
WHERE o2.iduser = o.iduser AND o2.idorderbook <> o.idorderbook
|
||||
AND o2.order_date_created > o.order_date_created)) AS riacquistato
|
||||
AND o2.order_date_created > o.order_date_created
|
||||
AND o2.order_date_created <= DATE_ADD(o.expireon, INTERVAL ? DAY))) AS riacquistato
|
||||
FROM orderbook o
|
||||
WHERE o.iduser IS NOT NULL AND o.expireon >= ? AND o.expireon < ? AND o.expireon < CURDATE()
|
||||
GROUP BY o.iduser", [substr($d1, 0, 10), substr($d2, 0, 10)]);
|
||||
WHERE o.iduser IS NOT NULL AND o.expireon >= ? AND o.expireon < ?
|
||||
AND o.expireon <= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY o.iduser", [$grazia, $g1, $g2, $grazia]);
|
||||
$m['scaduti'] = count($scad);
|
||||
$m['riacquisto'] = pctv(count(array_filter($scad, fn($r) => (int) $r['riacquistato'] === 1)), count($scad));
|
||||
|
||||
@@ -226,8 +235,26 @@ $d1 = $inizio->format('Y-m-d 00:00:00');
|
||||
$d2 = $fine->format('Y-m-d 00:00:00');
|
||||
$d1p = $inizioP->format('Y-m-d 00:00:00');
|
||||
|
||||
$A = metriche($pdo, $d1, $d2, $now, $SOGLIE['soglia_debole']);
|
||||
$P = metriche($pdo, $d1p, $d1, $now, $SOGLIE['soglia_debole']);
|
||||
$A = metriche($pdo, $d1, $d2, $now, $SOGLIE['soglia_debole'], $SOGLIE['giorni_rinnovo']);
|
||||
$P = metriche($pdo, $d1p, $d1, $now, $SOGLIE['soglia_debole'], $SOGLIE['giorni_rinnovo']);
|
||||
|
||||
// Vendite e presenze sono stagionali (settembre pieno, estate vuota): se ci sono dati,
|
||||
// le confrontiamo con lo STESSO periodo dell'anno scorso invece che con quello appena prima.
|
||||
$d1y = date('Y-m-d H:i:s', strtotime($d1 . ' -1 year'));
|
||||
$d2y = date('Y-m-d H:i:s', strtotime($d2 . ' -1 year'));
|
||||
$primoOrdine = q1($pdo, "SELECT MIN(order_date_created) FROM orderbook");
|
||||
$haAnnoPrima = $primoOrdine && $primoOrdine <= substr($d1y, 0, 10);
|
||||
$Y = $haAnnoPrima ? metriche($pdo, $d1y, $d2y, $d2y, $SOGLIE['soglia_debole'], $SOGLIE['giorni_rinnovo']) : $P;
|
||||
$etichettaY = $haAnnoPrima ? 'stesso periodo ' . date('Y', strtotime($d1y)) : 'periodo prima';
|
||||
|
||||
/** Abbonati con un pacchetto valido in una certa data (non dipende dalla durata dei pacchetti). */
|
||||
function abbonati(PDO $pdo, string $data): int
|
||||
{
|
||||
return (int) q1($pdo, "SELECT COUNT(DISTINCT iduser) FROM orderbook
|
||||
WHERE iduser IS NOT NULL AND order_date_created <= ? AND expireon >= ?", [$data, $data]);
|
||||
}
|
||||
$abbOggi = abbonati($pdo, date('Y-m-d'));
|
||||
$abbPrima = $haAnnoPrima ? abbonati($pdo, date('Y-m-d', strtotime('-1 year'))) : abbonati($pdo, substr($d1, 0, 10));
|
||||
|
||||
// --- Certificati (situazione di oggi)
|
||||
$cert = q($pdo, "SELECT COUNT(*) AS attivi,
|
||||
@@ -257,17 +284,18 @@ $add = function (string $titolo, string $icona, string $valore, string $stato, ?
|
||||
};
|
||||
|
||||
/** Testo del confronto con il periodo precedente. */
|
||||
$conf = function ($cur, $prev, string $unita = '', bool $punti = false): ?string {
|
||||
$conf = function ($cur, $prev, string $unita = '', bool $punti = false, string $rif = 'prima'): ?string {
|
||||
if ($prev === null || $cur === null) return null;
|
||||
$tail = ' (' . $rif . ': ' . n0($prev) . $unita . ')';
|
||||
if ($punti) {
|
||||
$d = round((float) $cur - (float) $prev, 1);
|
||||
if (abs($d) < 0.5) return '= stabile (prima ' . n0($prev) . $unita . ')';
|
||||
return ($d > 0 ? '▲ +' : '▼ ') . number_format($d, 0, ',', '.') . ' punti (prima ' . n0($prev) . $unita . ')';
|
||||
if (abs($d) < 0.5) return '= stabile' . $tail;
|
||||
return ($d > 0 ? '▲ +' : '▼ ') . number_format($d, 0, ',', '.') . ' punti' . $tail;
|
||||
}
|
||||
$d = delta($cur, $prev);
|
||||
if ($d === null) return 'prima: ' . n0($prev);
|
||||
if (abs($d) < 1) return '= stabile (prima ' . n0($prev) . ')';
|
||||
return ($d > 0 ? '▲ +' : '▼ ') . number_format($d, 0, ',', '.') . '% (prima ' . n0($prev) . ')';
|
||||
if ($d === null) return $rif . ': ' . n0($prev);
|
||||
if (abs($d) < 1) return '= stabile' . $tail;
|
||||
return ($d > 0 ? '▲ +' : '▼ ') . number_format($d, 0, ',', '.') . '%' . $tail;
|
||||
};
|
||||
|
||||
$trendStato = function ($cur, $prev) use ($SOGLIE): string {
|
||||
@@ -290,15 +318,30 @@ $add(
|
||||
n0($A['lezioni_svolte']) . ' lezioni svolte · ' . n0($A['piene_pct'] ?? 0) . '% al completo'
|
||||
);
|
||||
|
||||
// 2. Lezioni vendute
|
||||
// 2. Abbonati attivi oggi (indipendente dalla durata dei pacchetti)
|
||||
$add(
|
||||
'Abbonati attivi',
|
||||
'fa-id-card',
|
||||
n0($abbOggi),
|
||||
$trendStato($abbOggi, $abbPrima),
|
||||
$conf($abbOggi, $abbPrima, '', false, $haAnnoPrima ? 'un anno fa' : 'inizio periodo'),
|
||||
[
|
||||
'Il numero di persone con un pacchetto valido tiene o cresce.',
|
||||
'Ci sono meno abbonati attivi: punta sui rinnovi di chi è in scadenza.',
|
||||
'Gli abbonati attivi sono in forte calo: è l\'indicatore più importante da recuperare.',
|
||||
],
|
||||
'con un pacchetto valido oggi'
|
||||
);
|
||||
|
||||
// 3. Lezioni vendute
|
||||
$add(
|
||||
'Lezioni vendute',
|
||||
'fa-ticket-alt',
|
||||
n0($A['vendute']),
|
||||
$trendStato($A['vendute'], $P['vendute']),
|
||||
$conf($A['vendute'], $P['vendute']),
|
||||
$trendStato($A['vendute'], $Y['vendute']),
|
||||
$conf($A['vendute'], $Y['vendute'], '', false, $etichettaY),
|
||||
[
|
||||
'Le vendite tengono o crescono rispetto al periodo prima.',
|
||||
'Le vendite tengono o crescono rispetto al ' . $etichettaY . '.',
|
||||
'Vendite in calo: una comunicazione o un\'offerta a chi ha il pacchetto in scadenza può aiutare.',
|
||||
'Vendite in forte calo: serve un\'azione commerciale (promozione, evento, newsletter).',
|
||||
],
|
||||
@@ -310,8 +353,8 @@ $add(
|
||||
'Nuovi clienti',
|
||||
'fa-user-plus',
|
||||
n0($A['nuovi']),
|
||||
$trendStato($A['nuovi'], $P['nuovi']),
|
||||
$conf($A['nuovi'], $P['nuovi']),
|
||||
$trendStato($A['nuovi'], $Y['nuovi']),
|
||||
$conf($A['nuovi'], $Y['nuovi'], '', false, $etichettaY),
|
||||
[
|
||||
'Arrivano persone nuove: la scuola si fa conoscere.',
|
||||
'Meno nuovi ingressi del solito: spingi lezioni di prova e passaparola.',
|
||||
@@ -325,11 +368,11 @@ $add(
|
||||
'Persone che praticano',
|
||||
'fa-users',
|
||||
n0($A['persone']),
|
||||
$trendStato($A['persone'], $P['persone']),
|
||||
$conf($A['persone'], $P['persone']),
|
||||
$trendStato($A['persone'], $Y['persone']),
|
||||
$conf($A['persone'], $Y['persone'], '', false, $etichettaY),
|
||||
[
|
||||
'La comunità che pratica è stabile o in crescita.',
|
||||
'Meno persone in sala del periodo prima: contatta chi non viene da un po\'.',
|
||||
'Meno persone in sala rispetto al ' . $etichettaY . ': contatta chi non viene da un po\'.',
|
||||
'Calo forte di persone attive: verifica abbandoni e pacchetti non rinnovati.',
|
||||
],
|
||||
'almeno una lezione frequentata'
|
||||
@@ -347,7 +390,7 @@ $add(
|
||||
'Una parte non rinnova: un promemoria prima della scadenza può fare la differenza.',
|
||||
'Molti non rinnovano: chiedi un riscontro a chi non torna e prepara un\'offerta di rinnovo.',
|
||||
],
|
||||
n0($A['scaduti']) . ' clienti con pacchetto scaduto nel periodo'
|
||||
'entro ' . $SOGLIE['giorni_rinnovo'] . ' giorni dalla scadenza · ' . n0($A['scaduti']) . ' pacchetti finiti, valutati solo dopo ' . $SOGLIE['giorni_rinnovo'] . ' giorni'
|
||||
);
|
||||
|
||||
// 6. Lezioni perse
|
||||
@@ -971,7 +1014,7 @@ $prevTxt = $inizioP->format('d/m/Y') . ' – ' . (clone $inizio)->modify('-1
|
||||
</div>
|
||||
<div class="sm-period-txt">
|
||||
<i class="far fa-calendar me-1"></i> <strong><?php echo e($periodoTxt); ?></strong>
|
||||
· confronto con <?php echo e($prevTxt); ?>
|
||||
· confronto con <?php echo e($prevTxt); ?><?php if ($haAnnoPrima) : ?> (vendite e presenze: <?php echo e($etichettaY); ?>)<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user