# 🛒 PRODUCT & ADD TO CART SYSTEM

## ✅ Complete Implementation

---

## 📦 1. Database Migrations

### Already Created Migrations:

#### 1.1 Categories Table
**File:** `database/migrations/2024_01_01_000002_create_categories_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->string('image')->nullable();
            $table->integer('sort_order')->default(0);
            $table->boolean('is_active')->default(true);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('categories');
    }
};
```

---

#### 1.2 Products Table
**File:** `database/migrations/2024_01_01_000003_create_products_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->foreignId('restaurant_id')->constrained()->onDelete('cascade');
            $table->foreignId('category_id')->constrained()->onDelete('cascade');
            $table->string('name');
            $table->string('slug');
            $table->text('description')->nullable();
            $table->decimal('price', 10, 2);
            $table->decimal('discount_price', 10, 2)->nullable();
            $table->enum('discount_type', ['percentage', 'fixed'])->nullable();
            $table->string('image');
            $table->json('images')->nullable();
            $table->boolean('is_available')->default(true);
            $table->boolean('is_featured')->default(false);
            $table->boolean('is_vegetarian')->default(false);
            $table->boolean('is_spicy')->default(false);
            $table->integer('preparation_time')->default(15);
            $table->integer('sort_order')->default(0);
            $table->decimal('rating', 3, 2)->default(0);
            $table->integer('total_reviews')->default(0);
            $table->integer('total_orders')->default(0);
            $table->timestamps();
            $table->softDeletes();
            
            $table->unique(['restaurant_id', 'slug']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
```

---

#### 1.3 Product Variations Table
**File:** `database/migrations/2024_01_01_000004_create_product_variations_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('product_variations', function (Blueprint $table) {
            $table->id();
            $table->foreignId('product_id')->constrained()->onDelete('cascade');
            $table->string('name'); // e.g., "Size", "Spice Level"
            $table->string('value'); // e.g., "Large", "Extra Spicy"
            $table->decimal('price_adjustment', 10, 2)->default(0);
            $table->boolean('is_available')->default(true);
            $table->integer('sort_order')->default(0);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('product_variations');
    }
};
```

---

#### 1.4 Product Add-ons Table
**File:** `database/migrations/2024_01_01_000005_create_product_addons_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('product_addons', function (Blueprint $table) {
            $table->id();
            $table->foreignId('product_id')->constrained()->onDelete('cascade');
            $table->string('name'); // e.g., "Extra Cheese", "Egg"
            $table->decimal('price', 10, 2);
            $table->boolean('is_available')->default(true);
            $table->integer('sort_order')->default(0);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('product_addons');
    }
};
```

---

#### 1.5 Carts Table
**File:** `database/migrations/2024_01_01_000006_create_carts_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('carts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->foreignId('restaurant_id')->constrained()->onDelete('cascade');
            $table->foreignId('product_id')->constrained()->onDelete('cascade');
            $table->integer('quantity')->default(1);
            $table->decimal('unit_price', 10, 2);
            $table->json('variations')->nullable(); // Selected variations
            $table->json('addons')->nullable(); // Selected addons
            $table->text('special_notes')->nullable();
            $table->decimal('total_price', 10, 2);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('carts');
    }
};
```

**Run Migrations:**
```bash
php artisan migrate
```

---

## 🎯 2. Models

### 2.1 Category Model
**File:** `app/Models/Category.php`

```php
<?php

namespace App\Models;

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

class Category extends Model
{
    protected $fillable = ['name', 'slug', 'description', 'image', 'sort_order', 'is_active'];

    protected $casts = ['is_active' => 'boolean'];

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

---

### 2.2 Product Model
**File:** `app/Models/Product.php`

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;

class Product extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'restaurant_id', 'category_id', 'name', 'slug', 'description', 'price',
        'discount_price', 'discount_type', 'image', 'images', 'is_available',
        'is_featured', 'is_vegetarian', 'is_spicy', 'preparation_time',
        'sort_order', 'rating', 'total_reviews', 'total_orders'
    ];

    protected $casts = [
        'images' => 'array',
        'price' => 'decimal:2',
        'discount_price' => 'decimal:2',
        'rating' => 'decimal:2',
        'is_available' => 'boolean',
        'is_featured' => 'boolean',
        'is_vegetarian' => 'boolean',
        'is_spicy' => 'boolean',
    ];

    public function restaurant(): BelongsTo
    {
        return $this->belongsTo(Restaurant::class);
    }

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

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

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

    public function reviews(): MorphMany
    {
        return $this->morphMany(Review::class, 'reviewable');
    }

    // Get final price (with discount)
    public function getFinalPriceAttribute()
    {
        return $this->discount_price ?? $this->price;
    }
}
```

---

### 2.3 ProductVariation Model
**File:** `app/Models/ProductVariation.php`

