update stats e nota template

This commit is contained in:
2026-07-16 14:34:09 +02:00
parent d256370712
commit 546b4ced69
8 changed files with 670 additions and 18 deletions
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddTemplateNoteToExcelTemplates extends AbstractMigration
{
public function change(): void
{
$this->table('excel_templates')
->addColumn('template_note', 'text', [
'null' => true,
'default' => null,
'after' => 'idroutine',
'comment' => 'Nota opzionale del template, aggiunta in fondo alle note in esportazione',
])
->update();
}
}
+5 -1
View File
@@ -265,7 +265,11 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label> <label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
<textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description'] ?? ''); ?></textarea> <textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description'] ?? ''); ?></textarea>
</div> </div>
<div class="mb-3">
<label class="form-label">Template Note</label>
<textarea name="template_note" class="form-control" rows="3"><?php echo htmlspecialchars($template['template_note'] ?? ''); ?></textarea>
<small class="text-muted">If set, this note will be appended at the end of the notes during export.</small>
</div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?> *</label> <label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table'] ?? 'datadb'); ?>" readonly required> <input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table'] ?? 'datadb'); ?>" readonly required>
+9
View File
@@ -151,6 +151,7 @@ try {
// Also fetch fixed fields stored in datadb // Also fetch fixed fields stored in datadb
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
SELECT d.idclient AS clienteId, et.idschema AS schemaId, et.id AS templateId, SELECT d.idclient AS clienteId, et.idschema AS schemaId, et.id AS templateId,
et.template_note,
d.cliente_responsabile_id, d.cliente_responsabile_id,
d.moltiplicatore_prezzo_id, d.moltiplicatore_prezzo_id,
d.anagrafica_certest_object_id, d.anagrafica_certest_object_id,
@@ -280,6 +281,14 @@ try {
$addnoteText = implode(' - ', $addnoteChunks); $addnoteText = implode(' - ', $addnoteChunks);
// Nota del template: appesa in fondo alle note in esportazione
$templateNote = trim((string)($result['template_note'] ?? ''));
if ($templateNote !== '') {
$addnoteText = $addnoteText !== ''
? $addnoteText . ' - ' . $templateNote
: $templateNote;
}
// Logga i fieldValues in error_log // Logga i fieldValues in error_log
$logFieldValues = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT); $logFieldValues = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT);
error_log($logFieldValues); error_log($logFieldValues);
@@ -39,6 +39,7 @@ try {
$button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff'); $button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff');
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff'); $button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
$button_label = trim($_POST['button_label'] ?? 'Click Me'); $button_label = trim($_POST['button_label'] ?? 'Click Me');
$template_note = trim($_POST['template_note'] ?? '');
// Allowed source types // Allowed source types
if (!in_array($source_type, ['XLS', 'API', 'JSON', 'PDF'], true)) { if (!in_array($source_type, ['XLS', 'API', 'JSON', 'PDF'], true)) {
@@ -122,6 +123,7 @@ try {
button_bg_color = ?, button_bg_color = ?,
button_text_color = ?, button_text_color = ?,
button_label = ?, button_label = ?,
template_note = ?,
updated_at = NOW() updated_at = NOW()
WHERE id = ? WHERE id = ?
"); ");
@@ -144,6 +146,7 @@ try {
$button_bg_color, $button_bg_color,
$button_text_color, $button_text_color,
$button_label, $button_label,
$template_note,
$id $id
]); ]);
+70 -11
View File
@@ -47,6 +47,27 @@
.filter-bar { .filter-bar {
gap: .75rem; gap: .75rem;
} }
.stat-card-extra {
background: linear-gradient(135deg, #021bff 0%, #010983 100%);
border: none;
}
.stat-card-extra .stat-label {
color: #d7d2f5;
}
.stat-card-extra .stat-value {
color: #ffffff !important;
}
.stat-card-extra small.text-muted {
color: #c9c2ec !important;
}
.kpi-row {
margin-bottom: 1rem;
}
</style> </style>
</head> </head>
@@ -71,7 +92,10 @@
<button class="btn btn-outline-secondary" id="btnToday">Oggi</button> <button class="btn btn-outline-secondary" id="btnToday">Oggi</button>
<button class="btn btn-outline-secondary" id="btnYesterday">Ieri</button> <button class="btn btn-outline-secondary" id="btnYesterday">Ieri</button>
</div> </div>
<button class="btn btn-primary btn-sm ms-auto" id="btnRefresh"> <a href="stats_export_lims_monthly.php" class="btn btn-outline-primary btn-sm ms-auto">
<i class="bx bx-calendar-alt"></i> Vista mensile
</a>
<button class="btn btn-primary btn-sm ms-2" id="btnRefresh">
<i class="bx bx-refresh"></i> Aggiorna <i class="bx bx-refresh"></i> Aggiorna
</button> </button>
</div> </div>
@@ -79,33 +103,51 @@
</div> </div>
<!-- ===== KPI ===== --> <!-- ===== KPI ===== -->
<div class="row"> <div class="row row-cols-2 row-cols-md-3 row-cols-lg-6 g-3 kpi-row">
<div class="col-12 col-lg-3 col-md-6"> <div class="col">
<div class="card radius-10 stat-card"> <div class="card radius-10 stat-card stat-card-extra h-100">
<div class="card-body"> <div class="card-body">
<div class="stat-label">Export (status L)</div> <div class="stat-label">Export mese <span id="kpiMonthLabel"></span></div>
<div class="stat-value text-primary" id="kpiExportMonth">0</div>
<small class="text-muted">Dati dal 16/07/2026</small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card stat-card-extra h-100">
<div class="card-body">
<div class="stat-label">Export anno <span id="kpiYearLabel"></span></div>
<div class="stat-value text-dark" id="kpiExportYear">0</div>
<small class="text-muted">Dati dal 16/07/2026</small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Export giorno (status L)</div>
<div class="stat-value text-primary" id="kpiExport">0</div> <div class="stat-value text-primary" id="kpiExport">0</div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-lg-3 col-md-6"> <div class="col">
<div class="card radius-10 stat-card"> <div class="card radius-10 stat-card h-100">
<div class="card-body"> <div class="card-body">
<div class="stat-label">Campioni inseriti LIMS</div> <div class="stat-label">Campioni inseriti LIMS</div>
<div class="stat-value text-info" id="kpiLimsSamples">0</div> <div class="stat-value text-info" id="kpiLimsSamples">0</div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-lg-3 col-md-6"> <div class="col">
<div class="card radius-10 stat-card"> <div class="card radius-10 stat-card h-100">
<div class="card-body"> <div class="card-body">
<div class="stat-label">% Esportati / inseriti</div> <div class="stat-label">% Esportati / inseriti</div>
<div class="stat-value text-success" id="kpiPerc"></div> <div class="stat-value text-success" id="kpiPerc"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-lg-3 col-md-6"> <div class="col">
<div class="card radius-10 stat-card"> <div class="card radius-10 stat-card h-100">
<div class="card-body"> <div class="card-body">
<div class="stat-label">Clienti distinti</div> <div class="stat-label">Clienti distinti</div>
<div class="stat-value text-warning" id="kpiClienti">0</div> <div class="stat-value text-warning" id="kpiClienti">0</div>
@@ -388,10 +430,27 @@
// KPI // KPI
$('#kpiExport').text(res.totals.export); $('#kpiExport').text(res.totals.export);
$('#kpiExportMonth').text(res.totalsMonth ? res.totalsMonth.export : 0);
$('#kpiExportYear').text(res.totalsYear ? res.totalsYear.export : 0);
$('#kpiClienti').text(res.byClient.length); $('#kpiClienti').text(res.byClient.length);
$('#kpiLimsSamples').text(res.limsSamples ?? 0); $('#kpiLimsSamples').text(res.limsSamples ?? 0);
$('#limsSamplesInput').val(res.limsSamples ?? ''); $('#limsSamplesInput').val(res.limsSamples ?? '');
// Etichette mese/anno dei box totali
if (res.month) {
const MESI_IT = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'
];
const mParts = res.month.split('-');
const mIdx = parseInt(mParts[1], 10) - 1;
if (mIdx >= 0 && mIdx < 12) {
$('#kpiMonthLabel').text('(' + MESI_IT[mIdx] + ' ' + mParts[0] + ')');
}
}
if (res.year) {
$('#kpiYearLabel').text('(' + res.year + ')');
}
const limsTot = parseInt(res.limsSamples ?? 0, 10); const limsTot = parseInt(res.limsSamples ?? 0, 10);
if (limsTot > 0) { if (limsTot > 0) {
const perc = (res.totals.export / limsTot) * 100; const perc = (res.totals.export / limsTot) * 100;
@@ -70,6 +70,26 @@ try {
// --- Totale export della giornata --- // --- Totale export della giornata ---
$totExport = array_sum(array_column($byClient, 'tot')); $totExport = array_sum(array_column($byClient, 'tot'));
// --- Totale export del MESE della data selezionata (status = 'l') ---
$stmtM = $pdo->prepare("
SELECT COUNT(*)
FROM datadb d
WHERE d.status = 'l'
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
");
$stmtM->execute(['m' => $d->format('Y-m')]);
$totExportMonth = (int)$stmtM->fetchColumn();
// --- Totale export dell'ANNO della data selezionata (status = 'l') ---
$stmtY = $pdo->prepare("
SELECT COUNT(*)
FROM datadb d
WHERE d.status = 'l'
AND YEAR(d.export_date) = :y
");
$stmtY->execute(['y' => $d->format('Y')]);
$totExportYear = (int)$stmtY->fetchColumn();
// --- Campioni totali inseriti nel LIMS per la giornata (tabella lims_daily_samples) --- // --- Campioni totali inseriti nel LIMS per la giornata (tabella lims_daily_samples) ---
$stmtL = $pdo->prepare("SELECT total_samples FROM lims_daily_samples WHERE sample_date = :d LIMIT 1"); $stmtL = $pdo->prepare("SELECT total_samples FROM lims_daily_samples WHERE sample_date = :d LIMIT 1");
$stmtL->execute(['d' => $date]); $stmtL->execute(['d' => $date]);
@@ -79,7 +99,11 @@ try {
echo json_encode([ echo json_encode([
'success' => true, 'success' => true,
'date' => $date, 'date' => $date,
'month' => $d->format('Y-m'),
'year' => (int)$d->format('Y'),
'totals' => ['export' => (int)$totExport], 'totals' => ['export' => (int)$totExport],
'totalsMonth' => ['export' => $totExportMonth],
'totalsYear' => ['export' => $totExportYear],
'byClient' => $byClient, 'byClient' => $byClient,
'byUser' => $byUser, 'byUser' => $byUser,
'limsSamples' => $limsSamples, 'limsSamples' => $limsSamples,
@@ -0,0 +1,426 @@
<?php include('include/headscript.php'); ?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>TRF-Project - Statistiche Export LIMS Mensili</title>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<style>
.stat-card .card-body {
padding: 1rem 1.25rem;
}
.stat-value {
font-size: 1.75rem;
font-weight: 700;
line-height: 1.1;
}
.stat-label {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: .03em;
color: #6c757d;
}
.chart-wrap {
position: relative;
height: 420px;
}
#tblClienti,
#tblUtenti {
font-size: 13px;
}
#tblClienti th,
#tblClienti td,
#tblUtenti th,
#tblUtenti td {
vertical-align: middle;
}
.filter-bar {
gap: .75rem;
}
.month-label {
font-size: 1.05rem;
font-weight: 600;
}
.stat-card-extra {
background: linear-gradient(135deg, #021bff 0%, #010983 100%);
border: none;
}
.stat-card-extra .stat-label {
color: #c9cfff;
}
.stat-card-extra .stat-value {
color: #ffffff !important;
}
.stat-card-extra small.text-muted {
color: #b3bbff !important;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<!-- ===== FILTRO MESE ===== -->
<div class="card radius-10">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap filter-bar">
<h6 class="mb-0 me-3"><i class="bx bx-bar-chart-alt-2"></i> Statistiche Export LIMS Mensili</h6>
<div class="d-flex align-items-center">
<label for="filterMonth" class="me-2 mb-0 fw-semibold">Mese:</label>
<input type="month" id="filterMonth" class="form-control form-control-sm" style="width:auto;">
</div>
<div class="btn-group btn-group-sm ms-2" role="group">
<button class="btn btn-outline-secondary" id="btnCurrentMonth">Mese corrente</button>
<button class="btn btn-outline-secondary" id="btnPrevMonth">Mese precedente</button>
</div>
<span class="month-label ms-2 text-primary" id="monthLabel"></span>
<a href="stats_export_lims.php" class="btn btn-outline-primary btn-sm ms-auto">
<i class="bx bx-calendar"></i> Vista giornaliera
</a>
<button class="btn btn-primary btn-sm ms-2" id="btnRefresh">
<i class="bx bx-refresh"></i> Aggiorna
</button>
</div>
</div>
</div>
<!-- ===== KPI (una sola riga) ===== -->
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-5 g-3">
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Export mese (status L)</div>
<div class="stat-value text-primary" id="kpiExport">0</div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card stat-card-extra h-100">
<div class="card-body">
<div class="stat-label">Export anno <span id="yearLabel"></span> (status L)</div>
<div class="stat-value text-dark" id="kpiExportYear">0</div>
<small class="text-muted">Dati a partire dal 16/07/2026</small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Campioni inseriti LIMS (mese)</div>
<div class="stat-value text-info" id="kpiLimsSamples">0</div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">% Esportati / inseriti</div>
<div class="stat-value text-success" id="kpiPerc"></div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Clienti distinti (mese)</div>
<div class="stat-value text-warning" id="kpiClienti">0</div>
</div>
</div>
</div>
</div>
<!-- ===== GRAFICI ===== -->
<div class="row mt-1">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Export per cliente (idclient) <small class="text-muted"> top 15</small></h6>
</div>
<div class="card-body">
<div class="chart-wrap"><canvas id="chartClienti"></canvas></div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Export per utente (user_id) <small class="text-muted"> top 15</small></h6>
</div>
<div class="card-body">
<div class="chart-wrap"><canvas id="chartUtenti"></canvas></div>
</div>
</div>
</div>
</div>
<!-- ===== LISTE ===== -->
<div class="row">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Dettaglio per cliente</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tblClienti" class="table table-striped table-bordered table-sm w-100">
<thead>
<tr>
<th>ID Cliente</th>
<th>Cliente</th>
<th>Export</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Dettaglio per utente</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tblUtenti" class="table table-striped table-bordered table-sm w-100">
<thead>
<tr>
<th>ID Utente</th>
<th>Utente</th>
<th>Export</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
let chartClienti = null;
let chartUtenti = null;
let dtClienti = null;
let dtUtenti = null;
const MESI_IT = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'
];
function currentMonthStr() {
const d = new Date();
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
return `${y}-${m}`;
}
function monthLabelFromStr(monthStr) {
const parts = (monthStr || '').split('-');
if (parts.length !== 2) return '—';
const y = parseInt(parts[0], 10);
const m = parseInt(parts[1], 10);
if (isNaN(y) || isNaN(m) || m < 1 || m > 12) return '—';
return `${MESI_IT[m - 1]} ${y}`;
}
function initTables() {
dtClienti = $('#tblClienti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'idclient'
},
{
data: 'clientname',
defaultContent: '—'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questo mese'
}
});
dtUtenti = $('#tblUtenti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'user_id'
},
{
data: 'username',
defaultContent: '—'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questo mese'
}
});
}
function renderBarChart(existing, canvasId, labels, values, label) {
if (existing) existing.destroy();
const ctx = document.getElementById(canvasId).getContext('2d');
return new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: label,
data: values,
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
const CHART_TOP_N = 15;
function topN(arr) {
return arr.slice(0, CHART_TOP_N);
}
function loadStats() {
const month = $('#filterMonth').val() || currentMonthStr();
$('#monthLabel').text(monthLabelFromStr(month));
$.getJSON('stats_export_lims_monthly_data.php', {
month: month
}, function(res) {
if (!res || !res.success) {
Swal.fire('Errore', (res && res.message) || 'Errore nel caricamento', 'error');
return;
}
// KPI mese
$('#kpiExport').text(res.totals.export);
$('#kpiClienti').text(res.byClient.length);
$('#kpiLimsSamples').text(res.limsSamples ?? 0);
const limsTot = parseInt(res.limsSamples ?? 0, 10);
if (limsTot > 0) {
const perc = (res.totals.export / limsTot) * 100;
$('#kpiPerc').text(perc.toFixed(1) + '%');
} else {
$('#kpiPerc').text('—');
}
// Box annuale
$('#kpiExportYear').text(res.totalsYear.export);
$('#yearLabel').text(res.year);
// Grafici
const clientTop = topN(res.byClient);
const userTop = topN(res.byUser);
chartClienti = renderBarChart(chartClienti, 'chartClienti',
clientTop.map(r => r.clientname ? (r.clientname + ' (' + r.idclient + ')') : ('ID ' + r.idclient)),
clientTop.map(r => r.tot), 'Export');
chartUtenti = renderBarChart(chartUtenti, 'chartUtenti',
userTop.map(r => r.username ? (r.username + ' (' + r.user_id + ')') : ('ID ' + r.user_id)),
userTop.map(r => r.tot), 'Export');
// Tabelle
dtClienti.clear().rows.add(res.byClient).draw();
dtUtenti.clear().rows.add(res.byUser).draw();
}).fail(function(xhr) {
Swal.fire('Errore', 'Richiesta fallita: ' + xhr.status, 'error');
});
}
$(document).ready(function() {
$('#filterMonth').val(currentMonthStr());
initTables();
loadStats();
$('#btnRefresh').on('click', loadStats);
$('#filterMonth').on('change', loadStats);
$('#btnCurrentMonth').on('click', function() {
$('#filterMonth').val(currentMonthStr());
loadStats();
});
$('#btnPrevMonth').on('click', function() {
const cur = $('#filterMonth').val() || currentMonthStr();
const parts = cur.split('-');
let y = parseInt(parts[0], 10);
let m = parseInt(parts[1], 10);
m -= 1;
if (m < 1) {
m = 12;
y -= 1;
}
$('#filterMonth').val(`${y}-${String(m).padStart(2, '0')}`);
loadStats();
});
});
</script>
</body>
</html>
@@ -0,0 +1,107 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
try {
// Mese richiesto (default mese corrente). Formato atteso: Y-m
$month = $_GET['month'] ?? date('Y-m');
$dm = DateTime::createFromFormat('Y-m', $month);
if (!$dm || $dm->format('Y-m') !== $month) {
throw new Exception('Mese non valido');
}
$year = $dm->format('Y');
// --- Export per cliente (status = 'l', giornata su export_date, filtro mese) ---
$stmtC = $pdo->prepare("
SELECT d.idclient AS idclient,
COUNT(*) AS tot
FROM datadb d
WHERE d.status = 'l'
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
GROUP BY d.idclient
ORDER BY tot DESC
");
$stmtC->execute(['m' => $month]);
$byClient = $stmtC->fetchAll(PDO::FETCH_ASSOC);
// --- Risolvi Nominativo cliente dalla cache locale ---
$clientNames = [];
$cacheFile = __DIR__ . '/cache/clienti.json';
if (is_file($cacheFile)) {
$cache = json_decode(file_get_contents($cacheFile), true);
foreach (($cache['value'] ?? []) as $c) {
$clientNames[(int)($c['IdCliente'] ?? 0)] = trim($c['Nominativo'] ?? '');
}
}
// --- Export per utente (status = 'l', filtro mese) ---
$stmtU = $pdo->prepare("
SELECT d.user_id AS user_id,
TRIM(CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,''))) AS username,
COUNT(*) AS tot
FROM datadb d
LEFT JOIN auth_users u ON u.id = d.user_id
WHERE d.status = 'l'
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
GROUP BY d.user_id, username
ORDER BY tot DESC
");
$stmtU->execute(['m' => $month]);
$byUser = $stmtU->fetchAll(PDO::FETCH_ASSOC);
// Normalizza tipi
foreach ($byClient as &$r) {
$r['idclient'] = $r['idclient'] !== null ? (int)$r['idclient'] : null;
$r['tot'] = (int)$r['tot'];
$nm = $clientNames[$r['idclient']] ?? '';
$r['clientname'] = $nm !== '' ? $nm : null;
}
unset($r);
foreach ($byUser as &$r) {
$r['user_id'] = $r['user_id'] !== null ? (int)$r['user_id'] : null;
$r['tot'] = (int)$r['tot'];
if (isset($r['username'])) $r['username'] = $r['username'] !== null && $r['username'] !== '' ? $r['username'] : null;
}
unset($r);
// --- Totale export del mese ---
$totExport = array_sum(array_column($byClient, 'tot'));
// --- Totale export dell'ANNO del mese selezionato ---
$stmtY = $pdo->prepare("
SELECT COUNT(*)
FROM datadb d
WHERE d.status = 'l'
AND YEAR(d.export_date) = :y
");
$stmtY->execute(['y' => $year]);
$totExportYear = (int)$stmtY->fetchColumn();
// --- Campioni LIMS: somma dei total_samples di tutti i giorni del mese ---
$stmtL = $pdo->prepare("
SELECT COALESCE(SUM(total_samples), 0)
FROM lims_daily_samples
WHERE DATE_FORMAT(sample_date, '%Y-%m') = :m
");
$stmtL->execute(['m' => $month]);
$limsSamples = (int)$stmtL->fetchColumn();
echo json_encode([
'success' => true,
'month' => $month,
'year' => (int)$year,
'totals' => ['export' => (int)$totExport],
'totalsYear' => ['export' => $totExportYear],
'byClient' => $byClient,
'byUser' => $byUser,
'limsSamples' => $limsSamples,
]);
} catch (Exception $e) {
error_log('Stats export LIMS monthly error: ' . $e->getMessage());
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}