# Model

This guide explains how to create Eloquent models following our established patterns.

## Base Model

All models should extend `BaseModel` which provides common functionality:

**Location:** `app/Models/BaseModel.php`

```php
use App\Models\BaseModel;

class YourEntity extends BaseModel
{
    // Model implementation
}
```

## Standard Model Template

```php
<?php

namespace App\Domains\YourDomain\YourModule\Models;

use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;

class YourEntity extends BaseModel
{
    use SoftDeletes;

    /**
     * Enable automatic company isolation
     */
    protected static bool $applyCompanyScope = true;

    /**
     * The table associated with the model
     */
    protected $table = 'your_entities';

    /**
     * The attributes that are mass assignable
     */
    protected $fillable = [
        'company_id',
        'name',
        'code',
        'description',
        'status',
        'amount',
        'date',
        'is_active',
        'metadata',
    ];

    /**
     * The attributes that should be cast
     */
    protected $casts = [
        'amount' => 'decimal:2',
        'date' => 'date',
        'is_active' => 'boolean',
        'metadata' => 'array',
        'status' => YourEntityStatus::class,  // Enum casting
    ];

    /**
     * Columns for filtering (used by Filterable trait)
     */
    public static array $filtersCols = [
        'company_id',
        'status',
        'is_active',
    ];

    /**
     * Columns for text search
     */
    public static array $searchCols = [
        'name',
        'code',
        'description',
    ];

    /**
     * Enable date range filtering
     */
    public static bool $searchDate = true;

    // ==================
    // RELATIONSHIPS
    // ==================

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

    public function createdBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by');
    }

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

    // ==================
    // SCOPES
    // ==================

    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    public function scopeByStatus($query, string $status)
    {
        return $query->where('status', $status);
    }

    // ==================
    // ACCESSORS
    // ==================

    public function getFormattedAmountAttribute(): string
    {
        return number_format($this->amount, 2);
    }

    // ==================
    // METHODS
    // ==================

    public function canBeDeleted(): bool
    {
        return $this->status !== 'completed';
    }

    public function markAsCompleted(): bool
    {
        return $this->update(['status' => 'completed']);
    }
}
```

## Key Model Properties

### Company Isolation

```php
/**
 * When true, automatically filters queries by company_id
 * and assigns company_id on record creation
 */
protected static bool $applyCompanyScope = true;
```

### Table Name

Always explicitly define the table name:

```php
protected $table = 'your_entities';
```

### Fillable Fields

List all mass-assignable attributes:

```php
protected $fillable = [
    'company_id',
    'name',
    // ... other fields
];
```

### Attribute Casting

Define type casts for proper data handling:

```php
protected $casts = [
    // Basic types
    'amount' => 'decimal:2',
    'quantity' => 'integer',
    'is_active' => 'boolean',

    // Date/Time
    'date' => 'date',
    'datetime_field' => 'datetime',

    // JSON
    'metadata' => 'array',
    'settings' => 'json',

    // Enums (PHP 8.1+)
    'status' => YourEntityStatus::class,
];
```

### Filtering Configuration

Configure for the Filterable trait:

```php
// Single value filters
public static array $filtersCols = ['status', 'category_id'];

// Multi-value filters (arrays)
public static array $multiFiltersCols = ['id'];

// Text search columns
public static array $searchCols = ['name', 'description'];

// Enable date range filtering
public static bool $searchDate = true;
```

## Relationships

### BelongsTo (Many-to-One)

```php
use Illuminate\Database\Eloquent\Relations\BelongsTo;

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

// With custom foreign key
public function creator(): BelongsTo
{
    return $this->belongsTo(User::class, 'created_by');
}
```

### HasMany (One-to-Many)

```php
use Illuminate\Database\Eloquent\Relations\HasMany;

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

// With ordering
public function sortedItems(): HasMany
{
    return $this->hasMany(YourEntityItem::class)->orderBy('order');
}
```

### HasOne (One-to-One)

