# Action & Service Orchestration

**NUMBER:** ADR-ACT-001
**STATUS:** CANONICAL AUTHORITY
**SCOPE:** Every class in `Actions/` and `Services/` folders across all Domains.
**RULE:** This is the ONLY definitive source for when to create an Action, when to call a Service directly, and what an Action may return. Any other document mentioning this topic is a REFERENCE, not a definition.
**VIOLATION:** Implementing orchestration patterns based on any other source constitutes an architectural breach.

> **Transaction placement rules:** `04_DATABASE_COMMUNICATION.md` → Section 6.1
> **Exception handling in Actions:** `02_ERROR_BOUNDARIES.md` → The Catch Rules
> **Event firing timing:** `01_EVENTS_OBSERVERS.md` → Section 3.2

---

## 1. Philosophy

**Orchestration On Demand.**

The Action exists solely to coordinate multiple steps into one atomic operation. It is NOT a mandatory layer — most operations are simple enough to flow directly from Controller to Service. Creating an Action when no orchestration is needed is YAGNI.

---

## 2. Absolute Prohibitions

```text
❌ PROHIBITED: Creating an Action that calls a single Service with no branching.
❌ PROHIBITED: Placing Business Validation inside the Service when it precedes the operation (it belongs in the Action).
❌ PROHIBITED: Returning Eloquent Models from Actions — return DTOs or void.
❌ PROHIBITED: Using try-catch in Actions to convert exceptions to responses (let them bubble up).
```

---

## 3. When to Call Service Directly

Call the Service directly from the Controller when the operation is a single, unbranched step.

| Scenario | Example | Why No Action |
|:---------|:--------|:--------------|
| List/Show reads | `GET /agents` | Single Query Class call |
| Read with single Query Class | `GET /agents/{id}` | Query Class + Resource, no coordination |
| Simple update | `PUT /customers/{id}` | Single Service call, no validation chain |
| Direct delete (trait-protected) | `DELETE /notes/{id}` | Single `$model->delete()` after route binding |
| Create without branching or transaction | — | Rare, but if truly single-step |

**Pattern:**
```php
public function show(int $agent): JsonResponse
{
    $agent = $this->getAgentQuery->findOrFail($agent);
    return $this->success(data: AgentResource::make($agent));
}
```

---

## 4. When to Create an Action

Create an Action when the operation requires coordination that does not belong inside a single Service.

### 4.1 Business Branching

The operation has multiple paths depending on state, and the branching logic must be decided before calling any Service.

```php
class CreateOrConvertInvestorAction
{
    public function execute(array $data): InvestorCreatedDTO
    {
        $client = $this->findClientQuery->byPhone($data['phone']);

        // Branching decision BEFORE any write
        if ($client) {
            $this->InvestorValidation->validateCanBeConverted($client);
            return $this->investorService->convertToInvestor($client, $data);
        }

        return $this->investorService->createNew($data);
    }
}
```

### 4.2 Business Validation Preceding the Operation

Validation that requires database state or business rules — not structural HTTP validation.

```php
class RecordPaymentAction
{
    public function execute(PaymentDTO $dto): PaymentResultDTO
    {
        // Business validation BEFORE the write
        $this->paymentValidation->validateLifoSequence($dto->contractId);
        $this->paymentValidation->validateAmountMatchesInstallment($dto);

        return $this->paymentService->record($dto);
    }
}
```

### 4.3 Multiple Service Calls in One Operation

When the success of the operation depends on multiple writes that must be coordinated.

```php
class CreateContractAction
{
    public function execute(CreateContractDTO $dto): ContractCreatedDTO
    {
        return DB::transaction(function () use ($dto) {
            $contract = $this->contractService->create($dto);
            $this->installmentService->generateSchedule($contract, $dto);
            
            event(new ContractCreated($contract));
            
            return ContractCreatedDTO::fromArray([...$contract->only('id', 'status')]);
        });
    }
}
```

### 4.4 Data Assembly from Multiple Sources

When building a complex DTO requires querying multiple sources before the write.

```php
class ProcessShareWithdrawalAction
{
    public function execute(WithdrawalDTO $dto): WithdrawalResultDTO
    {
        $currentBalance = $this->shareBalanceQuery->getActiveBalance($dto->clientId);
        $latestLog = $this->latestShareLogQuery->get($dto->clientId);

        $enrichedDto = $this->enrichWithdrawalDto($dto, $currentBalance, $latestLog);

        return $this->shareService->processWithdrawal($enrichedDto);
    }
}
```

### 4.5 Operation Requires DB::transaction

When multiple writes must succeed or fail together.

> **Note:** A single Service call that internally uses `DB::transaction` does NOT require an Action. The Action is needed only when YOU are coordinating multiple Services/Queries inside the transaction.

### 4.6 Operation Fires an Event After Success

When a side-effect Event must fire after the primary operation completes.

```php
class RecordInstallmentPaymentAction
{
    public function execute(PaymentDTO $dto): PaymentResultDTO
    {
        return DB::transaction(function () use ($dto) {
            $payment = $this->paymentService->create($dto);
            $this->contractService->markInstallmentAsPaid($dto->installmentId, $dto->amount);
            
            event(new InstallmentPaymentRecorded($payment));
            
            return PaymentResultDTO::fromArray([...$payment->only('id')]);
        });
    }
}
```

