# Polymorphic Coupon Implementation

## Overview
The coupon system now uses a **polymorphic relationship** instead of separate foreign keys for vendor, category, and brand. This provides a cleaner, more flexible, and maintainable structure.

## Benefits of Polymorphic Approach

### 1. **Cleaner Database Schema**
**Before (Multiple Foreign Keys):**
```php
- vendor_id (nullable)
- category_id (nullable)
- brand_id (nullable)
```

**After (Polymorphic):**
```php
- applicable_id (nullable)
- applicable_type (nullable)
```

### 2. **Easier to Extend**
Adding new coupon types (e.g., product-specific, tag-specific) only requires updating the enum, not adding new columns.

### 3. **Better Performance**
- Single relationship to eager load instead of three separate relationships
- Cleaner queries with fewer joins

### 4. **Type Safety**
The polymorphic relationship ensures that only valid model types can be associated.

## Database Structure

### Migration
```php
Schema::table('coupons', function (Blueprint $table) {
    // Creates: applicable_id, applicable_type
    $table->nullableMorphs('applicable', 'coupons_applicable_index');
    $table->boolean('is_first_order')->default(false);
});
```

### Resulting Columns
- `applicable_id` - ID of the related model (vendor/category/brand)
- `applicable_type` - Fully qualified class name of the related model
- `is_first_order` - Boolean flag for first order coupons

## Model Implementation

### Coupon Model
```php
/**
 * Polymorphic relationship to applicable model
 */
public function applicable()
{
    return $this->morphTo();
}
```

### Usage Examples
```php
// Vendor-specific coupon
$coupon->applicable_type = User::class;
$coupon->applicable_id = 5;
$coupon->applicable; // Returns User model instance

// Category-specific coupon
$coupon->applicable_type = Category::class;
$coupon->applicable_id = 3;
$coupon->applicable; // Returns Category model instance

// Brand-specific coupon
$coupon->applicable_type = Brand::class;
$coupon->applicable_id = 7;
$coupon->applicable; // Returns Brand model instance
```

## API Changes

### Request Format (Admin)
```json
{
  "apply_on": "vendor",
  "applicable_id": 5,
  // applicable_type is auto-set based on apply_on
  "code": "VENDOR20",
  "type": "percentage",
  "value": 20
}
```

### Response Format
```json
{
  "id": 1,
  "apply_on": "vendor",
  "applicable_id": 5,
  "applicable_type": "App\\Models\\User",
  "applicable_name": "Store Name"
}
```

## Validation

### Request Validation
```php
'applicable_id' => [
    Rule::requiredIf(in_array($this->apply_on, [
        CouponApplyOn::VENDOR->value,
        CouponApplyOn::CATEGORY->value,
        CouponApplyOn::BRAND->value
    ])),
    'nullable',
    'numeric',
    function ($attribute, $value, $fail) {
        // Validate existence based on apply_on type
        if ($this->apply_on === CouponApplyOn::VENDOR->value) {
            if (!User::where('id', $value)->exists()) {
                $fail('The selected vendor does not exist.');
            }
        }
        // ... similar for category and brand
    }
]
```

### Auto-Setting applicable_type
```php
protected function prepareForValidation()
{
    if ($this->has('apply_on') && $this->has('applicable_id')) {
        $applicableType = match ($this->apply_on) {
            CouponApplyOn::VENDOR->value => User::class,
            CouponApplyOn::CATEGORY->value => Category::class,
            CouponApplyOn::BRAND->value => Brand::class,
            default => null
        };
        
        if ($applicableType) {
            $this->merge(['applicable_type' => $applicableType]);
        }
    }
}
```

## Resource Implementation

### Getting Applicable Name
```php
private function getApplicableName(): ?string
{
    if (!$this->applicable) {
        return null;
    }

    return match ($this->applicable_type) {
        User::class => $this->applicable->profile?->store_name,
        Category::class, Brand::class => $this->applicable->name,
        default => null
    };
}
```