```php
<?php

namespace App\Models;

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

class ProductVariation extends Model
{
    protected $fillable = ['product_id', 'name', 'value', 'price_adjustment', 'is_available', 'sort_order'];

    protected $casts = [
        'price_adjustment' => 'decimal:2',
        'is_available' => 'boolean',
    ];

    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class);
    }
}
```

---

### 2.4 ProductAddon Model
**File:** `app/Models/ProductAddon.php`

```php
<?php

namespace App\Models;

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

class ProductAddon extends Model
{
    protected $fillable = ['product_id', 'name', 'price', 'is_available', 'sort_order'];

    protected $casts = [
        'price' => 'decimal:2',
        'is_available' => 'boolean',
    ];

    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class);
    }
}
```

---

### 2.5 Cart Model
**File:** `app/Models/Cart.php`

```php
<?php

namespace App\Models;

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

class Cart extends Model
{
    protected $fillable = [
        'user_id', 'restaurant_id', 'product_id', 'quantity', 'unit_price',
        'variations', 'addons', 'special_notes', 'total_price'
    ];

    protected $casts = [
        'variations' => 'array',
        'addons' => 'array',
        'unit_price' => 'decimal:2',
        'total_price' => 'decimal:2',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function restaurant(): BelongsTo
    {
        return $this->belongsTo(Restaurant::class);
    }

    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class);
    }
}
```

---

## 🎮 3. Controllers

### 3.1 ProductController
**File:** `app/Http/Controllers/API/ProductController.php`

```php
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class ProductController extends Controller
{
    // List products
    public function index(Request $request)
    {
        $query = Product::with(['restaurant', 'category', 'variations', 'addons'])
            ->where('is_available', true);

        if ($request->restaurant_id) {
            $query->where('restaurant_id', $request->restaurant_id);
        }

        if ($request->category_id) {
            $query->where('category_id', $request->category_id);
        }

        if ($request->search) {
            $query->where('name', 'like', "%{$request->search}%");
        }

        $products = $query->paginate(20);

        return response()->json($products);
    }

    // Get product details
    public function show($id)
    {
        $product = Product::with(['restaurant', 'category', 'variations', 'addons', 'reviews.user'])
            ->findOrFail($id);

        return response()->json($product);
    }

    // Create product (Manager)
    public function store(Request $request)
    {
        $validated = $request->validate([
            'restaurant_id' => 'required|exists:restaurants,id',
            'category_id' => 'required|exists:categories,id',
            'name' => 'required|string|max:255',
            'description' => 'nullable|string',
            'price' => 'required|numeric|min:0',
            'discount_price' => 'nullable|numeric|min:0',
            'image' => 'nullable|string',
            'is_vegetarian' => 'boolean',
            'is_spicy' => 'boolean',
            'preparation_time' => 'nullable|integer|min:0',
        ]);

        $product = Product::create([
            'restaurant_id' => $validated['restaurant_id'],
            'category_id' => $validated['category_id'],
            'name' => $validated['name'],
            'slug' => Str::slug($validated['name']),
            'description' => $validated['description'] ?? null,
            'price' => $validated['price'],
            'discount_price' => $validated['discount_price'] ?? null,
            'image' => $validated['image'] ?? 'default.jpg',
            'is_vegetarian' => $validated['is_vegetarian'] ?? false,
            'is_spicy' => $validated['is_spicy'] ?? false,
            'preparation_time' => $validated['preparation_time'] ?? 15,
        ]);

        return response()->json($product, 201);
    }

    // Update product
    public function update(Request $request, $id)
    {
        $product = Product::findOrFail($id);

        $validated = $request->validate([
            'name' => 'sometimes|string|max:255',
            'description' => 'nullable|string',
            'price' => 'sometimes|numeric|min:0',
            'discount_price' => 'nullable|numeric|min:0',
            'is_available' => 'boolean',
        ]);

        if (isset($validated['name'])) {
            $validated['slug'] = Str::slug($validated['name']);
        }

        $product->update($validated);

        return response()->json($product);
    }

    // Delete product
    public function destroy($id)
    {
        $product = Product::findOrFail($id);
        $product->delete();

        return response()->json(['message' => 'Product deleted successfully']);
    }
}
```

---

### 3.2 CartController
**File:** `app/Http/Controllers/API/CartController.php`

```php
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Cart;
use App\Models\Product;
use Illuminate\Http\Request;

class CartController extends Controller
{
    // Get user cart
    public function index(Request $request)
    {
        $carts = Cart::with(['product', 'restaurant'])
            ->where('user_id', $request->user()->id)
            ->get();

        $subtotal = $carts->sum('total_price');
        
        // Get restaurant details for fees
        $restaurant = $carts->first()?->restaurant;
        $deliveryFee = $restaurant?->delivery_fee ?? 0;
        $serviceCharge = $subtotal * (($restaurant?->service_charge_percentage ?? 0) / 100);
        $tax = $subtotal * (($restaurant?->tax_percentage ?? 0) / 100);
        $total = $subtotal + $deliveryFee + $serviceCharge + $tax;

        return response()->json([
            'items' => $carts,
            'subtotal' => $subtotal,
            'delivery_fee' => $deliveryFee,
            'service_charge' => $serviceCharge,
            'tax' => $tax,
            'total' => $total,
        ]);
    }

    // Add to cart
    public function store(Request $request)
    {
        $validated = $request->validate([
            'product_id' => 'required|exists:products,id',
            'quantity' => 'required|integer|min:1',
            'variations' => 'nullable|array',
            'variations.*.id' => 'exists:product_variations,id',
            'variations.*.name' => 'string',
            'variations.*.value' => 'string',
            'variations.*.price_adjustment' => 'numeric',
            'addons' => 'nullable|array',
            'addons.*.id' => 'exists:product_addons,id',
            'addons.*.name' => 'string',
            'addons.*.price' => 'numeric',
            'special_notes' => 'nullable|string|max:500',
        ]);

        $product = Product::findOrFail($validated['product_id']);

        // Check if user has items from different restaurant
        $existingCart = Cart::where('user_id', $request->user()->id)->first();
        if ($existingCart && $existingCart->restaurant_id !== $product->restaurant_id) {
            return response()->json([
                'message' => 'You can only order from one restaurant at a time. Clear your cart first.'
            ], 400);
        }

        // Calculate price
        $unitPrice = $product->discount_price ?? $product->price;
        
        // Add variations price
        $variationsPrice = 0;
        if (!empty($validated['variations'])) {
            foreach ($validated['variations'] as $variation) {
                $variationsPrice += $variation['price_adjustment'] ?? 0;
            }
        }

        // Add addons price
        $addonsPrice = 0;
        if (!empty($validated['addons'])) {
            foreach ($validated['addons'] as $addon) {
                $addonsPrice += $addon['price'] ?? 0;
            }
        }

        $totalPrice = ($unitPrice + $variationsPrice + $addonsPrice) * $validated['quantity'];

        $cart = Cart::create([
            'user_id' => $request->user()->id,
            'restaurant_id' => $product->restaurant_id,
            'product_id' => $product->id,
            'quantity' => $validated['quantity'],
            'unit_price' => $unitPrice,
            'variations' => $validated['variations'] ?? null,
            'addons' => $validated['addons'] ?? null,
            'special_notes' => $validated['special_notes'] ?? null,
            'total_price' => $totalPrice,
        ]);

        return response()->json([
            'message' => 'Product added to cart',
            'cart' => $cart->load('product')
        ], 201);
    }

    // Update cart item
    public function update(Request $request, $id)
    {
        $cart = Cart::where('user_id', $request->user()->id)->findOrFail($id);

        $validated = $request->validate([
            'quantity' => 'required|integer|min:1',
        ]);

        // Recalculate total price
        $unitPrice = $cart->unit_price;
        $variationsPrice = 0;
        $addonsPrice = 0;

        if ($cart->variations) {
            foreach ($cart->variations as $variation) {
                $variationsPrice += $variation['price_adjustment'] ?? 0;
            }
        }

        if ($cart->addons) {
            foreach ($cart->addons as $addon) {
                $addonsPrice += $addon['price'] ?? 0;
            }
        }

        $totalPrice = ($unitPrice + $variationsPrice + $addonsPrice) * $validated['quantity'];

        $cart->update([
            'quantity' => $validated['quantity'],
            'total_price' => $totalPrice,
        ]);

        return response()->json($cart);
    }

    // Remove from cart
    public function destroy(Request $request, $id)
    {
        $cart = Cart::where('user_id', $request->user()->id)->findOrFail($id);
        $cart->delete();

        return response()->json(['message' => 'Item removed from cart']);
    }

    // Clear cart
    public function clear(Request $request)
    {
        Cart::where('user_id', $request->user()->id)->delete();

        return response()->json(['message' => 'Cart cleared']);
    }
}
```

---

## 🛣️ 4. API Routes

**File:** `routes/api.php`

```php
// Public routes
Route::get('/products', [ProductController::class, 'index']);
Route::get('/products/{id}', [ProductController::class, 'show']);
Route::get('/categories', [CategoryController::class, 'index']);

// Protected routes
Route::middleware('auth:sanctum')->group(function () {
    
    // Products (Manager)
    Route::post('/products', [ProductController::class, 'store']);
    Route::put('/products/{id}', [ProductController::class, 'update']);
    Route::delete('/products/{id}', [ProductController::class, 'destroy']);

    // Cart
    Route::get('/cart', [CartController::class, 'index']);
    Route::post('/cart', [CartController::class, 'store']);
    Route::put('/cart/{id}', [CartController::class, 'update']);
    Route::delete('/cart/{id}', [CartController::class, 'destroy']);
    Route::delete('/cart', [CartController::class, 'clear']);
});
```

