# Service Pattern

## Overview

The Service Pattern encapsulates business logic in dedicated service classes, keeping controllers thin and models focused on data representation. Services orchestrate operations across multiple repositories and models.

## Benefits

- **Single Responsibility**: Controllers handle HTTP, services handle business logic
- **Reusability**: Same logic can be used across controllers, commands, jobs
- **Testability**: Business logic can be tested independently
- **Maintainability**: Centralized location for domain operations

## Implementation Structure

```
app/Domains/{Domain}/
└── Services/
    └── {Entity}Services.php
```

## Basic Service Implementation

**Location**: `app/Domains/Accounting/Finance/Services/DefaultTreasureServices.php`

```php
<?php

namespace App\Domains\Accounting\Finance\Services;

use App\Domains\Accounting\Finance\DTOs\DefaultTreasureDTO;
use App\Domains\Core\User\Models\UserSetting;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

class DefaultTreasureServices
{
    /**
     * Get paginated list of default treasures
     */
    public function index(): LengthAwarePaginator
    {
        return UserSetting::query()
            ->with(['user:id,name', 'treasure:code,title,id'])
            ->whereHas('treasure')
            ->whereHas('user', function ($q) {
                return $q->where('users.company_id', company()->id);
            })
            ->latest("users_settings.created_at")
            ->paginate(perPage: request("perPage") ?? 10);
    }

    /**
     * Find a specific default treasure setting
     */
    public function find($id): UserSetting
    {
        return UserSetting::query()
            ->with(['user:id,name', 'treasure:code,title,id'])
            ->whereHas('treasure')
            ->whereHas('user', function ($q) {
                return $q->where('users.company_id', company()->id);
            })
            ->findOrFail($id);
    }

    /**
     * Update or create default treasure setting
     */
    public function update(DefaultTreasureDTO $dto, $user): void
    {
        DB::transaction(function () use ($dto, $user) {
            $user->settings()->updateOrCreate(
                ['user_id' => $user->id],
                [
                    'user_id' => $dto->user_id,
                    'treasure_id' => $dto->treasure_id,
                ]
            );
        });
    }

    /**
     * Delete a default treasure setting
     */
    public function delete($id): void
    {
        $setting = $this->find($id);
        $setting->delete();
    }
}
```

## Service with Repository Injection

```php
<?php

namespace App\Domains\Catalog\Products\Services;

use App\Domains\Catalog\Products\Contracts\ProductRepositoryContract;
use App\Domains\Catalog\Products\DTOs\ProductDTO;
use App\Domains\Catalog\Products\Events\ProductCreated;
use App\Domains\Catalog\Products\Models\Product;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class ProductService
{
    public function __construct(
        private ProductRepositoryContract $repository
    ) {}

    /**
     * Get all products with filtering
     */
    public function list(array $filters = []): Collection
    {
        return $this->repository->all()
            ->when(isset($filters['category_id']), function ($query) use ($filters) {
                return $query->where('category_id', $filters['category_id']);
            })
            ->when(isset($filters['is_active']), function ($query) use ($filters) {
                return $query->where('is_active', $filters['is_active']);
            });
    }

    /**
     * Create a new product
     */
    public function create(ProductDTO $dto): Product
    {
        return DB::transaction(function () use ($dto) {
            // Validate business rules
            $this->validateProductCreation($dto);

            // Create the product
            $product = $this->repository->create($dto->toArray());

            // Handle related data
            if ($dto->variants) {
                $this->createVariants($product, $dto->variants);
            }

            // Dispatch domain event
            event(new ProductCreated($product));

            return $product;
        });
    }

    /**
     * Update existing product
     */
    public function update(Product $product, ProductDTO $dto): Product
    {
        return DB::transaction(function () use ($product, $dto) {
            $this->repository->update($product, $dto->toArray());

            // Sync variants if provided
            if ($dto->variants !== null) {
                $this->syncVariants($product, $dto->variants);
            }

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

    /**
     * Delete a product
     */
    public function delete(Product $product): bool
    {
        // Check for dependencies
        if ($product->orderItems()->exists()) {
            throw new \Exception('Cannot delete product with existing orders');
        }

        return $this->repository->delete($product);
    }

    /**
     * Validate business rules for product creation
     */
    private function validateProductCreation(ProductDTO $dto): void
    {
        // Check for duplicate SKU
        if ($this->repository->findBySku($dto->sku)) {
            throw new \Exception('Product with this SKU already exists');
        }

        // Additional business validations
    }

    /**
     * Create product variants
     */
    private function createVariants(Product $product, array $variants): void
    {
        foreach ($variants as $variant) {
            $product->variants()->create($variant);
        }
    }

    /**
     * Sync product variants
     */
    private function syncVariants(Product $product, array $variants): void
    {
        $product->variants()->delete();
        $this->createVariants($product, $variants);
    }
}
```

