NODE JS introduction: socket polling
This commit is contained in:
@@ -234,25 +234,50 @@ class FinancialReports
|
||||
|
||||
// ── Monthly Trend ─────────────────────────────────────────────────────────
|
||||
// Revenue vs expense per period for the last N months (defaults to 6).
|
||||
// Returns rows: [{period, revenue, expense}]
|
||||
// Reads from etl_gl_summary; falls back to td_gl_item for periods not yet in ETL.
|
||||
|
||||
public function getMonthlyTrend(int $months = 6): array
|
||||
{
|
||||
$from = date('Y-m', strtotime('-' . ($months - 1) . ' months'));
|
||||
$sth = $this->pdo->prepare("
|
||||
SELECT
|
||||
g.period,
|
||||
SUM(CASE WHEN a.account_type = 'revenue' THEN i.credit - i.debit ELSE 0 END) AS revenue,
|
||||
SUM(CASE WHEN a.account_type = 'expense' THEN i.debit - i.credit ELSE 0 END) AS expense
|
||||
FROM td_gl g
|
||||
JOIN td_gl_item i ON i.gl_id = g.id AND i.company_id = g.company_id
|
||||
JOIN md_account a ON a.company_id = i.company_id AND a.account_code = i.account_code
|
||||
WHERE g.company_id = :cid AND g.period >= :from_period
|
||||
GROUP BY g.period
|
||||
ORDER BY g.period ASC
|
||||
|
||||
$sth = $this->pdo->prepare("
|
||||
SELECT period, acc_code, SUM(debit) AS d, SUM(credit) AS c
|
||||
FROM etl_gl_summary
|
||||
WHERE company_id = :cid AND acc_code >= 4000 AND period >= :from
|
||||
GROUP BY period, acc_code
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT g.period, i.account_code AS acc_code, SUM(i.debit) AS d, SUM(i.credit) AS c
|
||||
FROM td_gl_item i
|
||||
JOIN td_gl g ON g.id = i.gl_id AND g.company_id = i.company_id
|
||||
LEFT JOIN (
|
||||
SELECT DISTINCT period FROM etl_gl_summary WHERE company_id = :cid2
|
||||
) etl ON etl.period = g.period
|
||||
WHERE i.company_id = :cid3
|
||||
AND i.account_code >= 4000
|
||||
AND g.source_type != 'voided'
|
||||
AND g.period >= :from2
|
||||
AND etl.period IS NULL
|
||||
GROUP BY g.period, i.account_code
|
||||
");
|
||||
$sth->execute([':cid' => $this->companyId, ':from_period' => $from]);
|
||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
$sth->execute([
|
||||
':cid' => $this->companyId, ':from' => $from,
|
||||
':cid2' => $this->companyId,
|
||||
':cid3' => $this->companyId, ':from2' => $from,
|
||||
]);
|
||||
|
||||
$by_period = [];
|
||||
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$p = $row['period'];
|
||||
$code = (int)$row['acc_code'];
|
||||
if (!isset($by_period[$p])) $by_period[$p] = ['period' => $p, 'revenue' => 0.0, 'expense' => 0.0];
|
||||
if ($code >= 4000 && $code < 5000) $by_period[$p]['revenue'] += (float)$row['c'] - (float)$row['d'];
|
||||
else $by_period[$p]['expense'] += (float)$row['d'] - (float)$row['c'];
|
||||
}
|
||||
|
||||
ksort($by_period);
|
||||
return array_values($by_period);
|
||||
}
|
||||
|
||||
// ── Dashboard focused methods ─────────────────────────────────────────────
|
||||
@@ -260,29 +285,48 @@ class FinancialReports
|
||||
|
||||
public function getDashboardPL(): array
|
||||
{
|
||||
// ETL first — all periods present in etl_gl_summary
|
||||
// Fallback — periods in td_gl but missing from ETL (gap fill)
|
||||
$sth = $this->pdo->prepare("
|
||||
SELECT
|
||||
a.account_type,
|
||||
COALESCE(SUM(i.debit), 0) AS total_debit,
|
||||
COALESCE(SUM(i.credit), 0) AS total_credit
|
||||
FROM md_account a
|
||||
JOIN td_gl_item i ON i.company_id = a.company_id AND i.account_code = a.account_code
|
||||
JOIN td_gl g ON g.company_id = i.company_id AND g.id = i.gl_id
|
||||
WHERE a.company_id = :cid
|
||||
AND a.account_type IN ('revenue', 'expense')
|
||||
AND a.is_posting = 1
|
||||
GROUP BY a.account_type
|
||||
SELECT acc_code, SUM(debit) AS d, SUM(credit) AS c
|
||||
FROM etl_gl_summary
|
||||
WHERE company_id = :cid AND acc_code >= 4000
|
||||
GROUP BY acc_code
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT i.account_code AS acc_code, SUM(i.debit) AS d, SUM(i.credit) AS c
|
||||
FROM td_gl_item i
|
||||
JOIN td_gl g ON g.id = i.gl_id AND g.company_id = i.company_id
|
||||
LEFT JOIN (
|
||||
SELECT DISTINCT period FROM etl_gl_summary WHERE company_id = :cid2
|
||||
) etl ON etl.period = g.period
|
||||
WHERE i.company_id = :cid3
|
||||
AND i.account_code >= 4000
|
||||
AND g.source_type != 'voided'
|
||||
AND etl.period IS NULL
|
||||
GROUP BY i.account_code
|
||||
");
|
||||
$sth->execute([':cid' => $this->companyId]);
|
||||
$sth->execute([
|
||||
':cid' => $this->companyId,
|
||||
':cid2' => $this->companyId,
|
||||
':cid3' => $this->companyId,
|
||||
]);
|
||||
|
||||
$revenue = 0.0;
|
||||
$expense = 0.0;
|
||||
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
if ($row['account_type'] === 'revenue') $revenue += (float)$row['total_credit'] - (float)$row['total_debit'];
|
||||
if ($row['account_type'] === 'expense') $expense += (float)$row['total_debit'] - (float)$row['total_credit'];
|
||||
$code = (int)$row['acc_code'];
|
||||
if ($code >= 4000 && $code < 5000) $revenue += (float)$row['c'] - (float)$row['d'];
|
||||
else $expense += (float)$row['d'] - (float)$row['c'];
|
||||
}
|
||||
|
||||
return ['revenue' => $revenue, 'expense' => $expense, 'net_profit' => $revenue - $expense];
|
||||
return [
|
||||
'revenue' => $revenue,
|
||||
'expense' => $expense,
|
||||
'net_profit' => $revenue - $expense,
|
||||
'etl_gaps' => $this->getEtlGaps(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDashboardJournalCount(): int
|
||||
@@ -404,6 +448,30 @@ class FinancialReports
|
||||
];
|
||||
}
|
||||
|
||||
// Returns periods that exist in td_gl but are missing from etl_gl_summary.
|
||||
// Used by getDashboardPL() to signal which periods need a rebuild queue job.
|
||||
private function getEtlGaps(string $from_period = ''): array
|
||||
{
|
||||
$sql = "
|
||||
SELECT DISTINCT g.period
|
||||
FROM td_gl g
|
||||
LEFT JOIN (
|
||||
SELECT DISTINCT period FROM etl_gl_summary WHERE company_id = :cid2
|
||||
) etl ON etl.period = g.period
|
||||
WHERE g.company_id = :cid
|
||||
AND g.source_type NOT IN ('voided', 'reversal')
|
||||
AND etl.period IS NULL
|
||||
" . ($from_period ? "AND g.period >= :from_period" : "") . "
|
||||
ORDER BY g.period
|
||||
";
|
||||
$params = [':cid' => $this->companyId, ':cid2' => $this->companyId];
|
||||
if ($from_period) $params[':from_period'] = $from_period;
|
||||
|
||||
$sth = $this->pdo->prepare($sql);
|
||||
$sth->execute($params);
|
||||
return $sth->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
// ── GL Movement ───────────────────────────────────────────────────────────
|
||||
// Per-account ledger: opening balance + chronological lines with running balance.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user