---

## 📡 5. API Examples

### 5.1 List Products
```http
GET /api/products?restaurant_id=1&category_id=2
```

**Response:**
```json
{
  "data": [
    {
      "id": 1,
      "name": "Margherita Pizza",
      "price": 1200,
      "discount_price": 1000,
      "image": "pizza.jpg",
      "variations": [
        {"id": 1, "name": "Size", "value": "Small", "price_adjustment": 0},
        {"id": 2, "name": "Size", "value": "Large", "price_adjustment": 300}
      ],
      "addons": [
        {"id": 1, "name": "Extra Cheese", "price": 150},
        {"id": 2, "name": "Mushrooms", "price": 100}
      ]
    }
  ]
}
```

---

### 5.2 Add to Cart
```http
POST /api/cart
Authorization: Bearer {token}
Content-Type: application/json

{
  "product_id": 1,
  "quantity": 2,
  "variations": [
    {
      "id": 2,
      "name": "Size",
      "value": "Large",
      "price_adjustment": 300
    }
  ],
  "addons": [
    {
      "id": 1,
      "name": "Extra Cheese",
      "price": 150
    }
  ],
  "special_notes": "Extra spicy please"
}
```

**Price Calculation:**
```
Base Price: 1000 (discount price)
Variation: +300 (Large size)
Addon: +150 (Extra cheese)
Unit Total: 1450
Quantity: 2
Total: 2900
```

**Response:**
```json
{
  "message": "Product added to cart",
  "cart": {
    "id": 1,
    "product_id": 1,
    "quantity": 2,
    "unit_price": 1000,
    "total_price": 2900,
    "variations": [...],
    "addons": [...],
    "special_notes": "Extra spicy please"
  }
}
```

---

### 5.3 Get Cart
```http
GET /api/cart
Authorization: Bearer {token}
```

**Response:**
```json
{
  "items": [
    {
      "id": 1,
      "product": {
        "name": "Margherita Pizza",
        "image": "pizza.jpg"
      },
      "quantity": 2,
      "unit_price": 1000,
      "total_price": 2900,
      "variations": [...],
      "addons": [...]
    }
  ],
  "subtotal": 2900,
  "delivery_fee": 200,
  "service_charge": 145,
  "tax": 290,
  "total": 3535
}
```

---

### 5.4 Update Cart Quantity
```http
PUT /api/cart/1
Authorization: Bearer {token}
Content-Type: application/json

{
  "quantity": 3
}
```

---

### 5.5 Remove from Cart
```http
DELETE /api/cart/1
Authorization: Bearer {token}
```

---

### 5.6 Clear Cart
```http
DELETE /api/cart
Authorization: Bearer {token}
```

---

## 💰 Price Calculation Logic

```php
// Base price
$basePrice = $product->discount_price ?? $product->price;

// Add variations
$variationsPrice = array_sum(array_column($variations, 'price_adjustment'));

// Add addons
$addonsPrice = array_sum(array_column($addons, 'price'));

// Calculate unit price
$unitPrice = $basePrice + $variationsPrice + $addonsPrice;

// Calculate total
$totalPrice = $unitPrice * $quantity;

// Cart summary
$subtotal = sum of all cart items total_price
$deliveryFee = restaurant->delivery_fee
$serviceCharge = subtotal * (restaurant->service_charge_percentage / 100)
$tax = subtotal * (restaurant->tax_percentage / 100)
$grandTotal = subtotal + deliveryFee + serviceCharge + tax
```

---

## ✅ Features Implemented

- ✅ Product categories
- ✅ Product variations (size, spice level)
- ✅ Product add-ons (extras)
- ✅ Discount support
- ✅ Add to cart with variations & addons
- ✅ Auto price calculation
- ✅ Cart persistence (database)
- ✅ Update cart quantity
- ✅ Remove from cart
- ✅ Clear cart
- ✅ Multi-restaurant restriction
- ✅ Special notes support
- ✅ Tax & service charge calculation

---

## 🧪 Test Flow

```bash
# 1. Login
POST /api/login

# 2. Browse products
GET /api/products?restaurant_id=1

# 3. Add to cart
POST /api/cart
{
  "product_id": 1,
  "quantity": 2,
  "variations": [...],
  "addons": [...]
}

# 4. View cart
GET /api/cart

# 5. Update quantity
PUT /api/cart/1
{"quantity": 3}

# 6. Proceed to checkout
POST /api/orders
```

---

**PRODUCT & CART SYSTEM COMPLETE!** 🛒✅