### 4.7 Delete with Pre-Deletion State Check

When deletion requires validating state before executing.

```php
class DeleteShareLogAction
{
    public function execute(int $logId): void
    {
        $log = $this->findShareLogQuery->findOrFail($logId);
        $this->shareValidation->validateIsLatestActive($log);
        
        $this->shareService->delete($log);
    }
}
```

### 4.8 Read Requiring Data Assembly from Multiple Sources

When a read endpoint must combine data from a Builder, Query Classes, and transformations.

```php
class GetContractDetailAction
{
    public function execute(int $contractId): array
    {
        $contract = $this->findContractQuery->findOrFail($contractId);
        $summary = $this->contractSummaryQuery->get($contractId);
        $nextInstallment = $this->nextUnpaidInstallmentQuery->forContract($contractId);
        
        return $this->contractDetailBuilder->build($contract, $summary, $nextInstallment);
    }
}
```

---

## 5. What an Action Returns

### 5.1 Return a DTO

When the Controller needs data to build the response.

```php
// Action
public function execute(CreateContractDTO $dto): ContractCreatedDTO { ... }

// Controller
$result = $this->createContractAction->execute($dto);
return $this->success(data: ContractResource::make($result));
```

### 5.2 Return void

When the operation is an update or deletion with no meaningful return data.

```php
// Action
public function execute(UpdateInvestorDTO $dto): void { ... }

// Controller
$this->updateInvestorAction->execute($dto);
return $this->success(msg: __('success/investor.updated'));
```

### 5.3 Return Types — Canonical Rule

| Return Type | When to Use | What Controller Does |
|:------------|:------------|:---------------------|
| DTO | Create operations, reads with assembly | Pass to Resource |
| void | Update, delete, state changes | Return `success()` without `data` |

**Prohibited:** Returning Eloquent Models from Actions. Always wrap in a DTO if data is needed.

---

## 6. Action Structure (Canonical Template)

```php
readonly class CreateContractAction
{
    public function __construct(
        private ContractService $contractService,
        private InstallmentService $installmentService,
    ) {}

    public function execute(CreateContractDTO $dto): ContractCreatedDTO
    {
        return DB::transaction(function () use ($dto) {
            $contract = $this->contractService->create($dto);
            $this->installmentService->generateSchedule($contract, $dto);

            event(new ContractCreated($contract));

            return ContractCreatedDTO::fromArray([
                'id'     => $contract->id,
                'status' => $contract->status,
            ]);
        });
    }
}
```

### 6. Data Transformation Boundary (CANONICAL)
- The transformation between Eloquent Models and DTOs follows strict ownership rules. Violating these rules creates hidden coupling between layers.

## 6.1 Model → DTO (Read Transformation)
- Owner: The Action (or Query Class for read-only operations).

- When a Service returns an Eloquent Model, the Action that called it is responsible for transforming it into a DTO before returning to the Controller.
```php
// CORRECT: Action transformspublic function execute(Client $client, CreateDTO $dto): CreatedDTO{
        $model = $this->service->create($dto);        return CreatedDTO::fromArray([        ...$model->only(['id', 'name', 'status']),        'created_at' => $model->created_at->toIso8601String(),    ]);
        }
        // PROHIBITED: Service transformspublic function create(CreateDTO $dto): CreatedDTO{    $model = Model::create($dto->toModelArray());
            return CreatedDTO::fromArray([...]); // ← Wrong layer
}
```
## 6.2 Why Not in Service?
- The Service's contract is: receive DTO → write to DB → return Model.
- Adding transformation inside the Service means it can no longer be called by
another Action that might need the raw Model for a different purpose.

## 6.3 Why Not in Resource?
- The Resource reads public properties blindly. It does not perform Model → DTO
conversion. It only formats DTO or Model properties for the HTTP response.

## 6.4 Canonical Pattern: only() + Spread
- The standard transformation uses only() to extract raw fields and manual
formatting for dates or computed values:


```php
return SomeDTO::fromArray([
    ...$model->only(['id', 'status', 'type']),  // Raw fields via spread
    'date_field' => $model->date_field->format('Y-m-d'),  // Formatted
]);
```

---

## 7. Decision Cheat Sheet

When writing a Controller method:

1. Does this operation branch based on business state?
   └─ YES → Create Action.

2. Does this operation need Business Validation before the write?
   └─ YES → Create Action (validation in Action, write in Service).

3. Does this operation call more than one Service?
   └─ YES → Create Action.

4. Does this operation need DB::transaction across multiple Services?
   └─ YES → Create Action.

5. Does this operation fire an Event after success?
   └─ YES → Create Action.

6. Does this operation assemble data from multiple sources for a read?
   └─ YES → Create Action (or use Response Builder if no business logic).

7. Is this a simple list/show/update/delete with no branching?
   └─ YES → Call Service or Query directly. No Action.

8. Am I creating an Action that calls exactly one Service with no validation?
   └─ YES → ERROR. Remove the Action. Call the Service directly.