# Service Layer

This guide explains how to implement the service layer for business logic following our established patterns.

## Overview

Services are the **business logic layer** of the application. They:

1. Receive DTOs from controllers
2. Execute business rules and validations
3. Interact with models for database operations
4. Handle transactions for data integrity
5. Return results to controllers

## Standard Service Template

```php
<?php

namespace App\Domains\YourDomain\YourModule\Services;

use App\Domains\YourDomain\YourModule\DTOs\YourEntityDTO;
use App\Domains\YourDomain\YourModule\Models\YourEntity;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class YourEntityService
{
    /**
     * Get paginated list
     */
    public function index(): LengthAwarePaginator
    {
        return YourEntity::query()
            ->filter()
            ->with(['category', 'createdBy'])
            ->latest('created_at')
            ->paginate(request('perPage', 15));
    }

    /**
     * Find by ID
     */
    public function find(int $id): YourEntity
    {
        return YourEntity::query()
            ->with(['category', 'items', 'createdBy'])
            ->findOrFail($id);
    }

    /**
     * Create new entity
     */
    public function store(YourEntityDTO $dto): YourEntity
    {
        return DB::transaction(function () use ($dto) {
            $entity = YourEntity::create([
                'company_id' => company()->id,
                'name' => $dto->name,
                'code' => $dto->code,
                'description' => $dto->description,
                'status' => $dto->status ?? 'draft',
                'amount' => $dto->amount ?? 0,
                'is_active' => $dto->is_active ?? true,
                'category_id' => $dto->category_id,
                'created_by' => auth()->id(),
            ]);

            // Handle nested items
            if (!empty($dto->items)) {
                $this->syncItems($entity, $dto->items);
            }

            Log::info('Entity created', ['id' => $entity->id]);

            return $entity->fresh(['category', 'items']);
        });
    }

    /**
     * Update existing entity
     */
    public function update(YourEntityDTO $dto, YourEntity $entity): YourEntity
    {
        return DB::transaction(function () use ($dto, $entity) {
            $entity->update([
                'name' => $dto->name ?? $entity->name,
                'code' => $dto->code ?? $entity->code,
                'description' => $dto->description ?? $entity->description,
                'status' => $dto->status ?? $entity->status,
                'amount' => $dto->amount ?? $entity->amount,
                'is_active' => $dto->is_active ?? $entity->is_active,
                'category_id' => $dto->category_id ?? $entity->category_id,
            ]);

            // Sync items if provided
            if ($dto->items !== null) {
                $this->syncItems($entity, $dto->items);
            }

            Log::info('Entity updated', ['id' => $entity->id]);

            return $entity->fresh(['category', 'items']);
        });
    }

    /**
     * Delete entity
     */
    public function destroy(YourEntity $entity): bool
    {
        return DB::transaction(function () use ($entity) {
            // Business rule validation
            if (!$entity->canBeDeleted()) {
                throw new \Exception(__('Entity cannot be deleted in current status'));
            }

            // Delete related items first
            $entity->items()->delete();

            // Delete the entity
            $entity->delete();

            Log::info('Entity deleted', ['id' => $entity->id]);

            return true;
        });
    }

    /**
     * Sync related items
     */
    private function syncItems(YourEntity $entity, array $items): void
    {
        // Delete existing items
        $entity->items()->delete();

        // Create new items
        foreach ($items as $itemData) {
            $entity->items()->create([
                'name' => $itemData['name'] ?? $itemData->name,
                'quantity' => $itemData['quantity'] ?? $itemData->quantity,
                'price' => $itemData['price'] ?? $itemData->price,
            ]);
        }
    }
}
```

## Service with Constructor Injection

For services that depend on other services:

```php
<?php

namespace App\Domains\YourDomain\YourModule\Services;

use App\Domains\Core\TemporaryFile\Services\TemporaryFileServices;
use App\Domains\Core\Notification\Services\NotificationService;

class YourEntityService
{
    public function __construct(
        private readonly TemporaryFileServices $temporaryFileServices,
        private readonly NotificationService $notificationService,
    ) {}

    public function store(YourEntityDTO $dto): YourEntity
    {
        return DB::transaction(function () use ($dto) {
            $entity = YourEntity::create([...]);

            // Use injected services
            if (!empty($dto->temporary_folders)) {
                $this->temporaryFileServices->moveTemporaryFilesToMedia(
                    $dto->temporary_folders,
                    $entity,
                    'your_entity',
                    'attachments'
                );
            }

            // Send notification
            $this->notificationService->send(
                $entity->createdBy,
                'Entity created',
                ['entity_id' => $entity->id]
            );

            return $entity;
        });
    }
}
```

## CRUD Patterns

### Index (List with Pagination)

