# API Contract: Get Customer Details

**Endpoint**: `GET /api/v1/customers/{id}`
**Permission**: `customers.view` (Spatie, `admin` guard)
**Auth**: `Authorization: Bearer {token}` (Sanctum)

## Request

### Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | Yes | `Bearer {sanctum_token}` |
| `Accept` | Yes | `application/json` |

### Path Parameters

| Param | Type | Description |
|---|---|---|
| `id` | integer | The `client_id` in the `clients` table (NOT a `customer_id`; the customer record IS the client record). |

### Example Request

```http
GET /api/v1/customers/42 HTTP/1.1
Authorization: Bearer {token}
Accept: application/json
```

## Success Response

### 200 OK

```json
{
  "success": true,
  "message": "تم جلب بيانات العميل بنجاح.",
  "data": {
    "id": 42,
    "name": "أحمد محمد",
    "phone": "0912345678",
    "description": "عميل مميز",
    "avatar": "avatars/abc123.jpg",
    "avatar_url": "http://host/storage/avatars/abc123.jpg",
    "client_type_flags": ["customer"],
    "created_at": "2026-06-07T10:30:00+03:00",
    "updated_at": "2026-06-07T10:30:00+03:00",
    "financial_summary": {
      "total_installment_amount": "5000.00",
      "total_collected_amount": "3500.00",
      "total_remaining_amount": "1500.00",
      "total_pending_installments": 3,
      "total_overdue_installments": 1,
      "total_received_payments": 7
    }
  }
}
```

### Empty Financial Summary

If the customer has no active or completed contracts, `financial_summary` is returned with all zeros (or `null` for amount fields). Example:

```json
{
  "financial_summary": {
    "total_installment_amount": "0.00",
    "total_collected_amount": "0.00",
    "total_remaining_amount": "0.00",
    "total_pending_installments": 0,
    "total_overdue_installments": 0,
    "total_received_payments": 0
  }
}
```

## Error Responses

### 401 Unauthorized

```json
{ "success": false, "message": "Unauthenticated." }
```

### 403 Forbidden (missing `customers.view` permission)

```json
{ "success": false, "message": "You do not have permission to perform this action." }
```

### 404 Not Found (id doesn't exist in `clients`)

```json
{ "success": false, "message": "The requested Client was not found." }
```

## Server-Side Behavior

1. `Client::findOrFail($id)` — throws `ModelNotFoundException` if not found; mapped to 404 via `ExceptionMappings.php`.
2. Compute financial summary via a single live query:
   ```sql
   SELECT
     SUM(i.amount) AS total_installment_amount,
     SUM(CASE WHEN i.status = 'paid' THEN i.amount ELSE 0 END) AS total_collected_amount,
     SUM(CASE WHEN i.status IN ('pending', 'overdue') THEN i.amount ELSE 0 END) AS total_remaining_amount,
     COUNT(CASE WHEN i.status = 'pending' THEN 1 END) AS total_pending_installments,
     COUNT(CASE WHEN i.status = 'overdue' THEN 1 END) AS total_overdue_installments,
     COUNT(CASE WHEN i.status = 'paid' THEN 1 END) AS total_received_payments
   FROM installments i
   INNER JOIN contracts c ON c.id = i.contract_id
   WHERE c.customer_id = ?
     AND c.status IN ('active', 'completed');
   ```
   Implementation in `CustomerService` via Eloquent:
   ```php
   $summary = Installment::query()
       ->join('contracts', 'contracts.id', '=', 'installments.contract_id')
       ->where('contracts.customer_id', $client->id)
       ->whereIn('contracts.status', ['active', 'completed'])
       ->selectRaw('
           SUM(installments.amount) AS total_installment_amount,
           SUM(CASE WHEN installments.status = \'paid\' THEN installments.amount ELSE 0 END) AS total_collected_amount,
           SUM(CASE WHEN installments.status IN (\'pending\', \'overdue\') THEN installments.amount ELSE 0 END) AS total_remaining_amount,
           COUNT(CASE WHEN installments.status = \'pending\' THEN 1 END) AS total_pending_installments,
           COUNT(CASE WHEN installments.status = \'overdue\' THEN 1 END) AS total_overdue_installments,
           COUNT(CASE WHEN installments.status = \'paid\' THEN 1 END) AS total_received_payments
       ')
       ->first();
   ```
3. Transform via `CustomerDetailResource`.
4. Return `success(data: $resource->toArray(...), msg: 'تم جلب بيانات العميل بنجاح.')`.

## Notes

- The detail endpoint reads live data — it is NOT served from the materialized view.
- `client_type_flags` is included in the response for transparency (so the client can confirm the customer flag is present).
- The financial summary uses the same decimal precision (DECIMAL(10,2)) as the source data.
- `bcmath` is used for any post-query math (none expected in this read-only flow, but the rule is documented for future extensions).

## Test Cases (Feature Tests)

| # | Scenario | Expected |
|---|---|---|
| 1 | GET an existing customer with mixed-status installments | 200, response has correct `financial_summary` matching a manual SQL aggregate |
| 2 | GET a customer with no active/completed contracts | 200, `financial_summary` fields are all zero |
| 3 | GET a customer with all paid installments | 200, `total_collected_amount == total_installment_amount`, `total_remaining_amount == 0`, `total_received_payments > 0` |
| 4 | GET a customer with all overdue installments | 200, `total_overdue_installments > 0`, `total_remaining_amount > 0` |
| 5 | GET non-existent id | 404 |
| 6 | GET as a user without `customers.view` | 403 |
| 7 | GET without `Authorization` | 401 |
| 8 | Verify the financial summary is read live (not from the MV) | Database query log: a SELECT on `installments JOIN contracts` is issued |
| 9 | Verify the contract.status filter is `active` or `completed` only | A draft contract's installments are NOT included in the summary |
| 10 | Verify response time stays < 1 s | Manual or `assertLessThan(1000, $elapsedMs)` |