## Complex Service with Multiple Dependencies

**Location**: `app/Domains/Construction/Project/Services/FinancialService.php`

```php
<?php

namespace App\Domains\Construction\Project\Services;

use App\Domains\Accounting\Services\InvoiceService;
use App\Domains\Construction\Project\Models\Project;
use App\Domains\Procurement\Services\PurchaseOrderService;
use Illuminate\Support\Facades\DB;

class FinancialService
{
    public function __construct(
        private InvoiceService $invoiceService,
        private PurchaseOrderService $purchaseOrderService
    ) {}

    /**
     * Calculate project financial summary
     */
    public function calculateSummary(Project $project): array
    {
        return [
            'budget' => $project->budget,
            'spent' => $this->calculateSpentAmount($project),
            'invoiced' => $this->calculateInvoicedAmount($project),
            'remaining' => $this->calculateRemainingBudget($project),
            'profit_margin' => $this->calculateProfitMargin($project),
        ];
    }

    /**
     * Calculate total spent amount
     */
    public function calculateSpentAmount(Project $project): float
    {
        $purchaseOrders = $this->purchaseOrderService
            ->getByProject($project->id);

        return $purchaseOrders->sum('total_amount');
    }

    /**
     * Calculate total invoiced amount
     */
    public function calculateInvoicedAmount(Project $project): float
    {
        $invoices = $this->invoiceService
            ->getByProject($project->id);

        return $invoices->sum('total_amount');
    }

    /**
     * Calculate remaining budget
     */
    public function calculateRemainingBudget(Project $project): float
    {
        return $project->budget - $this->calculateSpentAmount($project);
    }

    /**
     * Calculate profit margin percentage
     */
    public function calculateProfitMargin(Project $project): float
    {
        $invoiced = $this->calculateInvoicedAmount($project);
        $spent = $this->calculateSpentAmount($project);

        if ($invoiced === 0.0) {
            return 0.0;
        }

        return (($invoiced - $spent) / $invoiced) * 100;
    }

    /**
     * Close project financially
     */
    public function closeProject(Project $project): void
    {
        DB::transaction(function () use ($project) {
            // Finalize all pending invoices
            $this->invoiceService->finalizeProjectInvoices($project->id);

            // Close pending purchase orders
            $this->purchaseOrderService->closeProjectOrders($project->id);

            // Update project status
            $project->update(['financial_status' => 'closed']);
        });
    }
}
```

## Service with Validation Logic

**Location**: `app/Core/Services/ValidationServices.php`

```php
<?php

namespace App\Core\Services;

class ValidationServices
{
    protected array $rules = [];

    /**
     * Register a validation rule
     */
    public function registerRule(string $key, callable $rule): void
    {
        $this->rules[$key] = $rule;
    }

    /**
     * Get a validation rule by key
     */
    public function getRule(string $key): callable
    {
        if (!isset($this->rules[$key])) {
            throw new \Exception(__('No validation rule found for this key.'));
        }

        return $this->rules[$key];
    }

    /**
     * Execute a validation rule
     */
    public function validate(string $key, mixed $value, array $params = []): bool
    {
        $rule = $this->getRule($key);

        return $rule($value, $params);
    }

    /**
     * Check if a rule exists
     */
    public function hasRule(string $key): bool
    {
        return isset($this->rules[$key]);
    }

    /**
     * Get all registered rules
     */
    public function allRules(): array
    {
        return array_keys($this->rules);
    }
}
```

## Automation Rule Service

**Location**: `app/Domains/Core/AutomationRule/Services/AutomationRuleServices.php`