```php
public function index(): LengthAwarePaginator
{
    return YourEntity::query()
        ->filter()                          // Apply Filterable trait
        ->with(['category', 'createdBy'])   // Eager load relations
        ->latest('created_at')              // Order by latest
        ->paginate(request('perPage', 15)); // Paginate results
}
```

### Index with Custom Filters

```php
public function index(array $filters = []): LengthAwarePaginator
{
    $query = YourEntity::query()
        ->with(['category', 'createdBy']);

    // Apply custom filters
    if (!empty($filters['status'])) {
        $query->where('status', $filters['status']);
    }

    if (!empty($filters['category_id'])) {
        $query->where('category_id', $filters['category_id']);
    }

    if (!empty($filters['date_from'])) {
        $query->whereDate('created_at', '>=', $filters['date_from']);
    }

    if (!empty($filters['date_to'])) {
        $query->whereDate('created_at', '<=', $filters['date_to']);
    }

    if (!empty($filters['search'])) {
        $search = $filters['search'];
        $query->where(function ($q) use ($search) {
            $q->where('name', 'like', "%{$search}%")
              ->orWhere('code', 'like', "%{$search}%");
        });
    }

    // Sorting
    $sortBy = $filters['sort_by'] ?? 'created_at';
    $sortOrder = $filters['sort_order'] ?? 'desc';
    $query->orderBy($sortBy, $sortOrder);

    return $query->paginate($filters['per_page'] ?? 15);
}
```

### Find with Relations

```php
public function find(int $id): YourEntity
{
    return YourEntity::query()
        ->with([
            'category',
            'items.product',
            'createdBy' => fn($q) => $q->select('id', 'name', 'email'),
        ])
        ->findOrFail($id);
}
```

### Store (Create)

```php
public function store(YourEntityDTO $dto): YourEntity
{
    return DB::transaction(function () use ($dto) {
        // 1. Validate business rules
        $this->validateBusinessRules($dto);

        // 2. Create main entity
        $entity = YourEntity::create([
            'company_id' => company()->id,
            'name' => $dto->name,
            'code' => $this->generateCode(),  // Auto-generate if needed
            'status' => 'draft',
            'created_by' => auth()->id(),
        ]);

        // 3. Create related records
        $this->createRelatedRecords($entity, $dto);

        // 4. Handle file attachments
        $this->handleAttachments($entity, $dto);

        // 5. Log and return
        Log::info('Entity created', [
            'id' => $entity->id,
            'user_id' => auth()->id(),
        ]);

        return $entity->fresh(['category', 'items']);
    });
}
```

### Update

```php
public function update(YourEntityDTO $dto, YourEntity $entity): YourEntity
{
    return DB::transaction(function () use ($dto, $entity) {
        // 1. Validate state transition
        if ($dto->status && !$this->canTransitionTo($entity, $dto->status)) {
            throw new \Exception(__('Invalid status transition'));
        }

        // 2. Update entity
        $entity->update(array_filter([
            'name' => $dto->name,
            'code' => $dto->code,
            'description' => $dto->description,
            'status' => $dto->status,
        ], fn($v) => $v !== null));

        // 3. Sync related records
        if ($dto->items !== null) {
            $this->syncItems($entity, $dto->items);
        }

        return $entity->fresh(['category', 'items']);
    });
}
```

### Destroy (Delete)

```php
public function destroy(YourEntity $entity): bool
{
    return DB::transaction(function () use ($entity) {
        // 1. Validate can be deleted
        if (!$entity->canBeDeleted()) {
            throw new \Exception(__('Cannot delete entity in current status'));
        }

        // 2. Delete related records
        $entity->items()->delete();
        $entity->clearMediaCollection('attachments');

        // 3. Delete entity
        $entity->delete();

        Log::info('Entity deleted', ['id' => $entity->id]);

        return true;
    });
}
```

## Business Logic Patterns

### State Transitions

```php
public function approve(YourEntity $entity): YourEntity
{
    return DB::transaction(function () use ($entity) {
        if ($entity->status !== 'pending') {
            throw new \Exception(__('Only pending entities can be approved'));
        }

        $entity->update([
            'status' => 'approved',
            'approved_at' => now(),
            'approved_by' => auth()->id(),
        ]);

        // Trigger side effects
        $this->notifyCreator($entity);

        return $entity->fresh();
    });
}

public function reject(YourEntity $entity, string $reason): YourEntity
{
    return DB::transaction(function () use ($entity, $reason) {
        if ($entity->status !== 'pending') {
            throw new \Exception(__('Only pending entities can be rejected'));
        }

        $entity->update([
            'status' => 'rejected',
            'rejection_reason' => $reason,
            'rejected_at' => now(),
            'rejected_by' => auth()->id(),
        ]);

        return $entity->fresh();
    });
}
```

### Bulk Operations

