Commit d1cdb878 authored by DevPilot's avatar DevPilot

feat(accounting): فحص جاهزية قبل إقفال الشهر

الإقفال كان بيتحقق من حاجة واحدة (القيود المسودة) وبعدين يقفل. أسوأ حاجة
في الإقفال إن المحاسب يقفل ويطلّع القوائم وبعدين يكتشف ناقص ويضطر يفتح تاني.

الشاشة دلوقتي بتعمل سبع فحوصات قبل الإقفال، كل واحد بلينك يوصّل للشاشة
اللي تحلّه: القيود المسودة، توازن الميزان، الشهر السابق، العمليات غير
المقيّدة، الفلوس الواقفة في حسابات وسيطة، الشيكات المستحقة، والمطابقة
البنكية.

المسودة وعدم التوازن موانع بتوقف الإقفال — الباقي تنبيهات والقرار للمحاسب.
والفحص بيتعاد في الكنترولر مش بس في الشاشة عشان حد ما يعديه بـ POST مباشر.
parent 7d6c6ada
...@@ -9,6 +9,7 @@ use App\Core\App; ...@@ -9,6 +9,7 @@ use App\Core\App;
use App\Core\Response; use App\Core\Response;
use App\Modules\Accounting\Models\FiscalYear; use App\Modules\Accounting\Models\FiscalYear;
use App\Modules\Accounting\Services\PeriodClosingService; use App\Modules\Accounting\Services\PeriodClosingService;
use App\Modules\Accounting\Services\PeriodCloseReadinessService;
class PeriodClosingController extends Controller class PeriodClosingController extends Controller
{ {
...@@ -26,6 +27,18 @@ class PeriodClosingController extends Controller ...@@ -26,6 +27,18 @@ class PeriodClosingController extends Controller
$periods = PeriodClosingService::getPeriodSummary($fiscalYearId); $periods = PeriodClosingService::getPeriodSummary($fiscalYearId);
} }
// الشهر اللي المحاسب بيجهّز لإقفاله — بشكل افتراضي أقدم شهر مفتوح
$target = (string) $request->get('period', '');
if ($target === '') {
foreach ($periods as $p) {
if (($p['status'] ?? '') !== 'closed') { $target = (string) $p['period']; break; }
}
}
$readiness = ($fiscalYearId > 0 && $target !== '')
? PeriodCloseReadinessService::check($fiscalYearId, $target)
: null;
$fiscalYears = FiscalYear::query() $fiscalYears = FiscalYear::query()
->where('is_archived', '=', 0) ->where('is_archived', '=', 0)
->orderBy('start_date', 'DESC') ->orderBy('start_date', 'DESC')
...@@ -36,6 +49,8 @@ class PeriodClosingController extends Controller ...@@ -36,6 +49,8 @@ class PeriodClosingController extends Controller
'fiscal_year' => $fiscalYear ? $fiscalYear->toArray() : null, 'fiscal_year' => $fiscalYear ? $fiscalYear->toArray() : null,
'fiscal_years' => $fiscalYears, 'fiscal_years' => $fiscalYears,
'fiscal_year_id' => $fiscalYearId, 'fiscal_year_id' => $fiscalYearId,
'readiness' => $readiness,
'target_period' => $target,
]); ]);
} }
...@@ -52,6 +67,17 @@ class PeriodClosingController extends Controller ...@@ -52,6 +67,17 @@ class PeriodClosingController extends Controller
return $this->redirect('/accounting/period-closing'); return $this->redirect('/accounting/period-closing');
} }
// الفحص بيتعاد هنا مش بس في الشاشة — عشان حد ما يعديش الموانع
// بـ POST مباشر من غير ما يفتح الشاشة.
$readiness = PeriodCloseReadinessService::check($fiscalYearId, $period);
if (!$readiness['can_close']) {
$blockers = array_filter($readiness['checks'], fn($c) => $c['status'] === PeriodCloseReadinessService::BLOCK);
$reasons = implode(' — ', array_column($blockers, 'detail'));
$session = App::getInstance()->session();
$session->flash('_alerts', [['type' => 'error', 'message' => 'مش ممكن الإقفال: ' . $reasons]]);
return $this->redirect('/accounting/period-closing?fiscal_year_id=' . $fiscalYearId . '&period=' . $period);
}
$result = PeriodClosingService::closeMonth($fiscalYearId, $period); $result = PeriodClosingService::closeMonth($fiscalYearId, $period);
$session = App::getInstance()->session(); $session = App::getInstance()->session();
......
...@@ -20,6 +20,58 @@ ...@@ -20,6 +20,58 @@
</div> </div>
</div> </div>
<?php if ($fiscal_year && !empty($readiness)): ?>
<!-- جاهزية الإقفال -->
<?php
use App\Modules\Accounting\Services\PeriodCloseReadinessService as RD;
$ok = $readiness['can_close'];
$styles = [
RD::PASS => ['#ECFDF5', '#A7F3D0', '#047857', '✓'],
RD::WARN => ['#FFFBEB', '#FDE68A', '#92400E', '!'],
RD::BLOCK => ['#FEF2F2', '#FECACA', '#B91C1C', '✕'],
];
?>
<div class="card" style="margin-bottom:18px;border-top:3px solid <?= $ok ? '#059669' : '#DC2626' ?>;">
<div style="padding:14px 18px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px;">
<div>
<strong style="font-size:15px;">جاهزية إقفال شهر <?= e($target_period) ?></strong>
<p style="margin:4px 0 0;color:#6B7280;font-size:12.5px;">
الفحوصات دي بتتعمل قبل الإقفال عشان ما تضطرش تفتح الشهر تاني بعد ما تطلّع القوائم.
</p>
</div>
<div style="text-align:left;">
<?php if ($ok): ?>
<span style="background:#ECFDF5;color:#047857;padding:6px 14px;border-radius:10px;font-size:13px;font-weight:700;">
جاهز للإقفال<?= $readiness['warnings'] > 0 ? ' — مع ' . (int) $readiness['warnings'] . ' تنبيه' : '' ?>
</span>
<?php else: ?>
<span style="background:#FEF2F2;color:#B91C1C;padding:6px 14px;border-radius:10px;font-size:13px;font-weight:700;">
<?= (int) $readiness['blockers'] ?> مانع لازم يتحل الأول
</span>
<?php endif; ?>
</div>
</div>
<div style="padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fill,minmax(330px,1fr));gap:10px;">
<?php foreach ($readiness['checks'] as $c): ?>
<?php [$bg, $bd, $fg, $icon] = $styles[$c['status']] ?? $styles[RD::WARN]; ?>
<div style="border:1px solid <?= $bd ?>;background:<?= $bg ?>;border-radius:7px;padding:10px 12px;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;">
<span style="width:19px;height:19px;border-radius:50%;background:<?= $fg ?>;color:#fff;
display:inline-flex;align-items:center;justify-content:center;
font-size:12px;font-weight:700;flex-shrink:0;"><?= $icon ?></span>
<strong style="font-size:13px;color:<?= $fg ?>;"><?= e($c['label']) ?></strong>
</div>
<div style="font-size:12.5px;color:#374151;line-height:1.7;"><?= e($c['detail']) ?></div>
<?php if (!empty($c['url']) && $c['status'] !== RD::PASS): ?>
<a href="<?= e($c['url']) ?>" style="font-size:12px;display:inline-block;margin-top:6px;">افتح الشاشة ›</a>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if ($fiscal_year): ?> <?php if ($fiscal_year): ?>
<div class="card"> <div class="card">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;"> <div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;">
......
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