```php
use Illuminate\Database\Eloquent\Relations\HasOne;

public function profile(): HasOne
{
    return $this->hasOne(Profile::class);
}
```

### BelongsToMany (Many-to-Many)

```php
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

public function tags(): BelongsToMany
{
    return $this->belongsToMany(Tag::class, 'entity_tags')
        ->withTimestamps();
}

// With pivot data
public function users(): BelongsToMany
{
    return $this->belongsToMany(User::class)
        ->withPivot('role', 'assigned_at')
        ->withTimestamps();
}
```

### Polymorphic Relations

```php
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;

// In parent model
public function comments(): MorphMany
{
    return $this->morphMany(Comment::class, 'commentable');
}

// In Comment model
public function commentable(): MorphTo
{
    return $this->morphTo();
}
```

## Model with Media Library

For models that handle file uploads:

```php
<?php

namespace App\Domains\YourDomain\YourModule\Models;

use App\Models\BaseModel;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class YourEntity extends BaseModel implements HasMedia
{
    use InteractsWithMedia;

    protected static bool $applyCompanyScope = true;

    protected $fillable = ['name', 'company_id'];

    /**
     * Register media collections
     */
    public function registerMediaCollections(): void
    {
        // Single file collection
        $this->addMediaCollection('document')
            ->singleFile();

        // Multiple files collection
        $this->addMediaCollection('attachments');

        // With accepted MIME types
        $this->addMediaCollection('images')
            ->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);
    }
}
```

## Enum Classes

Create enums for status fields:

**Location:** `app/Domains/YourDomain/YourModule/Enums/YourEntityStatus.php`

```php
<?php

namespace App\Domains\YourDomain\YourModule\Enums;

enum YourEntityStatus: string
{
    case Draft = 'draft';
    case Pending = 'pending';
    case Active = 'active';
    case Completed = 'completed';
    case Cancelled = 'cancelled';

    /**
     * Get human-readable label
     */
    public function label(): string
    {
        return match($this) {
            self::Draft => __('Draft'),
            self::Pending => __('Pending'),
            self::Active => __('Active'),
            self::Completed => __('Completed'),
            self::Cancelled => __('Cancelled'),
        };
    }

    /**
     * Get all values as array
     */
    public static function values(): array
    {
        return array_column(self::cases(), 'value');
    }

    /**
     * Get options for select fields
     */
    public static function options(): array
    {
        return collect(self::cases())->map(fn($case) => [
            'value' => $case->value,
            'label' => $case->label(),
        ])->toArray();
    }
}
```

## Database Migration

Create a migration for your model:

```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('your_entities', function (Blueprint $table) {
            $table->id();

            // Foreign keys
            $table->foreignId('company_id')->constrained()->cascadeOnDelete();
            $table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();

            // Main fields
            $table->string('name');
            $table->string('code')->nullable();
            $table->text('description')->nullable();
            $table->string('status')->default('draft');

            // Numeric fields
            $table->decimal('amount', 15, 2)->default(0);
            $table->integer('quantity')->default(0);

            // Date fields
            $table->date('date')->nullable();
            $table->datetime('completed_at')->nullable();

            // Boolean fields
            $table->boolean('is_active')->default(true);

            // JSON fields
            $table->json('metadata')->nullable();

            // Timestamps and soft deletes
            $table->timestamps();
            $table->softDeletes();

            // Indexes
            $table->index(['company_id', 'status']);
            $table->unique(['company_id', 'code']);
        });
    }

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

## Best Practices

### DO:

1. **Always set `$applyCompanyScope`** for tenant data
2. **Define explicit table names**
3. **Use proper type casting**
4. **Create indexes** for frequently queried columns
5. **Use soft deletes** for important data
6. **Define relationships** with proper return types

### DON'T:

1. **Don't put business logic** in models (use Services)
2. **Don't skip the company_id** foreign key
3. **Don't use `$guarded = []`** - be explicit with `$fillable`
4. **Don't forget unique constraints** per company
5. **Don't hardcode values** - use Enums instead
