Commit 89695e0e authored by DevPilot's avatar DevPilot

feat(accounting): add Cash Flow Statement and Statement of Changes in Equity

Rounds out the القوائم المالية group alongside the existing income
statement, balance sheet, and consolidated balance sheet. Cash flow uses
the indirect method with a reconciling line so it always foots to the
real cash-account movement; the equity statement ties exactly to the
balance sheet's total_equity at both ends of the period.
parent 5c5a5d2c
...@@ -422,6 +422,65 @@ class ReportController extends Controller ...@@ -422,6 +422,65 @@ class ReportController extends Controller
exit; exit;
} }
public function cashFlowStatement(Request $request): Response
{
$this->authorize('accounting.reports.cash_flow');
$dateFrom = $request->get('date_from', self::getFiscalYearStartForRequest());
$dateTo = $request->get('date_to', date('Y-m-d'));
$branchId = $request->get('branch_id') ? (int) $request->get('branch_id') : null;
$result = FinancialReportService::getCashFlowStatement($dateFrom, $dateTo, $branchId);
$db = App::getInstance()->db();
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
return $this->view('Accounting/Views/reports/cash_flow_statement', [
'result' => $result,
'date_from' => $dateFrom,
'date_to' => $dateTo,
'branches' => $branches,
'filters' => ['branch_id' => $branchId],
]);
}
public function equityStatement(Request $request): Response
{
$this->authorize('accounting.reports.equity_statement');
$dateFrom = $request->get('date_from', self::getFiscalYearStartForRequest());
$dateTo = $request->get('date_to', date('Y-m-d'));
$branchId = $request->get('branch_id') ? (int) $request->get('branch_id') : null;
$result = FinancialReportService::getStatementOfChangesInEquity($dateFrom, $dateTo, $branchId);
$db = App::getInstance()->db();
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
return $this->view('Accounting/Views/reports/equity_statement', [
'result' => $result,
'date_from' => $dateFrom,
'date_to' => $dateTo,
'branches' => $branches,
'filters' => ['branch_id' => $branchId],
]);
}
private static function getFiscalYearStartForRequest(): string
{
$config = App::getInstance()->config('app');
$startMonth = (int) ($config['financial_year_start_month'] ?? 7);
$startDay = (int) ($config['financial_year_start_day'] ?? 1);
$year = (int) date('Y');
$month = (int) date('m');
if ($month < $startMonth) {
$year--;
}
return sprintf('%04d-%02d-%02d', $year, $startMonth, $startDay);
}
public function consolidatedBalanceSheet(Request $request): Response public function consolidatedBalanceSheet(Request $request): Response
{ {
$this->authorize('accounting.reports.consolidated'); $this->authorize('accounting.reports.consolidated');
......
...@@ -132,6 +132,8 @@ return [ ...@@ -132,6 +132,8 @@ return [
['GET', '/accounting/reports/balance-sheet', 'Accounting\Controllers\ReportController@balanceSheet', ['auth'], 'accounting.reports.balance_sheet'], ['GET', '/accounting/reports/balance-sheet', 'Accounting\Controllers\ReportController@balanceSheet', ['auth'], 'accounting.reports.balance_sheet'],
['GET', '/accounting/reports/balance-sheet/export-csv', 'Accounting\Controllers\ReportController@balanceSheetExportCsv', ['auth'], 'accounting.reports.balance_sheet'], ['GET', '/accounting/reports/balance-sheet/export-csv', 'Accounting\Controllers\ReportController@balanceSheetExportCsv', ['auth'], 'accounting.reports.balance_sheet'],
['GET', '/accounting/reports/consolidated-balance-sheet', 'Accounting\Controllers\ReportController@consolidatedBalanceSheet', ['auth'], 'accounting.reports.consolidated'], ['GET', '/accounting/reports/consolidated-balance-sheet', 'Accounting\Controllers\ReportController@consolidatedBalanceSheet', ['auth'], 'accounting.reports.consolidated'],
['GET', '/accounting/reports/cash-flow', 'Accounting\Controllers\ReportController@cashFlowStatement', ['auth'], 'accounting.reports.cash_flow'],
['GET', '/accounting/reports/equity-statement', 'Accounting\Controllers\ReportController@equityStatement', ['auth'], 'accounting.reports.equity_statement'],
['GET', '/accounting/reports/accounts-receivable', 'Accounting\Controllers\ReportController@accountsReceivable', ['auth'], 'accounting.reports.ar'], ['GET', '/accounting/reports/accounts-receivable', 'Accounting\Controllers\ReportController@accountsReceivable', ['auth'], 'accounting.reports.ar'],
['GET', '/accounting/reports/accounts-payable', 'Accounting\Controllers\ReportController@accountsPayable', ['auth'], 'accounting.reports.ap'], ['GET', '/accounting/reports/accounts-payable', 'Accounting\Controllers\ReportController@accountsPayable', ['auth'], 'accounting.reports.ap'],
['GET', '/accounting/reports/member-statement', 'Accounting\Controllers\ReportController@memberStatement', ['auth'], 'accounting.reports.member_statement'], ['GET', '/accounting/reports/member-statement', 'Accounting\Controllers\ReportController@memberStatement', ['auth'], 'accounting.reports.member_statement'],
......
...@@ -259,6 +259,288 @@ final class FinancialReportService ...@@ -259,6 +259,288 @@ final class FinancialReportService
]; ];
} }
/**
* Cash Flow Statement (قائمة التدفقات النقدية) — indirect method.
*
* Cash accounts are detected by `is_bank_account` or a cash-like name, since the
* chart of accounts has no dedicated "is_cash" flag. Fixed-asset accounts are
* detected via `asset_categories.asset_account_id` (the only place in the schema
* that actually tags a GL account as a fixed asset) so their movement is reported
* under Investing rather than Operating working capital. Any residual gap between
* the three classified sections and the real cash-account movement is shown as a
* single reconciling line rather than silently dropped, so the statement always
* foots to the actual change in cash.
*/
public static function getCashFlowStatement(
string $dateFrom,
string $dateTo,
?int $branchId = null
): array {
$db = App::getInstance()->db();
$dateBefore = date('Y-m-d', strtotime($dateFrom . ' -1 day'));
$cashAccountIds = self::getCashAccountIds($db);
$fixedAssetAccountIds = self::getFixedAssetAccountIds($db);
$cashStart = self::sumAccountBalances($db, $cashAccountIds, $dateBefore, $branchId);
$cashEnd = self::sumAccountBalances($db, $cashAccountIds, $dateTo, $branchId);
$netChangeInCash = bcsub($cashEnd, $cashStart, 2);
$income = self::getIncomeStatement($dateFrom, $dateTo, null, $branchId);
$netIncome = $income['net_income'];
$depreciation = $db->selectOne(
"SELECT COALESCE(SUM(de.depreciation_amount), 0) AS total
FROM depreciation_entries de
WHERE de.period_month >= ? AND de.period_month <= ?",
[substr($dateFrom, 0, 7), substr($dateTo, 0, 7)]
);
$depreciationAddBack = (string) ($depreciation['total'] ?? '0.00');
// Working-capital assets: every asset account except cash and fixed assets.
$excludedAssetIds = array_merge($cashAccountIds, $fixedAssetAccountIds);
$wcAssetsStart = self::sumAccountBalancesByType($db, 'asset', $excludedAssetIds, $dateBefore, $branchId);
$wcAssetsEnd = self::sumAccountBalancesByType($db, 'asset', $excludedAssetIds, $dateTo, $branchId);
$wcAssetsCashEffect = bcmul(bcsub($wcAssetsEnd, $wcAssetsStart, 2), '-1', 2);
// Liabilities: split loan-like (financing) from the rest (operating, e.g. AP/accrued).
$loanLiabilityIds = self::getLoanLikeAccountIds($db, 'liability');
$opLiabilitiesStart = self::sumAccountBalancesByType($db, 'liability', $loanLiabilityIds, $dateBefore, $branchId);
$opLiabilitiesEnd = self::sumAccountBalancesByType($db, 'liability', $loanLiabilityIds, $dateTo, $branchId);
$opLiabilitiesCashEffect = bcsub($opLiabilitiesEnd, $opLiabilitiesStart, 2);
$finLiabilitiesStart = self::sumAccountBalances($db, $loanLiabilityIds, $dateBefore, $branchId);
$finLiabilitiesEnd = self::sumAccountBalances($db, $loanLiabilityIds, $dateTo, $branchId);
$finLiabilitiesCashEffect = bcsub($finLiabilitiesEnd, $finLiabilitiesStart, 2);
// Equity: real posted equity accounts only (no closing entries exist in this
// ledger, so their movement during the period is genuine capital in/out).
$equityAccountIds = self::getAccountIdsByType($db, 'equity');
$equityStart = self::sumAccountBalances($db, $equityAccountIds, $dateBefore, $branchId);
$equityEnd = self::sumAccountBalances($db, $equityAccountIds, $dateTo, $branchId);
$equityCashEffect = bcsub($equityEnd, $equityStart, 2);
// Investing: cost of fixed-asset accounts only (accumulated depreciation is a
// pure allocation, already covered by the add-back above).
$fixedAssetCostIds = self::getFixedAssetCostAccountIds($db);
$fixedAssetsStart = self::sumAccountBalances($db, $fixedAssetCostIds, $dateBefore, $branchId);
$fixedAssetsEnd = self::sumAccountBalances($db, $fixedAssetCostIds, $dateTo, $branchId);
$capexCashEffect = bcmul(bcsub($fixedAssetsEnd, $fixedAssetsStart, 2), '-1', 2);
$operatingTotal = bcadd(bcadd(bcadd($netIncome, $depreciationAddBack, 2), $wcAssetsCashEffect, 2), $opLiabilitiesCashEffect, 2);
$investingTotal = $capexCashEffect;
$financingTotal = bcadd($finLiabilitiesCashEffect, $equityCashEffect, 2);
$classifiedTotal = bcadd(bcadd($operatingTotal, $investingTotal, 2), $financingTotal, 2);
$reconcilingItem = bcsub($netChangeInCash, $classifiedTotal, 2);
return [
'date_from' => $dateFrom,
'date_to' => $dateTo,
'operating' => [
'net_income' => $netIncome,
'depreciation_add_back' => $depreciationAddBack,
'working_capital_assets' => $wcAssetsCashEffect,
'operating_liabilities' => $opLiabilitiesCashEffect,
'total' => $operatingTotal,
],
'investing' => [
'fixed_assets_purchased_net' => $capexCashEffect,
'total' => $investingTotal,
],
'financing' => [
'loans_net' => $finLiabilitiesCashEffect,
'equity_net' => $equityCashEffect,
'total' => $financingTotal,
],
'reconciling_item' => $reconcilingItem,
'net_change_in_cash' => $netChangeInCash,
'cash_start' => $cashStart,
'cash_end' => $cashEnd,
];
}
/**
* Statement of Changes in Equity (قائمة التغيرات في حقوق الملكية).
*
* Ties exactly to getBalanceSheet()'s totals: opening total_equity (which already
* embeds accumulated profit from inception) + net income for the period + real
* equity-account movements during the period = closing total_equity.
*/
public static function getStatementOfChangesInEquity(
string $dateFrom,
string $dateTo,
?int $branchId = null
): array {
$db = App::getInstance()->db();
$dateBefore = date('Y-m-d', strtotime($dateFrom . ' -1 day'));
$openingSheet = self::getBalanceSheet($dateBefore, null, $branchId);
$closingSheet = self::getBalanceSheet($dateTo, null, $branchId);
$income = self::getIncomeStatement($dateFrom, $dateTo, null, $branchId);
$netIncomePeriod = $income['net_income'];
$accounts = $db->select(
"SELECT coa.id, coa.account_code, coa.name_ar, coa.name_en
FROM chart_of_accounts coa
WHERE coa.account_type = 'equity' AND coa.is_archived = 0 AND coa.is_header = 0
ORDER BY coa.account_code ASC"
);
$rows = [];
$totalOpening = '0.00';
$totalMovement = '0.00';
$totalClosing = '0.00';
foreach ($accounts as $acc) {
$opening = self::sumAccountBalances($db, [(int) $acc['id']], $dateBefore, $branchId);
$closing = self::sumAccountBalances($db, [(int) $acc['id']], $dateTo, $branchId);
$movement = bcsub($closing, $opening, 2);
if (bccomp($opening, '0.00', 2) === 0 && bccomp($closing, '0.00', 2) === 0) {
continue;
}
$rows[] = [
'account_code' => $acc['account_code'],
'name_ar' => $acc['name_ar'],
'name_en' => $acc['name_en'],
'opening' => $opening,
'movement' => $movement,
'closing' => $closing,
];
$totalOpening = bcadd($totalOpening, $opening, 2);
$totalMovement = bcadd($totalMovement, $movement, 2);
$totalClosing = bcadd($totalClosing, $closing, 2);
}
return [
'date_from' => $dateFrom,
'date_to' => $dateTo,
'accounts' => $rows,
'total_opening' => $totalOpening,
'capital_movement' => $totalMovement,
'net_income_period' => $netIncomePeriod,
'total_closing' => $totalClosing,
'opening_equity_total' => $openingSheet['total_equity'],
'closing_equity_total' => $closingSheet['total_equity'],
];
}
private static function getCashAccountIds($db): array
{
$rows = $db->select(
"SELECT id FROM chart_of_accounts
WHERE is_archived = 0 AND is_header = 0 AND account_type = 'asset'
AND (is_bank_account = 1
OR name_ar LIKE '%نقد%' OR name_ar LIKE '%صندوق%'
OR name_en LIKE '%Cash%' OR name_en LIKE '%Bank%')"
);
return array_map(fn($r) => (int) $r['id'], $rows);
}
private static function getFixedAssetCostAccountIds($db): array
{
$rows = $db->select(
"SELECT DISTINCT asset_account_id AS id FROM asset_categories WHERE asset_account_id IS NOT NULL"
);
return array_map(fn($r) => (int) $r['id'], $rows);
}
private static function getFixedAssetAccountIds($db): array
{
$rows = $db->select(
"SELECT asset_account_id AS id FROM asset_categories WHERE asset_account_id IS NOT NULL
UNION SELECT depreciation_account_id AS id FROM asset_categories WHERE depreciation_account_id IS NOT NULL"
);
return array_map(fn($r) => (int) $r['id'], $rows);
}
private static function getLoanLikeAccountIds($db, string $accountType): array
{
$rows = $db->select(
"SELECT id FROM chart_of_accounts
WHERE is_archived = 0 AND is_header = 0 AND account_type = ?
AND (name_ar LIKE '%قرض%' OR name_ar LIKE '%سلف%' OR name_en LIKE '%Loan%' OR name_en LIKE '%Note Payable%')",
[$accountType]
);
return array_map(fn($r) => (int) $r['id'], $rows);
}
private static function getAccountIdsByType($db, string $accountType): array
{
$rows = $db->select(
"SELECT id FROM chart_of_accounts WHERE is_archived = 0 AND is_header = 0 AND account_type = ?",
[$accountType]
);
return array_map(fn($r) => (int) $r['id'], $rows);
}
/**
* Cumulative posted balance of a specific set of accounts as of a date,
* respecting each account's natural debit/credit sign.
*/
private static function sumAccountBalances($db, array $accountIds, string $asOfDate, ?int $branchId): string
{
if (empty($accountIds)) return '0.00';
return self::balanceQuery($db, 'coa.id IN (' . implode(',', array_fill(0, count($accountIds), '?')) . ')', $accountIds, $asOfDate, $branchId);
}
/**
* Cumulative posted balance of all accounts of a given type, excluding a set of ids.
*/
private static function sumAccountBalancesByType($db, string $accountType, array $excludeIds, string $asOfDate, ?int $branchId): string
{
$where = 'coa.account_type = ?';
$params = [$accountType];
if (!empty($excludeIds)) {
$where .= ' AND coa.id NOT IN (' . implode(',', array_fill(0, count($excludeIds), '?')) . ')';
$params = array_merge($params, $excludeIds);
}
return self::balanceQuery($db, $where, $params, $asOfDate, $branchId);
}
private static function balanceQuery($db, string $accountWhere, array $accountParams, string $asOfDate, ?int $branchId): string
{
$branchWhere = '';
$params = [$asOfDate];
if ($branchId !== null) {
$branchWhere = ' AND jel.branch_id = ?';
$params[] = $branchId;
}
$params = array_merge($params, $accountParams);
// Accounts of mixed nature would need a per-row sum; in practice a given
// filter set here is always single-nature (all-debit assets, all-credit
// liabilities/equity), so grouping collapses to at most one row.
$rows = $db->select(
"SELECT coa.account_nature,
COALESCE(SUM(jel.debit), 0) as total_debit,
COALESCE(SUM(jel.credit), 0) as total_credit
FROM chart_of_accounts coa
LEFT JOIN journal_entry_lines jel ON jel.account_id = coa.id
AND jel.journal_entry_id IN (
SELECT id FROM journal_entries
WHERE status = 'posted' AND entry_date <= ? AND is_archived = 0
)
{$branchWhere}
WHERE coa.is_archived = 0 AND {$accountWhere}
GROUP BY coa.account_nature",
$params
);
$total = '0.00';
foreach ($rows as $r) {
$balance = $r['account_nature'] === 'debit'
? bcsub((string) $r['total_debit'], (string) $r['total_credit'], 2)
: bcsub((string) $r['total_credit'], (string) $r['total_debit'], 2);
$total = bcadd($total, $balance, 2);
}
return $total;
}
private static function getFiscalYearStart(string $date): string private static function getFiscalYearStart(string $date): string
{ {
$config = App::getInstance()->config('app'); $config = App::getInstance()->config('app');
......
...@@ -74,6 +74,8 @@ ...@@ -74,6 +74,8 @@
<a href="/accounting/reports/trial-balance" class="btn btn-outline">ميزان المراجعة</a> <a href="/accounting/reports/trial-balance" class="btn btn-outline">ميزان المراجعة</a>
<a href="/accounting/reports/income-statement" class="btn btn-outline">قائمة الدخل</a> <a href="/accounting/reports/income-statement" class="btn btn-outline">قائمة الدخل</a>
<a href="/accounting/reports/balance-sheet" class="btn btn-outline">الميزانية العمومية</a> <a href="/accounting/reports/balance-sheet" class="btn btn-outline">الميزانية العمومية</a>
<a href="/accounting/reports/cash-flow" class="btn btn-outline">قائمة التدفقات النقدية</a>
<a href="/accounting/reports/equity-statement" class="btn btn-outline">التغيرات في حقوق الملكية</a>
<a href="/accounting/reports/treasury" class="btn btn-outline">الخزينة والمدفوعات</a> <a href="/accounting/reports/treasury" class="btn btn-outline">الخزينة والمدفوعات</a>
<a href="/accounting/reports/revenue-analysis" class="btn btn-outline">تحليل الإيرادات</a> <a href="/accounting/reports/revenue-analysis" class="btn btn-outline">تحليل الإيرادات</a>
<?php if (!empty($is_super_admin)): ?> <?php if (!empty($is_super_admin)): ?>
......
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>قائمة التدفقات النقدية<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Filters -->
<div class="card" style="margin-bottom:15px;">
<div style="padding:15px 20px;">
<form method="GET" action="/accounting/reports/cash-flow" style="display:flex;gap:10px;flex-wrap:wrap;align-items:end;">
<div>
<label class="form-label" style="font-size:12px;">من تاريخ</label>
<input type="date" name="date_from" class="form-input" value="<?= e($date_from) ?>">
</div>
<div>
<label class="form-label" style="font-size:12px;">إلى تاريخ</label>
<input type="date" name="date_to" class="form-input" value="<?= e($date_to) ?>">
</div>
<div>
<label class="form-label" style="font-size:12px;">الفرع</label>
<select name="branch_id" class="form-select">
<option value="">الكل</option>
<?php foreach ($branches as $b): ?>
<option value="<?= (int)$b['id'] ?>" <?= (int)($filters['branch_id'] ?? 0) === (int)$b['id'] ? 'selected' : '' ?>><?= e($b['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary">عرض</button>
</form>
</div>
</div>
<div class="card">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;text-align:center;">
<h2 style="margin:0;">قائمة التدفقات النقدية</h2>
<p style="color:#6B7280;margin:5px 0 0;">من <?= e($date_from) ?> إلى <?= e($date_to) ?></p>
</div>
<div style="padding:20px;max-width:700px;margin:0 auto;">
<h3 style="color:#0D7377;border-bottom:2px solid #0D7377;padding-bottom:8px;">الأنشطة التشغيلية</h3>
<table style="width:100%;">
<tr><td style="padding:5px 0;">صافي الدخل للفترة</td><td style="padding:5px 0;direction:ltr;text-align:left;"><?= money($result['operating']['net_income']) ?></td></tr>
<tr><td style="padding:5px 0;">إضافة: مصروف الإهلاك (غير نقدي)</td><td style="padding:5px 0;direction:ltr;text-align:left;"><?= money($result['operating']['depreciation_add_back']) ?></td></tr>
<tr><td style="padding:5px 0;">التغير في الأصول المتداولة (غير النقدية)</td><td style="padding:5px 0;direction:ltr;text-align:left;"><?= money($result['operating']['working_capital_assets']) ?></td></tr>
<tr><td style="padding:5px 0;">التغير في الخصوم التشغيلية</td><td style="padding:5px 0;direction:ltr;text-align:left;"><?= money($result['operating']['operating_liabilities']) ?></td></tr>
<tr style="font-weight:700;border-top:1px solid #0D7377;">
<td style="padding:10px 0;">صافي النقد من الأنشطة التشغيلية</td>
<td style="padding:10px 0;direction:ltr;text-align:left;color:#0D7377;"><?= money($result['operating']['total']) ?></td>
</tr>
</table>
<h3 style="color:#EA580C;border-bottom:2px solid #EA580C;padding-bottom:8px;margin-top:20px;">الأنشطة الاستثمارية</h3>
<table style="width:100%;">
<tr><td style="padding:5px 0;">شراء أصول ثابتة (صافي)</td><td style="padding:5px 0;direction:ltr;text-align:left;"><?= money($result['investing']['fixed_assets_purchased_net']) ?></td></tr>
<tr style="font-weight:700;border-top:1px solid #EA580C;">
<td style="padding:10px 0;">صافي النقد من الأنشطة الاستثمارية</td>
<td style="padding:10px 0;direction:ltr;text-align:left;color:#EA580C;"><?= money($result['investing']['total']) ?></td>
</tr>
</table>
<h3 style="color:#7C3AED;border-bottom:2px solid #7C3AED;padding-bottom:8px;margin-top:20px;">الأنشطة التمويلية</h3>
<table style="width:100%;">
<tr><td style="padding:5px 0;">صافي حركة القروض</td><td style="padding:5px 0;direction:ltr;text-align:left;"><?= money($result['financing']['loans_net']) ?></td></tr>
<tr><td style="padding:5px 0;">صافي حركة رأس المال</td><td style="padding:5px 0;direction:ltr;text-align:left;"><?= money($result['financing']['equity_net']) ?></td></tr>
<tr style="font-weight:700;border-top:1px solid #7C3AED;">
<td style="padding:10px 0;">صافي النقد من الأنشطة التمويلية</td>
<td style="padding:10px 0;direction:ltr;text-align:left;color:#7C3AED;"><?= money($result['financing']['total']) ?></td>
</tr>
</table>
<?php if (bccomp($result['reconciling_item'], '0.00', 2) !== 0): ?>
<table style="width:100%;margin-top:15px;">
<tr>
<td style="padding:5px 0;color:#6B7280;">بنود أخرى غير مصنفة (تسوية)</td>
<td style="padding:5px 0;direction:ltr;text-align:left;color:#6B7280;"><?= money($result['reconciling_item']) ?></td>
</tr>
</table>
<?php endif; ?>
<div style="font-weight:700;border-top:3px double #374151;padding:12px 0;margin-top:15px;display:flex;justify-content:space-between;">
<span>صافي التغير في النقدية</span>
<span style="direction:ltr;font-size:20px;"><?= money($result['net_change_in_cash']) ?></span>
</div>
<table style="width:100%;margin-top:10px;color:#6B7280;font-size:13px;">
<tr><td style="padding:3px 0;">النقدية في بداية الفترة</td><td style="padding:3px 0;direction:ltr;text-align:left;"><?= money($result['cash_start']) ?></td></tr>
<tr><td style="padding:3px 0;">النقدية في نهاية الفترة</td><td style="padding:3px 0;direction:ltr;text-align:left;"><?= money($result['cash_end']) ?></td></tr>
</table>
</div>
</div>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>قائمة التغيرات في حقوق الملكية<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Filters -->
<div class="card" style="margin-bottom:15px;">
<div style="padding:15px 20px;">
<form method="GET" action="/accounting/reports/equity-statement" style="display:flex;gap:10px;flex-wrap:wrap;align-items:end;">
<div>
<label class="form-label" style="font-size:12px;">من تاريخ</label>
<input type="date" name="date_from" class="form-input" value="<?= e($date_from) ?>">
</div>
<div>
<label class="form-label" style="font-size:12px;">إلى تاريخ</label>
<input type="date" name="date_to" class="form-input" value="<?= e($date_to) ?>">
</div>
<div>
<label class="form-label" style="font-size:12px;">الفرع</label>
<select name="branch_id" class="form-select">
<option value="">الكل</option>
<?php foreach ($branches as $b): ?>
<option value="<?= (int)$b['id'] ?>" <?= (int)($filters['branch_id'] ?? 0) === (int)$b['id'] ? 'selected' : '' ?>><?= e($b['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary">عرض</button>
</form>
</div>
</div>
<div class="card">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;text-align:center;">
<h2 style="margin:0;">قائمة التغيرات في حقوق الملكية</h2>
<p style="color:#6B7280;margin:5px 0 0;">من <?= e($date_from) ?> إلى <?= e($date_to) ?></p>
</div>
<div style="padding:20px;">
<div class="table-responsive">
<table class="data-table" style="width:100%;">
<thead>
<tr>
<th>الحساب</th>
<th>الرصيد الافتتاحي</th>
<th>الحركة خلال الفترة</th>
<th>الرصيد الختامي</th>
</tr>
</thead>
<tbody>
<?php foreach ($result['accounts'] as $acc): ?>
<tr>
<td><span style="color:#6B7280;font-size:12px;direction:ltr;"><?= e($acc['account_code']) ?></span> <?= e($acc['name_ar']) ?></td>
<td style="direction:ltr;text-align:left;"><?= money($acc['opening']) ?></td>
<td style="direction:ltr;text-align:left;"><?= money($acc['movement']) ?></td>
<td style="direction:ltr;text-align:left;font-weight:600;"><?= money($acc['closing']) ?></td>
</tr>
<?php endforeach; ?>
<tr style="font-weight:600;border-top:1px solid #7C3AED;">
<td>إجمالي حسابات رأس المال المسجلة</td>
<td style="direction:ltr;text-align:left;"><?= money($result['total_opening']) ?></td>
<td style="direction:ltr;text-align:left;"><?= money($result['capital_movement']) ?></td>
<td style="direction:ltr;text-align:left;"><?= money($result['total_closing']) ?></td>
</tr>
<tr>
<td colspan="2"></td>
<td style="padding-top:10px;">صافي ربح الفترة (غير مقفل)</td>
<td style="direction:ltr;text-align:left;padding-top:10px;"><?= money($result['net_income_period']) ?></td>
</tr>
</tbody>
</table>
</div>
<div style="margin-top:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="card" style="padding:15px 20px;background:#F9FAFB;">
<div style="color:#6B7280;font-size:13px;">إجمالي حقوق الملكية — بداية الفترة</div>
<div style="font-size:22px;font-weight:700;direction:ltr;text-align:left;"><?= money($result['opening_equity_total']) ?></div>
</div>
<div class="card" style="padding:15px 20px;background:#F0FDF4;">
<div style="color:#6B7280;font-size:13px;">إجمالي حقوق الملكية — نهاية الفترة</div>
<div style="font-size:22px;font-weight:700;direction:ltr;text-align:left;color:#059669;"><?= money($result['closing_equity_total']) ?></div>
</div>
</div>
</div>
</div>
<?php $__template->endSection(); ?>
...@@ -20,6 +20,8 @@ PermissionRegistry::register('accounting', [ ...@@ -20,6 +20,8 @@ PermissionRegistry::register('accounting', [
'accounting.reports.income_statement' => ['ar' => 'قائمة الدخل', 'en' => 'Income Statement'], 'accounting.reports.income_statement' => ['ar' => 'قائمة الدخل', 'en' => 'Income Statement'],
'accounting.reports.balance_sheet' => ['ar' => 'الميزانية العمومية', 'en' => 'Balance Sheet'], 'accounting.reports.balance_sheet' => ['ar' => 'الميزانية العمومية', 'en' => 'Balance Sheet'],
'accounting.reports.consolidated' => ['ar' => 'ميزانية موحدة', 'en' => 'Consolidated Balance Sheet'], 'accounting.reports.consolidated' => ['ar' => 'ميزانية موحدة', 'en' => 'Consolidated Balance Sheet'],
'accounting.reports.cash_flow' => ['ar' => 'قائمة التدفقات النقدية', 'en' => 'Cash Flow Statement'],
'accounting.reports.equity_statement' => ['ar' => 'قائمة التغيرات في حقوق الملكية', 'en' => 'Statement of Changes in Equity'],
'accounting.reports.ar' => ['ar' => 'تقرير المدينين', 'en' => 'Accounts Receivable Report'], 'accounting.reports.ar' => ['ar' => 'تقرير المدينين', 'en' => 'Accounts Receivable Report'],
'accounting.reports.ap' => ['ar' => 'تقرير الدائنين', 'en' => 'Accounts Payable Report'], 'accounting.reports.ap' => ['ar' => 'تقرير الدائنين', 'en' => 'Accounts Payable Report'],
'accounting.reports.member_statement' => ['ar' => 'كشف حساب عضو', 'en' => 'Member Statement'], 'accounting.reports.member_statement' => ['ar' => 'كشف حساب عضو', 'en' => 'Member Statement'],
...@@ -185,6 +187,8 @@ MenuRegistry::register('accounting', [ ...@@ -185,6 +187,8 @@ MenuRegistry::register('accounting', [
['label_ar' => 'قائمة الدخل', 'label_en' => 'Income Statement', 'route' => '/accounting/reports/income-statement', 'permission' => 'accounting.reports.income_statement','order' => 11], ['label_ar' => 'قائمة الدخل', 'label_en' => 'Income Statement', 'route' => '/accounting/reports/income-statement', 'permission' => 'accounting.reports.income_statement','order' => 11],
['label_ar' => 'الميزانية العمومية', 'label_en' => 'Balance Sheet', 'route' => '/accounting/reports/balance-sheet', 'permission' => 'accounting.reports.balance_sheet', 'order' => 12], ['label_ar' => 'الميزانية العمومية', 'label_en' => 'Balance Sheet', 'route' => '/accounting/reports/balance-sheet', 'permission' => 'accounting.reports.balance_sheet', 'order' => 12],
['label_ar' => 'ميزانية موحدة', 'label_en' => 'Consolidated BS', 'route' => '/accounting/reports/consolidated-balance-sheet', 'permission' => 'accounting.reports.consolidated', 'order' => 13], ['label_ar' => 'ميزانية موحدة', 'label_en' => 'Consolidated BS', 'route' => '/accounting/reports/consolidated-balance-sheet', 'permission' => 'accounting.reports.consolidated', 'order' => 13],
['label_ar' => 'قائمة التدفقات النقدية', 'label_en' => 'Cash Flow Statement', 'route' => '/accounting/reports/cash-flow', 'permission' => 'accounting.reports.cash_flow', 'order' => 14],
['label_ar' => 'قائمة التغيرات في حقوق الملكية', 'label_en' => 'Statement of Changes in Equity', 'route' => '/accounting/reports/equity-statement', 'permission' => 'accounting.reports.equity_statement', 'order' => 15],
['label_ar' => 'المدينون (AR)', 'label_en' => 'Accounts Receivable', 'route' => '/accounting/reports/accounts-receivable', 'permission' => 'accounting.reports.ar', 'order' => 14], ['label_ar' => 'المدينون (AR)', 'label_en' => 'Accounts Receivable', 'route' => '/accounting/reports/accounts-receivable', 'permission' => 'accounting.reports.ar', 'order' => 14],
['label_ar' => 'الدائنون (AP)', 'label_en' => 'Accounts Payable', 'route' => '/accounting/reports/accounts-payable', 'permission' => 'accounting.reports.ap', 'order' => 15], ['label_ar' => 'الدائنون (AP)', 'label_en' => 'Accounts Payable', 'route' => '/accounting/reports/accounts-payable', 'permission' => 'accounting.reports.ap', 'order' => 15],
['label_ar' => 'كشف حساب عضو', 'label_en' => 'Member Statement', 'route' => '/accounting/reports/member-statement', 'permission' => 'accounting.reports.member_statement','order' => 16], ['label_ar' => 'كشف حساب عضو', 'label_en' => 'Member Statement', 'route' => '/accounting/reports/member-statement', 'permission' => 'accounting.reports.member_statement','order' => 16],
......
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