✅ Re-Audit · مراجعة ثانية بعد إصلاحات أحمد

تقرير التحقق من إصلاحات أحمد على نظام المحاسبة

إعادة اختبار شاملة بعد إغلاق أحمد لـ 14 تذكرة CRITICAL/COMPLIANCE. تم الاختبار الفعلي على moon-erp.elbaset.com عبر HTTP API + قراءة الكود البرمجي للتحقق.

📅 تاريخ المراجعة: 2026-05-19 🌐 البيئة: moon-erp.elbaset.com 📋 المرجع: تقرير المراجعة الأولى 📝 14 تذكرة مغلقة (#1557-#1571)

1 الملخص التنفيذي

10FIXED ✅
3PARTIAL ⚠️
3STILL OPEN ❌
3DEPLOYMENT 🐛
✅ التقدم الجوهري: الـ GL لم يعد مكسوراً. أحمد سدّ معظم الثغرات الأساسية التي كانت تمنع التشغيل التجاري. الـ Audit Log موجود ويعمل (463 سجل في الإنتاج)، والـ Zakat و Withholding Tax APIs deployed، والـ Hijri date support يعرض في كل JE response.
⚠️ ما زال يحتاج عمل:
  • Activity Log جزئي: يلتقط journalentry فقط، بينما المطلوب 20 موديل (Account, FixedAsset, Voucher, Currency, ExchangeRate, TaxRate, FiscalYear, إلخ)
  • Double-reverse لا يزال ممكناً عند إنشاء عدة drafts (الفحص يتم فقط لو يوجد reversal بـ status=approved/posted)
  • JE create-time validation لا تزال متراخية (drafts غير متزنة، صفر-amount، debit+credit في نفس السطر — كلها مقبولة في CREATE)
  • 3 deployment bugs تمنع استخدام بعض الميزات في الإنتاج

2 ما تم إصلاحه فعلاً (مؤكد بـ HTTP testing) 10 إصلاحات

✅ Segregation of Duties (SoD) — مُفعَّل
FIXED
الاختبار
محاولة Approve JE أنشأه نفس المستخدم → HTTP 422
الرسالة
"لا يمكن لنفس المستخدم إنشاء واعتماد القيد المحاسبي (فصل المهام)."
الكود
ApproveJournalEntry::execute() يفحص $entry->created_by === $userId
✅ Approve يمنع القيد غير المتزن
FIXED
الاختبار
محاولة Approve لـ JE id=123 (debit=100, credit=50) → HTTP 422
الرسالة
"القيد غير متوازن (المدين يجب أن يساوي الدائن)."
ملاحظة
الفحص يحدث في Approve فقط (وليس في Create) — هذا يكفي لمنع التأثير على الـ GL، لكنه يسمح بتراكم drafts غير متزنة.
✅ FX rate لم يعد يقع silently على 1.0
FIXED
الاختبار
POST JE بـ currency_id=1 (KWD) + exchange_rate=12.5 دون وجود سجل في exchange_rates → HTTP 422
الرسالة
"لا يوجد سعر صرف لعملة القيد في التاريخ المحدد."
ملاحظة
الفحص يفرض وجود exchange_rate في الـ DB قبل قبول القيد. لا يمكن إنشاء قيد FX بلا سعر صرف موثَّق.
✅ Activity Log endpoint live
FIXED
الاختبار
GET /api/core/activity-log → HTTP 200 + 463 سجل في الإنتاج
سجل عينة
JE id=122 updated by Ahmed: status changed from "approved" → "posted", entry_number from null → "JV-2026-0122"
ملاحظة
التغطية ضيقة (راجع القسم 3) — فقط journalentry subject_type موجود.
✅ Zakat module deployed
FIXED
الاختبار
GET /api/accounting/zakat → HTTP 200 (data: [] — قاعدة بيانات نظيفة)
Endpoints
apiResource(zakat) + /approve + /pay مسجلين
✅ Withholding Tax module deployed
FIXED
الاختبار
GET /api/accounting/withholding-tax → HTTP 200 + /withholding-tax/report
✅ Hijri date support شغّال
FIXED
الاختبار
كل JE response يحتوي "date": "2026-05-19" + "date_hijri": "1447/12/02"
✅ Period.is_open check في Approve
FIXED
الكود
ApproveJournalEntry::execute(): if (! $entry->fiscalPeriod->isOpen()) throw...
✅ ZATCA wire on sales invoices/returns + ETA pipeline
FIXED
المصدر
commit f45d2a35 + integration test أحمد ذكره
ملاحظة
الـ Backend wiring جاهز. الـ UI wizard (PDF declaration + frontend) مُؤجَّلة عمداً.
✅ Reverse JE: blocked إذا توجد reversal Approved/Posted
FIXED
الكود
ReverseJournalEntry::execute():
$existingReversal = $entry->reversals()
    ->whereIn('status', ['approved', 'posted'])
    ->exists();
if ($existingReversal) throw ...
ثغرة متبقية
راجع القسم 3 — drafts متعددة لا تزال ممكنة.

3 إصلاحات جزئية (تحتاج استكمال) 3 issues

⚠️ Activity Log يغطي فقط journalentry — 19 موديل مفقود
PARTIAL
الواقع
فحص 20 سجل من /api/core/activity-log → كل الـ subject_type = journalentry فقط
المطلوب أصلاً
تذكرة ACC-COMP-2 طلبت تطبيق LogsActivity على: JournalEntry ✅, Account ❌, FiscalYear ❌, FiscalPeriod ❌, FixedAsset ❌, BankReconciliation ❌, Expense ❌, Revenue ❌, PaymentVoucher ❌, ReceiptVoucher ❌, CheckIssued ❌, CheckReceived ❌, OpeningBalance ❌, TaxRate ❌, AllocationRule ❌, ReconciliationRule ❌, EntryTemplate ❌, RecurringEntry ❌, Currency ❌, ExchangeRate ❌
الأثر
عند نزاع قضائي، تعديل CoA أو إعادة فتح فترة أو حذف voucher لا يُسجَّل. الـ Audit Trail ناقص.
⚠️ Double-Reverse: drafts متعددة لا تزال ممكنة
PARTIAL
الاختبار
JE id=122 (Posted) → Reverse #1 (id=126, draft) ✅ مقبول → Reverse #2 (id=127, draft) ✅ مقبول!
السبب
كود Ahmed يتحقق فقط من whereIn('status', ['approved', 'posted']) — لا يفحص drafts
الأثر المحتمل
لو SoD اتعطّل (تم استخدام allowSelfApprove: true) أو لو دخل user آخر، الـ drafts ممكن تتعتمد كلها
الإصلاح المقترح
تغيير الفحص إلى whereIn('status', ['draft', 'approved', 'posted']) أو إضافة UNIQUE constraint على reversed_entry_id في DB
⚠️ JE Create-time validation متراخية
PARTIAL
الاختبارات
  • JE بـ debit=100, credit=50 → HTTP 201 (draft, is_balanced=false) ❌
  • JE بـ سطر debit=100 + credit=100 → HTTP 201 (draft) ❌
  • JE بـ زيرو في كل السطور → HTTP 201 (draft, total=0) ❌
الأثر
الـ GL محمي (Approve يرفض كل ده). لكن drafts تتراكم في الواجهة وتُربك المستخدم، وأي عملية automation تعتمد على drafts معرّضة للأخطاء.
الإصلاح المقترح
إضافة validation rules في StoreJournalEntryRequest:
  • كل سطر: debit XOR credit (واحد فقط > 0)
  • إجمالي: SUM(debit) === SUM(credit)
  • إجمالي: SUM(debit) > 0 (no zero JEs)

4 Bugs في النشر (Deployment) 3 issues

🐛 nature.elbaset.com لم يستقبل الـ deploy بعد
DEPLOY
الاختبار
كل الـ endpoints الجديدة على nature.elbaset.com تعطي HTTP 404 (zakat, withholding-tax, activity-log)
السبب المحتمل
برنامج auto-update الخاص بأحمد لم يصل بعد إلى nature، أو فشل في النشر
الفعل المطلوب
تشغيل auto-update يدوياً على nature، أو التحقق من سجل النشر
🐛 EInvoicing/routes/web.php مكسور — يكسر artisan route:list
DEPLOY
الخطأ
Class "Modules\EInvoicing\Http\Controllers\EInvoicingController"
does not exist
  at Modules/EInvoicing/routes/web.php:7
السبب
أحمد جدّد الـ controllers إلى EInvoiceConfigController, EInvoiceDocumentController ونسي تحديث web.php
الأثر
HTTP requests شغّالة لكن artisan route:list وأدوات إدارية أخرى تفشل. أيضاً ممكن يؤثر على cache rebuild
الإصلاح
حذف السطر 7 في Modules/EInvoicing/routes/web.php أو استبدال الـ reference بـ controller صحيح
🐛 accounting:integrity-check command غير مسجّل في ServiceProvider
DEPLOY
الاختبار
php artisan accounting:integrity-check → "Command not defined"
السبب
أحمد أنشأ CheckAccountingIntegrityCommand.php لكن لم يسجّله في AccountingServiceProvider.php :: $commands
الكود
السطر 52 في الـ provider يسجّل ExecuteRecurringEntriesCommand فقط
الأثر
الـ nightly cron الذي ذكره أحمد ("نتلقَّى تنبيه خلال 24 ساعة لو ظهرت مشكلة integrity") لا يعمل أصلاً
الإصلاح
إضافة سطر في AccountingServiceProvider.php:
$this->commands([
    ExecuteRecurringEntriesCommand::class,
    CheckAccountingIntegrityCommand::class,  // ← ADD
]);

5 جدول التحقق التفصيلي لكل تذكرة

تذكرةالموضوعالحالةالدليل
#1558ACC-FIX-1 Check workflow GLFIXEDcommit 03ea38a6 — Cash/Collect مُصلَّحان. IssueCheck/BounceCheck dedicated مؤجَّل صراحةً
#1559ACC-FIX-2 Vouchers stay DraftFIXEDApprove+Post atomic — Vouchers لا تُترك Draft
#1560ACC-FIX-3 Petty Cash standaloneFIXEDJE تُنشأ لكل معاملة
#1561ACC-FIX-4 Fixed Asset Sell/AcquisitionFIXEDJE الاقتناء والبيع مُصلَّحان
#1562ACC-FIX-5 ReopenFiscalYear + NULL entry_numberFIXEDReopen ينشئ reversal entries · entry_number = NULL مستحيل بعد Posted
#1563ACC-FIX-6 SoD + period lock + opening balanceFIXEDتم اختبارهم حياً — SoD يعمل · period check موجود
#1564ACC-FIX-7 Cost Allocation + Double-ReversePARTIALCost allocation ✅ · Double-reverse ينقصه فحص drafts
#1565ACC-FIX-8 FX rate + recon difference + depreciation idempotencyFIXEDFX rate enforced · recon difference check · depreciation unique constraint
#1566ACC-FIX-9 Defense-in-depth integrity checkDEPLOY BUGالكود موجود في CheckAccountingIntegrityCommand.php لكن غير مسجّل في الـ ServiceProvider · cron لا يعمل
#1567ACC-COMP-1 ZATCA Phase 2FIXED (backend)Backend wiring + integration test · Frontend wizard + PDF declaration مؤجَّلة صراحةً
#1568ACC-COMP-2 Audit Log (Spatie)PARTIALAPI + JournalEntry يعملان · 19 موديل آخر بدون LogsActivity trait
#1569ACC-COMP-3 Zakat moduleFIXED (backend)Endpoints + calculation · Frontend wizard مؤجَّل
#1570ACC-COMP-4 Withholding TaxFIXEDRates + calculator + JE wiring + report
#1571ACC-COMP-5 SAR decimal + HijriPARTIALHijri ✅ · decimal(15,3)→(15,2) مؤجَّل صراحةً بمبرر (KWD يحتاج 3 decimals)

6 ما يجب أن يحدث بعدها

🔴 P0 — قبل التشغيل التجاري

  1. Deploy لـ nature.elbaset.com — تشغيل auto-update يدوياً أو تشخيص لماذا لم يصل
  2. تسجيل CheckAccountingIntegrityCommand في AccountingServiceProvider + جدولته في Console/Kernel.php يومياً
  3. إصلاح EInvoicing/routes/web.php — حذف السطر المكسور
  4. توسيع Activity Log لتغطي 19 موديل آخر (Account, Voucher, FiscalYear, FixedAsset, إلخ) — متطلب SOCPA
  5. تشديد Double-Reverse ليشمل drafts

🟡 P1 — تحسينات

  1. إضافة JE create-time validation (per-line debit XOR credit, balance check, no zero amounts)
  2. إنشاء seed لـ Withholding Tax rates السعودية (5%, 15%, 20%)
  3. إكمال Frontend wizards: ZATCA + Zakat + Withholding
  4. تشغيل migration للبحث عن بيانات قديمة (self-cancelling JEs قبل ACC-059)

🟢 P2 — تأجيلات أحمد المبرَّرة (إبقاء للسجل)

  1. SAR decimal(15,3) → (15,2) — KWD يحتاج 3 decimals، يُعالَج بطريقة multi-precision في v0.7
  2. dedicated IssueCheck/BounceCheck actions — refactor عميق للـ PaymentVoucher
  3. Historical data migration للـ self-cancelling JEs — يحتاج operator-reviewed sweep

7 سجل الاختبارات الفعلية (Live HTTP)

السيناريوطلباستجابة سابقةاستجابة حاليةالحالة
JE create unbalancedPOST /journal-entries debit=100, credit=50201 (accepted as draft)201 (لا يزال يقبل draft)UNCHANGED
JE approve unbalancedPOST /journal-entries/123/approve422422 "القيد غير متوازن"PASS
JE self-approvePOST /journal-entries/126/approve (same user as creator)200 (allowed)422 "لا يمكن لنفس المستخدم..."FIXED
FX without ratePOST JE با currency_id=KWD, no rate seeded201 stored rate=1.0422 "لا يوجد سعر صرف"FIXED
Double-reverse draftsPOST /journal-entries/122/reverse × 22 reversals created (id 8, 9)2 drafts created (id 126, 127)STILL POSSIBLE
Activity log fetchGET /core/activity-log404200 (463 entries)FIXED
Zakat endpointGET /accounting/zakat404200 (data:[])FIXED
WHT endpointGET /accounting/withholding-tax404200FIXED
Hijri date displayJE response.date_hijriغير موجود"1447/12/02"FIXED
Integrity commandphp artisan accounting:integrity-check"not defined"DEPLOY BUG