added fatturato
This commit is contained in:
@@ -73,6 +73,8 @@ if ($action === 'data') {
|
||||
|
||||
$monthly = [];
|
||||
$customers = [];
|
||||
$yearIn = '1=0';
|
||||
$yp = $params;
|
||||
|
||||
if ($years) {
|
||||
$ph = [];
|
||||
@@ -110,8 +112,41 @@ if ($action === 'data') {
|
||||
$customers = array_values($customers);
|
||||
}
|
||||
|
||||
// --- FATTURATO per data documento (fatture + acconti - note di credito) ---
|
||||
$invoiced = ['available' => false, 'summary' => [], 'monthly' => []];
|
||||
try {
|
||||
$iFrom = MagoSalesStats::invoiceFrom();
|
||||
$iWhere = preg_replace('/\bx\./', 'i.', $where); // stessi filtri cliente/agente/area
|
||||
$iYtd = $ytd ? " AND DATE_FORMAT(i.document_date, '%m-%d') <= DATE_FORMAT(CURDATE(), '%m-%d')" : '';
|
||||
|
||||
$invoiced['summary'] = MagoSalesStats::fetchAllQ($pdo, "
|
||||
SELECT YEAR(i.document_date) AS y,
|
||||
COALESCE(SUM(i.revenue), 0) AS value,
|
||||
SUM(i.is_credit_note = 0) AS docs,
|
||||
SUM(i.is_credit_note = 1) AS credit_notes,
|
||||
COALESCE(SUM(CASE WHEN i.is_credit_note = 1 THEN -i.revenue END), 0) AS credit_value,
|
||||
SUM(i.no_amount) AS missing_amounts
|
||||
FROM $iFrom
|
||||
WHERE $iWhere AND i.document_date IS NOT NULL $iYtd
|
||||
GROUP BY y ORDER BY y
|
||||
", $params);
|
||||
|
||||
if ($years) {
|
||||
$invoiced['monthly'] = MagoSalesStats::fetchAllQ($pdo, "
|
||||
SELECT YEAR(i.document_date) AS y, MONTH(i.document_date) AS m, SUM(i.revenue) AS v
|
||||
FROM $iFrom
|
||||
WHERE $iWhere AND " . str_replace('x.order_date', 'i.document_date', $yearIn) . " $iYtd
|
||||
GROUP BY y, m ORDER BY y, m
|
||||
", $yp);
|
||||
}
|
||||
$invoiced['available'] = (bool)$pdo->query("SELECT 1 FROM mago_sale_doc LIMIT 1")->fetchColumn();
|
||||
} catch (Throwable $e) {
|
||||
$invoiced['error'] = 'Fatturato non disponibile: eseguire la migrazione e la sincronizzazione';
|
||||
}
|
||||
|
||||
MagoSalesStats::jsonOut([
|
||||
'success' => true,
|
||||
'invoiced' => $invoiced,
|
||||
'metric' => $metric,
|
||||
'ytd' => $ytd,
|
||||
'today' => date('Y-m-d'),
|
||||
@@ -444,6 +479,26 @@ if ($action === 'data') {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.year-card .inv {
|
||||
margin-top: 8px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 6px 10px;
|
||||
font-size: .85rem;
|
||||
color: var(--c-muted);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.year-card .inv b {
|
||||
color: var(--c-ink);
|
||||
font-size: 1rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.year-card .deltas {
|
||||
margin-top: 10px;
|
||||
font-size: .88rem;
|
||||
@@ -659,6 +714,25 @@ if ($action === 'data') {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FATTURATO (per data fattura) -->
|
||||
<div class="row g-3 mb-3" id="invRow">
|
||||
<div class="col-xl-6">
|
||||
<div class="chart-card">
|
||||
<div class="chart-title">Fatturato per mese</div>
|
||||
<div class="chart-hint">Imponibile fatture + acconti − note di credito, per data fattura</div>
|
||||
<div id="chInvMonthly" style="min-height:340px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-6">
|
||||
<div class="chart-card">
|
||||
<div class="chart-title">Fatturato progressivo dell'anno</div>
|
||||
<div class="chart-hint">Somma da gennaio del fatturato, anno su anno</div>
|
||||
<div id="chInvCumul" style="min-height:340px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="warn-strip mb-3" id="invMissing" style="display:none;"></div>
|
||||
|
||||
<!-- CLIENTI -->
|
||||
<div class="chart-card">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2">
|
||||
@@ -871,6 +945,7 @@ if ($action === 'data') {
|
||||
renderCards(d);
|
||||
renderMonthly(d);
|
||||
renderCumul(d);
|
||||
renderInvoiced(d);
|
||||
renderCustomers(d);
|
||||
}).catch(e => {
|
||||
$('#content').css('opacity', 1);
|
||||
@@ -899,6 +974,19 @@ if ($action === 'data') {
|
||||
$('#missingAmounts').toggle(missing > 0).html(missing > 0 ?
|
||||
`<i class="fa-solid fa-circle-exclamation me-1"></i> ${fmtNum(missing)} ordini negli anni scelti non hanno ancora gli importi: lancia una sincronizzazione.` : '');
|
||||
|
||||
// riga fatturato nella carta anno (se i documenti sono sincronizzati)
|
||||
const inv = {};
|
||||
((d.invoiced && d.invoiced.summary) || []).forEach(r => inv[+r.y] = r);
|
||||
const invLine = y => {
|
||||
if (!d.invoiced || !d.invoiced.available) return '';
|
||||
const r = inv[y],
|
||||
p = inv[y - 1];
|
||||
const v = r ? +r.value : 0;
|
||||
const dl = p ? deltaSpan(pct(v, +p.value), `vs ${y - 1}`) : '';
|
||||
const cn = r && +r.credit_notes ? ` title="${fmtNum(r.docs)} fatture, ${fmtNum(r.credit_notes)} note di credito (−${esc(fmtEur(r.credit_value, 2))})"` : (r ? ` title="${fmtNum(r.docs)} fatture"` : '');
|
||||
return `<div class="inv"${cn}><span>Fatturato</span><b>${fmtEurShort(v)}</b>${dl}</div>`;
|
||||
};
|
||||
|
||||
$('#yearCards').html(d.years.slice().reverse().map(y => {
|
||||
const r = byYear[y] || {
|
||||
orders: 0,
|
||||
@@ -924,16 +1012,17 @@ if ($action === 'data') {
|
||||
<div><span>Medio</span><b>${fmtEurShort(avg)}</b></div>
|
||||
<div><span>Clienti</span><b>${fmtNum(r.customers)}</b></div>
|
||||
</div>
|
||||
${invLine(y)}
|
||||
<div class="deltas">${deltas}</div>
|
||||
</div>`;
|
||||
}).join(''));
|
||||
}
|
||||
|
||||
/* ---------------- GRAFICI ---------------- */
|
||||
function seriesByYear(d) {
|
||||
function seriesByYear(d, rows) {
|
||||
const map = {};
|
||||
d.years.forEach(y => map[y] = Array(12).fill(0));
|
||||
d.monthly.forEach(r => {
|
||||
(rows || d.monthly).forEach(r => {
|
||||
if (map[+r.y]) map[+r.y][+r.m - 1] = +r.v;
|
||||
});
|
||||
// mesi futuri: vuoti (non zero) per l'anno in corso, e per tutti se "a pari data"
|
||||
@@ -964,9 +1053,9 @@ if ($action === 'data') {
|
||||
}
|
||||
};
|
||||
|
||||
function renderMonthly(d) {
|
||||
const map = seriesByYear(d);
|
||||
upsert('monthly', 'chMonthly', {
|
||||
function renderMonthly(d, key, el, rows, fmtV, fmtA) {
|
||||
const map = seriesByYear(d, rows);
|
||||
upsert(key || 'monthly', el || 'chMonthly', {
|
||||
chart: {
|
||||
...base,
|
||||
type: 'line',
|
||||
@@ -989,14 +1078,14 @@ if ($action === 'data') {
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: fmtAxis
|
||||
formatter: fmtA || fmtAxis
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
y: {
|
||||
formatter: fmtMetric
|
||||
formatter: fmtV || fmtMetric
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
@@ -1009,14 +1098,14 @@ if ($action === 'data') {
|
||||
});
|
||||
}
|
||||
|
||||
function renderCumul(d) {
|
||||
const map = seriesByYear(d);
|
||||
function renderCumul(d, key, el, rows, fmtV, fmtA) {
|
||||
const map = seriesByYear(d, rows);
|
||||
const cum = {};
|
||||
d.years.forEach(y => {
|
||||
let s = 0;
|
||||
cum[y] = map[y].map(v => v === null ? null : (s += v));
|
||||
});
|
||||
upsert('cumul', 'chCumul', {
|
||||
upsert(key || 'cumul', el || 'chCumul', {
|
||||
chart: {
|
||||
...base,
|
||||
type: 'area',
|
||||
@@ -1046,14 +1135,14 @@ if ($action === 'data') {
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: fmtAxis
|
||||
formatter: fmtA || fmtAxis
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
y: {
|
||||
formatter: fmtMetric
|
||||
formatter: fmtV || fmtMetric
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
@@ -1066,6 +1155,20 @@ if ($action === 'data') {
|
||||
});
|
||||
}
|
||||
|
||||
// fatturato: sempre in € (indipendente dal selettore N° ordini / Valore)
|
||||
function renderInvoiced(d) {
|
||||
const inv = d.invoiced || {};
|
||||
$('#invRow').toggle(!!inv.available);
|
||||
const miss = (inv.summary || []).filter(r => d.years.includes(+r.y)).reduce((a, r) => a + (+r.missing_amounts || 0), 0);
|
||||
$('#invMissing').toggle(!inv.available || miss > 0).html(!inv.available ?
|
||||
'<i class="fa-solid fa-circle-info me-1"></i> Fatturato non ancora disponibile: eseguire la migrazione <b>mago_sale_doc</b> e una sincronizzazione.' :
|
||||
`<i class="fa-solid fa-circle-exclamation me-1"></i> ${fmtNum(miss)} fatture negli anni scelti non hanno ancora l'importo: lancia una sincronizzazione.`);
|
||||
if (!inv.available) return;
|
||||
const fv = v => v === null || v === undefined ? '—' : fmtEur(v);
|
||||
renderMonthly(d, 'invMonthly', 'chInvMonthly', inv.monthly || [], fv, fmtEurShort);
|
||||
renderCumul(d, 'invCumul', 'chInvCumul', inv.monthly || [], fv, fmtEurShort);
|
||||
}
|
||||
|
||||
/* ---------------- CLIENTI ---------------- */
|
||||
function classify(c, last, prev) {
|
||||
const a = c.v[last] || 0,
|
||||
|
||||
Reference in New Issue
Block a user