---
description: Service layer patterns. Events for side effects. No inline emails/notifications.
globs: ["app/Domain/**/Services/**", "app/Domain/**/Events/**", "app/Domain/**/Listeners/**"]
---

# Service Layer & Events

## Service Pattern (EVERY service)

```php
namespace App\Domain\{Module}\Services;

class {Entity}Service
{
    public function create(array $data, User $actor): Model
    {
        return DB::transaction(function () use ($data, $actor) {
            $entity = Entity::create([...]);
            EntityCreated::dispatch($entity); // fires AFTER commit
            return $entity;
        });
    }
}
```

## Service Rules

1. ALL writes inside `DB::transaction()`
2. Receive explicit params — NEVER use `auth()`, `request()`, `session()` inside services
3. Return the entity — caller decides response
4. Throw domain exceptions for business rule violations
5. Dispatch events — NEVER send email/SMS/notification directly
6. One service per domain entity
7. Services CAN call other services (constructor injection)
8. Services NEVER call controllers or Livewire components

## Event Rules

1. All domain events implement `ShouldDispatchAfterCommit`
2. Events carry: the entity + the actor (User)
3. Listeners implement `ShouldQueue` (async side effects)
4. One listener = ONE side effect
5. Listener failure does NOT roll back the original action
6. NEVER send email/notification from service or controller — use event + listener

## Exception Pattern

```php
namespace App\Domain\Shared\Exceptions;

class DomainException extends \RuntimeException {}
class InsufficientBalanceException extends DomainException {}
class InvalidStatusTransitionException extends DomainException {}
// ... etc
```

Controllers/Livewire catch these and show appropriate error messages.

## Dependency Direction

Services call DOWN the dependency chain, never up:
- POSService → InvoiceService → PaymentService → TransactionService
- EnrollmentService → PricingService, AttendanceGenerationService
- NEVER: PaymentService → POSService (use events instead)
