# إصلاح حماية الحذف من القيود الأجنبية
## Foreign Key Constraint Protection

## 📋 ملخص المشكلة

كان النظام يسمح بحذف سجلات رئيسية (Master Data) لها سجلات فرعية مرتبطة بها عبر Foreign Keys، مما يسبب خطأ:

```
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails
```

## ✅ الإصلاح المطبق

تم إضافة حماية من الحذف في جميع النماذج الرئيسية باستخدام `deleting` event في Laravel. هذه الحماية تتحقق من وجود سجلات مرتبطة قبل السماح بالحذف وتعرض رسالة خطأ واضحة باللغة العربية.

## 📦 النماذج التي تم إصلاحها

### 1. Customer (العملاء)
**الملف:** `app/Models/Customer.php`

**العلاقات المحمية:**
- ✅ المبيعات (sales)
- ✅ مرتجعات المبيعات (saleReturns)
- ✅ المدفوعات (payments) - **السبب الرئيسي للخطأ**
- ✅ المزارع (farms)

**رسالة الخطأ:**
```
لا يمكن حذف العميل. يوجد X مدفوعات مرتبطة بهذا العميل.
```

---

### 2. Supplier (الموردين)
**الملف:** `app/Models/Supplier.php`

**العلاقات المحمية:**
- ✅ المشتريات (purchases)
- ✅ مرتجعات المشتريات (purchaseReturns)
- ✅ المدفوعات (payments)
- ✅ الوارد (inbounds)
- ✅ الصادر (outbounds)

**رسالة الخطأ:**
```
لا يمكن حذف المورد. يوجد X فاتورة مشتريات مرتبطة بهذا المورد.
```

---

### 3. Product (المنتجات)
**الملف:** `app/Models/Product.php`

**العلاقات المحمية:**
- ✅ عناصر المبيعات (saleItems)
- ✅ عناصر المشتريات (purchaseItems)
- ✅ حركات المخزون (stockMovements)
- ✅ وحدات المنتج (productUnits)

**رسالة الخطأ:**
```
لا يمكن حذف المنتج. يوجد X عنصر في فواتير المبيعات مرتبط بهذا المنتج.
```

---

### 4. Cashbox (الصناديق)
**الملف:** `app/Models/Cashbox.php`

**العلاقات المحمية:**
- ✅ المبيعات (sales)
- ✅ المشتريات (purchases)
- ✅ الحركات المالية (cashMovements)
- ✅ مرتجعات المبيعات (saleReturns)
- ✅ مرتجعات المشتريات (purchaseReturns)

**رسالة الخطأ:**
```
لا يمكن حذف الصندوق. يوجد X فاتورة مبيعات مرتبطة بهذا الصندوق.
```

---

### 5. Category (الفئات)
**الملف:** `app/Models/Category.php`

**العلاقات المحمية:**
- ✅ المنتجات (products)

**رسالة الخطأ:**
```
لا يمكن حذف الفئة. يوجد X منتج مرتبط بهذه الفئة.
```

---

### 6. Unit (الوحدات)
**الملف:** `app/Models/Unit.php`

**العلاقات المحمية:**
- ✅ المنتجات (products) - التي تستخدم هذه الوحدة كوحدة أساسية
- ✅ وحدات المنتج (productUnits)

**رسالة الخطأ:**
```
لا يمكن حذف الوحدة. يوجد X منتج يستخدم هذه الوحدة كوحدة أساسية.
```

---

### 7. Warehouse (المخازن)
**الملف:** `app/Models/Warehouse.php`

**العلاقات المحمية:**
- ✅ المبيعات (sales)
- ✅ المشتريات (purchases)
- ✅ حركات المخزون (stockMovements)

**رسالة الخطأ:**
```
لا يمكن حذف المخزن. يوجد X فاتورة مبيعات مرتبطة بهذا المخزن.
```

---

### 8. Merchant (التجار)
**الملف:** `app/Models/Merchant.php`