```php
<?php

namespace App\Domains\Core\AutomationRule\Services;

use App\Domains\Core\AutomationRule\DTOs\AutomationRuleDTO;
use App\Domains\Core\AutomationRule\Models\AutomationRule;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

class AutomationRuleServices
{
    public function __construct(
        private ConditionProcessor $conditionProcessor,
        private ActionProcessor $actionProcessor
    ) {}

    /**
     * Get paginated automation rules
     */
    public function index(): LengthAwarePaginator
    {
        return AutomationRule::query()
            ->filter()
            ->with('automatable')
            ->latest()
            ->paginate(request('perPage') ?? 10);
    }

    /**
     * Find automation rule by ID
     */
    public function find(int $id): AutomationRule
    {
        return AutomationRule::query()
            ->with('automatable')
            ->findOrFail($id);
    }

    /**
     * Store a new automation rule
     */
    public function store(AutomationRuleDTO $dto): void
    {
        DB::transaction(function () use ($dto) {
            AutomationRule::query()->create([
                'name' => $dto->name,
                'automatable_type' => $dto->automatable_type,
                'automatable_id' => $dto->automatable_id,
                'trigger_status' => $dto->trigger_status,
                'conditions' => $dto->conditions,
                'actions' => $dto->actions,
            ]);
        });
    }

    /**
     * Update an automation rule
     */
    public function update(AutomationRuleDTO $dto): void
    {
        DB::transaction(function () use ($dto) {
            $rule = $this->find($dto->id);

            $rule->update([
                'name' => $dto->name,
                'automatable_type' => $dto->automatable_type,
                'automatable_id' => $dto->automatable_id,
                'trigger_status' => $dto->trigger_status,
                'conditions' => $dto->conditions,
                'actions' => $dto->actions,
            ]);
        });
    }

    /**
     * Delete an automation rule
     */
    public function destroy(int $id): void
    {
        $rule = $this->find($id);
        $rule->delete();
    }

    /**
     * Evaluate and execute automation rules for a model
     */
    public function executeRules($model, string $event): void
    {
        $rules = AutomationRule::query()
            ->where('automatable_type', get_class($model))
            ->where('trigger_status', $event)
            ->get();

        foreach ($rules as $rule) {
            if ($this->conditionProcessor->evaluate($rule->conditions, $model)) {
                $this->actionProcessor->execute(
                    $rule->actions,
                    $model,
                    $rule->conditions,
                    $event
                );
            }
        }
    }
}
```

## Usage in Controllers

```php
<?php

namespace App\Http\Controllers\V1\Accounting;

use App\Domains\Accounting\Finance\DTOs\DefaultTreasureDTO;
use App\Domains\Accounting\Finance\Services\DefaultTreasureServices;
use App\Http\Controllers\Controller;
use App\Http\Requests\V1\Accounting\StoreDefaultTreasureRequest;
use App\Http\Resources\V1\Accounting\DefaultTreasureResource;
use Illuminate\Http\JsonResponse;

class DefaultTreasureController extends Controller
{
    public function __construct(
        private DefaultTreasureServices $service
    ) {}

    public function index(): JsonResponse
    {
        $treasures = $this->service->index();

        return $this->sendPaginatedResponse(
            DefaultTreasureResource::collection($treasures)
        );
    }

    public function show(int $id): JsonResponse
    {
        $treasure = $this->service->find($id);

        return $this->sendSuccessResponse(
            new DefaultTreasureResource($treasure)
        );
    }

    public function store(StoreDefaultTreasureRequest $request): JsonResponse
    {
        $dto = DefaultTreasureDTO::fromRequest($request->validated());

        $this->service->update($dto, $request->user());

        return $this->sendSuccessResponse(
            message: __('Default treasure updated successfully'),
            code: 201
        );
    }
}
```

## Service Registration (Singleton)

**Location**: `app/Providers/AppServiceProvider.php`

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Register as singleton for shared state
        $this->app->singleton('validation.services', function () {
            return new \App\Core\Services\ValidationServices();
        });

        // Regular binding for stateless services
        $this->app->bind(
            \App\Domains\Accounting\Finance\Services\DefaultTreasureServices::class,
            \App\Domains\Accounting\Finance\Services\DefaultTreasureServices::class
        );
    }
}
```

## Services in the Project

| Domain | Service | Purpose |
|--------|---------|---------|
| Accounting | `DefaultTreasureServices` | Default treasure management |
| Accounting | `InvoiceServices` | Invoice operations |
| Construction | `FinancialService` | Project financial calculations |
| Core | `ValidationServices` | Dynamic validation rules |
| Core | `AutomationRuleServices` | Automation rule execution |
| Management | `PayrollService` | Payroll processing |
| Catalog | `ProductService` | Product CRUD and business logic |

## Best Practices

### Do

- Use dependency injection for repositories and other services
- Wrap multi-step operations in database transactions
- Throw domain-specific exceptions
- Keep services focused on a single domain
- Use DTOs for input data

### Don't

- Access request data directly in services
- Return HTTP responses from services
- Put controller logic in services
- Create circular dependencies between services
- Skip validation in services

## Related Patterns

- [Repository Pattern](REPOSITORY_PATTERN.md) - Data access abstraction used by services
- [DTO Pattern](DTO_PATTERN.md) - Data transfer objects for service input
- [Event Pattern](EVENT_PATTERN.md) - Dispatching domain events from services