```php
public function bulkApprove(array $ids): int
{
    return DB::transaction(function () use ($ids) {
        $count = YourEntity::whereIn('id', $ids)
            ->where('status', 'pending')
            ->update([
                'status' => 'approved',
                'approved_at' => now(),
                'approved_by' => auth()->id(),
            ]);

        Log::info('Bulk approval', [
            'count' => $count,
            'user_id' => auth()->id(),
        ]);

        return $count;
    });
}

public function bulkDelete(array $ids): int
{
    return DB::transaction(function () use ($ids) {
        $entities = YourEntity::whereIn('id', $ids)->get();

        foreach ($entities as $entity) {
            if ($entity->canBeDeleted()) {
                $entity->items()->delete();
                $entity->delete();
            }
        }

        return $entities->count();
    });
}
```

### Calculations

```php
public function calculateTotals(YourEntity $entity): array
{
    $items = $entity->items;

    $subtotal = $items->sum(fn($item) => $item->quantity * $item->price);
    $taxRate = cs('tax_rate') ?? 0.15;
    $tax = $subtotal * $taxRate;
    $total = $subtotal + $tax;

    return [
        'subtotal' => round($subtotal, 2),
        'tax_rate' => $taxRate,
        'tax' => round($tax, 2),
        'total' => round($total, 2),
    ];
}

public function recalculateEntity(YourEntity $entity): YourEntity
{
    return DB::transaction(function () use ($entity) {
        $totals = $this->calculateTotals($entity);

        $entity->update([
            'subtotal' => $totals['subtotal'],
            'tax' => $totals['tax'],
            'total' => $totals['total'],
        ]);

        return $entity->fresh();
    });
}
```

### Service-to-Service Communication

```php
public function complete(YourEntity $entity): YourEntity
{
    return DB::transaction(function () use ($entity) {
        $entity->update(['status' => 'completed']);

        // Call other services
        app(AccountingService::class)->createTransaction([
            'source_type' => YourEntity::class,
            'source_id' => $entity->id,
            'amount' => $entity->total,
        ]);

        app(NotificationService::class)->notify(
            $entity->createdBy,
            'Entity completed',
            ['entity_id' => $entity->id]
        );

        return $entity->fresh();
    });
}
```

## Repository Pattern (Optional)

For complex queries, use a repository:

```php
// Repository
class YourEntityRepository
{
    public function findByStatus(string $status): Collection
    {
        return YourEntity::where('status', $status)->get();
    }

    public function getStatistics(): array
    {
        return [
            'total' => YourEntity::count(),
            'pending' => YourEntity::where('status', 'pending')->count(),
            'completed' => YourEntity::where('status', 'completed')->count(),
        ];
    }
}

// Service using Repository
class YourEntityService
{
    public function __construct(
        private readonly YourEntityRepository $repository
    ) {}

    public function getDashboard(): array
    {
        return [
            'statistics' => $this->repository->getStatistics(),
            'pending' => $this->repository->findByStatus('pending'),
        ];
    }
}
```

## Error Handling

```php
public function store(YourEntityDTO $dto): YourEntity
{
    try {
        return DB::transaction(function () use ($dto) {
            // Validation
            if (YourEntity::where('code', $dto->code)->exists()) {
                throw new \InvalidArgumentException(__('Code already exists'));
            }

            return YourEntity::create([...]);
        });
    } catch (\InvalidArgumentException $e) {
        // Re-throw validation errors
        throw $e;
    } catch (\Exception $e) {
        // Log unexpected errors
        Log::error('Failed to create entity', [
            'error' => $e->getMessage(),
            'dto' => $dto->toArray(),
        ]);

        throw new \Exception(__('Failed to create entity'));
    }
}
```

## Best Practices

### DO:

1. **Wrap writes in transactions** - `DB::transaction()`
2. **Use DTOs for input** - Never use raw arrays
3. **Return fresh models** - `$entity->fresh(['relations'])`
4. **Log important operations** - Create, update, delete
5. **Validate business rules** - Before database operations
6. **Keep methods focused** - Single responsibility

### DON'T:

1. **Don't access Request directly** - Use DTOs
2. **Don't skip transactions** - Data integrity is critical
3. **Don't put HTTP logic** - No response formatting
4. **Don't hardcode values** - Use settings/config
5. **Don't forget error handling** - Catch and log errors

## Method Naming Conventions

| Action | Method Name |
|--------|-------------|
| List | `index()` |
| Find one | `find($id)` |
| Create | `store(DTO)` |
| Update | `update(DTO, Model)` |
| Delete | `destroy(Model)` |
| Status change | `approve()`, `reject()`, `complete()` |
| Bulk operations | `bulkApprove()`, `bulkDelete()` |
| Calculations | `calculate...()`, `recalculate...()` |
