Commit 34bbb7fa authored by Mahmoud Aglan's avatar Mahmoud Aglan

push all

parent e4076074
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -6,6 +6,7 @@ namespace App\Modules\Accounting\Controllers; ...@@ -6,6 +6,7 @@ namespace App\Modules\Accounting\Controllers;
use App\Core\Controller; use App\Core\Controller;
use App\Core\Request; use App\Core\Request;
use App\Core\App; use App\Core\App;
use App\Modules\Accounting\Services\JournalService;
use App\Core\Response; use App\Core\Response;
use App\Modules\Accounting\Models\HistoricalBalance; use App\Modules\Accounting\Models\HistoricalBalance;
...@@ -64,7 +65,6 @@ class OpeningEntryController extends Controller ...@@ -64,7 +65,6 @@ class OpeningEntryController extends Controller
} }
$db = App::getInstance()->db(); $db = App::getInstance()->db();
$session = App::getInstance()->session();
$fy = $db->selectOne("SELECT * FROM fiscal_years WHERE id = ? AND status = 'open'", [$fiscalYearId]); $fy = $db->selectOne("SELECT * FROM fiscal_years WHERE id = ? AND status = 'open'", [$fiscalYearId]);
if (!$fy) { if (!$fy) {
...@@ -100,35 +100,26 @@ class OpeningEntryController extends Controller ...@@ -100,35 +100,26 @@ class OpeningEntryController extends Controller
->withError('القيد غير متوازن — المدين: ' . number_format($totalDebit, 2) . ' الدائن: ' . number_format($totalCredit, 2)); ->withError('القيد غير متوازن — المدين: ' . number_format($totalDebit, 2) . ' الدائن: ' . number_format($totalCredit, 2));
} }
$now = date('Y-m-d H:i:s'); // Through JournalService, not around it: the fiscal-year and closed-period
$entryNumber = 'OPN-' . substr($fy['start_date'], 0, 4) . '-001'; // checks, the balance validation and the account-balance maintenance all
// live there, and an opening entry is not exempt from any of them.
$db->insert('journal_entries', [ $result = JournalService::createEntry([
'entry_number' => $entryNumber,
'entry_date' => $fy['start_date'], 'entry_date' => $fy['start_date'],
'fiscal_year_id' => $fiscalYearId,
'reference_type' => 'opening', 'reference_type' => 'opening',
'description' => 'قيد افتتاحي — ' . ($fy['name_ar'] ?? ''), 'reference_number' => 'OPN-' . substr((string) $fy['start_date'], 0, 4),
'total_debit' => $totalDebit, 'description_ar' => 'قيد افتتاحي — ' . ($fy['name_ar'] ?? ''),
'total_credit' => $totalCredit, 'source_module' => 'accounting',
'status' => 'posted', 'is_auto_generated' => 0,
'posted_at' => $now, ], array_map(static fn(array $l): array => [
'created_by' => (int)($session->get('employee_id') ?? 0) ?: null, 'account_id' => $l['account_id'],
'created_at' => $now, 'debit' => number_format($l['debit'], 2, '.', ''),
'updated_at' => $now, 'credit' => number_format($l['credit'], 2, '.', ''),
]); 'description_ar' => $l['description'],
], $lines), true);
$journalEntryId = (int)$db->lastInsertId();
if (empty($result['success'])) {
foreach ($lines as $line) { return $this->redirect('/accounting/opening-entries/create')
$db->insert('journal_entry_lines', [ ->withError($result['error'] ?? 'فشل إنشاء القيد الافتتاحي');
'journal_entry_id' => $journalEntryId,
'account_id' => $line['account_id'],
'debit' => $line['debit'],
'credit' => $line['credit'],
'description_ar' => $line['description'],
'created_at' => $now,
]);
} }
return $this->redirect('/accounting/opening-entries?fiscal_year_id=' . $fiscalYearId) return $this->redirect('/accounting/opening-entries?fiscal_year_id=' . $fiscalYearId)
......
...@@ -1455,6 +1455,10 @@ class RevenueMappingController extends Controller ...@@ -1455,6 +1455,10 @@ class RevenueMappingController extends Controller
'description_ar' => ($row['description_ar'] ?? '') !== '' ? (string) $row['description_ar'] : null, 'description_ar' => ($row['description_ar'] ?? '') !== '' ? (string) $row['description_ar'] : null,
'max_amount' => ($row['max_amount'] ?? '') !== '' ? (string) $row['max_amount'] : null, 'max_amount' => ($row['max_amount'] ?? '') !== '' ? (string) $row['max_amount'] : null,
'min_amount' => null, 'min_amount' => null,
'is_appropriation' => !empty($row['is_appropriation']) ? 1 : 0,
'appropriation_source_account_id' => !empty($row['appropriation_source_account_id'])
? (int) $row['appropriation_source_account_id'] : null,
'revenue_account_id' => !empty($row['revenue_account_id']) ? (int) $row['revenue_account_id'] : null,
'is_active' => 1, 'is_active' => 1,
]; ];
} }
......
...@@ -49,52 +49,29 @@ class DailyTransactionService ...@@ -49,52 +49,29 @@ class DailyTransactionService
]; ];
} }
$fy = $db->selectOne("SELECT id FROM fiscal_years WHERE is_current = 1 AND is_archived = 0"); // Through JournalService: a consolidated daily entry must respect the
if (!$fy) { // closed-period lock and maintain account balances like any other.
return null; $result = JournalService::createEntry([
}
$now = date('Y-m-d H:i:s');
$entryNumber = 'DT-' . str_replace('-', '', $date) . '-001';
$existing = $db->selectOne("SELECT id FROM journal_entries WHERE entry_number = ?", [$entryNumber]);
if ($existing) {
$suffix = $db->selectOne(
"SELECT COUNT(*) as cnt FROM journal_entries WHERE entry_number LIKE ?",
['DT-' . str_replace('-', '', $date) . '%']
);
$num = ((int)($suffix['cnt'] ?? 0)) + 1;
$entryNumber = 'DT-' . str_replace('-', '', $date) . '-' . str_pad((string)$num, 3, '0', STR_PAD_LEFT);
}
$db->insert('journal_entries', [
'entry_number' => $entryNumber,
'entry_date' => $date, 'entry_date' => $date,
'fiscal_year_id' => (int)$fy['id'],
'reference_type' => 'daily_consolidated', 'reference_type' => 'daily_consolidated',
'description' => 'قيد مجمع — حركات يومية ' . $date, 'reference_number' => 'DT-' . str_replace('-', '', $date),
'total_debit' => $totalDebit, 'description_ar' => 'قيد مجمع — حركات يومية ' . $date,
'total_credit' => $totalCredit, 'source_module' => 'accounting',
'status' => 'posted', 'is_auto_generated' => 1,
'posted_at' => $now, ], array_map(static fn(array $l): array => [
'created_at' => $now, 'account_id' => $l['account_id'],
'updated_at' => $now, 'debit' => number_format((float) $l['debit'], 2, '.', ''),
]); 'credit' => number_format((float) $l['credit'], 2, '.', ''),
'cost_center_id' => $l['cost_center_id'],
$journalEntryId = (int)$db->lastInsertId(); 'description_ar' => $l['description_ar'],
], $lines), true);
foreach ($lines as $line) {
$db->insert('journal_entry_lines', [ if (empty($result['success'])) {
'journal_entry_id' => $journalEntryId, return null;
'account_id' => $line['account_id'],
'debit' => $line['debit'],
'credit' => $line['credit'],
'cost_center_id' => $line['cost_center_id'],
'description_ar' => $line['description_ar'],
'created_at' => $now,
]);
} }
$journalEntryId = (int) $result['journal_entry_id'];
$txIds = array_column($transactions, 'id'); $txIds = array_column($transactions, 'id');
if (!empty($txIds)) { if (!empty($txIds)) {
$placeholders = implode(',', array_fill(0, count($txIds), '?')); $placeholders = implode(',', array_fill(0, count($txIds), '?'));
......
...@@ -209,10 +209,15 @@ final class JournalService ...@@ -209,10 +209,15 @@ final class JournalService
return ['success' => false, 'error' => 'القيد ملغي — لا يمكن ترحيله']; return ['success' => false, 'error' => 'القيد ملغي — لا يمكن ترحيله'];
} }
// Re-validate period is open // Re-validate the period AND the year. A draft raised while both were
// open must not slip into a year that has since been closed.
$fiscalYear = FiscalYear::find((int) $entry->fiscal_year_id);
if (!$fiscalYear || !$fiscalYear->isOpen()) {
return ['success' => false, 'error' => 'السنة المالية مغلقة — لا يمكن ترحيل القيد'];
}
$period = substr($entry->entry_date, 0, 7); $period = substr($entry->entry_date, 0, 7);
if (PeriodClosing::isPeriodClosed((int) $entry->fiscal_year_id, $period)) { if (PeriodClosing::isPeriodClosed((int) $entry->fiscal_year_id, $period)) {
return ['success' => false, 'error' => 'الفترة مغلقة — لا يمكن ترحيل القيد']; return ['success' => false, 'error' => 'الفترة ' . $period . ' مغلقة — لا يمكن ترحيل القيد'];
} }
$db->beginTransaction(); $db->beginTransaction();
......
...@@ -340,11 +340,53 @@ final class AllocationPlanService ...@@ -340,11 +340,53 @@ final class AllocationPlanService
$warnings[] = 'تاريخ السريان قديم جدًا — تأكد إنه مقصود'; $warnings[] = 'تاريخ السريان قديم جدًا — تأكد إنه مقصود';
} }
// ── Appropriation lines ─────────────────────────────────────
// An earmark is not a different kind of revenue. The main entry credits
// revenue in full; the share is moved out of equity separately. So an
// appropriation line needs BOTH ends of that second entry.
foreach ($lines as $i => $l) {
if (empty($l['is_appropriation'])) {
continue;
}
$n = $i + 1;
if ($l['allocation_method'] === 'remainder') {
$errors[] = "البند {$n}: بند «الباقي» ما ينفعش يكون تخصيص — التخصيص لازم نسبة أو مبلغ محدد";
}
if ((int) ($l['revenue_account_id'] ?? 0) <= 0) {
$errors[] = "البند {$n}: التخصيص محتاج حساب الإيراد اللي هيتسجّل فيه المبلغ كامل الأول";
}
if ((int) ($l['appropriation_source_account_id'] ?? 0) <= 0) {
$errors[] = "البند {$n}: التخصيص محتاج حساب مصدر التخصيص (أرباح مرحّلة أو حساب تخصيص)";
}
if (($l['line_type'] ?? '') === 'revenue') {
$errors[] = "البند {$n}: بند التخصيص وجهته صندوق أو احتياطي — مش حساب إيراد";
}
}
// ── Each target, against its own direction ────────────────── // ── Each target, against its own direction ──────────────────
if (!$targets) { if (!$targets) {
$errors[] = 'ما فيش أي مصدر إيراد مطابق للنطاق اللي اخترته'; $errors[] = 'ما فيش أي مصدر إيراد مطابق للنطاق اللي اخترته';
} }
$enforceAccrual = self::setting('accounting.enforce_accrual_before_collection', '1') === '1';
$deferCategories = array_filter(array_map(
'trim',
explode(',', self::setting('accounting.deferral_required_categories', 'subscription'))
));
// Line types that put revenue into the income statement right now.
$recognisesRevenue = static function (array $l) use ($accounts): bool {
if (!empty($l['is_appropriation'])) {
return true; // the main entry still credits revenue
}
if (($l['line_type'] ?? '') !== 'revenue') {
return false;
}
$a = $accounts[(int) ($l['account_id'] ?? 0)] ?? null;
return $a === null || $a['account_type'] === 'revenue';
};
$seenDirections = []; $seenDirections = [];
foreach ($targets as $t) { foreach ($targets as $t) {
$s = $t['stream']; $s = $t['stream'];
...@@ -373,6 +415,40 @@ final class AllocationPlanService ...@@ -373,6 +415,40 @@ final class AllocationPlanService
. '" ومش مناسب لبند "' . $allowedTypes[$lt]['label'] . '"'; . '" ومش مناسب لبند "' . $allowedTypes[$lt]['label'] . '"';
} }
} }
// ── Revenue is earned once, not twice ───────────────
// If the stream already recognises revenue when the club raises
// the claim, then collection is a cash-for-receivable swap. A
// revenue line here would book the same income a second time and
// leave the receivable outstanding for ever.
if ($enforceAccrual && $stage === 'collection' && self::recognisesAtAccrual((int) $s['id'])) {
foreach ($lines as $i => $l) {
if ($recognisesRevenue($l)) {
$errors[] = $s['name_ar'] . ': البند ' . ($i + 1)
. ' بيسجّل إيراد وقت التحصيل، والمصدر ده بيسجّل إيراده وقت الاستحقاق. '
. 'التحصيل هنا تسوية ذمم — استخدم نوع البند «تسوية ذمم مدينة».';
break;
}
}
}
// ── Cash received ≠ revenue earned ──────────────────
// A year's subscription collected today is a promise to serve for
// a year, not a year's income. EAS 48 / IFRS 15 require deferral.
if (\in_array($s['category'], $deferCategories, true)
&& \in_array($stage, ['collection', 'accrual'], true)) {
foreach ($lines as $i => $l) {
if (($l['line_type'] ?? '') === 'revenue'
&& ($l['recognition_method'] ?? 'immediate') === 'immediate'
&& empty($l['is_appropriation'])) {
$errors[] = $s['name_ar'] . ' ('
. (self::CATEGORY_LABELS[$s['category']] ?? $s['category']) . '): البند ' . ($i + 1)
. ' بيسجّل الإيراد كامل فورًا. الفئة دي الخدمة فيها بتتقدّم على مدى فترة، '
. 'فلازم نوع البند يكون «إيراد مؤجل» ويترحّل شهريًا.';
break;
}
}
}
} }
} }
...@@ -493,6 +569,9 @@ final class AllocationPlanService ...@@ -493,6 +569,9 @@ final class AllocationPlanService
'recognized_account_id' => self::nullableInt($l['recognized_account_id'] ?? null), 'recognized_account_id' => self::nullableInt($l['recognized_account_id'] ?? null),
'description_ar' => self::nullableString($l['description_ar'] ?? null), 'description_ar' => self::nullableString($l['description_ar'] ?? null),
'max_amount' => self::nullableString($l['max_amount'] ?? null), 'max_amount' => self::nullableString($l['max_amount'] ?? null),
'is_appropriation' => !empty($l['is_appropriation']) ? 1 : 0,
'appropriation_source_account_id' => self::nullableInt($l['appropriation_source_account_id'] ?? null),
'revenue_account_id' => self::nullableInt($l['revenue_account_id'] ?? null),
'is_active' => 1, 'is_active' => 1,
'created_at' => $now, 'created_at' => $now,
'updated_at' => $now, 'updated_at' => $now,
...@@ -601,6 +680,54 @@ final class AllocationPlanService ...@@ -601,6 +680,54 @@ final class AllocationPlanService
// ──────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────
/**
* Does this stream already put revenue in the income statement at accrual?
* If it does, collection must not do it again.
*/
public static function recognisesAtAccrual(int $streamId): bool
{
$db = App::getInstance()->db();
$row = $db->selectOne(
"SELECT 1 AS ok
FROM revenue_posting_rules r
JOIN revenue_posting_rule_lines l ON l.rule_id = r.id AND l.is_active = 1
JOIN chart_of_accounts c ON c.id = l.account_id
WHERE r.stream_id = ?
AND r.stage = 'accrual'
AND r.status = 'active'
AND r.effective_from <= CURDATE()
AND (r.effective_to IS NULL OR r.effective_to >= CURDATE())
AND (l.line_type = 'revenue' OR l.is_appropriation = 1)
AND c.account_type = 'revenue'
LIMIT 1",
[$streamId]
);
return $row !== null;
}
/** A policy switch out of system_config, so finance can change it. */
public static function setting(string $key, string $default): string
{
static $cache = [];
if (\array_key_exists($key, $cache)) {
return $cache[$key];
}
try {
$row = App::getInstance()->db()->selectOne(
"SELECT config_value FROM system_config WHERE config_key = ?",
[$key]
);
$cache[$key] = $row !== null && $row['config_value'] !== null
? (string) $row['config_value']
: $default;
} catch (\Throwable) {
$cache[$key] = $default; // config table missing before its migration
}
return $cache[$key];
}
private static function isDate(string $d): bool private static function isDate(string $d): bool
{ {
return (bool) preg_match('/^\d{4}-\d{2}-\d{2}$/', $d) && strtotime($d) !== false; return (bool) preg_match('/^\d{4}-\d{2}-\d{2}$/', $d) && strtotime($d) !== false;
......
...@@ -246,6 +246,13 @@ final class RevenueAllocator ...@@ -246,6 +246,13 @@ final class RevenueAllocator
'recognized_account_id' => isset($line['recognized_account_id']) && $line['recognized_account_id'] 'recognized_account_id' => isset($line['recognized_account_id']) && $line['recognized_account_id']
? (int) $line['recognized_account_id'] ? (int) $line['recognized_account_id']
: null, : null,
'is_appropriation' => !empty($line['is_appropriation']),
'appropriation_source_account_id' => isset($line['appropriation_source_account_id']) && $line['appropriation_source_account_id']
? (int) $line['appropriation_source_account_id']
: null,
'revenue_account_id' => isset($line['revenue_account_id']) && $line['revenue_account_id']
? (int) $line['revenue_account_id']
: null,
]; ];
} }
......
...@@ -79,7 +79,82 @@ final class RevenuePostingEngine ...@@ -79,7 +79,82 @@ final class RevenuePostingEngine
RevenueRecognitionService::schedule($plan['deferrals'], $ctx, $entryId, $plan['stream']['id'] ?? null); RevenueRecognitionService::schedule($plan['deferrals'], $ctx, $entryId, $plan['stream']['id'] ?? null);
} }
return ['success' => true, 'journal_entry_id' => $entryId, 'used_rule' => true]; // Earmarks move AFTER the income is recognised, as a transfer out of
// equity — never as a reduction of the revenue itself. Its own entry, so
// the income statement and the appropriation read separately.
$appropriationEntryId = null;
if (!empty($plan['appropriations'])) {
$appropriationEntryId = self::postAppropriations($plan, $ctx, $entryId);
}
return [
'success' => true,
'journal_entry_id' => $entryId,
'appropriation_entry_id' => $appropriationEntryId,
'used_rule' => true,
];
}
/**
* Post the earmark transfers as one separate entry:
* Dr appropriation source (retained earnings / appropriation account)
* Cr the fund
*
* A failure here does not undo the revenue entry — the income was genuinely
* earned and recognised. It is logged so the transfer can be re-run.
*/
private static function postAppropriations(array $plan, array $ctx, int $originEntryId): ?int
{
$lines = [];
foreach ($plan['appropriations'] as $ap) {
if (bccomp($ap['amount'], '0.00', self::SCALE) <= 0) {
continue;
}
$lines[] = [
'account_id' => $ap['source_account_id'],
'debit' => $ap['amount'],
'credit' => '0.00',
'description_ar' => $ap['description_ar'],
'cost_center_id' => $ap['cost_center_id'],
'branch_id' => $ap['branch_id'],
];
$lines[] = [
'account_id' => $ap['fund_account_id'],
'debit' => '0.00',
'credit' => $ap['amount'],
'description_ar' => $ap['description_ar'],
'cost_center_id' => $ap['cost_center_id'],
'branch_id' => $ap['branch_id'],
];
}
if (count($lines) < 2) {
return null;
}
$result = JournalService::createEntry([
'entry_date' => $plan['header']['entry_date'] ?? date('Y-m-d'),
'description_ar' => 'تخصيص من الإيراد — ' . ($plan['header']['description_ar'] ?? ''),
'reference_type' => 'appropriation',
'reference_id' => $originEntryId,
'reference_number' => $ctx['reference_number'] ?? null,
'source_module' => $plan['header']['source_module'] ?? null,
'branch_id' => $plan['header']['branch_id'] ?? null,
'cost_center_id' => $plan['header']['cost_center_id'] ?? null,
'is_auto_generated' => 1,
'notes' => 'تخصيص أرباح مقابل القيد رقم ' . $originEntryId
. ' — الإيراد اتسجّل كامل، وده نقل للصندوق/الاحتياطي.',
], $lines, true);
if (empty($result['success'])) {
Logger::error('appropriation entry failed', [
'origin_entry_id' => $originEntryId,
'error' => $result['error'] ?? null,
]);
return null;
}
return (int) $result['journal_entry_id'];
} }
/** /**
...@@ -95,7 +170,8 @@ final class RevenuePostingEngine ...@@ -95,7 +170,8 @@ final class RevenuePostingEngine
$blank = [ $blank = [
'resolved' => false, 'stream' => null, 'rule' => null, 'allocation' => null, 'resolved' => false, 'stream' => null, 'rule' => null, 'allocation' => null,
'header' => [], 'lines' => [], 'deferrals' => [], 'errors' => [], 'warnings' => [], 'header' => [], 'lines' => [], 'deferrals' => [], 'appropriations' => [],
'errors' => [], 'warnings' => [],
]; ];
$amount = self::money((string) ($ctx['amount'] ?? '0')); $amount = self::money((string) ($ctx['amount'] ?? '0'));
...@@ -204,15 +280,56 @@ final class RevenuePostingEngine ...@@ -204,15 +280,56 @@ final class RevenuePostingEngine
} }
$deferrals = []; $deferrals = [];
$appropriations = [];
foreach ($alloc['allocations'] as $a) { foreach ($alloc['allocations'] as $a) {
if (bccomp($a['amount'], '0.00', self::SCALE) <= 0) { if (bccomp($a['amount'], '0.00', self::SCALE) <= 0) {
continue; continue;
} }
self::assertPostable($a['account_id'], 'حساب البند', $errors);
$lineDesc = $a['description_ar'] ?: $description; $lineDesc = $a['description_ar'] ?: $description;
// ── Earmark, not a different kind of income ─────────────
// "20% goes to the facilities fund" does not change what the club
// earned — it decides what to do with it afterwards. So the main
// entry credits REVENUE for this share, and the earmark leaves as a
// separate transfer out of equity. Crediting the fund here instead
// would understate revenue and invent a liability owed to nobody.
if (!empty($a['is_appropriation'])) {
$revenueAccountId = $a['revenue_account_id'];
$sourceAccountId = $a['appropriation_source_account_id'];
if (!$revenueAccountId || !$sourceAccountId) {
$errors[] = 'بند التخصيص بدون حساب إيراد أو حساب مصدر تخصيص';
continue;
}
self::assertPostable($revenueAccountId, 'حساب إيراد التخصيص', $errors);
self::assertPostable($sourceAccountId, 'حساب مصدر التخصيص', $errors);
self::assertPostable($a['account_id'], 'حساب الصندوق', $errors);
$jLines[] = [
'account_id' => $revenueAccountId,
'debit' => $isInflow ? '0.00' : $a['amount'],
'credit' => $isInflow ? $a['amount'] : '0.00',
'description_ar' => $lineDesc,
'cost_center_id' => $a['cost_center_id'] ?? $rule['cost_center_id'] ?? null,
'branch_id' => $a['branch_id'] ?? $ctx['branch_id'] ?? null,
];
$appropriations[] = [
'rule_line_id' => $a['rule_line_id'],
'source_account_id' => (int) $sourceAccountId,
'fund_account_id' => (int) $a['account_id'],
'amount' => $a['amount'],
'cost_center_id' => $a['cost_center_id'] ?? $rule['cost_center_id'] ?? null,
'branch_id' => $a['branch_id'] ?? $ctx['branch_id'] ?? null,
'description_ar' => 'تخصيص — ' . $lineDesc,
];
continue;
}
self::assertPostable($a['account_id'], 'حساب البند', $errors);
// Contra-revenue always reduces revenue, so it is always a debit — // Contra-revenue always reduces revenue, so it is always a debit —
// on a collection with a discount and on a refund alike. Every other // on a collection with a discount and on a refund alike. Every other
// line type follows the entry's direction. // line type follows the entry's direction.
...@@ -297,6 +414,7 @@ final class RevenuePostingEngine ...@@ -297,6 +414,7 @@ final class RevenuePostingEngine
], ],
'lines' => $jLines, 'lines' => $jLines,
'deferrals' => $deferrals, 'deferrals' => $deferrals,
'appropriations' => $appropriations,
'errors' => $errors, 'errors' => $errors,
'warnings' => $warnings, 'warnings' => $warnings,
'totals' => ['debit' => $dr, 'credit' => $cr], 'totals' => ['debit' => $dr, 'credit' => $cr],
......
...@@ -411,6 +411,41 @@ ...@@ -411,6 +411,41 @@
</div> </div>
</div> </div>
</div> </div>
<?php if ($direction !== 'outflow'): ?>
<div class="wz-approp-box" style="margin-top:10px;">
<label style="display:flex;gap:7px;align-items:flex-start;font-size:12px;cursor:pointer;">
<input type="checkbox" class="wz-approp" style="margin-top:2px;">
<span>
<strong>الجزء ده تخصيص، مش نوع إيراد تاني</strong>
<span style="display:block;color:#6B7280;font-size:11px;margin-top:2px;">
يعني الفلوس دي النادي كسبها فعلًا، وإحنا بس بنحجزها لصندوق. الإيراد هيتسجّل
كامل، والحجز هيتعمل بقيد تخصيص منفصل من الأرباح.
</span>
</span>
</label>
<div class="wz-approp-fields" style="display:none;margin-top:9px;padding:10px;background:#EEF2FF;border:1px solid #C7D2FE;border-radius:6px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<div style="position:relative;">
<label class="form-label" style="font-size:11px;">١ — الإيراد يتسجّل في</label>
<input type="text" class="form-input acct-search wz-ap-rev-search" placeholder="حساب الإيراد" autocomplete="off">
<input type="hidden" class="wz-ap-rev">
<div class="acct-results"></div>
</div>
<div style="position:relative;">
<label class="form-label" style="font-size:11px;">٢ — التخصيص يتخصم من</label>
<input type="text" class="form-input acct-search wz-ap-src-search" placeholder="أرباح مرحّلة / حساب تخصيص" autocomplete="off">
<input type="hidden" class="wz-ap-src">
<div class="acct-results"></div>
</div>
</div>
<div class="form-help" style="color:#3730A3;">
و«يروح لحساب» فوق هو الصندوق/الاحتياطي اللي هياخد الفلوس.
</div>
</div>
</div>
<?php endif; ?>
<div class="wz-after" style="margin-top:8px;font-size:11.5px;color:#6B7280;"></div> <div class="wz-after" style="margin-top:8px;font-size:11.5px;color:#6B7280;"></div>
</div> </div>
</template> </template>
...@@ -464,6 +499,9 @@ ...@@ -464,6 +499,9 @@
'type' => $l['line_type'], 'type' => $l['line_type'],
'desc' => $l['description_ar'], 'desc' => $l['description_ar'],
'months' => $l['recognition_months'], 'months' => $l['recognition_months'],
'approp' => !empty($l['is_appropriation']),
'apRev' => (int) ($l['revenue_account_id'] ?? 0),
'apSrc' => (int) ($l['appropriation_source_account_id'] ?? 0),
'recAcct' => (int) ($l['recognized_account_id'] ?? 0), 'recAcct' => (int) ($l['recognized_account_id'] ?? 0),
'recLbl' => $l['recognized_code'] ? ($l['recognized_code'] . ' — ' . $l['recognized_name']) : '', 'recLbl' => $l['recognized_code'] ? ($l['recognized_code'] . ' — ' . $l['recognized_name']) : '',
'defer' => $l['recognition_method'] === 'straight_line', 'defer' => $l['recognition_method'] === 'straight_line',
...@@ -582,9 +620,23 @@ ...@@ -582,9 +620,23 @@
var type = el.querySelector('.wz-type'); var type = el.querySelector('.wz-type');
var defer = el.querySelector('.wz-defer'); var defer = el.querySelector('.wz-defer');
wireSearch(el.querySelector('.wz-search'), el.querySelector('.wz-acct'), el.querySelector('.acct-results')); var results = el.querySelectorAll('.acct-results');
wireSearch(el.querySelector('.wz-search'), el.querySelector('.wz-acct'), results[0]);
wireSearch(el.querySelector('.wz-rec-search'), el.querySelector('.wz-rec-acct'), wireSearch(el.querySelector('.wz-rec-search'), el.querySelector('.wz-rec-acct'),
el.querySelectorAll('.acct-results')[1], function () { return 'revenue'; }); results[1], function () { return 'revenue'; });
var approp = el.querySelector('.wz-approp');
var apFields = el.querySelector('.wz-approp-fields');
var apRev = el.querySelector('.wz-ap-rev');
var apSrc = el.querySelector('.wz-ap-src');
if (approp) {
wireSearch(el.querySelector('.wz-ap-rev-search'), apRev, results[2], function () { return 'revenue'; });
wireSearch(el.querySelector('.wz-ap-src-search'), apSrc, results[3]);
approp.addEventListener('change', function () {
apFields.style.display = approp.checked ? 'block' : 'none';
recalc();
});
}
function syncDefer() { function syncDefer() {
defer.style.display = DEFERRABLE.indexOf(type.value) !== -1 ? 'block' : 'none'; defer.style.display = DEFERRABLE.indexOf(type.value) !== -1 ? 'block' : 'none';
...@@ -623,6 +675,12 @@ ...@@ -623,6 +675,12 @@
el.querySelector('.wz-rec-acct').value = data.recAcct || ''; el.querySelector('.wz-rec-acct').value = data.recAcct || '';
el.querySelector('.wz-rec-search').value = data.recLbl || ''; el.querySelector('.wz-rec-search').value = data.recLbl || '';
} }
if (data.approp && approp) {
approp.checked = true;
apFields.style.display = 'block';
apRev.value = data.apRev || '';
apSrc.value = data.apSrc || '';
}
} }
syncDefer(); syncDefer();
recalc(); recalc();
...@@ -657,7 +715,12 @@ ...@@ -657,7 +715,12 @@
account: el.querySelector('.wz-acct').value, account: el.querySelector('.wz-acct').value,
desc: el.querySelector('.wz-desc').value, desc: el.querySelector('.wz-desc').value,
months: el.querySelector('.wz-months').value, months: el.querySelector('.wz-months').value,
recAcct: el.querySelector('.wz-rec-acct').value recAcct: el.querySelector('.wz-rec-acct').value,
approp: !!(el.querySelector('.wz-approp') && el.querySelector('.wz-approp').checked),
apRev: el.querySelector('.wz-ap-rev') ? el.querySelector('.wz-ap-rev').value : '',
apSrc: el.querySelector('.wz-ap-src') ? el.querySelector('.wz-ap-src').value : '',
apRevLabel: el.querySelector('.wz-ap-rev-search') ? el.querySelector('.wz-ap-rev-search').value : '',
apSrcLabel: el.querySelector('.wz-ap-src-search') ? el.querySelector('.wz-ap-src-search').value : ''
}; };
}); });
...@@ -808,7 +871,10 @@ ...@@ -808,7 +871,10 @@
recognition_method: deferred ? 'straight_line' : 'immediate', recognition_method: deferred ? 'straight_line' : 'immediate',
recognition_months: deferred ? s.months : '', recognition_months: deferred ? s.months : '',
recognized_account_id: deferred ? s.recAcct : '', recognized_account_id: deferred ? s.recAcct : '',
max_amount: '' max_amount: '',
is_appropriation: s.approp ? 1 : 0,
revenue_account_id: s.approp ? s.apRev : '',
appropriation_source_account_id: s.approp ? s.apSrc : ''
}); });
}); });
var remAcct = document.getElementById('wz-rem-acct').value; var remAcct = document.getElementById('wz-rem-acct').value;
...@@ -844,6 +910,18 @@ ...@@ -844,6 +910,18 @@
&& !el.querySelector('.wz-rec-acct').value) { && !el.querySelector('.wz-rec-acct').value) {
msgs.push('الجزء ' + (i + 1) + ': إيراد مؤجل من غير حساب يترحّل له.'); msgs.push('الجزء ' + (i + 1) + ': إيراد مؤجل من غير حساب يترحّل له.');
} }
var ap = el.querySelector('.wz-approp');
if (ap && ap.checked) {
if (!el.querySelector('.wz-ap-rev').value) {
msgs.push('الجزء ' + (i + 1) + ': تخصيص من غير حساب الإيراد اللي هيتسجّل فيه.');
}
if (!el.querySelector('.wz-ap-src').value) {
msgs.push('الجزء ' + (i + 1) + ': تخصيص من غير حساب مصدر التخصيص.');
}
if (el.querySelector('.wz-method').value === 'remainder') {
msgs.push('الجزء ' + (i + 1) + ': بند الباقي ما ينفعش يكون تخصيص.');
}
}
}); });
if (pct > 100) msgs.push('مجموع النسب ' + pct + '٪ — أكبر من ١٠٠٪.'); if (pct > 100) msgs.push('مجموع النسب ' + pct + '٪ — أكبر من ١٠٠٪.');
if (Number(document.getElementById('tgt-count').textContent || 0) < 1) { if (Number(document.getElementById('tgt-count').textContent || 0) < 1) {
......
<?php
declare(strict_types=1);
use App\Core\Database;
/**
* Two accounting controls that could not be expressed before.
*
* 1. APPROPRIATION. "20% of the membership fee goes to the facilities fund" is
* almost never a revenue classification — it is an earmark. Crediting a fund
* liability straight out of the collection understates revenue and invents a
* liability owed to nobody. A line marked as an appropriation now credits the
* REVENUE account in the main entry, and the earmark is posted as a separate
* transfer out of equity: Dr appropriation source / Cr the fund.
*
* 2. DEFERRAL POLICY. Which stream categories may not recognise revenue on the
* day cash arrives. Held in system_config so finance can change it without a
* developer.
*
* Idempotent — safe to re-run.
*/
return static function (Database $db): void {
$has = static function (string $table, string $column) use ($db): bool {
return (bool) $db->selectOne(
"SELECT 1 AS ok FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?",
[$table, $column]
);
};
if (!$has('revenue_posting_rule_lines', 'is_appropriation')) {
$db->raw("
ALTER TABLE `revenue_posting_rule_lines`
ADD COLUMN `is_appropriation` TINYINT(1) NOT NULL DEFAULT 0
COMMENT '1 = earmark: revenue is recognised in full, the share is transferred out of equity separately'
AFTER `line_type`
");
}
if (!$has('revenue_posting_rule_lines', 'appropriation_source_account_id')) {
$db->raw("
ALTER TABLE `revenue_posting_rule_lines`
ADD COLUMN `appropriation_source_account_id` BIGINT UNSIGNED NULL
COMMENT 'Debited by the transfer — retained earnings or an appropriation account'
AFTER `is_appropriation`
");
}
if (!$has('revenue_posting_rule_lines', 'revenue_account_id')) {
$db->raw("
ALTER TABLE `revenue_posting_rule_lines`
ADD COLUMN `revenue_account_id` BIGINT UNSIGNED NULL
COMMENT 'Appropriation lines only: the revenue account credited in the main entry'
AFTER `appropriation_source_account_id`
");
}
// ── Deferral policy, editable from settings ─────────────────────────
$settings = [
[
'config_key' => 'accounting.deferral_required_categories',
'config_value' => 'subscription',
'config_type' => 'string',
'group_name' => 'accounting',
'description_ar' => 'فئات الإيراد اللي ممنوع تسجّل إيرادها فورًا وقت التحصيل — لازم إيراد مؤجل. افصل بينهم بفاصلة.',
'description_en' => 'Revenue categories that may not recognise revenue immediately on collection. Comma separated.',
],
[
'config_key' => 'accounting.enforce_accrual_before_collection',
'config_value' => '1',
'config_type' => 'boolean',
'group_name' => 'accounting',
'description_ar' => 'امنع تسجيل الإيراد وقت التحصيل لو المصدر له قاعدة استحقاق — التحصيل ساعتها تسوية ذمم.',
'description_en' => 'Block revenue lines at collection when the stream already recognises at accrual.',
],
];
foreach ($settings as $s) {
$exists = $db->selectOne("SELECT id FROM system_config WHERE config_key = ?", [$s['config_key']]);
if ($exists) {
continue;
}
$db->insert('system_config', $s + [
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
};
<?php
declare(strict_types=1);
use App\Core\Database;
/**
* Three "مقدم عضوية" accounts were opened under 4105 إيرادات النادي, so money
* received before the service is delivered has been sitting in REVENUE:
*
* 410503 مقدم عضويه 2,774,495.00
* 410505 باقى مقدم عضويه 180,263.00
* 410504 جزء من مقدم 77,500.00
* ─────────────
* 3,032,258.00
*
* An advance is a contract liability (EAS 48 / IFRS 15) — the club owes a
* service, not a sale it has made. Leaving it in revenue overstates income,
* overstates the VAT base, and is the first thing an auditor pulls. The
* correctly-typed account already exists next door: 23081115 ايرادات مدفوعة
* مقدما, under 230811 حسابات دائنة أخري.
*
* This migration mirrors the three accounts into the liability section keeping
* their separate meanings, posts ONE dated correcting entry moving the balances,
* and blocks the old accounts from further posting. It does not delete them —
* the history stays readable.
*
* Idempotent: keyed on the entry's reference number, so a re-run posts nothing.
* Reversible: the entry can be reversed from the journal screen like any other.
*/
return static function (Database $db): void {
$REF = 'RECLASS-ADVANCES-001';
if ($db->selectOne("SELECT id FROM journal_entries WHERE reference_number = ?", [$REF])) {
return; // already corrected
}
// The liability parent that already holds ايرادات مدفوعة مقدما.
$parent = $db->selectOne(
"SELECT id, account_code, level FROM chart_of_accounts
WHERE account_code = '230811' AND is_archived = 0"
);
if (!$parent) {
return; // chart differs — leave it alone
}
$parentId = (int) $parent['id'];
$parentLevel = (int) ($parent['level'] ?? 4);
$map = [
'410503' => ['code' => '23081119', 'name' => 'مقدم عضوية — مقبوض مقدمًا'],
'410504' => ['code' => '23081120', 'name' => 'جزء من مقدم عضوية — مقبوض مقدمًا'],
'410505' => ['code' => '23081121', 'name' => 'باقي مقدم عضوية — مقبوض مقدمًا'],
];
$now = date('Y-m-d H:i:s');
$lines = [];
$total = '0.00';
foreach ($map as $oldCode => $new) {
$old = $db->selectOne(
"SELECT id, account_code, name_ar, current_balance, account_type
FROM chart_of_accounts WHERE account_code = ? AND is_archived = 0",
[$oldCode]
);
if (!$old || $old['account_type'] !== 'revenue') {
continue; // already fixed, or not there
}
// Mirror account in the liability section.
$target = $db->selectOne("SELECT id FROM chart_of_accounts WHERE account_code = ?", [$new['code']]);
if ($target) {
$targetId = (int) $target['id'];
} else {
$targetId = (int) $db->insert('chart_of_accounts', [
'account_code' => $new['code'],
'name_ar' => $new['name'],
'name_en' => 'Membership advance — contract liability',
'account_type' => 'liability',
'account_nature' => 'credit',
'parent_id' => $parentId,
'level' => $parentLevel + 1,
'is_header' => 0,
'is_active' => 1,
'is_archived' => 0,
'current_balance' => '0.00',
'notes' => 'مُنشأ بقيد تصحيح ' . $REF . ' — نقل مقدمات الأعضاء من الإيرادات للالتزامات',
'created_at' => $now,
'updated_at' => $now,
]);
}
// A revenue account carries a credit balance; debit it back to zero.
$balance = (string) $old['current_balance'];
if (bccomp($balance, '0.00', 2) === 0) {
continue;
}
$lines[] = [
'account_id' => (int) $old['id'],
'debit' => $balance,
'credit' => '0.00',
'description_ar' => 'إقفال ' . $old['name_ar'] . ' — إعادة تبويب من الإيرادات',
];
$lines[] = [
'account_id' => $targetId,
'debit' => '0.00',
'credit' => $balance,
'description_ar' => 'إثبات ' . $new['name'] . ' — التزام عقدي',
];
$total = bcadd($total, $balance, 2);
}
if (bccomp($total, '0.00', 2) <= 0) {
return; // nothing to move
}
// Post into the open fiscal year, dated today, through the same path as any
// other entry so the period lock and balance maintenance both apply.
\App\Core\App::getInstance()->setDb($db);
$result = \App\Modules\Accounting\Services\JournalService::createEntry([
'entry_date' => date('Y-m-d'),
'reference_type' => 'reclassification',
'reference_number' => $REF,
'description_ar' => 'قيد تصحيح — إعادة تبويب مقدمات العضوية من الإيرادات إلى الالتزامات (' . $total . ' جنيه)',
'description_en' => 'Reclassification of membership advances from revenue to contract liability',
'source_module' => 'accounting',
'is_auto_generated' => 1,
'notes' => 'المقدم التزام عقدي وليس إيرادًا — معيار المحاسبة المصري ٤٨. '
. 'الحسابات القديمة اتقفلت ومنعت من الترحيل، والأرصدة اتنقلت لحسابات الالتزامات المقابلة.',
], $lines, true);
if (empty($result['success'])) {
throw new \RuntimeException('فشل قيد إعادة التبويب: ' . ($result['error'] ?? 'سبب غير معروف'));
}
// Stop anything posting to the revenue-side accounts again. They stay
// visible with a zero balance so the history reads straight.
foreach (array_keys($map) as $oldCode) {
$db->query(
"UPDATE chart_of_accounts
SET is_active = 0,
notes = CONCAT(COALESCE(notes, ''), ' | موقوف بقيد ', ?, ' — الرصيد اتنقل لحساب الالتزامات المقابل'),
updated_at = ?
WHERE account_code = ? AND account_type = 'revenue'",
[$REF, $now, $oldCode]
);
}
};
# تعلّم النظام المحاسبي — خطوة بخطوة من الشاشة
> ده مش دليل مرجعي، ده **درس عملي**. كل خطوة بتقول لك: افتح فين، هتشوف إيه،
> دوس على إيه، اكتب إيه، وهيحصل إيه بعدها. لو عمرك ما فتحت النظام قبل كده،
> ابدأ من الدرس ١ وامشي بالترتيب.
>
> **آخر تحديث:** ٥ سبتمبر ٢٠٢٦
>
> للتفاصيل النظرية (ليه بنعمل كده) — راجع `docs/دليل-النظام-المحاسبي-الكامل.md`.
> الملف ده بيقول **إزاي** بس.
---
## المحتويات
**الجزء الأول — قبل ما تلمس أي حاجة**
1. [ازاي تدخل وتلاقي المحاسبة](#١--ازاي-تدخل-وتلاقي-المحاسبة)
2. [خريطة القايمة — إيه كل شاشة](#٢--خريطة-القايمة--إيه-كل-شاشة)
3. [ترتيب الإعداد الصحيح](#٣--ترتيب-الإعداد-الصحيح)
**الجزء الثاني — الإعداد لأول مرة**
4. [اتأكد إن فيه سنة مالية مفتوحة](#٤--اتأكد-إن-فيه-سنة-مالية-مفتوحة)
5. [افهم دليل الحسابات قبل ما تعدّل فيه](#٥--افهم-دليل-الحسابات-قبل-ما-تعدّل-فيه)
6. [اعمل حساب جديد](#٦--اعمل-حساب-جديد)
7. [عدّل حساب موجود — نقل، تحويل، حذف](#٧--عدّل-حساب-موجود--نقل-تحويل-حذف)
8. [عرّف ملف ضريبي](#٨--عرّف-ملف-ضريبي)
9. [القيود الافتتاحية — أرصدة أول المدة](#٩--القيود-الافتتاحية--أرصدة-أول-المدة)
**الجزء الثالث — محرك القيود (أهم جزء)**
10. [اقرا شاشة محرك القيود](#١٠--اقرا-شاشة-محرك-القيود)
11. [أول توزيع بالمعالج — من الأول للآخر](#١١--أول-توزيع-بالمعالج--من-الأول-للآخر)
12. [اعمل حساب جديد وإنت جوّه المعالج](#١٢--اعمل-حساب-جديد-وإنت-جوّه-المعالج)
13. [بند إيراد مؤجل](#١٣--بند-إيراد-مؤجل)
14. [بند تخصيص (صندوق / احتياطي)](#١٤--بند-تخصيص-صندوق--احتياطي)
15. [غيّر اتجاه مصدر من تحصيل لصرف](#١٥--غيّر-اتجاه-مصدر-من-تحصيل-لصرف)
16. [مركز التوصيل — شوف الناقص](#١٦--مركز-التوصيل--شوف-الناقص)
17. [شاشة الفحص — لو حاجة مش شغالة](#١٧--شاشة-الفحص--لو-حاجة-مش-شغالة)
**الجزء الرابع — الشغل اليومي**
18. [حصّل مستحق من شاشة المطالبات](#١٨--حصّل-مستحق-من-شاشة-المطالبات)
19. [عرّف مصدر مطالبة جديد](#١٩--عرّف-مصدر-مطالبة-جديد)
20. [عرّف نوع سند](#٢٠--عرّف-نوع-سند)
21. [اعمل سند صرف](#٢١--اعمل-سند-صرف)
22. [اعتمد وارحّل وألغي سند](#٢٢--اعتمد-وارحّل-وألغي-سند)
23. [قيد يومية يدوي](#٢٣--قيد-يومية-يدوي)
24. [رحّل قيد واعكسه](#٢٤--رحّل-قيد-واعكسه)
25. [الحركات اليومية — إدخال سريع](#٢٥--الحركات-اليومية--إدخال-سريع)
**الجزء الخامس — آخر الشهر**
26. [رحّل الإيراد المؤجل](#٢٦--رحّل-الإيراد-المؤجل)
27. [اقفل الشهر](#٢٧--اقفل-الشهر)
28. [التقارير](#٢٨--التقارير)
**الجزء السادس**
29. [تمرين كامل — من صفر لقيد مرحّل](#٢٩--تمرين-كامل--من-صفر-لقيد-مرحّل)
30. [لو ظهرت لك الرسالة دي…](#٣٠--لو-ظهرت-لك-الرسالة-دي)
---
# الجزء الأول — قبل ما تلمس أي حاجة
## ١ — ازاي تدخل وتلاقي المحاسبة
1. افتح المتصفح وادخل على رابط النظام.
2. سجّل الدخول باسم المستخدم والباسورد بتاعك.
3. على **يمين** الشاشة هتلاقي القايمة الجانبية.
4. دوّر على بند اسمه **«المحاسبة والدفتر العام»** — أيقونته آلة حاسبة.
5. دوس عليه، هتتفتح تحته قايمة فرعية طويلة.
> **مش شايف البند؟** يبقى دورك مالوش صلاحية `accounting.reports.view`.
> كلّم اللي عنده صلاحية الأدوار يزوّدك.
**كل شاشة في الدليل ده ليها رابط مباشر.** لو تحب تختصر، اكتب الرابط في المتصفح
على طول — مثال: `/accounting/revenue-mapping`.
---
## ٢ — خريطة القايمة — إيه كل شاشة
القايمة طويلة. دي الشاشات اللي هتستخدمها فعلًا، مرتّبة حسب أهميتها ليك:
| البند في القايمة | الرابط | بتعمل إيه |
|---|---|---|
| **لوحة التحكم** | `/accounting` | ملخص الشهر + إجراءات سريعة |
| **دليل الحسابات** | `/accounting/chart-of-accounts` | شجرة الحسابات كلها |
| **توزيع الإيرادات** | `/accounting/revenue-mapping` | محرك القيود — قلب النظام |
| **مركز التوصيل** | `/accounting/revenue-mapping/connections` | إيه اللي موصّل وإيه اللي لأ |
| **المطالبات والتحصيل** | `/accounting/billing` | كل مبلغ مستحق في النظام |
| **سندات الصرف والقبض** | `/accounting/vouchers` | صرف وقبض من غير قيود يدوية |
| **الإيراد المؤجل** | `/accounting/revenue-mapping/recognition` | الترحيل الشهري |
| **قيود اليومية** | `/accounting/journal-entries` | كل القيود، يدوي وآلي |
| **السنوات المالية** | `/accounting/fiscal-years` | لازم واحدة مفتوحة |
| **إقفال الفترات** | `/accounting/period-closing` | إقفال شهري |
| **القيود الافتتاحية** | `/accounting/opening-entries` | أرصدة أول المدة |
| **الحركات اليومية** | `/accounting/daily-transactions` | إدخال سريع مجمّع |
| **ميزان المراجعة** | `/accounting/reports/trial-balance` | التقرير اللي بيثبت إن كله متوازن |
وفيه شاشات تانية (مراكز التكلفة، الموازنات، الأوراق التجارية، المطابقة البنكية،
القروض، خطابات الضمان…) — مش محتاجها في أول أسبوع.
---
## ٣ — ترتيب الإعداد الصحيح
**ما تبدأش من محرك القيود.** فيه حاجات لازم تبقى موجودة الأول، وإلا هتاخد أخطاء
مش فاهم سببها.
```mermaid
flowchart TD
A["١ — سنة مالية مفتوحة"] --> B["٢ — دليل حسابات فيه<br/>الحسابات اللي هتحتاجها"]
B --> C["٣ — ملفات ضريبية<br/>(لو عندك ض.ق.م)"]
C --> D["٤ — قيود افتتاحية<br/>(لو ناقل من نظام قديم)"]
D --> E["٥ — توزيع الإيرادات<br/>بالمعالج"]
E --> F["٦ — دلوقتي بس<br/>ابدأ تحصّل وتصرف"]
style A fill:#FEE2E2,stroke:#DC2626
style E fill:#DBEAFE,stroke:#2563EB
style F fill:#D1FAE5,stroke:#059669
```
> **الخطوة ١ دي مش اختيارية.** من غير سنة مالية مفتوحة، **كل** قيد آلي في النظام
> بيفشل في صمت — الدفعة بتتسجّل والقيد ما بيتعملش.
---
# الجزء الثاني — الإعداد لأول مرة
## ٤ — اتأكد إن فيه سنة مالية مفتوحة
**افتح:** القايمة ← المحاسبة ← **السنوات المالية**
(`/accounting/fiscal-years`)
**اللي هتشوفه:** جدول فيه أعمدة: الاسم · تاريخ البداية · تاريخ النهاية · الحالة ·
الحالية.
**اللي تعمله:**
1. دوّر على السنة الحالية (٢٠٢٦).
2. بُص على عمود **الحالة** — لازم تكون **«مفتوحة»** بلون أخضر.
3. بُص على عمود **الحالية** — لازم يكون فيه **«نعم»** خضراء قدام سنة واحدة بس.
**لو السنة مش موجودة:**
1. دوس زرار **«+ سنة مالية جديدة»** فوق على الشمال.
2. املا الاسم بالعربي والإنجليزي.
3. حدّد تاريخ البداية والنهاية.
4. **حفظ**.
> **معلومة مريحة:** النظام بيعمل سنة السنة الحالية لوحده أول ما يشتغل لو مش
> لاقيها. فغالبًا هتلاقيها موجودة خلاص.
---
## ٥ — افهم دليل الحسابات قبل ما تعدّل فيه
**افتح:** القايمة ← المحاسبة ← **دليل الحسابات**
(`/accounting/chart-of-accounts`)
**اللي هتشوفه:** شجرة. كل سطر فيه:
```
110101 الصندوق الرئيسي أصول 1,250,000.00 تعديل إدارة
└─ الكود └─ الاسم └─ النوع └─ الرصيد
```
الحسابات المتفرعة بتبقى مزاحة لجوّه، والحساب **الرئيسي** اسمه بيبقى **بولد** ومن
غير رصيد قصاده.
### الفرق اللي لازم تفهمه دلوقتي
| النوع | شكله | القيد بينزل عليه؟ |
|---|---|---|
| **حساب رئيسي** (Header) | اسمه بولد، مفيش رصيد قصاده | **لأ خالص** |
| **حساب ترحيل** (Detail) | اسمه عادي، وقصاده رصيد | **أيوه** |
> **دي أكتر حاجة بتوقّع الناس.** لو ربطت قيد على حساب رئيسي، القيد **هيفشل**
> والفلوس هتفضل بره الدفاتر. النظام بيرفض قبل الحفظ ويقول لك، بس لازم تفهم ليه.
### جرّب البحث
1. في خانة **«بحث»** فوق، اكتب كلمة زي `عضوي`.
2. الشجرة بتتفلتر وإنت بتكتب — من غير ما تدوس أي زرار.
3. جنب عنوان الشجرة هيظهر لك `(١٢ نتيجة)`.
4. من خانة **«النوع»** جنبها، اختار **إيرادات** عشان تشوف حسابات الإيراد بس.
**الأنواع الخمسة:** أصول · خصوم · حقوق ملكية · إيرادات · مصروفات.
كل نوع ليه لون في الكود على الشمال.
---
## ٦ — اعمل حساب جديد
**افتح:** دليل الحسابات ← زرار **«+ حساب جديد»** (فوق على الشمال)
(`/accounting/chart-of-accounts/create`)
**املا الحقول بالترتيب ده:**
| الحقل | إجباري | اكتب فيه إيه |
|---|:---:|---|
| **كود الحساب** | ✔ | رقم متسلسل تحت الأب. مثال: `410520` |
| **الحساب الأب** | — | اختار الحساب الرئيسي اللي هيتفرّع منه |
| **اسم الحساب (عربي)** | ✔ | الاسم اللي هيظهر في كل الشاشات |
| **اسم الحساب (إنجليزي)** | ✔ | نفس الاسم بالإنجليزي |
| **نوع الحساب** | ✔ | أصول / خصوم / حقوق ملكية / إيرادات / مصروفات |
| **طبيعة الحساب** | ✔ | مدين ولا دائن |
| **رصيد افتتاحي** | — | سيبه `0.00` لو حساب جديد |
| **مركز التكلفة الافتراضي** | — | اختياري |
| **حساب رئيسي (لا يُرحّل إليه)** | — | **سيب المربع ده فاضي** لو الحساب ده هياخد قيود |
**بعدين:** دوس **«حفظ الحساب»**.
### ازاي تعرف الطبيعة (مدين ولا دائن)؟
قاعدة بسيطة تحفظها:
| النوع | الطبيعة |
|---|---|
| أصول | **مدين** |
| مصروفات | **مدين** |
| خصوم | **دائن** |
| حقوق ملكية | **دائن** |
| إيرادات | **دائن** |
---
## ٧ — عدّل حساب موجود — نقل، تحويل، حذف
في دليل الحسابات، قصاد كل حساب فيه لينكين: **تعديل** و**إدارة**.
- **تعديل** → بيفتح فورم زي فورم الإنشاء، بيغيّر الاسم والنوع والوصف.
- **إدارة** → بيفتح نافذة فيها العمليات الخطيرة. دي اللي هنشرحها.
**اعمل كده:**
1. دوس **«إدارة»** قصاد أي حساب.
2. هتفتح نافذة فيها اسم الحساب والكود، وتحتها سطر **«جارٍ الفحص…»**.
3. استنّى ثانية — النظام بيفحص الحساب ده مرتبط بإيه.
**النتيجة هتبقى واحدة من اتنين:**
| اللي هيظهر | معناه |
|---|---|
| صندوق **أخضر**: «حساب فارغ — يمكن حذفه أو نقله بحرية» | مفيش عليه أي حركة |
| صندوق **أصفر**: «مرتبط بـ:» وتحتها قايمة | فيه حاجات معتمدة عليه |
### أ — نقل الحساب تحت أب تاني
1. من القايمة المنسدلة **«نقل الحساب تحت حساب رئيسي آخر»** اختار الأب الجديد.
2. دوس **«نقل»**.
> النقل بيحرّك الحسابات المتفرّعة معاه. وبيترفض لو النوع مختلف (مش هينفع تحط
> حساب إيراد تحت حساب أصول) أو لو هيعمل دايرة.
### ب — حوّله لحساب رئيسي أو العكس
فيه زرار واحد بيقلب:
- لو الحساب **ترحيل** → الزرار هيقول **«حوّله لحساب رئيسي»**
- لو الحساب **رئيسي** → الزرار هيقول **«حوّله لحساب ترحيل»**
> بعد التحويل لرئيسي، الحساب **مش هيقبل قيود**. اتأكد إنه فاضي الأول.
### ج — حذف أو أرشفة
الزرار الأخير بيتغيّر لوحده حسب حالة الحساب:
| حالة الحساب | الزرار | اللي هيحصل |
|---|---|---|
| فاضي تمامًا | **«حذف الحساب»** | بيتشال نهائي |
| عليه حركات مرحّلة | **«أرشفة الحساب»** | بيتأرشف عشان القيود القديمة تفضل مقروءة |
| مرتبط بحاجات تانية | الزرار **مطفي** | لازم تحل الارتباطات الأول |
---
## ٨ — عرّف ملف ضريبي
اعمل الخطوة دي لو عندك ضريبة قيمة مضافة. لو مفيش، عدّيها.
**افتح:** المحاسبة ← توزيع الإيرادات ← زرار **«الملفات الضريبية»**
(`/accounting/revenue-mapping/tax-profiles`)
**اللي هتشوفه:** فورم **«إضافة ملف ضريبي»** فوق، وتحتها جدول بالملفات الموجودة.
**املا الفورم:**
| الحقل | مثال | ملاحظة |
|---|---|---|
| **الكود** | `VAT14` | بالإنجليزي، حروف كبيرة |
| **الاسم** | `ضريبة قيمة مضافة 14%` | اللي هيظهر في القوايم |
| **المعالجة** | خاضعة بالسعر العام | ٥ خيارات، شوف الجدول تحت |
| **النسبة %** | `14` | |
| **علاقة السعر بالضريبة** | السعر شامل الضريبة | **أهم حقل — اقرا تحت** |
| **حساب ضريبة المخرجات** | ابحث واختار | لازم حساب **التزام** |
| **السند القانوني** | `قانون 67 لسنة 2016` | اختياري |
| **ساري من** | تاريخ النهاردة | |
**بعدين:** دوس **«حفظ»**.
### الخمس معالجات
| الاختيار في القايمة | استخدمه لما |
|---|---|
| **خاضعة بالسعر العام** | الحالة العادية — ١٤٪ |
| **سلع وخدمات الجدول** | السلع اللي ليها نسبة خاصة بالجدول |
| **بسعر صفر (الخصم مسموح)** | نسبة صفر، وضريبة المدخلات **تتخصم** |
| **معفاة (لا خصم مدخلات)** | معفاة، وضريبة المدخلات **ما تتخصمش** |
| **خارج نطاق الضريبة** | الحركة أصلًا مش خاضعة |
> الفرق بين **«بسعر صفر»** و**«معفاة»** مش شكلي — المعفاة بتمنعك من خصم ضريبة
> المدخلات. اسأل المحاسب لو مش متأكد.
### الحقل اللي بيغلّط الناس
**«علاقة السعر بالضريبة»** ليه خيارين، والفرق بينهم **بيغيّر المبلغ فعليًا**:
| الاختيار | لو المبلغ ١١٤٠ والنسبة ١٤٪ |
|---|---|
| **السعر شامل الضريبة (تُستخرج منه)** | الإيراد `1,000` والضريبة `140` ✅ |
| **الضريبة تُضاف على السعر** | الإيراد `1,140` والضريبة `159.60` والعميل يدفع `1,299.60` |
لو السعر اللي إنت بتقوله للعميل هو الرقم النهائي — اختار **«شامل»**.
### حقل «حساب ضريبة المخرجات»
1. اكتب **حرفين على الأقل** في الخانة.
2. استنّى لحظة، هتنزل قايمة بالحسابات المطابقة.
3. دوس على الحساب اللي عايزه.
4. الخانة هتتملى بـ `الكود — الاسم`.
> البحث ده مفلتر على حسابات **الالتزام** بس — عشان الضريبة **مش إيراد**، دي فلوس
> بتتحصّل لصالح مصلحة الضرائب.
**لو سبت الحقل ده فاضي**، الملف هيظهر في الجدول بعلامة حمرا **«غير محدد»**.
---
## ٩ — القيود الافتتاحية — أرصدة أول المدة
اعمل الخطوة دي لو إنت ناقل من نظام قديم وعندك أرصدة موجودة.
**افتح:** المحاسبة ← **القيود الافتتاحية** ← زرار **«إنشاء قيد افتتاحي»**
(`/accounting/opening-entries/create`)
**اعمل كده:**
1. من **«السنة المالية»** اختار السنة اللي الأرصدة دي بتاعتها.
2. في الجدول، أول سطر: اختار **الحساب** من القايمة.
3. اكتب الرصيد في خانة **مدين** أو **دائن** — واحدة بس، مش الاتنين.
4. البيان مكتوب فيه `قيد افتتاحي` — سيبه أو غيّره.
5. دوس **«+ سطر»** وكرّر لكل حساب عنده رصيد.
6. بُص على آخر صف في الجدول — فيه **الإجمالي** مدين ودائن، وجنبهم كلمة.
**الكلمة دي هي كل حاجة:**
| اللي هيظهر | اللون | يعني |
|---|---|---|
| **متوازن** | أخضر | تمام، تقدر تحفظ |
| **فرق: 5,000.00** | أحمر | فيه ٥٠٠٠ ناقصة، ما تحفظش |
7. لما يقول **متوازن**، دوس **«حفظ القيد الافتتاحي»**.
---
# الجزء الثالث — محرك القيود (أهم جزء)
## ١٠ — اقرا شاشة محرك القيود
**افتح:** القايمة ← المحاسبة ← **توزيع الإيرادات**
(`/accounting/revenue-mapping`)
الشاشة دي اسمها على الشاشة **«محرك القيود المحاسبية»**. خدها ٣ أجزاء:
### الجزء الأول — الكروت الستة فوق
| الكارت | معناه | تدوس عليه؟ |
|---|---|:---:|
| **مسارات القيد** | إجمالي مصادر الإيراد | — |
| **مربوطة بحسابات** | اللي ليها قاعدة شغالة | — |
| **غير مربوطة** | **مفيش قيد بيتعمل ليها** | ✔ فلتر |
| **حسابات لا تقبل الترحيل** | مربوطة على حساب رئيسي — القيد بيفشل | ✔ فلتر |
| **على حساب مجمَّع** | كلها راميه على حساب واحد | ✔ فلتر |
| **إيراد مؤجل قائم** | الرصيد المؤجل الحالي | — |
الكروت اللي فيها ✔ **قابلة للضغط** — بتفلتر الجدول تحت على المشكلة دي.
> **أول حاجة تعملها:** دوس على **«غير مربوطة»**. الرقم ده لازم يبقى صفر في الآخر.
### الجزء التاني — شريط الفلاتر
فيه: **بحث** (اسم أو كود) · **التصنيف** · **الحالة** · زرار **«تصفية»**.
### الجزء التالت — الجدول
المصادر متجمّعة حسب الفئة (العضويات، الاشتراكات، الأنشطة…). كل صف فيه:
- **مسار القيد** — اسم المصدر وكوده
- **المراحل والتوزيع** — التقسيمة الحالية بالتفصيل، أو بادج أحمر **«غير مربوط»**
- **الحركة الفعلية** — المبلغ اللي المصدر ده حصّله فعلًا وعدد العمليات
- **الأزرار** — زرار لكل مرحلة، وجنبه ⚙ للوضع المتقدّم
### الأزرار الفوقانية
| الزرار | بيعمل إيه |
|---|---|
| **وزّع على مجموعة** | يفتح المعالج — **ده اللي هتستخدمه** |
| **فحص الحالة** | شاشة تشخيص المشاكل |
| **الإيراد المؤجل** | شاشة الترحيل الشهري |
| **الملفات الضريبية** | اللي عملناها في الدرس ٨ |
| **مزامنة المصادر** | يكتشف أنواع الدفعات الجديدة من البيانات |
---
## ١١ — أول توزيع بالمعالج — من الأول للآخر
ده **أهم درس في الملف كله**. هنعمل مثال حقيقي:
> **المطلوب:** كل عضويات النادي، لما العضو يدفع، المبلغ يتقسم:
> ٢٠٪ لصندوق النشاط · ٣٠٪ لإيرادات العضويات · والباقي لصندوق دعم المنشآت.
**افتح:** توزيع الإيرادات ← زرار **«وزّع على مجموعة»**
هتلاقي نفسك في شاشة اسمها **«معالج توزيع المبالغ»**.
---
### الخطوة ١ — «التقسيمة دي هتحكم إيه»
هتشوف **٥ كروت** جنب بعض:
| الكارت | معناه |
|---|---|
| المصدر ده بس | مصدر واحد |
| **كل فئة إيراد** | كل المصادر من نفس النوع ← **اختار ده** |
| مصادر أختارها | تعلّم بإيدك |
| كل اللي لسه مش موصّل | أي مصدر مالوش قاعدة |
| كل مصادر الإيراد | كل حاجة |
**اعمل كده:**
1. دوس على كارت **«كل فئة إيراد»**.
→ الصفحة **هتعمل ريفريش لوحدها**. ده طبيعي، متستغربش.
2. تحت الكروت هتظهر قايمة كل الفئات. كل فئة مكتوب جنبها عدد المصادر وكام غير موصّل.
3. دوس على فئة **«العضويات»**.
→ ريفريش تاني.
4. انزل تحت شوية، هتلاقي خانتين جنب بعض:
**خانة «المرحلة»:**
- افتحها، هتلاقي: استحقاق · تحصيل · صرف · ارتجاع · إعدام / إسقاط · تحويل داخلي
- اللي جنبها **✓** يعني فيه قاعدة شغالة عليها خلاص
- **اختار «تحصيل»** (لأن إحنا بنتكلم عن لحظة الدفع)
**خانة «فئة العضو»:**
- الافتراضي **«كل الأعضاء — قاعدة عامة»**
- لو عايز قاعدة خاصة بالعضو العامل بس، اختاره من هنا
- في المثال ده سيبها على **كل الأعضاء**
5. تحت الخانتين هتلاقي **شريط أزرق** مكتوب فيه:
```
هتتطبّق على 11 قاعدة قيد — حركة تحصيل [اعرض القايمة]
```
6. دوس **«اعرض القايمة»** عشان تتأكد.
→ هتنزل قايمة بكل مصدر ومرحلته. الصفوف **الصفرا** معناها اتخطّى، ومكتوب جنبها ليه.
دوس تاني على نفس الزرار (اللي بقى اسمه **«إخفاء القايمة»**) عشان تقفلها.
> **الشريط ده لونه بيتغيّر.** أزرق = حركة تحصيل. **بنفسجي** = حركة صرف، ومعناه
> إن البنود هتبقى مصروفات وأصول مش إيرادات.
---
### الخطوة ٢ — «المبلغ اللي هنوزّعه»
**خانة «مبلغ مرجعي للحساب»:**
- النظام بيملاها لوحده بمتوسط اللي المصدر ده حصّله فعلًا
- **دي للتوضيح بس** — النسب بتتطبّق على أي مبلغ حقيقي، مش على الرقم ده
- في المثال بتاعنا، اكتب `150000`
**خانة «المعالجة الضريبية»:**
- الافتراضي **«بدون ضريبة»**
- لو الإيراد ده عليه ض.ق.م، اختار الملف اللي عملته في الدرس ٨
- في المثال ده سيبها **بدون ضريبة**
**خانتين اختياريين:** مركز التكلفة · الفرع. سيبهم فاضيين.
---
### الخطوة ٣ — «اقتطع الأجزاء»
دي الخطوة الممتعة. فوق خالص في الكارت هتلاقي:
```
الباقي غير الموزَّع 150,000.00
[████████████████████████████████████████████████]
```
**الرقم ده بيتحدّث مع كل حرف بتكتبه.** خلّي عينك عليه.
**اعمل كده:**
1. دوس زرار **«+ اقتطع جزء»** (فوق على الشمال في نفس الكارت).
→ هينزل لك كارت أبيض اسمه **«الجزء 1»**.
2. جوّه الكارت:
- **«الطريقة»** → سيبها **«نسبة %»**
- **«نسبة % من الصافي»** → اكتب `20`
- **«يروح لحساب»** → اكتب `النشاط` واستنّى، هتنزل قايمة، دوس على
**«صندوق النشاط الرياضي»**
3. اول ما تختار الحساب، تحت الكارت هيظهر سطر رمادي:
```
20% من 150,000.00 · قبله 150,000.00 · بياخد 30,000.00 · يفضل 120,000.00
```
4. **«نوع البند»** → اختار **«إيراد»**
(الأنواع المتاحة: إيراد · إيراد مؤجل · تحصيل لحساب الغير (التزام) ·
خصم من الإيراد · تسوية ذمم مدينة · حقوق ملكية)
5. **«وصف البند في القيد»** → اختياري، سيبه فاضي.
6. دوس **«+ اقتطع جزء»** تاني للجزء التاني:
- نسبة `30`
- حساب: `إيرادات عضويات`
- نوع البند: **إيراد**
7. الرقم الأخضر فوق دلوقتي المفروض يقول **`75,000.00`**، والشريط الملوّن اتقسم
لتلات ألوان.
**كل كارت جزء فيه ٣ أزرار على الشمال:**
| الزرار | بيعمل إيه |
|---|---|
| ▲ | يطلّع الجزء فوق |
| ▼ | ينزّل الجزء تحت |
| **حذف** | يشيل الجزء |
> **الترتيب بيهم؟** بيهم في المبالغ الثابتة بس. النسب دايمًا بتتحسب من **صافي المبلغ**،
> مش من اللي فاضل. يعني ٢٠٪ بعدين ٣٠٪ = ٥٠٪ من المبلغ، مش ٢٠٪ وبعدين ٣٠٪ من الـ٨٠٪.
---
### الخطوة ٤ — «الباقي بعد كل الاقتطاعات يروح لـ»
ده الصندوق **الأخضر** تحت خالص في نفس الكارت. **إجباري.**
**اعمل كده:**
1. في الخانة **«ابحث عن الحساب النهائي بالكود أو الاسم»** اكتب `دعم` واختار
**«صندوق دعم المنشآت»**.
2. القايمة اللي جنبها اختار منها نوع البند — **«إيراد»**.
**ليه إجباري؟** لأن ده البند اللي بيبلع الفرق. لو المبلغ الحقيقي طلع ٠٫٠٣ جنيه
و٢٠٪ منه = ٠٫٠٠٦، النظام بيقرّبها لـ ٠٫٠١، والفرق ده لازم حد ياخده — وإلا القيد
هيبقى مش متوازن.
---
### الخطوة ٥ — راجع القيد على الشمال
على شمال الشاشة فيه كارت ثابت اسمه **«القيد الناتج»**. بيتحدّث لحظيًا.
هتشوف فيه:
**٣ مربعات فوق:**
```
المحصَّل الضريبة الصافي
150,000.00 0.00 150,000.00
```
**جدول القيد:**
| الحساب | مدين | دائن |
|---|---:|---:|
| النقدية / البنك | 150,000.00 | |
| صندوق النشاط الرياضي | | 30,000.00 |
| إيرادات عضويات | | 45,000.00 |
| صندوق دعم المنشآت (الباقي) | | 75,000.00 |
| **الإجمالي** | **150,000.00** | **150,000.00** |
**وتحت الجدول السطر اللي بيهم:**
-**القيد متوازن** — بالأخضر → تمام
-**القيد غير متوازن** — بالأحمر → **ما تحفظش**، راجع الأرقام
وآخر سطر: *«نفس القيد ده هيتطبّق على ١١ قاعدة قيد.»*
---
### الخطوة ٦ — التاريخ والسبب
انزل تحت شوية، فيه كارت فيه خانتين:
| الخانة | اكتب فيها |
|---|---|
| **ساري اعتبارًا من** | التاريخ اللي التوزيع يبدأ منه (الافتراضي النهاردة) |
| **سبب التغيير** | `قرار مجلس الإدارة رقم 45` — مفيد جدًا للمراجع بعدين |
> **القيود المرحّلة قبل التاريخ ده ما بتتغيّرش.** التعديل بيعمل **إصدار جديد**،
> والقديم بيتقفل بتاريخ. عمرك ما هتخسر تاريخ.
---
### الخطوة ٧ — احفظ
1. دوس الزرار الأزرق الكبير: **«حفظ وتفعيل على 11 قاعدة»**.
2. **هتظهر لك رسالة تأكيد:** *«هتطبّق نفس التقسيمة على ١١ قاعدة قيد. تمام؟»*
(الرسالة دي بتظهر بس لو النطاق أكتر من قاعدة واحدة.)
3. دوس **موافق**.
**خلاص.** من دلوقتي، أي عضو يدفع عضوية، القيد هيتعمل بالتقسيمة دي لوحده.
> **الحفظ كله أو ولا حاجة.** لو مصدر واحد من الـ١١ فيه مشكلة، النظام بيرفض الدفعة
> كلها وما بيكتبش ولا صف — عشان الشجرة ما تفضلش نص موصّلة.
**زراير تانية جنبه:**
- **«الوضع المتقدّم»** → نفس الحاجة بس بخيارات أكتر وأعقد
- **«إلغاء»** → رجوع من غير حفظ
---
## ١٢ — اعمل حساب جديد وإنت جوّه المعالج
مش لازم تخرج من المعالج عشان تعمل حساب.
**اعمل كده:**
1. في أي كارت «جزء»، فوق خانة **«يروح لحساب»** على الشمال، فيه لينك صغير
**«+ حساب جديد»**. دوس عليه.
2. هتفتح نافذة اسمها **«حساب جديد»** فيها حقلين:
| الحقل | اعمل إيه |
|---|---|
| **تحت أي حساب رئيسي؟** | اختار من القايمة |
| **اسم الحساب** | اكتب الاسم بالعربي |
3. دوس **«إنشاء وتحديد»**.
**اللي هيحصل:** النظام بيولّد الكود لوحده، بيعمل الحساب، **وبيحطّه في الخانة على طول**.
مش محتاج تدوّر عليه.
> لو فيه مشكلة، هتظهر لك رسالة حمرا جوّه النافذة نفسها.
---
## ١٣ — بند إيراد مؤجل
استخدمه لما تقبض فلوس دلوقتي والخدمة على مدار السنة (اشتراك سنوي مثلًا).
**اعمل كده:**
1. في كارت الجزء، من **«نوع البند»** اختار **«إيراد مؤجل»**.
2. **على طول** هيفتح تحته **مربع أصفر** فيه حقلين:
| الحقل | اكتب فيه |
|---|---|
| **يترحّل على كام شهر** | `12` للاشتراك السنوي |
| **حساب الإيراد اللي يترحّل له** | ابحث واختار حساب الإيراد النهائي |
3. حقل «حساب الإيراد» ده مفلتر على حسابات **الإيراد** بس.
> **مهم:** حساب البند نفسه (خانة «يروح لحساب» فوق) لازم يكون حساب **التزام** —
> زي «إيرادات مدفوعة مقدمًا». والحساب اللي في المربع الأصفر هو حساب **الإيراد**
> اللي الفلوس هتترحّل له شهر بشهر.
**لو نسيت تملا المربع الأصفر:** النظام هيرفض الحفظ ويقول لك
*«الجزء 1: إيراد مؤجل من غير حساب يترحّل له.»*
---
## ١٤ — بند تخصيص (صندوق / احتياطي)
الحالة دي مهمة ومحدش بينتبه لها.
### السؤال اللي لازم تجاوب عليه الأول
> الـ٢٠٪ اللي رايحة لصندوق كذا — **بتشتري خدمة مختلفة**؟ ولا هي مجرد **حجز فلوس**
> النادي كسبها فعلًا؟
| الحالة | اللي تعمله |
|---|---|
| بتشتري خدمة مختلفة (جيم، حمام سباحة، لوكر) | **توزيع إيراد عادي** — سيب الشيك بوكس فاضي |
| مجرد حجز لصندوق / احتياطي | **علّم الشيك بوكس** ← الشرح تحت |
**ليه الفرق مهم؟** لو حجزت فلوس النادي كسبها فعلًا كـ«التزام»، الإيراد المُعلن
هيقلّ — وده غلط محاسبيًا. الصح إن الإيراد يتسجّل **كامل**، وبعدين يتعمل قيد
تخصيص منفصل من الأرباح.
**اعمل كده:**
1. في كارت الجزء، تحت خالص، فيه شيك بوكس:
**☐ الجزء ده تخصيص، مش نوع إيراد تاني**
2. علّم عليه.
3. هيفتح مربع **أزرق فاتح** فيه حقلين:
| الحقل | اكتب فيه |
|---|---|
| **١ — الإيراد يتسجّل في** | حساب الإيراد الحقيقي (مفلتر على الإيرادات) |
| **٢ — التخصيص يتخصم من** | أرباح مرحّلة أو حساب تخصيص |
4. وخانة **«يروح لحساب»** فوق تفضل هي **الصندوق/الاحتياطي** اللي هياخد الفلوس.
**النتيجة:** النظام بيعمل **قيدين** — واحد بالإيراد الكامل، وواحد منفصل بالتخصيص.
> الشيك بوكس ده **ما بيظهرش خالص** على الحركات الصادرة (الصرف) — لأنه مالوش معنى هناك.
>
> وبند **«الباقي»** ما ينفعش يكون تخصيص. لو جرّبت، النظام هيقول لك.
---
## ١٥ — غيّر اتجاه مصدر من تحصيل لصرف
لو لقيت مصدر مصنّف غلط (مسيّر رواتب مثلًا مصنّف «تحصيل» وهو المفروض «صرف»):
**اعمل كده:**
1. في المعالج، في الخطوة ١، اختار كارت **«المصدر ده بس»**.
2. انزل للشريط الملوّن. هتلاقي سطر زيادة:
```
اتجاه الحركة لـ مسيّر رواتب: تحصيل (البنود دائنة) غيّره لـ صرف
```
3. دوس على لينك **«غيّره لـ صرف»**.
4. هتظهر رسالة تأكيد:
*«تغيير الاتجاه بيغيّر أنواع البنود المتاحة. القواعد القديمة والقيود المرحّلة
ما بتتغيّرش. تمام؟»*
5. دوس موافق.
**اللي بيتغيّر بعدها:**
| قبل (تحصيل) | بعد (صرف) |
|---|---|
| البنود **دائنة** | البنود **مدينة** |
| الأنواع: إيراد، مؤجل، تحصيل للغير… | الأنواع: مصروف، مصروف مقدَّم، أصل ثابت، مخزون، تسوية ذمم دائنة، إعدام / إسقاط، دفع لحساب الغير |
| الشريط **أزرق** | الشريط **بنفسجي** |
| النقدية مدينة | النقدية دائنة |
> **قاعدة صارمة:** مينفعش تحط تقسيمة واحدة على نطاق فيه مصادر تحصيل ومصادر صرف
> مع بعض. النظام هيرفض ويقول لك *«قسّمهم على دفعتين»*.
---
## ١٦ — مركز التوصيل — شوف الناقص
**افتح:** القايمة ← المحاسبة ← **مركز التوصيل**
(`/accounting/revenue-mapping/connections`)
الشاشة دي بتجاوب على سؤال واحد: **إيه اللي لسه مش داخل الدفاتر، وليه؟**
**٤ كروت فوق:**
| الكارت | معناه |
|---|---|
| **موصولة بالدفاتر** | تمام |
| **تُوصَّل من الشاشة** | **إنت تقدر تصلّحها دلوقتي** |
| **تحتاج تعديل برمجي** | محتاجة مبرمج، مش إنت |
| **المبلغ الموجود خارج الدفاتر** | إجمالي الفلوس اللي لسه مش مسجّلة |
**تحتهم ٣ جداول:**
### جدول ١ — «تُوصَّل الآن من الشاشة — بدون مبرمج» (خلفية صفرا)
دي اللي تشتغل عليها. كل صف فيه المبلغ الحالي وعدد السجلات وزرار **«اربط الحسابات»**.
**اعمل كده:** دوس **«اربط الحسابات»** → هتروح على شاشة التوزيع لهذا المصدر.
### جدول ٢ — «تحتاج تعديل برمجي أولًا» (خلفية حمرا)
**مفيش زرار ربط هنا عن قصد.** الوحدة دي بتسجّل الفلوس في جدولها الخاص ومش بترسل
أي حدث للمحاسبة. لو ربطت حسابات هنا **مش هيحصل حاجة**. العمود الأخير بيقول لك
إيه الناقص بالظبط عشان تبعته للمبرمج.
### جدول ٣ — «موصولة — ويمكن إعادة توزيعها في أي وقت» (خلفية خضرا)
التوزيع الحالي ظاهر قدام كل مسار. الزرار بيتغيّر حسب الحالة:
| لو المسار | الزرار |
|---|---|
| على حساب واحد | **«قسّم على حسابات»** |
| متقسّم خلاص | **«عدّل التوزيع»** |
وجنبه زرار **«متقدّم»**.
---
## ١٧ — شاشة الفحص — لو حاجة مش شغالة
**افتح:** توزيع الإيرادات ← زرار **«فحص الحالة»**
(`/accounting/revenue-mapping/diagnostics`)
الشاشة دي بتقول لك كل حاجة مكسورة، من واقع البيانات الحقيقية. **٥ أقسام**:
| القسم | يعني إيه | اعمل إيه |
|---|---|---|
| **حسابات مُعرَّفة في الكود ولا تقبل الترحيل** | حسابات رئيسية القيد بيفشل عليها | شوف عمود «الحالة الآن» — لو **«مُعالَج عبر المحرك»** يبقى تمام |
| **قواعد توزيع تشير إلى حسابات لا تقبل الترحيل** | قاعدة رابطة على حساب رئيسي | دوس **«تصحيح»** |
| **ما هو مُرحَّل فعليًا إلى «٤١٠٥١٥ — إيرادات متنوعه»** | كل حاجة مرمية في حساب واحد | قسّمها من المعالج |
| **أنواع المدفوعات في البيانات الفعلية وحالة ربطها** | كل نوع دفعة وحالته | دوس **«فتح»** على اللي حالته **«بدون قاعدة توزيع»** |
| **آخر حالات فشل الترحيل** | القيود اللي فشلت والرسالة | اقرا الرسالة وصلّح السبب |
> القسم الأخير ده **أول مكان تبص فيه** لو حد قال لك «الدفعة اتسجّلت والقيد مش موجود».
---
# الجزء الرابع — الشغل اليومي
## ١٨ — حصّل مستحق من شاشة المطالبات
**افتح:** القايمة ← المحاسبة ← **المطالبات والتحصيل**
(`/accounting/billing`)
**اللي هتشوفه:** كروت فوق، كل كارت مصدر مطالبة ومكتوب فيه إجمالي المستحق وعدد
المستحقات.
**اعمل كده:**
1. دوس على الكارت اللي عايزه (مثلًا **«فواتير الإيجار»**).
→ الكارت هيتحدد بإطار أزرق، وتحت هيظهر جدول المستحقات.
2. فوق الجدول شريط أصفر بيقول: **`12` مستحق بإجمالي `84,500.00`**.
3. الجدول فيه: المرجع · الدافع · التاريخ · المبلغ · زرار **«تحصيل»**.
4. لو الليستة طويلة، استخدم خانة **«بحث»** فوق على الشمال.
5. دوس **«تحصيل»** قصاد السطر اللي عايزه.
→ هتفتح نافذة اسمها **«تحصيل مستحق»** وتحت العنوان المرجع واسم الدافع.
**املا النافذة:**
| الحقل | ملاحظة |
|---|---|
| **المبلغ** | بيتملا لوحده. **لو المصدر مش بيسمح بالتحصيل الجزئي، الخانة هتبقى مقفولة** — والسطر تحتها بيقول لك |
| **طريقة الدفع** | نقدي · فيزا · شيك · تحويل بنكي |
| **الخزنة** | اختار الخزنة اللي الفلوس هتدخلها |
**الحقول بتتغيّر حسب طريقة الدفع:**
- اخترت **شيك** → هتظهر: رقم الشيك · البنك · تاريخ استحقاق الشيك
- اخترت **فيزا** أو **تحويل بنكي** → هتظهر: مرجع العملية
6. دوس **«تأكيد التحصيل»**.
**اللي بيحصل:** التحصيل بيمرّ على نفس مسار دفعة العضو — إيصال، وعهدة خزنة،
**وقيد في الدفاتر بالتقسيمة اللي عملتها في الدرس ١١**.
---
## ١٩ — عرّف مصدر مطالبة جديد
**دي حاجة ما بتحتاجش مبرمج.** بتقول للنظام: الجدول ده فيه مبالغ مستحقة.
**افتح:** المطالبات ← زرار **«إعداد مصادر المطالبة»** ← زرار **«+ مصدر جديد»**
(`/accounting/billing/sources/0/edit`)
### كارت «التعريف»
| الحقل | مثال |
|---|---|
| **الاسم بالعربي** | `فواتير الإيجار` |
| **الكود** | `rental_invoice` |
| **وصف مختصر** | اختياري |
### كارت «من أين يقرأ المبالغ»
1. من **«الجدول»** اختار الجدول.
**بتظهر بس الجداول اللي فيها عمود رقمي عشري.** وجنب كل جدول عدد صفوفه التقريبي.
2. أول ما تختار الجدول، كل قوايم الأعمدة تحته **بتتملى لوحدها** بأعمدة الجدول ده.
| العمود | إجباري | يعني |
|---|:---:|---|
| **عمود المعرّف** | ✔ | غالبًا `id` |
| **عمود المبلغ** | ✔ | المبلغ المستحق |
| **عمود التاريخ** | — | |
| **عمود الرقم المرجعي** | — | |
| **عمود العضو** | — | |
| **عمود اللاعب** | — | |
| **عمود اسم الدافع** | — | |
> **لازم تحدد عمود واحد على الأقل من التلاتة الأخيرة** (عضو أو لاعب أو اسم)،
> وإلا مش هينفع نعرف المطالبة على مين.
### كارت «شروط لسه مستحق»
1. دوس **«+ شرط»**.
2. اختار العمود، والعامل، والقيمة.
**العوامل المتاحة:** يساوي · لا يساوي · أكبر من · أصغر من · أكبر أو يساوي ·
أصغر أو يساوي · ضمن (افصل بفاصلة) · ليس ضمن · فارغ · غير فارغ.
> الصفوف اللي اتحصّلت من النظام بتتشال **تلقائيًا** حتى لو ما حددتش شرط.
### كارت «بعد التحصيل» (اختياري بالكامل)
لو الجدول عنده أعمدة تتحدّث بعد السداد، حددها هنا:
عمود رقم الدفعة · عمود رقم الإيصال · عمود الحالة · عمود تاريخ السداد ·
قيمة الحالة بعد السداد (مثلًا `paid`).
> لو الجدول مفيهوش الأعمدة دي، التحصيل بيتسجّل عندنا وبيشتغل عادي.
**احفظ.** كل الأعمدة بتتأكد من وجودها فعليًا في قاعدة البيانات قبل الحفظ —
مش هينحفظ إعداد غلط.
**في شاشة المصادر بعدها،** لو المصدر إعداده اتكسر (حد شال عمود مثلًا)، هيظهر
بادج أحمر **«غير صالح»** ومكتوب تحته الخطأ.
---
## ٢٠ — عرّف نوع سند
اعمل نوع لكل مصروف متكرر — دعاية، صيانة، كهرباء — عشان الصرف يبقى بضغطة.
**افتح:** السندات ← زرار **«أنواع السندات»**
(`/accounting/vouchers/types`)
**املا فورم «إضافة نوع»:**
| الحقل | مثال |
|---|---|
| **الكود** | `PV_ADS` |
| **الاسم** | `سند صرف دعاية` |
| **الاتجاه** | **صرف — فلوس خرجت** / قبض — فلوس دخلت |
| **بادئة الترقيم** | `PV` |
| **يحتاج اعتماد؟** | لا / نعم |
| **ترتيب العرض** | `100` |
دوس **«حفظ النوع»**.
في الجدول تحت هتشوف كل نوع وعدد السندات اللي اتعملت بيه وحالته.
---
## ٢١ — اعمل سند صرف
**افتح:** القايمة ← المحاسبة ← **سندات الصرف والقبض** ← زرار **«+ سند جديد»**
(`/accounting/vouchers/create`)
> الفكرة هنا: **اكتب الصرف بلغة عادية والنظام يبني القيد المزدوج لوحده.**
### كارت «بيانات السند»
1. **نوع السند** ✔ — اختار من القايمة. كل نوع مكتوب جنبه **صرف** أو **قبض**.
2. **التاريخ** ✔ — الافتراضي النهاردة.
3. **البيان** ✔ — مثال: `مصاريف دعاية — حملة الموسم الصيفي`.
4. **المستفيد** — اسم الجهة أو الشخص.
5. **رقم المرجع** — رقم الفاتورة أو العقد.
> **لاحظ:** أول ما تختار النوع، **عناوين الكروت تحت بتتغيّر**:
> - صرف → «من أي حساب خرجت الفلوس» و«الفلوس دي راحت على إيه» و«المستفيد (المدفوع له)»
> - قبض → «في أي حساب دخلت الفلوس» و«الفلوس دي جاية من إيه» و«الدافع»
### كارت «من أي حساب خرجت الفلوس»
1. **الحساب** ✔ — اكتب `صندوق` أو `بنك` واختار من القايمة.
(لو النوع ليه حساب افتراضي، الخانة بتتملى لوحدها بـ «الحساب الافتراضي للنوع».)
2. **طريقة الدفع** — نقدي / تحويل بنكي / شيك / فيزا.
لو اخترت **شيك**، هتظهر ٣ خانات: رقم الشيك · البنك · تاريخ الشيك.
### كارت «الفلوس دي راحت على إيه»
1. دوس **«+ بند»** (لو مفيش بند ظاهر — بند واحد بيظهر لوحده).
2. **الحساب** — اكتب `دعاية` أو `صيانة` واختار.
3. **المبلغ**.
4. **بيان البند** · **مركز التكلفة** · **ضريبة** — كلهم اختياريين.
5. كرّر لكل بند.
تحت الكارت سطر **«الإجمالي»** بيتحدّث لوحده.
### على الشمال — «القيد الناتج»
بيتحدّث لحظيًا. البنود مدينة والحساب اللي الفلوس خرجت منه دائن، وتحت الجدول
**✓ القيد متوازن**.
> *«الضريبة على أي بند بتتفصل تلقائيًا عند الترحيل.»*
### الحفظ — عندك اختيارين
| الزرار | اللي بيحصل |
|---|---|
| **«حفظ وترحيل»** | يعمل السند **ويرحّله للدفاتر على طول** |
| **«حفظ كمسودة»** | يعمل السند بس، الترحيل بعدين |
---
## ٢٢ — اعتمد وارحّل وألغي سند
**افتح:** السندات (`/accounting/vouchers`) ودوس **«عرض»** قصاد أي سند.
**اللي هتشوفه:** فوق اسم النوع ورقم السند، والبادج بالحالة، والمبلغ بخط كبير.
**الحالات الأربعة:**
| الحالة | اللون |
|---|---|
| **مسودة** | رمادي |
| **بانتظار الاعتماد** | أصفر |
| **مُرحَّل** | أخضر |
| **ملغي** | أحمر |
**الأزرار تحت الصفحة بتتغيّر حسب الحالة:**
| الحالة الحالية | الأزرار المتاحة |
|---|---|
| بانتظار الاعتماد | **«اعتماد السند»** |
| مسودة | **«ترحيل إلى الدفاتر»** |
| أي حالة غير ملغي | **«إلغاء السند»** |
| مُرحَّل | **«إلغاء السند وعكس قيده»** |
**لما تدوس إلغاء:** هتظهر لك نافذة صغيرة تسألك **«سبب الإلغاء؟»**. لازم تكتب سبب،
لو سبتها فاضية العملية بتتلغي.
**لما السند يبقى مُرحَّل**، هيظهر كارت زيادة اسمه **«القيد المُرحَّل»** فيه القيد
كامل بالمدين والدائن، وزرار **«فتح القيد»** يوديك على القيد نفسه.
---
## ٢٣ — قيد يومية يدوي
استخدمه للحالات اللي مفيش لها شاشة — التسويات والأخطاء.
**افتح:** القايمة ← المحاسبة ← **قيود اليومية** ← زرار **«+ قيد جديد»**
(`/accounting/journal-entries/create`)
**اعمل كده:**
1. **تاريخ القيد** ✔ — الافتراضي النهاردة.
2. **رقم المرجع** — اختياري.
3. **الفرع** — اختياري.
4. **الوصف (عربي)** ✔.
5. **مركز التكلفة** — اختياري.
**في جدول «سطور القيد»** (بيبدأ بسطرين):
| العمود | اعمل إيه |
|---|---|
| **الحساب** | اختار من القايمة |
| **البيان** | اختياري |
| **مدين** | اكتب المبلغ، أو سيبه `0.00` |
| **دائن** | اكتب المبلغ، أو سيبه `0.00` |
| **X** | يشيل السطر (مش بيشيل لو فاضل سطرين بس) |
6. محتاج سطور زيادة؟ دوس **«+ سطر جديد»**.
7. **بُص على آخر صفين في الجدول:**
```
الإجمالي 50,000.00 50,000.00
```
ولو فيه فرق، هيظهر **صف أحمر** زيادة:
```
الفرق 5,000.00
```
8. اكتب **ملاحظات** لو حابب.
9. دوس **«حفظ كمسودة»**.
> **لاحظ:** الزرار مكتوب عليه **«حفظ كمسودة»** — القيد **مش بيتحفظ مرحّل**.
> الترحيل خطوة منفصلة، ودي حماية مقصودة.
---
## ٢٤ — رحّل قيد واعكسه
**افتح:** قيود اليومية ← دوس **«عرض»** قصاد القيد.
**اللي هتشوفه:** رقم القيد والوصف، والحالة في دايرة ملوّنة، وتحتها ٤ خانات:
التاريخ · المصدر · أنشأه · رحّله.
**الحالات:**
| الحالة | اللون | يعني |
|---|---|---|
| **مسودة** | برتقالي | لسه مش في الدفاتر |
| **مرحّل** | أخضر | في الدفاتر ومؤثّر على الأرصدة |
| **معكوس** | أحمر | القيد ده اتعمل له عكس (بيظهر مشطوب في القايمة) |
### ترحيل قيد
1. لازم القيد يكون **مسودة**.
2. دوس **«ترحيل القيد»**.
3. رسالة تأكيد: *«هل تريد ترحيل هذا القيد؟»* → موافق.
### عكس قيد
1. لازم القيد يكون **مرحّل** ومش عكس لقيد تاني.
2. دوس **«عكس القيد»** (الزرار الأحمر).
3. رسالة تأكيد: *«هل تريد عكس هذا القيد؟ سيتم إنشاء قيد عكسي.»* → موافق.
> **العكس ما بيمسحش القيد الأصلي.** بيعمل قيد جديد بالعكس، والاتنين بيفضلوا
> موجودين. ده الصح محاسبيًا — عمرك ما تمسح قيد مرحّل.
### فلترة القيود
في شاشة القايمة (`/accounting/journal-entries`) فيه شريط فلاتر:
**بحث** (رقم القيد، الوصف، كود الحساب، اسم الحساب) · **الحالة** · **المصدر**
(المدفوعات / المبيعات / الموارد البشرية / المحاسبة) · **من تاريخ** · **إلى تاريخ**.
> عمود **المصدر** في الجدول بيقول لك القيد ده جه منين: مدفوعات · مبيعات · رواتب ·
> غرامات · أقساط · محاسبة · **يدوي**.
---
## ٢٥ — الحركات اليومية — إدخال سريع
الشاشة دي للكميات — لما يكون عندك ٥٠ حركة تدخّلها بسرعة وتجمّعهم في قيد واحد.
**افتح:** القايمة ← المحاسبة ← **الحركات اليومية**
(`/accounting/daily-transactions`)
**اعمل كده:**
1. فوق فيه **«التاريخ»** — غيّره لو عايز يوم تاني. الصفحة بتعمل ريفريش لوحدها.
2. في كارت **«إدخال سريع»**:
| الحقل | إجباري |
|---|:---:|
| **الحساب** | ✔ |
| **المبلغ** | ✔ |
| **الاتجاه** (مدين / دائن) | ✔ |
| **نوع اليومية** | — |
| **مركز التكلفة** | — |
| **رقم المستند** | — |
| **بيان** | — |
3. دوس **«تسجيل»**. كرّر لكل حركة.
4. في كارت **«حركات اليوم»** تحت، كل حركة حالتها **«معلّق»** (برتقالي).
5. لما تخلّص كل حركات اليوم، دوس الزرار الأزرق فوق:
**«تجميع في قيد (23 حركة)»**
6. رسالة تأكيد: *«سيتم تجميع كل الحركات غير المجمعة في قيد يومية واحد. متابعة؟»*
→ موافق.
7. الحالات بتتغيّر لـ **«مجمّع»** (أخضر).
> بُص على صف **الإجمالي** تحت الجدول — المدين والدائن لازم يتساووا **قبل** ما
> تدوس التجميع.
---
# الجزء الخامس — آخر الشهر
## ٢٦ — رحّل الإيراد المؤجل
اعمل الخطوة دي **كل شهر**، قبل ما تقفل الشهر.
**افتح:** القايمة ← المحاسبة ← **الإيراد المؤجل**
(`/accounting/revenue-mapping/recognition`)
**اللي هتشوفه:** كارتين فوق:
```
إجمالي الإيراد المؤجل القائم يستحق حتى 2026-09
1,240,000.00 103,333.00
31 استحقاق في 4 قيد
```
**اعمل كده:**
1. في خانة **«الفترة»** حدّد الشهر (شكلها `2026-09`).
2. دوس **«معاينة»** — الأرقام فوق بتتحدّث.
3. لو الرقم أكبر من صفر، هيظهر زرار أزرق مكتوب عليه:
**«ترحيل تحقق الإيراد حتى 2026-09 — 103,333.00»**
4. دوس عليه.
5. رسالة تأكيد: *«سيتم ترحيل قيود تحقق الإيراد حتى فترة 2026-09. متابعة؟»* → موافق.
**تحت الصفحة جدولين:**
- **«جدول الاستحقاق القائم»** — كل فترة والمبلغ المستحق فيها
- **«آخر ما تم تحققه»** — اللي اترحّل خلاص، وجنب كل سطر زرار **«عرض»** يوديك للقيد
> **لو الجدول فاضي** ومكتوب *«لا يوجد إيراد مؤجل»*، يبقى مفيش أي بند من نوع
> «إيراد مؤجل» متفعّل. ارجع للدرس ١٣.
---
## ٢٧ — اقفل الشهر
**افتح:** القايمة ← المحاسبة ← **إقفال الفترات**
(`/accounting/period-closing`)
**اعمل كده:**
1. من قايمة **«السنة المالية»** فوق اختار السنة. الصفحة بتعمل ريفريش لوحدها.
2. هيظهر جدول بـ١٢ شهر، كل صف فيه: الفترة · من · إلى · عدد القيود · إجمالي الحركة ·
الحالة · تاريخ الإغلاق.
**الحالات:**
| الحالة | اللون |
|---|---|
| **مفتوحة** | أخضر |
| **مغلقة** | رمادي |
| **أعيد فتحها** | أصفر |
| **قيد الإغلاق** | أزرق |
3. قصاد الشهر المفتوح، دوس زرار **«إغلاق»**.
4. رسالة تأكيد: *«إغلاق الفترة 2026-09؟»* → موافق.
**بعد الإقفال:** أي قيد بتاريخ داخل الشهر ده **هيترفض**. ودي مش مشكلة — دي الفايدة.
### إعادة فتح شهر مغلق
1. قصاد الشهر المغلق، دوس زرار **«إعادة فتح»** (البرتقالي).
2. هتظهر نافذة تكتب فيها **«سبب إعادة الفتح:»**.
3. **لازم تكتب سبب** — لو سبتها فاضية العملية بتتلغي.
> السبب ده بيتسجّل. لو المراجع سأل «الشهر ده اتفتح تاني ليه؟» — الإجابة موجودة.
---
## ٢٨ — التقارير
كلهم في نفس القايمة تحت. مفيش تعقيد — تفتح، تحدّد الفترة، تشوف.
| التقرير | الرابط | امتى تستخدمه |
|---|---|---|
| **ميزان المراجعة** | `/accounting/reports/trial-balance` | **أول تقرير تفتحه** — بيثبت إن المدين = الدائن. وفيه **تصدير CSV** |
| **دفتر الأستاذ** | `/accounting/reports/general-ledger` | كل حركات حساب معيّن بالترتيب |
| **قائمة الدخل** | `/accounting/reports/income-statement` | الإيرادات ناقص المصروفات |
| **الميزانية العمومية** | `/accounting/reports/balance-sheet` | الأصول والخصوم وحقوق الملكية. **فيه تصدير CSV** |
| **ميزانية موحدة** | `/accounting/reports/consolidated-balance-sheet` | كل الفروع مع بعض |
| **المدينون (AR)** | `/accounting/reports/accounts-receivable` | اللي علينا نحصّله |
| **الدائنون (AP)** | `/accounting/reports/accounts-payable` | اللي إحنا مدينين بيه |
| **كشف حساب عضو** | `/accounting/reports/member-statement` | كل حركة على عضو واحد |
| **الخزينة والمدفوعات** | `/accounting/reports/treasury` | حركة الكاش |
| **تحليل الإيرادات** | `/accounting/reports/revenue-analysis` | الإيراد حسب النوع |
**وكشوف تانية:** كشف حساب عميل · كشف حساب مورد · حركة النقدية اليومية.
> **الروتين الشهري باختصار:**
> رحّل الإيراد المؤجل ← افتح ميزان المراجعة واتأكد إنه متوازن ← اقفل الشهر.
---
# الجزء السادس
## ٢٩ — تمرين كامل — من صفر لقيد مرحّل
جرّب ده بنفسك، هياخد منك ١٥ دقيقة ويثبّت كل حاجة:
```mermaid
flowchart TD
A["١ — /accounting/fiscal-years<br/>اتأكد إن ٢٠٢٦ مفتوحة"]
B["٢ — /accounting/chart-of-accounts<br/>ابحث عن حساب إيراد وشوف كوده"]
C["٣ — /accounting/revenue-mapping<br/>دوس على كارت «غير مربوطة»"]
D["٤ — اختار أي مصدر<br/>ودوس «ربط الحسابات»"]
E["٥ — في المعالج:<br/>نطاق «المصدر ده بس» + مرحلة «تحصيل»"]
F["٦ — مبلغ مرجعي 1000<br/>من غير ضريبة"]
G["٧ — اقتطع جزء 30%<br/>لأي حساب إيراد"]
H["٨ — حدد حساب الباقي"]
I["٩ — راجع «القيد الناتج»<br/>لازم ✓ متوازن"]
J["١٠ — احفظ"]
K["١١ — /accounting/journal-entries<br/>شوف القيد لما حد يدفع"]
A --> B --> C --> D --> E --> F --> G --> H --> I --> J --> K
style A fill:#FEE2E2,stroke:#DC2626
style I fill:#FEF3C7,stroke:#B45309
style K fill:#D1FAE5,stroke:#059669
```
**اختبار إنك فهمت:** لو غيّرت المبلغ المرجعي من `1000` لـ `3` وضغطت خارج الخانة،
لازم تشوف الـ٣٠٪ بقت `0.90` والباقي `2.10` — والقيد فاضل **متوازن**.
---
## ٣٠ — لو ظهرت لك الرسالة دي…
### في المعالج (بتظهر في صندوق أحمر قبل الحفظ)
| الرسالة | اعمل إيه |
|---|---|
| «لازم تحدد الحساب اللي يروح له الباقي.» | انزل للصندوق الأخضر واختار حساب |
| «الجزء 1: فيه قيمة من غير حساب.» | كتبت نسبة وما اخترتش حساب |
| «الجزء 1: إيراد مؤجل من غير حساب يترحّل له.» | املا المربع الأصفر |
| «الجزء 1: تخصيص من غير حساب الإيراد اللي هيتسجّل فيه.» | املا المربع الأزرق |
| «الجزء 1: بند الباقي ما ينفعش يكون تخصيص.» | شيل علامة التخصيص من بند الباقي |
| «مجموع النسب 120٪ — أكبر من 100٪.» | قلّل النسب |
| «النطاق اللي اخترته مفيهوش أي قاعدة قيد.» | غيّر الفئة أو المرحلة |
### من السيرفر بعد الحفظ
| الرسالة | معناها |
|---|---|
| «الحساب 210101 نوعه liability ومش مناسب لبند إيراد» | نوع البند مش متوافق مع نوع الحساب |
| «حساب رئيسي، والقيد ما بينزلش عليه» | اختار حساب فرعي، أو حوّله لحساب ترحيل من شاشة دليل الحسابات |
| «موقوف أو مؤرشف» | الحساب مش نشط |
| «واقع في فترة مقفولة (2026-09)» | غيّر تاريخ السريان، أو افتح الفترة |
| «اتخطّى — المرحلة دي ما تنطبقش على الأجور» | طبيعي، المرحلة مش من مراحل الفئة دي |
| «قسّمهم على دفعتين» | خلطت مصادر تحصيل مع مصادر صرف في نطاق واحد |
### في المعاينة على الشمال
| اللي هتشوفه | معناه |
|---|---|
| **✗ القيد غير متوازن** | فيه فرق. غالبًا حساب الباقي مش محدد |
| «اقتطاع أكبر من المتاح — تم تخفيضه.» | مبلغ ثابت أكبر من الموجود |
| «الحساب النهائي — لسه ما اتحددش» بالأحمر | حدد حساب الباقي |
### في شاشات تانية
| المكان | الرسالة | اعمل إيه |
|---|---|---|
| المطالبات | «تعذّر قراءة هذا المصدر» | دوس على لينك **«تصحيح الإعداد»** جنبها |
| مصادر المطالبة | بادج **«غير صالح»** | جدول أو عمود اتغيّر — عدّل المصدر |
| دليل الحسابات | «الحذف غير متاح — عالج الارتباطات أعلاه أولًا» | اقرا القايمة الصفرا فوق في نفس النافذة |
| الفحص | أي سطر في «آخر حالات فشل الترحيل» | اقرا الرسالة — بتقول السبب بالظبط |
---
## ملخص للطباعة — الروتين اليومي
```mermaid
flowchart RL
A["الصبح<br/>/accounting/billing<br/>حصّل المستحقات"]
B["على مدار اليوم<br/>/accounting/vouchers<br/>سندات الصرف"]
C["آخر اليوم<br/>/accounting/journal-entries<br/>اتأكد القيود اتعملت"]
D["آخر الشهر<br/>رحّل المؤجل ← ميزان المراجعة ← اقفل"]
A --> B --> C --> D
style A fill:#DBEAFE,stroke:#2563EB
style D fill:#D1FAE5,stroke:#059669
```
**٥ حاجات لو افتكرتهم مش هتغلط:**
1. **من غير سنة مالية مفتوحة، مفيش قيد بيتعمل** — وما بيقول لكش.
2. **الحساب الرئيسي ما بياخدش قيود.** الاسم البولد في الشجرة = ممنوع.
3. **بند «الباقي» إجباري** في أي تقسيمة — هو اللي بيخلّي القيد متوازن.
4. **راجع «القيد الناتج» على الشمال قبل ما تحفظ.** لازم ✓ خضرا.
5. **فلوس مش بتاعتنا = «تحصيل لحساب الغير»**، مش إيراد.
---
*ملفات ذات صلة:*
- `docs/دليل-النظام-المحاسبي-الكامل.md` — الشرح النظري وليه بنعمل كده
- `docs/معالج-توزيع-المبالغ.md` — تفاصيل المعالج
- `docs/كيف-أوصّل-أي-إيراد.md` — سيناريوهات لكل نوع إيراد
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