560 lines
24 KiB
PHP
560 lines
24 KiB
PHP
<?php
|
|
/**
|
|
* FinancialReports
|
|
*
|
|
* Generates all financial statements from td_gl + td_gl_item joined with md_account.
|
|
* Read-only — no writes. All results are reusable across API endpoints and future reports.
|
|
*/
|
|
class FinancialReports
|
|
{
|
|
private PDO $pdo;
|
|
private int $companyId;
|
|
|
|
public function __construct(PDO $pdo, int $company_id)
|
|
{
|
|
$this->pdo = $pdo;
|
|
$this->companyId = $company_id;
|
|
}
|
|
|
|
// ── Trial Balance ─────────────────────────────────────────────────────────
|
|
// Returns every posting account with brought-forward balance, period movement,
|
|
// and carry-forward balance for the selected date range.
|
|
|
|
private function normalizeDate(string $date, bool $end_of_month = false): string
|
|
{
|
|
$date = trim($date);
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
|
return $date;
|
|
}
|
|
if (preg_match('/^\d{4}-\d{2}$/', $date)) {
|
|
return $end_of_month ? date('Y-m-t', strtotime($date . '-01')) : $date . '-01';
|
|
}
|
|
return date('Y-m-d');
|
|
}
|
|
|
|
public function getTrialBalance(string $from_date, string $to_date, int $dept_id = 0): array
|
|
{
|
|
$from_date = $this->normalizeDate($from_date);
|
|
$to_date = $this->normalizeDate($to_date, true);
|
|
|
|
$sth = $this->pdo->prepare("
|
|
SELECT
|
|
a.account_code,
|
|
a.account_name,
|
|
a.account_type,
|
|
COALESCE(SUM(CASE WHEN COALESCE(g.journal_date, DATE(g.created_at)) < :from_date THEN i.debit ELSE 0 END), 0) AS opening_debit,
|
|
COALESCE(SUM(CASE WHEN COALESCE(g.journal_date, DATE(g.created_at)) < :from_date THEN i.credit ELSE 0 END), 0) AS opening_credit,
|
|
COALESCE(SUM(CASE WHEN COALESCE(g.journal_date, DATE(g.created_at)) BETWEEN :from_date AND :to_date THEN i.debit ELSE 0 END), 0) AS period_debit,
|
|
COALESCE(SUM(CASE WHEN COALESCE(g.journal_date, DATE(g.created_at)) BETWEEN :from_date AND :to_date THEN i.credit ELSE 0 END), 0) AS period_credit
|
|
FROM md_account a
|
|
LEFT JOIN td_gl_item i ON i.company_id = a.company_id
|
|
AND i.account_code = a.account_code
|
|
AND (:dept_id = 0 OR i.department_id = :dept_id)
|
|
LEFT JOIN td_gl g ON g.id = i.gl_id
|
|
AND g.company_id = a.company_id
|
|
AND COALESCE(g.journal_date, DATE(g.created_at)) <= :to_date
|
|
WHERE a.company_id = :cid AND a.is_posting = 1 AND a.status = 1
|
|
GROUP BY a.account_code, a.account_name, a.account_type
|
|
HAVING opening_debit <> 0
|
|
OR opening_credit <> 0
|
|
OR period_debit <> 0
|
|
OR period_credit <> 0
|
|
ORDER BY a.account_code
|
|
");
|
|
$sth->execute([
|
|
':cid' => $this->companyId,
|
|
':from_date' => $from_date,
|
|
':to_date' => $to_date,
|
|
':dept_id' => $dept_id,
|
|
]);
|
|
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$total_opening_debit = 0.0;
|
|
$total_opening_credit = 0.0;
|
|
$total_period_debit = 0.0;
|
|
$total_period_credit = 0.0;
|
|
$total_closing_debit = 0.0;
|
|
$total_closing_credit = 0.0;
|
|
|
|
foreach ($rows as &$row) {
|
|
$opening_balance = (float)$row['opening_debit'] - (float)$row['opening_credit'];
|
|
$period_debit = (float)$row['period_debit'];
|
|
$period_credit = (float)$row['period_credit'];
|
|
$closing_balance = $opening_balance + $period_debit - $period_credit;
|
|
|
|
$row['opening_balance'] = $opening_balance;
|
|
$row['opening_debit'] = $opening_balance > 0 ? $opening_balance : 0.0;
|
|
$row['opening_credit'] = $opening_balance < 0 ? abs($opening_balance) : 0.0;
|
|
$row['period_debit'] = $period_debit;
|
|
$row['period_credit'] = $period_credit;
|
|
$row['closing_balance'] = $closing_balance;
|
|
$row['closing_debit'] = $closing_balance > 0 ? $closing_balance : 0.0;
|
|
$row['closing_credit'] = $closing_balance < 0 ? abs($closing_balance) : 0.0;
|
|
|
|
$total_opening_debit += $row['opening_debit'];
|
|
$total_opening_credit += $row['opening_credit'];
|
|
$total_period_debit += $row['period_debit'];
|
|
$total_period_credit += $row['period_credit'];
|
|
$total_closing_debit += $row['closing_debit'];
|
|
$total_closing_credit += $row['closing_credit'];
|
|
}
|
|
unset($row);
|
|
|
|
return [
|
|
'rows' => $rows,
|
|
'total_opening_debit' => $total_opening_debit,
|
|
'total_opening_credit' => $total_opening_credit,
|
|
'total_period_debit' => $total_period_debit,
|
|
'total_period_credit' => $total_period_credit,
|
|
'total_closing_debit' => $total_closing_debit,
|
|
'total_closing_credit' => $total_closing_credit,
|
|
'total_debit' => $total_closing_debit,
|
|
'total_credit' => $total_closing_credit,
|
|
'from_date' => $from_date,
|
|
'to_date' => $to_date,
|
|
'from_period' => substr($from_date, 0, 7),
|
|
'to_period' => substr($to_date, 0, 7),
|
|
];
|
|
}
|
|
|
|
// ── Profit & Loss ─────────────────────────────────────────────────────────
|
|
// Returns revenue and expense accounts for the date range.
|
|
// revenue net = credit - debit; expense net = debit - credit (normal balances).
|
|
|
|
public function getProfitAndLoss(string $from_date, string $to_date, int $dept_id = 0): array
|
|
{
|
|
$from_date = $this->normalizeDate($from_date);
|
|
$to_date = $this->normalizeDate($to_date, true);
|
|
|
|
$sth = $this->pdo->prepare("
|
|
SELECT
|
|
a.account_code,
|
|
a.account_name,
|
|
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
|
|
AND (:dept_id = 0 OR i.department_id = :dept_id)
|
|
JOIN td_gl g ON g.id = i.gl_id
|
|
AND g.company_id = a.company_id
|
|
AND COALESCE(g.journal_date, DATE(g.created_at)) BETWEEN :from_date AND :to_date
|
|
WHERE a.company_id = :cid
|
|
AND a.account_type IN ('revenue','expense')
|
|
AND a.is_posting = 1
|
|
GROUP BY a.account_code, a.account_name, a.account_type
|
|
ORDER BY a.account_type DESC, a.account_code
|
|
");
|
|
$sth->execute([
|
|
':cid' => $this->companyId,
|
|
':from_date' => $from_date,
|
|
':to_date' => $to_date,
|
|
':dept_id' => $dept_id,
|
|
]);
|
|
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$total_revenue = 0.0;
|
|
$total_expense = 0.0;
|
|
foreach ($rows as $r) {
|
|
if ($r['account_type'] === 'revenue') $total_revenue += $r['total_credit'] - $r['total_debit'];
|
|
if ($r['account_type'] === 'expense') $total_expense += $r['total_debit'] - $r['total_credit'];
|
|
}
|
|
|
|
return [
|
|
'rows' => $rows,
|
|
'total_revenue' => $total_revenue,
|
|
'total_expense' => $total_expense,
|
|
'net_profit' => $total_revenue - $total_expense,
|
|
'from_date' => $from_date,
|
|
'to_date' => $to_date,
|
|
'from_period' => substr($from_date, 0, 7),
|
|
'to_period' => substr($to_date, 0, 7),
|
|
];
|
|
}
|
|
|
|
// ── Balance Sheet ─────────────────────────────────────────────────────────
|
|
// Cumulative balances for asset/liability/equity accounts up to as_of_period.
|
|
// Retained earnings = cumulative net P&L (revenue - expense) through that period.
|
|
|
|
public function getBalanceSheet(string $as_of_date, int $dept_id = 0): array
|
|
{
|
|
$as_of_date = $this->normalizeDate($as_of_date, true);
|
|
|
|
$sth = $this->pdo->prepare("
|
|
SELECT
|
|
a.account_code,
|
|
a.account_name,
|
|
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
|
|
AND (:dept_id = 0 OR i.department_id = :dept_id)
|
|
JOIN td_gl g ON g.id = i.gl_id
|
|
AND g.company_id = a.company_id
|
|
AND COALESCE(g.journal_date, DATE(g.created_at)) <= :as_of
|
|
WHERE a.company_id = :cid
|
|
AND a.account_type IN ('asset','liability','equity')
|
|
AND a.is_posting = 1 AND a.status = 1
|
|
GROUP BY a.account_code, a.account_name, a.account_type
|
|
ORDER BY a.account_type, a.account_code
|
|
");
|
|
$sth->execute([':cid' => $this->companyId, ':as_of' => $as_of_date, ':dept_id' => $dept_id]);
|
|
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$sth = $this->pdo->prepare("
|
|
SELECT
|
|
COALESCE(SUM(CASE WHEN a.account_type = 'revenue'
|
|
THEN i.credit - i.debit ELSE 0 END), 0) AS net_revenue,
|
|
COALESCE(SUM(CASE WHEN a.account_type = 'expense'
|
|
THEN i.debit - i.credit ELSE 0 END), 0) AS net_expense
|
|
FROM md_account a
|
|
JOIN td_gl_item i ON i.company_id = a.company_id
|
|
AND i.account_code = a.account_code
|
|
AND (:dept_id = 0 OR i.department_id = :dept_id)
|
|
JOIN td_gl g ON g.id = i.gl_id
|
|
AND g.company_id = a.company_id
|
|
AND COALESCE(g.journal_date, DATE(g.created_at)) <= :as_of
|
|
WHERE a.company_id = :cid
|
|
AND a.account_type IN ('revenue','expense')
|
|
AND a.is_posting = 1
|
|
");
|
|
$sth->execute([':cid' => $this->companyId, ':as_of' => $as_of_date, ':dept_id' => $dept_id]);
|
|
$pl = $sth->fetch(PDO::FETCH_ASSOC);
|
|
|
|
return [
|
|
'rows' => $rows,
|
|
'retained_earnings' => (float)($pl['net_revenue'] ?? 0) - (float)($pl['net_expense'] ?? 0),
|
|
'as_of_date' => $as_of_date,
|
|
'as_of_period' => substr($as_of_date, 0, 7),
|
|
];
|
|
}
|
|
|
|
// ── Monthly Trend ─────────────────────────────────────────────────────────
|
|
// Revenue vs expense per period for the last N months (defaults to 6).
|
|
// 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 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' => $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 ─────────────────────────────────────────────
|
|
// One method per dashboard card/section. Used by separate REST endpoints.
|
|
|
|
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 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,
|
|
':cid2' => $this->companyId,
|
|
':cid3' => $this->companyId,
|
|
]);
|
|
|
|
$revenue = 0.0;
|
|
$expense = 0.0;
|
|
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$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,
|
|
'etl_gaps' => $this->getEtlGaps(),
|
|
];
|
|
}
|
|
|
|
public function getDashboardJournalCount(): int
|
|
{
|
|
$sth = $this->pdo->prepare("SELECT COUNT(*) FROM td_gl WHERE company_id = :cid");
|
|
$sth->execute([':cid' => $this->companyId]);
|
|
return (int)$sth->fetchColumn();
|
|
}
|
|
|
|
public function getDashboardBySource(): array
|
|
{
|
|
$sth = $this->pdo->prepare("
|
|
SELECT source_type, COUNT(*) AS cnt
|
|
FROM td_gl
|
|
WHERE company_id = :cid
|
|
GROUP BY source_type
|
|
ORDER BY cnt DESC
|
|
");
|
|
$sth->execute([':cid' => $this->companyId]);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
public function getDashboardRecentEntries(int $limit = 10): array
|
|
{
|
|
$limit = max(1, min(100, $limit));
|
|
$sth = $this->pdo->prepare("
|
|
SELECT
|
|
g.id, g.source_type,
|
|
COALESCE(g.reference, '') AS reference,
|
|
COALESCE(g.description, '') AS description,
|
|
COALESCE(g.journal_date, DATE(g.created_at)) AS journal_date,
|
|
g.period,
|
|
SUM(i.debit) AS total_debit,
|
|
SUM(i.credit) AS total_credit
|
|
FROM td_gl g
|
|
JOIN td_gl_item i ON i.gl_id = g.id AND i.company_id = g.company_id
|
|
WHERE g.company_id = :cid AND g.source_type != 'voided'
|
|
GROUP BY g.id, g.source_type, g.reference, g.description, g.journal_date, g.period, g.created_at
|
|
ORDER BY g.created_at DESC
|
|
LIMIT {$limit}
|
|
");
|
|
$sth->execute([':cid' => $this->companyId]);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
public function getDashboardSummary(int $trend_months = 6, int $recent_limit = 10): array
|
|
{
|
|
$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
|
|
");
|
|
$sth->execute([':cid' => $this->companyId]);
|
|
|
|
$total_revenue = 0.0;
|
|
$total_expense = 0.0;
|
|
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
if ($row['account_type'] === 'revenue') {
|
|
$total_revenue += (float)$row['total_credit'] - (float)$row['total_debit'];
|
|
}
|
|
if ($row['account_type'] === 'expense') {
|
|
$total_expense += (float)$row['total_debit'] - (float)$row['total_credit'];
|
|
}
|
|
}
|
|
|
|
$sth = $this->pdo->prepare("SELECT COUNT(*) FROM td_gl WHERE company_id = :cid");
|
|
$sth->execute([':cid' => $this->companyId]);
|
|
$journal_count = (int)$sth->fetchColumn();
|
|
|
|
$sth = $this->pdo->prepare("
|
|
SELECT source_type, COUNT(*) AS cnt
|
|
FROM td_gl
|
|
WHERE company_id = :cid
|
|
GROUP BY source_type
|
|
ORDER BY cnt DESC
|
|
");
|
|
$sth->execute([':cid' => $this->companyId]);
|
|
$by_source = $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$sth = $this->pdo->prepare("
|
|
SELECT
|
|
g.id, g.source_type,
|
|
COALESCE(g.reference, '') AS reference,
|
|
COALESCE(g.description, '') AS description,
|
|
COALESCE(g.journal_date, DATE(g.created_at)) AS journal_date,
|
|
g.period,
|
|
SUM(i.debit) AS total_debit,
|
|
SUM(i.credit) AS total_credit
|
|
FROM td_gl g
|
|
JOIN td_gl_item i ON i.gl_id = g.id AND i.company_id = g.company_id
|
|
WHERE g.company_id = :cid
|
|
GROUP BY g.id, g.source_type, g.reference, g.description, g.journal_date, g.period, g.created_at
|
|
ORDER BY g.created_at DESC
|
|
LIMIT {$recent_limit}
|
|
");
|
|
$sth->execute([':cid' => $this->companyId]);
|
|
$recent = $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
return [
|
|
'revenue' => $total_revenue,
|
|
'expense' => $total_expense,
|
|
'net_profit' => $total_revenue - $total_expense,
|
|
'journal_count' => $journal_count,
|
|
'trend' => $this->getMonthlyTrend($trend_months),
|
|
'by_source' => $by_source,
|
|
'recent' => $recent,
|
|
];
|
|
}
|
|
|
|
// 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.
|
|
|
|
public function getGLMovement(string $account_code, string $from_date, string $to_date, int $dept_id = 0): array
|
|
{
|
|
$from_date = $this->normalizeDate($from_date);
|
|
$to_date = $this->normalizeDate($to_date, true);
|
|
|
|
// Opening balance = all movements before from_date
|
|
$sth = $this->pdo->prepare("
|
|
SELECT
|
|
COALESCE(SUM(i.debit), 0) AS opening_debit,
|
|
COALESCE(SUM(i.credit), 0) AS opening_credit
|
|
FROM td_gl_item i
|
|
JOIN td_gl g ON g.id = i.gl_id AND g.company_id = i.company_id
|
|
WHERE i.company_id = :cid
|
|
AND i.account_code = :code
|
|
AND (:dept_id = 0 OR i.department_id = :dept_id)
|
|
AND COALESCE(g.journal_date, DATE(g.created_at)) < :from_date
|
|
");
|
|
$sth->execute([
|
|
':cid' => $this->companyId,
|
|
':code' => $account_code,
|
|
':from_date' => $from_date,
|
|
':dept_id' => $dept_id,
|
|
]);
|
|
$ob = $sth->fetch(PDO::FETCH_ASSOC);
|
|
$opening_balance = (float)$ob['opening_debit'] - (float)$ob['opening_credit'];
|
|
|
|
// Period lines
|
|
$sth = $this->pdo->prepare("
|
|
SELECT
|
|
g.id AS gl_id,
|
|
g.source_type,
|
|
COALESCE(g.reference, '') AS reference,
|
|
COALESCE(g.description, '') AS gl_description,
|
|
g.period,
|
|
i.department_id,
|
|
COALESCE(d.dept_code, '') AS dept_code,
|
|
COALESCE(d.dept_name, '') AS dept_name,
|
|
COALESCE(g.journal_date, DATE(g.created_at)) AS entry_date,
|
|
DATE_FORMAT(g.created_at, '%d/%m/%Y %H:%i') AS posted_at,
|
|
i.debit,
|
|
i.credit,
|
|
COALESCE(i.description, '') AS line_description
|
|
FROM td_gl_item i
|
|
JOIN td_gl g ON g.id = i.gl_id AND g.company_id = i.company_id
|
|
LEFT JOIN md_department d ON d.company_id = i.company_id AND d.id = i.department_id
|
|
WHERE i.company_id = :cid
|
|
AND i.account_code = :code
|
|
AND (:dept_id = 0 OR i.department_id = :dept_id)
|
|
AND COALESCE(g.journal_date, DATE(g.created_at)) BETWEEN :from_date AND :to_date
|
|
ORDER BY g.journal_date ASC, g.id ASC, i.id ASC
|
|
");
|
|
$sth->execute([
|
|
':cid' => $this->companyId,
|
|
':code' => $account_code,
|
|
':from_date' => $from_date,
|
|
':to_date' => $to_date,
|
|
':dept_id' => $dept_id,
|
|
]);
|
|
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Account metadata
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT account_name, account_type
|
|
FROM md_account
|
|
WHERE company_id = :cid AND account_code = :code LIMIT 1"
|
|
);
|
|
$sth->execute([':cid' => $this->companyId, ':code' => $account_code]);
|
|
$acct = $sth->fetch(PDO::FETCH_ASSOC);
|
|
|
|
return [
|
|
'account_code' => $account_code,
|
|
'account_name' => $acct['account_name'] ?? '',
|
|
'account_type' => $acct['account_type'] ?? '',
|
|
'opening_balance' => $opening_balance,
|
|
'rows' => $rows,
|
|
'from_date' => $from_date,
|
|
'to_date' => $to_date,
|
|
'from_period' => substr($from_date, 0, 7),
|
|
'to_period' => substr($to_date, 0, 7),
|
|
];
|
|
}
|
|
}
|