**العلاقات المحمية:**
- ✅ الصادر (outbounds)

**رسالة الخطأ:**
```
لا يمكن حذف التاجر. يوجد X صادر مرتبط بهذا التاجر.
```

---

### 9. Farm (المزارع)
**الملف:** `app/Models/Farm.php`

**العلاقات المحمية:**
- ✅ المبيعات (sales)
- ✅ مرتجعات المبيعات (saleReturns)

**رسالة الخطأ:**
```
لا يمكن حذف المزرعة. يوجد X فاتورة مبيعات مرتبطة بهذه المزرعة.
```

---

### 10. ChartOfAccount (الدليل المحاسبي)
**الملف:** `app/Models/Accounting/ChartOfAccount.php`

**العلاقات المحمية:**
- ✅ الحسابات الفرعية (children)
- ✅ سطور القيود المحاسبية (journalLines)
- ✅ الأرصدة (balances)

**رسالة الخطأ:**
```
لا يمكن حذف الحساب. يوجد X قيد محاسبي مرتبط بهذا الحساب.
```

---

## 🔍 كيفية عمل الحماية

تم إضافة الكود التالي في دالة `booted()` لكل نموذج:

```php
protected static function booted()
{
    // ... existing code ...
    
    static::deleting(function ($model) {
        // التحقق من وجود سجلات مرتبطة
        if ($model->relatedRecords()->exists()) {
            throw new \Exception(__('رسالة الخطأ المناسبة', [
                'count' => $model->relatedRecords()->count()
            ]));
        }
    });
}
```

## 💡 فوائد هذا الإصلاح

1. **منع أخطاء قاعدة البيانات**: لا مزيد من أخطاء Foreign Key Constraint
2. **رسائل واضحة للمستخدم**: رسائل خطأ بالعربية تشرح السبب
3. **حماية البيانات**: منع حذف بيانات حساسة بطريق الخطأ
4. **الالتزام بقواعد النظام**: ضمان سلامة البيانات (Data Integrity)
5. **تجربة مستخدم أفضل**: معرفة عدد السجلات المرتبطة بوضوح

## 🧪 اختبار الإصلاح

لاختبار أن الإصلاح يعمل:

1. حاول حذف عميل له مدفوعات ← يجب أن تظهر رسالة خطأ واضحة
2. حاول حذف منتج مستخدم في فواتير ← يجب أن تظهر رسالة خطأ واضحة
3. حاول حذف صندوق له حركات مالية ← يجب أن تظهر رسالة خطأ واضحة

## 📝 ملاحظات مهمة

- **الإصلاح شامل**: تم تطبيقه على جميع النماذج الرئيسية (Master Data)
- **لا يؤثر على الأداء**: التحقق يتم فقط عند محاولة الحذف
- **متوافق مع Filament**: يعمل مع جميع actions (DeleteAction, DeleteBulkAction)
- **الرسائل قابلة للترجمة**: استخدام `__()` للترجمة

## ✨ التحسينات المستقبلية المقترحة

1. إضافة خيار "حذف مع السجلات المرتبطة" للمدراء فقط (مع تأكيد إضافي)
2. إضافة صفحة تفاصيل تعرض جميع السجلات المرتبطة قبل الحذف
3. إضافة soft delete بدلاً من الحذف النهائي
4. إضافة سجل audit log لمحاولات الحذف

## 📅 التاريخ

**تاريخ الإصلاح:** 7 فبراير 2026  
**المشكلة الأصلية:** خطأ عند حذف عميل له مدفوعات  
**الحل:** إضافة حماية شاملة لجميع النماذج الرئيسية

---

## 📚 دليل تطبيق الحماية على أنظمة أخرى

### 🎓 الطريقة العامة - خطوة بخطوة

يمكنك تطبيق نفس الحماية على أي نظام Laravel آخر باتباع الخطوات التالية:

---

### الخطوة 1️⃣: تحديد النماذج الرئيسية (Master Data)

حدد النماذج التي تحتوي على بيانات أساسية ولها علاقات مع جداول أخرى:

**أمثلة شائعة في أي نظام:**
- العملاء (Customers)
- الموردين (Suppliers/Vendors)
- المنتجات (Products)
- الفئات (Categories)
- المستخدمين (Users)
- الفروع (Branches)
- الأقسام (Departments)
- الموظفين (Employees)

**كيف تحددها؟**
```bash
# ابحث في ملفات Migration عن جداول بها foreign keys
grep -r "foreign\|constrained" database/migrations/
```

---

### الخطوة 2️⃣: تحليل العلاقات (Relationships)

لكل نموذج، افتح ملف Model وحدد جميع علاقات `HasMany` و `HasManyThrough`:

**مثال: ملف Customer.php**
```php
// العلاقات التي تحتاج حماية
public function orders(): HasMany
{
    return $this->hasMany(Order::class);
}

public function invoices(): HasMany
{
    return $this->hasMany(Invoice::class);
}

public function payments(): HasMany
{
    return $this->hasMany(Payment::class);
}
```

📝 **ملاحظة:** علاقات `BelongsTo` و `BelongsToMany` عادة لا تحتاج حماية لأنها في الاتجاه المعاكس.

---

### الخطوة 3️⃣: إضافة كود الحماية

في كل نموذج يحتاج حماية، أضف `deleting` event داخل دالة `booted()`:

**القالب العام:**
```php
protected static function booted()
{
    // إذا كان لديك كود موجود في booted() احتفظ به
    
    static::deleting(function ($model) {
        // التحقق من العلاقة الأولى
        if ($model->relationName1()->exists()) {
            throw new \Exception(__(
                'لا يمكن حذف :model. يوجد :count :relation مرتبط.',
                [
                    'model' => 'اسم النموذج',
                    'count' => $model->relationName1()->count(),
                    'relation' => 'اسم العلاقة'
                ]
            ));
        }
        
        // التحقق من العلاقة الثانية
        if ($model->relationName2()->exists()) {
            throw new \Exception(__(
                'لا يمكن حذف :model. يوجد :count :relation مرتبط.',
                [
                    'model' => 'اسم النموذج',
                    'count' => $model->relationName2()->count(),
                    'relation' => 'اسم العلاقة'
                ]
            ));
        }
        
        // ... كرر لكل علاقة تحتاج حماية
    });
}
```

---

### الخطوة 4️⃣: مثال تطبيقي كامل

**السيناريو:** نظام مدرسي به Student و Courses و Grades

**ملف: app/Models/Student.php**
```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Student extends Model
{
    protected $fillable = [
        'name',
        'email',
        'phone',
        'enrollment_date',
    ];
    
    protected static function booted()
    {
        // إضافة حماية من الحذف
        static::deleting(function ($student) {
            // التحقق من وجود كورسات مسجلة
            if ($student->enrollments()->exists()) {
                throw new \Exception(__(
                    'لا يمكن حذف الطالب. يوجد :count كورس مسجل لهذا الطالب.',
                    ['count' => $student->enrollments()->count()]
                ));
            }
            
            // التحقق من وجود درجات
            if ($student->grades()->exists()) {
                throw new \Exception(__(
                    'لا يمكن حذف الطالب. يوجد :count درجة مسجلة لهذا الطالب.',
                    ['count' => $student->grades()->count()]
                ));
            }
            
            // التحقق من وجود حضور وغياب
            if ($student->attendances()->exists()) {
                throw new \Exception(__(
                    'لا يمكن حذف الطالب. يوجد :count سجل حضور لهذا الطالب.',
                    ['count' => $student->attendances()->count()]
                ));
            }
        });
    }
    
    // العلاقات
    public function enrollments(): HasMany
    {
        return $this->hasMany(Enrollment::class);
    }
    
    public function grades(): HasMany
    {
        return $this->hasMany(Grade::class);
    }
    
    public function attendances(): HasMany
    {
        return $this->hasMany(Attendance::class);
    }
}
```

---

### الخطوة 5️⃣: إضافة ملفات الترجمة (اختياري)

إذا كنت تستخدم نظام الترجمة في Laravel:

**ملف: lang/ar/messages.php**
```php
<?php

return [
    'cannot_delete_has_relations' => 'لا يمكن حذف :model. يوجد :count :relation مرتبط.',
    
    // رسائل خاصة
    'cannot_delete_student' => 'لا يمكن حذف الطالب',
    'enrollments' => 'كورس|كورسات',
    'grades' => 'درجة|درجات',
    'attendances' => 'سجل حضور|سجلات حضور',
];
```

**استخدامها في الكود:**
```php
throw new \Exception(__(
    'messages.cannot_delete_has_relations',
    [
        'model' => __('messages.cannot_delete_student'),
        'count' => $count,
        'relation' => trans_choice('messages.enrollments', $count)
    ]
));
```

---

### الخطوة 6️⃣: قائمة فحص شاملة

قبل إطلاق التحديث، تحقق من:

- [ ] ✅ جميع نماذج Master Data لها حماية
- [ ] ✅ جميع العلاقات المهمة محمية
- [ ] ✅ رسائل الخطأ واضحة ومفهومة
- [ ] ✅ الرسائل بلغة المستخدم (عربي/إنجليزي)
- [ ] ✅ تم اختبار الحماية على بيئة التطوير
- [ ] ✅ لا توجد حلقات لا نهائية (Circular References)

---

### 🔧 أدوات مساعدة

#### أداة 1: سكريبت للبحث عن جميع العلاقات

**ملف: find_relationships.php**
```php
<?php
// ضع هذا الملف في مجلد root المشروع وشغله
require __DIR__ . '/vendor/autoload.php';

$app = require_once __DIR__ . '/bootstrap/app.php';

$modelsPath = app_path('Models');
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($modelsPath));

foreach ($files as $file) {
    if ($file->isFile() && $file->getExtension() === 'php') {
        $content = file_get_contents($file->getPathname());
        
        // ابحث عن HasMany relationships
        if (preg_match_all('/public function (\w+)\(\):\s*HasMany/', $content, $matches)) {
            echo "ملف: " . $file->getFilename() . "\n";
            echo "العلاقات: " . implode(', ', $matches[1]) . "\n\n";
        }
    }
}
```

**تشغيله:**
```bash
php find_relationships.php
```

---

#### أداة 2: قالب كود سريع (Code Snippet)

**لمستخدمي VS Code - ملف snippets:**
```json
{
    "Delete Protection": {
        "prefix": "delete-protect",
        "body": [
            "protected static function booted()",
            "{",
            "    static::deleting(function ($$model) {",
            "        if ($$model->${1:relationName}()->exists()) {",
            "            throw new \\Exception(__(", 
            "                '${2:لا يمكن حذف السجل. يوجد :count ${3:سجل مرتبط}.',",
            "                ['count' => $$model->${1:relationName}()->count()]",
            "            ));",
            "        }",
            "    });",
            "}"
        ],
        "description": "Adding delete protection to model"
    }
}
```

---

### 🎯 حالات خاصة

#### حالة 1: حذف cascade للسجلات الفرعية
إذا أردت حذف السجلات الفرعية تلقائياً:

```php
static::deleting(function ($model) {
    // حذف السجلات الفرعية أولاً
    $model->comments()->delete();
    $model->likes()->delete();
    
    // لكن امنع الحذف إذا كان هناك أوردرات
    if ($model->orders()->exists()) {
        throw new \Exception('لا يمكن الحذف، يوجد أوردرات');
    }
});
```

#### حالة 2: تحذير فقط بدون منع
إذا أردت تحذير المستخدم فقط دون منع الحذف:

```php
static::deleting(function ($model) {
    if ($model->oldRecords()->exists()) {
        Log::warning("تم حذف سجل له سجلات قديمة: {$model->id}");
        
        // إرسال إشعار للمدير
        Notification::send(
            User::role('admin')->get(),
            new RecordDeletedWithOldData($model)
        );
    }
    // لا نرمي Exception، نسمح بالحذف
});
```

#### حالة 3: حماية مشروطة
حماية بناءً على دور المستخدم:

```php
static::deleting(function ($model) {
    // السماح للمدير فقط
    if (!auth()->user()?->hasRole('super_admin')) {
        if ($model->transactions()->exists()) {
            throw new \Exception('لا يمكن الحذف. اتصل بالمدير.');
        }
    }
});
```

---

### 🧪 اختبارات Unit Tests

**ملف: tests/Unit/DeleteProtectionTest.php**
```php
<?php

namespace Tests\Unit;

use Tests\TestCase;
use App\Models\Customer;
use App\Models\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;

class DeleteProtectionTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function it_prevents_deleting_customer_with_orders()
    {
        $customer = Customer::factory()->create();
        $order = Order::factory()->create(['customer_id' => $customer->id]);

        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('لا يمكن حذف');

        $customer->delete();
    }

    /** @test */
    public function it_allows_deleting_customer_without_orders()
    {
        $customer = Customer::factory()->create();

        $customer->delete();

        $this->assertDatabaseMissing('customers', ['id' => $customer->id]);
    }
}
```

**تشغيل الاختبارات:**
```bash
php artisan test --filter DeleteProtectionTest
```

---

### 📊 جدول مرجعي سريع

| النموذج | العلاقات المشتركة | أولوية الحماية |
|---------|-------------------|----------------|
| Customer | orders, invoices, payments | 🔴 عالية جداً |
| Product | order_items, stock_movements | 🔴 عالية جداً |
| Category | products, subcategories | 🟡 متوسطة |
| User | posts, comments | 🟡 متوسطة |
| Employee | attendances, salaries | 🔴 عالية جداً |
| Branch | employees, transactions | 🔴 عالية جداً |

---

### 💡 نصائح مهمة

1. **لا تبالغ في الحماية**: ليس كل علاقة تحتاج حماية. مثلاً:
   - ✅ احمي: Orders, Invoices, Payments
   - ❌ لا تحمي: Logs, Notifications, Temporary Data

2. **استخدم Soft Deletes**: أفضل من الحذف النهائي
   ```php
   use Illuminate\Database\Eloquent\SoftDeletes;
   
   class Customer extends Model
   {
       use SoftDeletes;
   }
   ```

3. **راقب الأداء**: إذا كانت العلاقة تحتوي على ملايين السجلات، استخدم:
   ```php
   if ($model->orders()->limit(1)->exists()) {
       // أسرع من count()
   }
   ```

4. **وثق القرارات**: اكتب تعليق لماذا اخترت حماية علاقة معينة
   ```php
   // حماية Orders لأنه مرتبط بالمحاسبة وقد يؤثر على التقارير المالية
   if ($model->orders()->exists()) {
       throw new \Exception('...');
   }
   ```

---

### 🔗 روابط مفيدة

- [Laravel Model Events Documentation](https://laravel.com/docs/eloquent#events)
- [Database Foreign Keys](https://laravel.com/docs/migrations#foreign-key-constraints)
- [Exception Handling](https://laravel.com/docs/errors)

---

## 🎯 الخلاصة

تم إصلاح مشكلة Foreign Key Constraint بشكل شامل في جميع أنحاء النظام. الآن لن يستطيع المستخدمون حذف أي سجل رئيسي له سجلات فرعية مرتبطة، وسيتلقون رسالة خطأ واضحة توضح السبب وعدد السجلات المرتبطة.

**يمكنك الآن تطبيق نفس الطريقة على أي نظام Laravel آخر باتباع الدليل أعلاه! 🚀**