## Eager Loading

### Before (Multiple Relationships)
```php
Coupon::with(['vendor.profile', 'category', 'brand'])->get();
```

### After (Single Relationship)
```php
Coupon::with(['applicable'])->get();
```

## Frontend Integration

### Form Field
Instead of separate dropdowns for vendor_id, category_id, brand_id:

```javascript
// Single field that changes based on apply_on
<select name="applicable_id">
  {applyOn === 'vendor' && vendors.map(v => <option value={v.id}>{v.name}</option>)}
  {applyOn === 'category' && categories.map(c => <option value={c.id}>{c.name}</option>)}
  {applyOn === 'brand' && brands.map(b => <option value={b.id}>{b.name}</option>)}
</select>
```

### Display Logic
```javascript
const getApplicableLabel = (coupon) => {
  if (!coupon.applicable_name) return 'N/A';
  
  const typeLabel = {
    'App\\Models\\User': 'Vendor',
    'App\\Models\\Category': 'Category',
    'App\\Models\\Brand': 'Brand'
  }[coupon.applicable_type] || 'Unknown';
  
  return `${typeLabel}: ${coupon.applicable_name}`;
};
```

## Migration Path

### For Existing Systems
If you already have coupons with vendor_id, category_id, brand_id:

```php
// Migration to convert existing data
public function up()
{
    // Add polymorphic columns
    Schema::table('coupons', function (Blueprint $table) {
        $table->nullableMorphs('applicable');
    });
    
    // Migrate existing data
    DB::table('coupons')->whereNotNull('vendor_id')->update([
        'applicable_id' => DB::raw('vendor_id'),
        'applicable_type' => User::class
    ]);
    
    DB::table('coupons')->whereNotNull('category_id')->update([
        'applicable_id' => DB::raw('category_id'),
        'applicable_type' => Category::class
    ]);
    
    DB::table('coupons')->whereNotNull('brand_id')->update([
        'applicable_id' => DB::raw('brand_id'),
        'applicable_type' => Brand::class
    ]);
    
    // Drop old columns
    Schema::table('coupons', function (Blueprint $table) {
        $table->dropColumn(['vendor_id', 'category_id', 'brand_id']);
    });
}
```

## Testing

### Test Cases
```php
// Test vendor-specific coupon
$coupon = Coupon::create([
    'apply_on' => CouponApplyOn::VENDOR,
    'applicable_id' => $vendor->id,
    'applicable_type' => User::class,
    // ... other fields
]);

$this->assertEquals($vendor->id, $coupon->applicable->id);
$this->assertInstanceOf(User::class, $coupon->applicable);

// Test category-specific coupon
$coupon = Coupon::create([
    'apply_on' => CouponApplyOn::CATEGORY,
    'applicable_id' => $category->id,
    'applicable_type' => Category::class,
    // ... other fields
]);

$this->assertEquals($category->id, $coupon->applicable->id);
$this->assertInstanceOf(Category::class, $coupon->applicable);
```

## Comparison Table

| Aspect | Multiple Foreign Keys | Polymorphic Relationship |
|--------|----------------------|--------------------------|
| Database Columns | 3 columns (vendor_id, category_id, brand_id) | 2 columns (applicable_id, applicable_type) |
| Eager Loading | `with(['vendor', 'category', 'brand'])` | `with(['applicable'])` |
| Extensibility | Add new column for each type | Just update enum |
| Type Safety | Relies on application logic | Built into relationship |
| Query Performance | Multiple potential joins | Single join |
| Code Complexity | More conditional logic | Cleaner, more maintainable |

## Conclusion

The polymorphic approach provides:
- ✅ Cleaner database schema
- ✅ Better performance
- ✅ Easier to extend
- ✅ More maintainable code
- ✅ Type-safe relationships
- ✅ Simpler queries

This is the recommended approach for the coupon system and follows Laravel best practices for polymorphic relationships.
