# Admin Ratings API

New endpoint. Ratings are **never created or edited from the admin
dashboard** — a rating is written automatically when a customer confirms
order completion with a star count (`POST .../orders/{order}/confirm-completion`).
This is a **read-only** list + details view for admins, with filters.

## 1. List Ratings

`GET /api/v1/admin/ratings`

Query params (all optional):

| Param                                                    | Notes                                                   |
| -------------------------------------------------------- | ------------------------------------------------------- |
| `filters[technician_id]` (or flat `?technician_id=`) | Ratings for one technician                              |
| `filters[customer_id]` (or flat `?customer_id=`)     | Ratings left by one customer (via the order)            |
| `filters[order_id]` (or flat `?order_id=`)           | Rating for one specific order                           |
| `filters[stars]` (or flat `?stars=`)                 | Exact star value,`1`–`5`                           |
| `search`                                               | Matches comment text, customer name, or technician name |
| `from`, `to`                                         | Date range on`created_at` (`YYYY-MM-DD`)            |
| `sort`/`per_page`/`paginate=false`                 | Same conventions as every other admin list              |

Real response, captured live (`?per_page=2`):

```json
{
  "status": "success",
  "message": "Ratings retrieved successfully.",
  "data": {
    "data": [
      {
        "id": 1,
        "order": {
          "id": 73,
          "uuid": "a8cc3837-78a0-434a-b6bb-80f8213f442a",
          "order_number": "ORD-260922-LIHG"
        },
        "customer": { "id": 35, "name": "Customer One" },
        "technician": { "id": 8, "name": "Tech Ahmed" },
        "stars": 5,
        "comment": "Excellent work, very professional",
        "created_at": "2026-09-22T16:32:26+03:00"
      },
      {
        "id": 2,
        "order": { "id": 74, "uuid": "774d59c8-...", "order_number": "ORD-260922-JBUA" },
        "customer": { "id": 36, "name": "Customer Two" },
        "technician": { "id": 9, "name": "Tech Sara" },
        "stars": 2,
        "comment": "Late arrival",
        "created_at": "2026-09-22T16:32:26+03:00"
      }
    ],
    "links": { "first": "...", "last": "...", "prev": null, "next": "..." },
    "meta": { "current_page": 1, "per_page": 2, "total": 3 },
    "stats": [
      { "key": "total", "value": 3, "label": "Total" },
      { "key": "1", "value": 0, "label": "1 Stars" },
      { "key": "2", "value": 1, "label": "2 Stars" },
      { "key": "3", "value": 0, "label": "3 Stars" },
      { "key": "4", "value": 1, "label": "4 Stars" },
      { "key": "5", "value": 1, "label": "5 Stars" }
    ],
    "average_stars": 3.67
  }
}
```

**`stats` and `average_stars` always reflect every filter *except*
`filters[stars]` itself** — same "don't collapse the breakdown" convention
used by every other admin list's `stats` block (e.g. status filters on
Orders/Users). So switching between star tabs in the UI won't zero out the
other tabs' counts.

`comment` is `null` when the customer left stars with no written feedback.

## 2. Rating Details

`GET /api/v1/admin/ratings/{rating}`

Same shape as one list row, not wrapped in `data.data`:

```json
{
  "status": "success",
  "message": "Rating retrieved successfully.",
  "data": {
    "id": 1,
    "order": { "id": 73, "uuid": "...", "order_number": "ORD-260922-LIHG" },
    "customer": { "id": 35, "name": "Customer One" },
    "technician": { "id": 8, "name": "Tech Ahmed" },
    "stars": 5,
    "comment": "Excellent work, very professional",
    "created_at": "2026-09-22T16:32:26+03:00"
  }
}
```

`404` if the ID doesn't exist.

## 3. Rating on the Order itself

Every order-details endpoint (`admin`, `client`, `company`, `technician`)
includes a `rating` field alongside the order:

```json
{
  "data": {
    "...": "rest of the order",
    "rating": {
      "id": 1,
      "customer": { "id": 35, "name": "Customer One", "image": "https://...", "user_type": "individual", "user_type_label": "Individual" },
      "technician": { "id": 8, "name": "Tech Ahmed", "image": "https://...", "user_type": "...", "user_type_label": "..." },
      "stars": 5,
      "comment": "Excellent work, very professional"
    }
  }
}
```

- `rating` is `null` (a clean JSON `null`, always the same shape across all
  four order-details variants) until the order is confirmed by the customer
  with a star rating.
- This "on the order" shape is intentionally smaller than the admin ratings
  list row above (no `order`/`created_at`, since you're already looking at
  the order) — don't expect field parity between the two.

**Bug fixed alongside this work**: `client`/`company`/`technician` order
details previously returned `rating` as `{"id":null,"customer":null,...}`
(an all-null object, not `null`) on any unrated order, because those three
resources were missing the null-guard the `admin` one already had. If your
frontend was checking `if (order.rating)` and getting a false "truthy"
result for that all-null object, that's now fixed — `rating` is a real
`null` there too. Re-test any code that special-cased this.

## 4. Errors

Standard envelope: `{"status": "fail", "message": "...", "data": null}`.

| Case                                   | HTTP                  |
| -------------------------------------- | --------------------- |
| Unauthenticated                        | 401                   |
| Unauthorized (missing RBAC permission) | 403                   |
| Rating not found                       | 404                   |
| Empty list                             | 200,`data.data: []` |

## Files changed

- `app/Http/Controllers/Api/V1/Admin/RatingController.php` (new) — `index`/`show`, thin delegation.
- `app/Services/Admin/RatingService.php` (new) — filtering, stats, average.
- `app/Http/Resources/Admin/Rating/RatingResource.php` (new).
- `app/Models/Rating.php` — added `casts()` (`stars` → int, `created_at` → datetime; was previously an uncast raw string since the model has no `updated_at` and sets `$timestamps = false`).
- `routes/api/v1/admin.php` — `admin.ratings.index`/`admin.ratings.show` (read-only `apiResource`, same pattern as `admin.invoices.*`).
- `lang/en/api.php`, `lang/ar/api.php` — added `ratings_retrieved`, `rating_retrieved`, `rating_stars`.
- `app/Http/Resources/Client/Order/OrderDetailsResource.php`,
  `Company/Order/OrderDetailsResource.php`,
  `Technician/Order/OrderDetailsResource.php` — added the missing
  `$this->rating ? new RatingResource(...) : null` guard (see bug note
  above).
