Commit 41116567 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Enhance trial balance report with 6-column layout and CSV export

Shows opening debit/credit, period debit/credit, closing debit/credit
columns matching standard Egyptian accounting trial balance format.
Adds CSV export endpoint with UTF-8 BOM for Arabic Excel support.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent babe6ea8
......@@ -202,6 +202,66 @@ class ReportController extends Controller
]);
}
public function trialBalanceExportCsv(): Response
{
$this->authorize('accounting.reports.trial_balance');
$currentFY = FiscalYear::current();
$dateFrom = $_GET['date_from'] ?? ($currentFY ? $currentFY->start_date : date('Y-01-01'));
$dateTo = $_GET['date_to'] ?? date('Y-m-d');
$costCenterId = !empty($_GET['cost_center_id']) ? (int) $_GET['cost_center_id'] : null;
$branchId = !empty($_GET['branch_id']) ? (int) $_GET['branch_id'] : null;
$result = LedgerService::getTrialBalance($dateFrom, $dateTo, null, $costCenterId, $branchId);
$filename = 'trial_balance_' . $dateFrom . '_to_' . $dateTo . '.csv';
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$output = fopen('php://output', 'w');
// BOM for Excel Arabic support
fwrite($output, "\xEF\xBB\xBF");
fputcsv($output, [
'كود الحساب',
'اسم الحساب',
'رصيد أول - مدين',
'رصيد أول - دائن',
'حركة الفترة - مدين',
'حركة الفترة - دائن',
'رصيد آخر - مدين',
'رصيد آخر - دائن',
]);
foreach ($result['accounts'] as $acc) {
fputcsv($output, [
$acc['account_code'],
$acc['name_ar'],
$acc['opening_debit'],
$acc['opening_credit'],
$acc['period_debit'],
$acc['period_credit'],
$acc['closing_debit'],
$acc['closing_credit'],
]);
}
fputcsv($output, [
'',
'الإجمالي',
$result['total_opening_debit'],
$result['total_opening_credit'],
$result['total_period_debit'],
$result['total_period_credit'],
$result['total_closing_debit'],
$result['total_closing_credit'],
]);
fclose($output);
exit;
}
public function generalLedger(): Response
{
$this->authorize('accounting.reports.general_ledger');
......
......@@ -61,6 +61,7 @@ return [
// ── Reports ──────────────────────────────────────────────
['GET', '/accounting/reports/trial-balance', 'Accounting\Controllers\ReportController@trialBalance', ['auth'], 'accounting.reports.trial_balance'],
['GET', '/accounting/reports/trial-balance/export-csv', 'Accounting\Controllers\ReportController@trialBalanceExportCsv', ['auth'], 'accounting.reports.trial_balance'],
['GET', '/accounting/reports/general-ledger', 'Accounting\Controllers\ReportController@generalLedger', ['auth'], 'accounting.reports.general_ledger'],
['GET', '/accounting/reports/income-statement', 'Accounting\Controllers\ReportController@incomeStatement', ['auth'], 'accounting.reports.income_statement'],
['GET', '/accounting/reports/balance-sheet', 'Accounting\Controllers\ReportController@balanceSheet', ['auth'], 'accounting.reports.balance_sheet'],
......
......@@ -139,7 +139,7 @@ final class LedgerService
$accounts = $db->select(
"SELECT coa.id, coa.account_code, coa.name_ar, coa.name_en,
coa.account_type, coa.account_nature, coa.level, coa.is_header,
coa.opening_balance,
coa.opening_balance, coa.current_balance,
COALESCE(SUM(jel.debit), 0) as total_debit,
COALESCE(SUM(jel.credit), 0) as total_credit
FROM chart_of_accounts coa
......@@ -152,13 +152,19 @@ final class LedgerService
WHERE coa.is_archived = 0 AND coa.is_header = 0
GROUP BY coa.id, coa.account_code, coa.name_ar, coa.name_en,
coa.account_type, coa.account_nature, coa.level, coa.is_header,
coa.opening_balance
coa.opening_balance, coa.current_balance
ORDER BY coa.account_code ASC",
$params
);
$totalDebit = '0.00';
$totalCredit = '0.00';
$totalOpeningDebit = '0.00';
$totalOpeningCredit = '0.00';
$totalPeriodDebit = '0.00';
$totalPeriodCredit = '0.00';
$totalClosingDebit = '0.00';
$totalClosingCredit = '0.00';
$filtered = [];
foreach ($accounts as &$acc) {
......@@ -166,53 +172,75 @@ final class LedgerService
$creditMovement = (string) $acc['total_credit'];
$opening = (string) $acc['opening_balance'];
// Calculate net balance
// Opening debit/credit split
if (bccomp($opening, '0.00', 2) >= 0) {
$acc['opening_debit'] = $opening;
$acc['opening_credit'] = '0.00';
} else {
$acc['opening_debit'] = '0.00';
$acc['opening_credit'] = bcmul($opening, '-1', 2);
}
// Period debit/credit (raw journal movements)
$acc['period_debit'] = $debitMovement;
$acc['period_credit'] = $creditMovement;
// Calculate closing balance
if ($acc['account_nature'] === 'debit') {
$balance = bcadd($opening, bcsub($debitMovement, $creditMovement, 2), 2);
} else {
$balance = bcadd($opening, bcsub($creditMovement, $debitMovement, 2), 2);
}
// Determine trial balance columns
// If no journal movements, use current_balance from DB
if (bccomp($debitMovement, '0.00', 2) === 0 && bccomp($creditMovement, '0.00', 2) === 0) {
$balance = (string) $acc['current_balance'];
}
// Closing debit/credit split
if (bccomp($balance, '0.00', 2) >= 0) {
if ($acc['account_nature'] === 'debit') {
$acc['tb_debit'] = $balance;
$acc['tb_credit'] = '0.00';
} else {
$acc['tb_debit'] = '0.00';
$acc['tb_credit'] = $balance;
}
$acc['closing_debit'] = $balance;
$acc['closing_credit'] = '0.00';
} else {
// Negative balance — show on opposite side
$absBalance = bcmul($balance, '-1', 2);
if ($acc['account_nature'] === 'debit') {
$acc['tb_debit'] = '0.00';
$acc['tb_credit'] = $absBalance;
} else {
$acc['tb_debit'] = $absBalance;
$acc['tb_credit'] = '0.00';
}
$acc['closing_debit'] = '0.00';
$acc['closing_credit'] = bcmul($balance, '-1', 2);
}
// Legacy tb_debit/tb_credit (closing columns)
$acc['tb_debit'] = $acc['closing_debit'];
$acc['tb_credit'] = $acc['closing_credit'];
$acc['balance'] = $balance;
if (!$includeZeroBalances && bccomp($balance, '0.00', 2) === 0
&& bccomp($debitMovement, '0.00', 2) === 0
&& bccomp($creditMovement, '0.00', 2) === 0) {
&& bccomp($creditMovement, '0.00', 2) === 0
&& bccomp($opening, '0.00', 2) === 0) {
continue;
}
$totalDebit = bcadd($totalDebit, $acc['tb_debit'], 2);
$totalCredit = bcadd($totalCredit, $acc['tb_credit'], 2);
$totalOpeningDebit = bcadd($totalOpeningDebit, $acc['opening_debit'], 2);
$totalOpeningCredit = bcadd($totalOpeningCredit, $acc['opening_credit'], 2);
$totalPeriodDebit = bcadd($totalPeriodDebit, $acc['period_debit'], 2);
$totalPeriodCredit = bcadd($totalPeriodCredit, $acc['period_credit'], 2);
$totalClosingDebit = bcadd($totalClosingDebit, $acc['closing_debit'], 2);
$totalClosingCredit = bcadd($totalClosingCredit, $acc['closing_credit'], 2);
$filtered[] = $acc;
}
unset($acc);
return [
'accounts' => $filtered,
'total_debit' => $totalDebit,
'total_credit' => $totalCredit,
'is_balanced' => bccomp($totalDebit, $totalCredit, 2) === 0,
'accounts' => $filtered,
'total_debit' => $totalDebit,
'total_credit' => $totalCredit,
'total_opening_debit' => $totalOpeningDebit,
'total_opening_credit' => $totalOpeningCredit,
'total_period_debit' => $totalPeriodDebit,
'total_period_credit' => $totalPeriodCredit,
'total_closing_debit' => $totalClosingDebit,
'total_closing_credit' => $totalClosingCredit,
'is_balanced' => bccomp($totalClosingDebit, $totalClosingCredit, 2) === 0,
];
}
......
......@@ -2,6 +2,10 @@
<?php $__template->section('title'); ?>ميزان المراجعة<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/accounting/reports/trial-balance/export-csv?date_from=<?= e($date_from) ?>&date_to=<?= e($date_to) ?>&cost_center_id=<?= (int)($filters['cost_center_id'] ?? 0) ?>&branch_id=<?= (int)($filters['branch_id'] ?? 0) ?>" class="btn btn-secondary" style="font-size:13px;">تصدير CSV</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Filters -->
<div class="card" style="margin-bottom:15px;">
......@@ -41,53 +45,58 @@
<!-- Report -->
<div class="card">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;">
<h3 style="margin:0;">ميزان المراجعة</h3>
<h3 style="margin:0;">ميزان المراجعة <span style="font-size:13px;color:#6B7280;font-weight:400;">(<?= count($result['accounts']) ?> حساب)</span></h3>
<span style="font-size:13px;color:#6B7280;">من <?= e($date_from) ?> إلى <?= e($date_to) ?></span>
</div>
<div class="table-responsive">
<table class="data-table" style="width:100%;">
<div style="overflow-x:auto;">
<table class="data-table" style="width:100%;min-width:900px;">
<thead>
<tr style="background:#0D7377;color:white;">
<th style="padding:12px;">كود الحساب</th>
<th style="padding:12px;">اسم الحساب</th>
<th style="padding:12px;">النوع</th>
<th style="padding:12px;text-align:center;">مدين</th>
<th style="padding:12px;text-align:center;">دائن</th>
<th style="padding:10px;" rowspan="2">كود</th>
<th style="padding:10px;" rowspan="2">اسم الحساب</th>
<th style="padding:10px;text-align:center;border-left:1px solid rgba(255,255,255,0.3);" colspan="2">رصيد أول المدة</th>
<th style="padding:10px;text-align:center;border-left:1px solid rgba(255,255,255,0.3);" colspan="2">حركة الفترة</th>
<th style="padding:10px;text-align:center;" colspan="2">رصيد آخر المدة</th>
</tr>
<tr style="background:#0D7377;color:white;">
<th style="padding:8px;text-align:center;font-size:12px;border-left:1px solid rgba(255,255,255,0.3);">مدين</th>
<th style="padding:8px;text-align:center;font-size:12px;">دائن</th>
<th style="padding:8px;text-align:center;font-size:12px;border-left:1px solid rgba(255,255,255,0.3);">مدين</th>
<th style="padding:8px;text-align:center;font-size:12px;">دائن</th>
<th style="padding:8px;text-align:center;font-size:12px;">مدين</th>
<th style="padding:8px;text-align:center;font-size:12px;">دائن</th>
</tr>
</thead>
<tbody>
<?php foreach ($result['accounts'] as $acc): ?>
<?php
$typeLabel = match($acc['account_type']) {
'asset' => 'أصول',
'liability' => 'خصوم',
'equity' => 'حقوق ملكية',
'revenue' => 'إيرادات',
'expense' => 'مصروفات',
default => '',
};
?>
<tr>
<td style="direction:ltr;text-align:right;font-weight:600;font-size:13px;"><?= e($acc['account_code']) ?></td>
<td><?= e($acc['name_ar']) ?></td>
<td style="font-size:12px;color:#6B7280;"><?= $typeLabel ?></td>
<td style="direction:ltr;text-align:center;<?= bccomp($acc['tb_debit'], '0.00', 2) > 0 ? 'font-weight:600;' : 'color:#D1D5DB;' ?>"><?= money($acc['tb_debit']) ?></td>
<td style="direction:ltr;text-align:center;<?= bccomp($acc['tb_credit'], '0.00', 2) > 0 ? 'font-weight:600;' : 'color:#D1D5DB;' ?>"><?= money($acc['tb_credit']) ?></td>
<td style="direction:ltr;text-align:right;font-weight:600;font-size:12px;padding:8px;white-space:nowrap;"><?= e($acc['account_code']) ?></td>
<td style="padding:8px;font-size:13px;"><?= e($acc['name_ar']) ?></td>
<td style="direction:ltr;text-align:center;padding:8px;font-size:12px;border-left:1px solid #E5E7EB;<?= bccomp($acc['opening_debit'], '0.00', 2) > 0 ? 'font-weight:600;' : 'color:#D1D5DB;' ?>"><?= money($acc['opening_debit']) ?></td>
<td style="direction:ltr;text-align:center;padding:8px;font-size:12px;<?= bccomp($acc['opening_credit'], '0.00', 2) > 0 ? 'font-weight:600;' : 'color:#D1D5DB;' ?>"><?= money($acc['opening_credit']) ?></td>
<td style="direction:ltr;text-align:center;padding:8px;font-size:12px;border-left:1px solid #E5E7EB;<?= bccomp($acc['period_debit'], '0.00', 2) > 0 ? 'font-weight:600;' : 'color:#D1D5DB;' ?>"><?= money($acc['period_debit']) ?></td>
<td style="direction:ltr;text-align:center;padding:8px;font-size:12px;<?= bccomp($acc['period_credit'], '0.00', 2) > 0 ? 'font-weight:600;' : 'color:#D1D5DB;' ?>"><?= money($acc['period_credit']) ?></td>
<td style="direction:ltr;text-align:center;padding:8px;font-size:12px;border-left:1px solid #E5E7EB;<?= bccomp($acc['closing_debit'], '0.00', 2) > 0 ? 'font-weight:600;' : 'color:#D1D5DB;' ?>"><?= money($acc['closing_debit']) ?></td>
<td style="direction:ltr;text-align:center;padding:8px;font-size:12px;<?= bccomp($acc['closing_credit'], '0.00', 2) > 0 ? 'font-weight:600;' : 'color:#D1D5DB;' ?>"><?= money($acc['closing_credit']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr style="font-weight:700;font-size:16px;background:#F9FAFB;">
<td style="padding:15px;" colspan="3">الإجمالي</td>
<td style="direction:ltr;text-align:center;padding:15px;"><?= money($result['total_debit']) ?></td>
<td style="direction:ltr;text-align:center;padding:15px;"><?= money($result['total_credit']) ?></td>
<tr style="font-weight:700;font-size:14px;background:#F9FAFB;border-top:2px solid #0D7377;">
<td style="padding:12px;" colspan="2">الإجمالي</td>
<td style="direction:ltr;text-align:center;padding:12px;border-left:1px solid #E5E7EB;"><?= money($result['total_opening_debit']) ?></td>
<td style="direction:ltr;text-align:center;padding:12px;"><?= money($result['total_opening_credit']) ?></td>
<td style="direction:ltr;text-align:center;padding:12px;border-left:1px solid #E5E7EB;"><?= money($result['total_period_debit']) ?></td>
<td style="direction:ltr;text-align:center;padding:12px;"><?= money($result['total_period_credit']) ?></td>
<td style="direction:ltr;text-align:center;padding:12px;border-left:1px solid #E5E7EB;"><?= money($result['total_closing_debit']) ?></td>
<td style="direction:ltr;text-align:center;padding:12px;"><?= money($result['total_closing_credit']) ?></td>
</tr>
<tr>
<td colspan="5" style="text-align:center;padding:10px;">
<td colspan="8" style="text-align:center;padding:10px;">
<?php if ($result['is_balanced']): ?>
<span style="color:#059669;font-weight:700;">الميزان متوازن</span>
<?php else: ?>
<span style="color:#DC2626;font-weight:700;">تحذير: الميزان غير متوازن — الفرق: <?= money(bcsub($result['total_debit'], $result['total_credit'], 2)) ?></span>
<span style="color:#DC2626;font-weight:700;">تحذير: الميزان غير متوازن — الفرق: <?= money(bcsub($result['total_closing_debit'], $result['total_closing_credit'], 2)) ?></span>
<?php endif; ?>
</td>
</tr>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment