﻿# Admin New Points

---

## 1. Packages Management — Teams Support & Package Type

### What Changed

Packages now support two types:
- **`individual`** — linked to one or more individual providers via `package_provider` pivot.
- **`team`** — linked to one or more teams via `package_team` pivot (many-to-many, same pattern as providers).

The old single `team_id` column on `packages` has been replaced by a proper `package_team` pivot table.

---

### Endpoints Overview

| Method | Endpoint | Description | Permission |
|---|---|---|---|
| `GET` | `/api/v1/admin/packages` | List packages (paginated, filterable) | `packages.index` |
| `POST` | `/api/v1/admin/packages` | Create a new package | `packages.store` |
| `GET` | `/api/v1/admin/packages/{id}` | Get full package details | `packages.show` |
| `PUT/PATCH` | `/api/v1/admin/packages/{id}` | Update package | `packages.update` |
| `DELETE` | `/api/v1/admin/packages/{id}` | Delete package (blocked if active subscribers) | `packages.destroy` |
| `PATCH` | `/api/v1/admin/packages/{id}/toggle-status` | Toggle `is_active` | `packages.toggle-status` |
| `GET` | `/api/v1/admin/packages/subscriptions` | List all subscriptions | `packages.subscriptions` |
| `GET` | `/api/v1/admin/packages/subscriptions/{id}` | Subscription detail | `packages.subscriptions` |
| `POST` | `/api/v1/admin/packages/subscriptions/{id}/cancel` | Cancel subscription (optional wallet refund) | `packages.subscriptions` |

---

### 1.1 List Packages

**`GET /api/v1/admin/packages`**

#### Available Filters & Search

| Parameter | Type | Description |
|---|---|---|
| `search` | `string` | Search by package name (partial match, both `ar` and `en`) |
| `is_active` | `boolean` | Filter by active status (`1` / `0`) |
| `package_type` | `string` | Filter by type: `individual` or `team` |
| `client_type` | `string` | Filter by target client: `all`, `individual`, or `company` |
| `sort_by` | `string` | Sort field: `price`, `orders_count`, `discount_percentage`, `created_at` |
| `sort_order` | `string` | `asc` or `desc` (default: `desc`) |
| `per_page` | `integer` | Items per page (default: `15`) |

**Example requests:**
```
GET /api/v1/admin/packages?package_type=team
GET /api/v1/admin/packages?package_type=individual&is_active=1
GET /api/v1/admin/packages?search=باقة&sort_by=price&sort_order=asc
GET /api/v1/admin/packages?is_active=0&per_page=25
```

#### Response (`200 OK`):
```json
{
  "status": "success",
  "message": "Packages retrieved successfully",
  "data": [
    {
      "id": 1,
      "name": "باقة الفرق الاحترافية",
      "description": "باقة مخصصة للفرق",
      "price": 500.00,
      "orders_count": 10,
      "discount_percentage": 20.00,
      "provider_discount_percentage": 10.00,
      "admin_discount_percentage": 10.00,
      "package_type": "team",
      "teams_data": [
        { "id": 3, "name": "فريق التصميم" },
        { "id": 7, "name": "فريق البرمجة" }
      ],
      "providers_count": 0,
      "is_active": true,
      "created_at": 1726567200,
      "updated_at": 1726567200
    },
    {
      "id": 2,
      "name": "Individual Premium",
      "description": null,
      "price": 300.00,
      "orders_count": 5,
      "discount_percentage": 15.00,
      "provider_discount_percentage": 5.00,
      "admin_discount_percentage": 10.00,
      "package_type": "individual",
      "teams_data": [],
      "providers_count": 3,
      "is_active": true,
      "created_at": 1726567100,
      "updated_at": 1726567100
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 15,
    "total": 2
  }
}
```

---

### 1.2 Create Package

**`POST /api/v1/admin/packages`**

#### Request Payload — Individual Package:
```json
{
  "ar": { "name": "باقة مميزة", "description": "وصف الباقة" },
  "en": { "name": "Premium Package", "description": "Package description" },
  "price": 300.00,
  "orders_count": 5,
  "discount_percentage": 15.00,
  "provider_discount_percentage": 5.00,
  "admin_discount_percentage": 10.00,
  "package_type": "individual",
  "provider_ids": [16, 22, 31],
  "is_active": true
}
```

#### Request Payload — Team Package:
```json
{
  "ar": { "name": "باقة الفرق", "description": "باقة للفرق" },
  "en": { "name": "Team Package", "description": "Package for teams" },
  "price": 500.00,
  "orders_count": 10,
  "discount_percentage": 20.00,
  "provider_discount_percentage": 10.00,
  "admin_discount_percentage": 10.00,
  "package_type": "team",
  "team_ids": [3, 7],
  "is_active": true
}
```

#### Validation Rules Summary

| Field | Rule |
|---|---|
| `ar.name` / `en.name` | `required\|string\|max:255` |
| `ar.description` / `en.description` | `nullable\|string` |
| `price` | `required\|numeric\|gt:0` |
| `orders_count` | `required\|integer\|min:1` |
| `discount_percentage` | `required\|numeric\|gt:0\|max:100` |
| `provider_discount_percentage` | `required\|numeric\|min:0\|max:100` |
| `admin_discount_percentage` | `required\|numeric\|min:0\|max:100` |
| `package_type` | `required\|in:individual,team` |
| `client_type` | `nullable\|in:all,individual,company` — which client account type can subscribe (`all` = no restriction, default) |
| `provider_ids` | `required_if:package_type,individual` — array of active approved provider user IDs |
| `team_ids` | `required_if:package_type,team` — array of team IDs |
| `is_active` | `nullable\|boolean` |

> **Discount split rule:** `provider_discount_percentage + admin_discount_percentage` must equal `discount_percentage` exactly (±0.01 tolerance).

---

### 1.3 Show Package

**`GET /api/v1/admin/packages/{id}`**

#### Response (`200 OK`):
```json
{
  "status": "success",
  "message": "Package retrieved successfully",
  "data": {
    "id": 1,
    "name": "باقة الفرق الاحترافية",
    "description": "باقة مخصصة للفرق",
    "translations": {
      "ar": { "name": "باقة الفرق الاحترافية", "description": "باقة مخصصة للفرق" },
      "en": { "name": "Professional Team Package", "description": "Package for teams" }
    },
    "price": 500.00,
    "orders_count": 10,
    "discount_percentage": 20.00,
    "provider_discount_percentage": 10.00,
    "admin_discount_percentage": 10.00,
    "providers_data": [],
    "providers_count": 0,
    "package_type": "team",
    "teams_data": [
      { "id": 3, "name": "فريق التصميم" },
      { "id": 7, "name": "فريق البرمجة" }
    ],
    "teams_count": 2,
    "is_active": true,
    "created_at": 1726567200,
    "updated_at": 1726567200
  }
}
```

---

### 1.4 Update Package

**`PUT /api/v1/admin/packages/{id}`**

All fields are `sometimes` (partial update supported). Same field rules as create.

- Sending `package_type: "team"` with `team_ids` will sync teams and clear any attached providers.
- Sending `package_type: "individual"` with `provider_ids` will sync providers and clear any attached teams.
- Omitting `package_type` preserves the current type; only the corresponding IDs array is synced if provided.

---

### 1.5 Toggle Status

**`PATCH /api/v1/admin/packages/{id}/toggle-status`**

No body required. Flips `is_active` between `true` and `false`.

---

### 1.6 Delete Package

**`DELETE /api/v1/admin/packages/{id}`**

> Blocked with `422` if any subscriber has an **active** subscription with remaining orders. Admin must cancel active subscriptions first.

---

### 1.7 Package Subscriptions

**`GET /api/v1/admin/packages/subscriptions`**

#### Available Filters

| Parameter | Type | Description |
|---|---|---|
| `search` | `string` | Search by subscription number, user name, phone, or email |
| `package_id` | `integer` | Filter by specific package |
| `user_id` | `integer` | Filter by specific client |
| `status` | `string` | `pending`, `active`, `completed`, `cancelled`, `failed` |
| `payment_method` | `string` | Filter by payment method |
| `per_page` | `integer` | Items per page (default: `15`) |

**`POST /api/v1/admin/packages/subscriptions/{id}/cancel`**

```json
{
  "cancel_reason": "Cancelled by admin request",
  "refund_wallet": true
}
```
- `refund_wallet: true` triggers a full wallet refund if the subscription was `active` and paid via wallet.

---

### Key Design Notes

- **`package_type`** is stored on the `packages` table (`individual` | `team`, default `individual`).
- **`client_type`** controls which client account type can subscribe (`all` | `individual` | `company`, default `all`). All existing packages default to `all` — fully backward compatible.
- **Individual packages** link to providers through `package_provider` pivot (`package_id`, `user_id`).
- **Team packages** link to teams through `package_team` pivot (`package_id`, `team_id`).
- A package can have **multiple teams** (many-to-many), same as multiple providers.
- When a client browses packages, the listing automatically filters to packages matching their `account_type` — a company client sees `client_type = company` or `client_type = all` packages only.
- Subscribing to a package with a mismatched `client_type` returns a `422` error.
- When a client subscribed to a **team package** browses teams, each team in the listing includes `is_in_package: true/false` and `package_discount_percentage` to show them which teams are covered by their active subscription.

---

## 2. Service Order Status — Pending Negotiation

### What Changed

A new service order status `pending_negotiation` has been added to handle orders where clients initiate negotiation (`is_negotiation = 1`).

### Status Translation

The new status is properly translated in all API responses through the `ServiceOrderHelper::getStatusLabel()` method:

- **Arabic**: "قيد التفاوض" 
- **English**: "Pending Negotiation"

### Status Flow

When a client creates an order with `is_negotiation = 1`:
1. Order status is set to `pending_negotiation`
2. Provider receives notification to respond with counter-offer
3. Status remains `pending_negotiation` until negotiation is accepted or times out
4. Auto-cancellation occurs if no response within `negotiation_timeout_minutes` setting

### Admin Dashboard Integration

The new status appears in:
- Order listing filters and status dropdowns (`?status=pending_negotiation`)
- Order detail `status_trans` field 
- Statistics overview in `pending_negotiation` count
- All order resources (ServiceOrderShowResource, etc.)

### Available Status Filters

Admin service orders endpoint supports filtering by all statuses:
```
GET /api/v1/admin/service-orders?status=pending_negotiation
GET /api/v1/admin/service-orders?status=pending
GET /api/v1/admin/service-orders?status=accepted
GET /api/v1/admin/service-orders?status=active
GET /api/v1/admin/service-orders?status=completed
GET /api/v1/admin/service-orders?status=cancelled
```

The status is fully integrated into the existing order status system and requires no frontend changes beyond recognizing the new status value.

---

## 3. Client Broker Code Toggle

### Endpoint

**`PATCH /api/v1/admin/clients/{client}/broker-code-status`**

No request body required. Flips the client's `is_broker_code_active` flag between `true` and `false`.

- **Permission:** `clients.update` (handled via the clients namespace middleware)
- **URL param:** `{client}` — the client's user ID (must be `user_type = client`)

### Response (`200 OK`)

```json
{
  "status": "success",
  "message": "Broker code enabled successfully.",
  "data": {
    "is_broker_code_active": true
  }
}
```

When disabling:
```json
{
  "status": "success",
  "message": "Broker code disabled successfully.",
  "data": {
    "is_broker_code_active": false
  }
}
```

### Related Broker Block Endpoints

These endpoints let the admin block/unblock a specific broker code from being used by a specific client.

| Method | Endpoint | Description |
|---|---|---|
| `POST` | `/api/v1/admin/clients/{client}/broker-block` | Block a broker from being used by this client |
| `DELETE` | `/api/v1/admin/clients/{client}/broker-block` | Unblock a broker for this client |

**`POST /api/v1/admin/clients/{client}/broker-block`** — Body:
```json
{
  "broker_user_id": 42,
  "reason": "Abuse of broker code"
}
```
- `broker_user_id` — required, must exist in `users` table
- `reason` — optional, string, max 500 chars
- Client and broker cannot be the same user (`422` otherwise)
- Idempotent — uses `firstOrCreate`, safe to call multiple times

**`DELETE /api/v1/admin/clients/{client}/broker-block`** — Body:
```json
{
  "broker_user_id": 42
}
```
- Returns `404` if no active block exists for this client/broker combination.

> There is also a standalone broker blocks resource at `/api/v1/admin/broker-blocks` (index, check, block, unblock) for managing blocks independent of a specific client context.

---

## 3. Service Order Show — Negotiation Data

When fetching a service order's details (client-facing), the response now includes a `negotiation_data` block whenever `is_negotiated` is `true`.

### Endpoint

**`GET /api/v1/client/service-orders/{order}`** (also applies to the admin service order show)

### Negotiation Fields in Response

```json
{
  "id": 101,
  "order_number": "ORD-20260001",
  "type": "individual",
  "is_negotiated": true,
  "negotiation_data": {
    "current_price": 350.00,
    "negotiation_turn": "provider",
    "accepted_at": 1726567200
  },
  "status": "pending_negotiation",
  "status_trans": "في انتظار التفاوض",
  "scheduled_date": "2026-09-20",
  "scheduled_time": "10:00",
  "pricing_details": {
    "service_price": 400.00,
    "broker_discount_amount": 0.00,
    "package_discount_amount": 0.00,
    "total_discount_amount": 0.00,
    "vat_percentage": 15.00,
    "vat_amount": 52.50,
    "app_fee_percentage": 5.00,
    "app_fee_amount": 17.50,
    "total_price": 420.00
  },
  "...": "other order fields"
}
```

When `is_negotiated` is `false`, `negotiation_data` is `null`.

### `negotiation_data` Fields

| Field | Type | Description |
|---|---|---|
| `current_price` | `float` | The latest negotiated price (reflects the most recent counter-offer) |
| `negotiation_turn` | `string` | Whose turn it is to act: `"client"` or `"provider"` |
| `accepted_at` | `integer\|null` | Unix timestamp when the negotiation was accepted; `null` if still in progress |

---

## 4. Admin Invoices

Four invoice sub-resources under `/api/v1/admin/invoices/`. All require authentication and the `permission` middleware.

### Endpoints Overview

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/api/v1/admin/invoices/service-orders` | List service order invoices (paginated) |
| `GET` | `/api/v1/admin/invoices/service-orders/{id}` | Service order invoice detail |
| `GET` | `/api/v1/admin/invoices/urgent-orders` | List urgent order invoices (paginated) |
| `GET` | `/api/v1/admin/invoices/urgent-orders/{id}` | Urgent order invoice detail |
| `GET` | `/api/v1/admin/invoices/template-purchases` | List template purchase invoices (paginated) |
| `GET` | `/api/v1/admin/invoices/template-purchases/{id}` | Template purchase invoice detail |
| `GET` | `/api/v1/admin/invoices/package-subscriptions` | List package subscription invoices (paginated) |
| `GET` | `/api/v1/admin/invoices/package-subscriptions/{id}` | Package subscription invoice detail |

### Common Query Filters (all list endpoints)

| Parameter | Type | Description |
|---|---|---|
| `search` | `string` | Search by client name, phone, or email (max 100 chars) |
| `client_id` | `integer` | Filter by specific client |
| `provider_id` | `integer` | Filter by specific provider (not applicable to template-purchases or package-subscriptions) |
| `order_number` | `string` | Partial match on order/purchase/subscription number (max 50 chars) |
| `date_from` | `date` | Filter paid_at >= this date (`Y-m-d`) |
| `date_to` | `date` | Filter paid_at <= this date (`Y-m-d`, must be >= `date_from`) |
| `per_page` | `integer` | Items per page, 1–100 (default: `15`) |
| `paginate` | `boolean` | Set `false` to return all results without pagination (default: `true`) |

Only **paid** records are included in all four lists (`paid_at IS NOT NULL` for orders; `status = 'paid'` for template purchases; `status IN ('active', 'completed')` and `paid_at IS NOT NULL` for package subscriptions).

---

### 4.1 Service Order Invoices

**`GET /api/v1/admin/invoices/service-orders`**

Includes orders in statuses: `pending`, `accepted`, `active`, `provider_finished`, `pending_admin_review`, `completed`.

#### Response item shape:

```json
{
  "invoice_number": "ORD-20260001",
  "invoice_type": "service_order",
  "issue_date": "2026-09-01",
  "payment_method": "wallet",
  "client_data": {
    "id": 7,
    "full_name": "أحمد محمد",
    "avatar": "https://cdn.example.com/avatars/7.jpg"
  },
  "provider_data": {
    "id": 20,
    "full_name": "خالد العتيبي",
    "avatar": null,
    "type": "individual"
  },
  "service_title": "تنظيف منازل",
  "scheduled_date": "2026-09-05",
  "scheduled_time": "09:00:00",
  "pricing": {
    "base_price": 300.00,
    "vat_percentage": 15.00,
    "vat_amount": 45.00,
    "app_fee_percentage": 5.00,
    "app_fee_amount": 15.00,
    "package_discount": 30.00,
    "broker_discount": 0.00,
    "total_discount": 30.00,
    "total_price": 330.00
  }
}
```

- `provider_data` will have `"type": "team"` and the team's `id`, `full_name`, `avatar` when the order is a team order.

---

### 4.2 Urgent Order Invoices

**`GET /api/v1/admin/invoices/urgent-orders`**

Includes orders in statuses: `accepted`, `completed`.

#### Response item shape:

```json
{
  "invoice_number": "URG-20260042",
  "invoice_type": "urgent_order",
  "issue_date": "2026-09-10",
  "client_data": {
    "id": 15,
    "full_name": "سارة علي",
    "avatar": null
  },
  "provider_data": {
    "id": 20,
    "full_name": "محمد الغامدي",
    "avatar": null
  },
  "service_title": "إطفاء حرائق",
  "emergency_authority": "الدفاع المدني",
  "emergency_case_type": "حريق",
  "location": "الرياض، حي النزهة",
  "pricing": {
    "base_price": 200.00,
    "vat_percentage": 15.00,
    "vat_amount": 30.00,
    "total_price": 230.00
  }
}
```

> Urgent orders do not have app fee, package discount, or broker discount fields.

---

### 4.3 Template Purchase Invoices

**`GET /api/v1/admin/invoices/template-purchases`**

Only records with `status = 'paid'` are returned. `provider_id` filter is not available for this endpoint.

#### Response item shape:

```json
{
  "invoice_number": "PUR-20260099",
  "invoice_type": "template_purchase",
  "issue_date": "2026-09-12",
  "payment_method": "card",
  "client_data": {
    "id": 27,
    "full_name": "نورة الحربي",
    "avatar": null
  },
  "template_data": {
    "id": 3,
    "title": "قالب الفيلا الكلاسيكية"
  },
  "pricing": {
    "base_price": 173.91,
    "vat_percentage": 15.00,
    "vat_amount": 26.09,
    "total_price": 200.00
  }
}
```

> `base_price` and `vat_amount` are derived from `amount_paid` using the global `tax_percentage` setting. VAT percentage is read from `setting('tax_percentage', 15.0)`.

---

### 4.4 Package Subscription Invoices

**`GET /api/v1/admin/invoices/package-subscriptions`**

Only records with `status IN ('active', 'completed')` and `paid_at IS NOT NULL` are returned. `provider_id` filter is not available for this endpoint.

#### Response item shape:

```json
{
  "invoice_number": "SUB-000045",
  "invoice_type": "package_subscription",
  "issue_date": "2026-09-15",
  "payment_method": "wallet",
  "client_data": {
    "id": 27,
    "full_name": "نورة الحربي",
    "avatar": null
  },
  "package_data": {
    "id": 3,
    "name": "باقة الفرق المتقدمة",
    "package_type": "team"
  },
  "subscription_details": {
    "orders_count": 10,
    "remaining_orders": 7,
    "discount_percentage": 20.00,
    "provider_discount_percentage": 10.00,
    "admin_discount_percentage": 10.00,
    "status": "active"
  },
  "pricing": {
    "base_price": 434.78,
    "vat_percentage": 15.00,
    "vat_amount": 65.22,
    "total_price": 500.00
  }
}
```

> `base_price` and `vat_amount` are derived from `price` using the global `tax_percentage` setting. VAT percentage is read from `setting('tax_percentage', 15.0)`.

---

### 4.5 Export — List as Excel/CSV

> **Not yet implemented — planned feature.**

Each list endpoint should support an export mode that returns all matching records as a downloadable file instead of paginated JSON.

**Proposed query param:**
```
GET /api/v1/admin/invoices/service-orders?export=excel
GET /api/v1/admin/invoices/service-orders?export=csv
GET /api/v1/admin/invoices/urgent-orders?export=excel
GET /api/v1/admin/invoices/template-purchases?export=csv
```

All existing filters (`search`, `client_id`, `provider_id`, `date_from`, `date_to`, `order_number`) apply to the export exactly as they do to the paginated list. The response should be a file download (`Content-Disposition: attachment`), not JSON.

---

### 4.6 Export — Single Invoice as PDF

> **Not yet implemented — planned feature.**

Each show endpoint should support a PDF export of the individual invoice.

**Proposed endpoints:**
```
GET /api/v1/admin/invoices/service-orders/{id}/pdf
GET /api/v1/admin/invoices/urgent-orders/{id}/pdf
GET /api/v1/admin/invoices/template-purchases/{id}/pdf
```

Returns a PDF file download with the invoice formatted for print — includes all fields from the show response (client, provider, pricing breakdown, discounts, dates).


---

## 5. Settings — Frontend Implementation Guide

All settings live under `/api/v1/admin/settings/`. Every request requires admin auth.

---

### 5.1 Fetch All Settings for a Group (for building a settings form)

**`GET /api/v1/admin/settings?group=service_order`**

This is the main endpoint for fetching settings to render a form. Pass the `group` query param to scope to the group you care about.

| Group value | What it controls |
|---|---|
| `service_order` | Order timeouts, fees, broker/package discounts, negotiation |
| `urgent_order` | Urgent order retry, radius, auto-cancel |
| `payment` | Tax, wallet limits, payment gateway credentials |
| `system` | Phone and general platform keys |

#### Response (`200 OK`):
```json
{
  "status": "success",
  "message": "Settings retrieved successfully",
  "data": [
    {
      "key": "negotiation_timeout_minutes",
      "value": 60,
      "group": "service_order",
      "type": "integer",
      "label": "Negotiation Timeout (minutes per turn — auto-cancel if no response, 0 = disabled)"
    },
    {
      "key": "service_order_pending_timeout_minutes",
      "value": 60,
      "group": "service_order",
      "type": "integer",
      "label": "Service Order Pending Timeout (minutes — auto-cancel if no provider accepts, 0 = disabled)"
    },
    {
      "key": "service_order_auto_complete_minutes",
      "value": 1440,
      "group": "service_order",
      "type": "integer",
      "label": "Service Order Auto-Complete After Provider Finishes (minutes, 0 = disabled)"
    },
    {
      "key": "max_revision_requests",
      "value": 1,
      "group": "service_order",
      "type": "integer",
      "label": "Maximum Revision Requests Per Order"
    },
    {
      "key": "app_fee_percentage",
      "value": 0,
      "group": "service_order",
      "type": "float",
      "label": "App Fee Percentage (% added to service orders)"
    },
    {
      "key": "broker_commission_percentage",
      "value": 0,
      "group": "service_order",
      "type": "float",
      "label": "Broker Commission Percentage (% of service fee)"
    },
    {
      "key": "broker_discount_percentage",
      "value": 0,
      "group": "service_order",
      "type": "float",
      "label": "Broker Discount Percentage (% off for client who uses a broker code)"
    }
  ]
}
```

**`type` field values:** `integer`, `float`, `string`, `boolean`, `url`, `phone`

Use `type` to decide what input to render (number input, toggle, text field, etc.).

> If a setting has never been saved, it won't appear in the list. Use `GET /api/v1/admin/settings/{key}` to fetch a single key — it returns the default value even if not yet saved.

---

### 5.2 Save Settings (all at once or by group)

#### Save any keys in one request — cross-group

**`POST /api/v1/admin/settings`**

Use this when you want to save keys from multiple groups in a single call, or just save everything at once.

```json
{
  "negotiation_timeout_minutes": 120,
  "service_order_pending_timeout_minutes": 60,
  "service_order_auto_complete_minutes": 1440,
  "max_revision_requests": 2,
  "app_fee_percentage": 5,
  "broker_commission_percentage": 10,
  "broker_discount_percentage": 5,
  "tax_percentage": 15
}
```

#### Response (`200 OK`):
```json
{
  "status": "success",
  "message": "Settings updated successfully",
  "data": [
    { "key": "negotiation_timeout_minutes", "value": 120, "group": "service_order", "type": "integer", "label": "..." },
    { "key": "app_fee_percentage", "value": 5, "group": "service_order", "type": "float", "label": "..." }
  ]
}
```

#### Save settings scoped to a specific group

**`POST /api/v1/admin/settings/group/{group}`**

Same behavior — same `setMany` logic under the hood. The only difference is the success message includes the group name. Use this if your UI has per-group save buttons.

**`POST /api/v1/admin/settings/group/service_order`**

```json
{
  "negotiation_timeout_minutes": 120,
  "app_fee_percentage": 5
}
```

Any key not included is left unchanged in both endpoints.

---

### 5.3 Fetch a Single Setting

**`GET /api/v1/admin/settings/{key}`**

Useful for checking a specific setting or confirming a save. Returns the default value with `"is_default": true` if the key has never been written.

```
GET /api/v1/admin/settings/negotiation_timeout_minutes
```

```json
{
  "status": "success",
  "message": "Setting retrieved",
  "data": {
    "key": "negotiation_timeout_minutes",
    "value": 60,
    "group": "service_order",
    "type": "integer",
    "label": "Negotiation Timeout (minutes per turn — auto-cancel if no response, 0 = disabled)",
    "is_default": true
  }
}
```

---

### 5.4 Key Settings Reference (service_order group)

These are the only `service_order` settings the frontend needs to expose:

| Key | Type | Default | Description |
|---|---|---|---|
| `negotiation_timeout_minutes` | `integer` | `60` | Minutes a party has to respond to a negotiation offer before the order is auto-cancelled. `0` = disabled |
| `service_order_pending_timeout_minutes` | `integer` | `60` | Minutes a normal order stays pending before auto-cancel if no provider accepts. `0` = disabled |
| `service_order_auto_complete_minutes` | `integer` | `1440` | Minutes after provider marks work done before the order auto-completes if client doesn't confirm. `0` = disabled |
| `golden_guarantee_window_minutes` | `integer` | `2880` | Minutes after provider finishes within which client can file a Golden Guarantee complaint. `0` = disabled |
| `max_revision_requests` | `integer` | `1` | How many revision rounds a client can request before the Golden Guarantee option unlocks |
| `app_fee_percentage` | `float` | `0` | Platform fee % added on top of the service price |
| `broker_commission_percentage` | `float` | `0` | % of the service fee paid as commission to the broker code owner |
| `broker_discount_percentage` | `float` | `0` | % discount given to the client who enters a broker code |

---

### 5.5 Negotiation Timeout — What Happens When It Fires

When `negotiation_timeout_minutes` > 0 and a party doesn't respond in time:

- Order status is set to `cancelled`
- **Both** the client and provider receive a push notification:

**Client notification (provider was the idle party):**
```
Title: انتهت مهلة التفاوض / Negotiation Timed Out
Body:  لم يستجب مزود الخدمة لعرضك خلال المهلة المحددة، لذا تم إلغاء الطلب تلقائياً.
       The provider did not respond to your offer within the allowed time, so the order was automatically cancelled.
```

**Client notification (client was the idle party — didn't accept/pay):**
```
Title: انتهت مهلة التفاوض / Negotiation Timed Out
Body:  لم تقم بالرد على العرض المقدم من مزود الخدمة خلال المهلة المحددة، لذا تم إلغاء الطلب تلقائياً.
       You did not respond to the provider's offer within the allowed time, so the order was automatically cancelled.
```

**Provider notification (provider was the idle party):**
```
Title: انتهت مهلة الرد على العرض / Offer Response Time Expired
Body:  لم تقم بالرد على عرض العميل خلال المهلة المحددة، لذا تم إلغاء الطلب تلقائياً.
       You did not respond to the client's offer within the allowed time, so the order was automatically cancelled.
```

**Provider notification (client was the idle party — provider was waiting):**
```
Title: انتهت مهلة التفاوض / Negotiation Timed Out
Body:  لم يقم العميل بالرد على عرضك خلال المهلة المحددة، لذا تم إلغاء الطلب تلقائياً.
       The client did not respond to your offer within the allowed time, so the order was automatically cancelled.
```

All notifications carry `notify_type: "orders"` and `notify_id: <order_id>` so the app can deep-link to the order.


---

## 6. Agency Service Flag

A service can be designated as the platform's "agency service" — the service clients are routed to when they tap "Request Agency" after submitting a service order.

### How it works

- One service at a time can be flagged with `is_agency_service: true`
- Flagging a new service automatically unflags the previous one — no manual cleanup needed
- A flagged service **cannot be deleted** — admin gets a `422` until the flag is removed
- The client app reads `agency_service_id` from `GET /home` to know which service to route to

### `is_agency_service` field

Returned in both the service index and show responses:

```json
{
  "id": 5,
  "title": "خدمة الوكالة",
  "is_active": true,
  "is_agency_service": true,
  "price_from": 0,
  "price_to": 0,
  ...
}
```

### Setting the agency service

**`PUT /api/v1/admin/services/{id}`**

```json
{
  "is_agency_service": true
}
```

- If another service was previously flagged, it is automatically unflagged
- Only one service can hold `is_agency_service: true` at any time

### Deleting a flagged service

**`DELETE /api/v1/admin/services/{id}`** returns `400` if the service is flagged:

```json
{
  "status": "error",
  "message": "Cannot delete the agency service. Remove the agency flag first."
}
```

To delete it, first update the service with `is_agency_service: false`, then delete.

### Client-side usage

`GET /api/v1/client/home` returns:

```json
{
  "agency_service_id": 5,
  ...
}
```

- `null` if no service is currently flagged → hide the "Request Agency" button
- Integer ID → use it to call `GET /api/v1/client/services/{agency_service_id}/providers` and display available providers for booking



---

## 7. Admin Statistics

All endpoints under `/api/v1/admin/statistics/`. Auth required.

### Endpoints (9 total)

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/api/v1/admin/statistics/overview` | Lightweight KPI cards — fast dashboard home |
| `GET` | `/api/v1/admin/statistics/users` | User breakdown + 12-month growth chart |
| `GET` | `/api/v1/admin/statistics/service-orders` | Service order deep stats |
| `GET` | `/api/v1/admin/statistics/urgent-orders` | Urgent order deep stats |
| `GET` | `/api/v1/admin/statistics/financials` | Financial summary + charts. Add `?period=monthly&year=2026` for period breakdown |
| `GET` | `/api/v1/admin/statistics/providers` | Provider list with revenue. `?filter=most_orders\|least_orders\|highest_rated\|lowest_rated&limit=20` |
| `GET` | `/api/v1/admin/statistics/services` | Top services by completed orders + revenue. `?limit=10` |
| `GET` | `/api/v1/admin/statistics/teams` | Team overview + top teams + top leaders. `?limit=10` |
| `GET` | `/api/v1/admin/statistics/packages` | Package and subscription statistics with trends. `?limit=10` |

---

### 7.1 Overview

**`GET /api/v1/admin/statistics/overview`**

Single-pass aggregates per entity. Populate the dashboard home with this. Call individual endpoints for deep data.

```json
{
  "users": {
    "clients":   { "total": 142, "active": 138, "disabled": 4, "new_this_month": 12 },
    "providers": { "total": 28, "pending_review": 3, "rejected": 2, "active": 25, "disabled": 3, "new_this_month": 2 }
  },
  "service_orders": {
    "total": 310, "pending": 18, "negotiating": 4, "in_progress": 22,
    "completed": 248, "cancelled": 18, "completion_rate": 80.0,
    "new_this_month": 31, "pending_gg_review": 2
  },
  "urgent_orders": {
    "total": 87, "searching": 1, "in_progress": 3, "completed": 74,
    "cancelled": 9, "completion_rate": 85.1, "new_this_month": 7
  },
  "financials": {
    "total_gross_revenue": 124500.00,
    "platform_revenue": 18675.00,
    "total_vat_collected": 15780.00,
    "total_app_fees": 2895.00,
    "revenue_this_month": { "value": 9200.00, "previous": 7800.00, "change": 1400.00, "change_pct": 17.9, "trend": "up" },
    "pending_withdrawals": { "count": 5, "amount": 3200.00 }
  },
  "providers": { "total": 34, "available_now": 21, "can_urgent": 12, "avg_rating": 4.3, "total_reviews": 189 },
  "teams": { "total": 8, "active": 7 },
  "packages": {
    "total": 15, "active": 12, "individual_packages": 8, "team_packages": 7, "new_this_month": 2
  },
  "package_subscriptions": {
    "total": 45, "active": 28, "completed": 12, "cancelled": 2,
    "new_this_month": 8, "total_revenue": 12500.00
  },
  "recent_activity": {
    "latest_service_orders": [
      { "id": 101, "order_number": "ORD-20260001", "status": "completed", "amount": 420.00, "created_at": 1726567200,
        "client_data": { "id": 7, "full_name": "أحمد محمد" }, "service_title": "تنظيف منازل" }
    ],
    "latest_urgent_orders": [
      { "id": 55, "order_number": "URG-20260055", "status": "accepted", "amount": 230.00, "created_at": 1726480800,
        "client_data": { "id": 15, "full_name": "سارة علي" } }
    ],
    "pending_withdrawals": [
      { "id": 12, "amount": 800.00, "created_at": 1726390000,
        "provider_data": { "id": 20, "full_name": "خالد العتيبي" } }
    ],
    "latest_registrations": [
      { "id": 142, "full_name": "نورة الحربي", "user_type": "client", "account_status": "accepted", "is_active": true, "created_at": 1726567100 }
    ],
    "pending_gg_reviews": [
      { "id": 88, "order_number": "ORD-20260088", "filed_at": 1726500000,
        "client_data": { "id": 27, "full_name": "محمد الغامدي" } }
    ]
  }
}
```

**Field notes:**
- `clients.active` / `clients.disabled` — based on `is_admin_active` (admin toggle)
- `providers.total` — accepted providers only (`account_status = accepted`)
- `providers.active` / `providers.disabled` — accepted + `is_admin_active` filter
- `pending_gg_review` — GG complaints awaiting admin decision → route to `/admin/golden-guarantee/{id}`
- `pending_withdrawals` → route to `/admin/withdrawals/{id}`
- All `this_month` fields return a delta object: `{ value, previous, change, change_pct, trend: "up|down|flat" }`

---

### 7.2 Users

**`GET /api/v1/admin/statistics/users`**

```json
{
  "clients":   { "total": 142, "active": 138, "disabled": 4, "banned": 1 },
  "providers": { "total": 28, "accepted": 28, "pending": 3, "rejected": 2, "disabled": 3, "banned": 0 },
  "admins":    { "total": 5, "active": 5, "disabled": 0 },
  "growth_chart": [
    { "period": "2026-08", "clients": 8, "providers": 1 },
    { "period": "2026-09", "clients": 12, "providers": 2 }
  ]
}
```

---

### 7.3 Service Orders

**`GET /api/v1/admin/statistics/service-orders`**

```json
{
  "totals": {
    "all": 310, "pending": 18, "pending_negotiation": 4,
    "accepted": 8, "active": 11, "provider_finished": 2,
    "pending_admin_review": 1, "completed": 248, "cancelled_rejected": 18
  },
  "rates": { "completion_rate": 80.0, "cancellation_rate": 5.8 },
  "golden_guarantee": { "total_filed": 14, "pending_review": 2, "resolved": 12 },
  "revisions": { "orders_with_revision": 22, "avg_revisions_per_order": 1.3 },
  "negotiations": { "negotiated_orders": 38, "negotiation_rate": 12.3, "avg_discount_pct": 8.4 },
  "volume_chart": [
    { "period": "2026-08", "in_progress": 14, "completed": 31, "cancelled": 2 }
  ]
}
```

---

### 7.4 Urgent Orders

**`GET /api/v1/admin/statistics/urgent-orders`**

```json
{
  "totals": {
    "all": 87, "pending_payment": 2, "searching_for_provider": 1,
    "accepted": 3, "completed": 74, "cancelled": 9
  },
  "rates": { "completion_rate": 85.1, "cancellation_rate": 10.3 },
  "revenue": {
    "total_gross": 16100.00, "total_vat": 1680.00,
    "provider_earnings": 11900.00, "platform_cut": 4200.00,
    "this_month": { "value": 1400.00, "previous": 1300.00, "change": 100.00, "change_pct": 7.7, "trend": "up" }
  },
  "volume_chart": [
    { "period": "2026-08", "active": 3, "completed": 9, "cancelled": 1 }
  ]
}
```

---

### 7.5 Financials

**`GET /api/v1/admin/statistics/financials`**

Optional params: `?period=monthly&year=2026` or `?period=yearly` — adds `period_breakdown` array to the response.

```json
{
  "service_orders": {
    "completed_count": 248,
    "gross_revenue": 108400.00, "vat_collected": 14100.00,
    "app_fee_collected": 5420.00, "provider_earnings": 88880.00,
    "this_month": { "value": 7800.00, "previous": 6500.00, "change": 1300.00, "change_pct": 20.0, "trend": "up" }
  },
  "urgent_orders": {
    "completed_count": 74,
    "gross_revenue": 16100.00, "vat_collected": 1680.00,
    "provider_earnings": 11900.00,
    "this_month": { "value": 1400.00, "previous": 1300.00, "change": 100.00, "change_pct": 7.7, "trend": "up" }
  },
  "summary": {
    "total_gross_revenue": 124500.00, "platform_revenue": 18675.00,
    "total_provider_earnings": 100780.00, "total_vat_collected": 15780.00,
    "total_completed_orders": 322,
    "this_month": { "value": 9200.00, "previous": 7800.00, "change": 1400.00, "change_pct": 17.9, "trend": "up" }
  },
  "withdrawals": {
    "pending_count": 5, "pending_amount": 3200.00,
    "approved_count": 41, "approved_amount": 28500.00, "rejected_count": 3
  },
  "revenue_chart": [
    { "period": "2026-08", "service_order_revenue": 6200.00, "urgent_order_revenue": 1100.00, "total_revenue": 7300.00 }
  ],
  "period_breakdown": null
}
```

> `platform_revenue` = VAT + app fees + UO platform cut. `provider_earnings` on SO = `base_price - app_fee - package_provider_discount_amount` (broker discount is absorbed by platform, not provider).

> `period_breakdown` is `null` unless `?period=` is passed. When provided, returns arrays of SO/UO/withdrawals grouped by month or year.

---

### 7.6 Providers

**`GET /api/v1/admin/statistics/providers?filter=most_orders&limit=20`**

| `filter` | Description |
|---|---|
| `most_orders` | Sorted by completed orders desc (default) |
| `least_orders` | Sorted by completed orders asc |
| `highest_rated` | Sorted by avg rating desc |
| `lowest_rated` | Sorted by avg rating asc |

```json
[
  {
    "provider_data": { "id": 20, "full_name": "خالد العتيبي" },
    "completed_orders": 48, "active_orders": 3,
    "service_orders_count": 41, "urgent_orders_count": 7,
    "service_orders_revenue": 18200.00, "urgent_orders_earnings": 2100.00,
    "total_revenue": 20300.00,
    "avg_rating": 4.7, "reviews_count": 39
  }
]
```

---

### 7.7 Services

**`GET /api/v1/admin/statistics/services?limit=10`**

```json
[
  {
    "service_data": { "id": 3, "title": "تنظيف منازل" },
    "total_orders": 88, "completed_orders": 71,
    "completion_rate": 80.7, "total_revenue": 42300.00
  }
]
```

---

### 7.8 Teams

**`GET /api/v1/admin/statistics/teams?limit=10`**

Returns team overview stats, top performing teams, and top team leaders all in one response.

```json
{
  "totals": { "total_teams": 8, "active_teams": 7, "inactive_teams": 1, "avg_team_size": 4.2 },
  "performance": {
    "team_orders": 120, "team_completion_rate": 83.3,
    "individual_orders": 190, "individual_completion_rate": 78.4,
    "team_orders_share_pct": 38.7
  },
  "revenue": {
    "team_revenue": 48600.00, "individual_revenue": 59800.00,
    "total_revenue": 108400.00, "team_revenue_share_pct": 44.8
  },
  "top_teams": [
    {
      "team_data": { "id": 3, "name": "فريق التصميم" },
      "service_title": "تصميم داخلي",
      "members_count": 4, "total_orders": 42, "completed_orders": 35,
      "completion_rate": 83.3, "revenue": 21000.00, "avg_revenue_per_order": 600.00
    }
  ],
  "top_leaders": [
    {
      "provider_data": { "id": 20, "full_name": "خالد العتيبي" },
      "teams_led": [ { "id": 3, "name": "فريق التصميم" } ],
      "teams_count": 1, "completed_orders": 35,
      "revenue": 21000.00, "avg_rating": 4.7, "reviews_count": 39
    }
  ]
}
```

---

### 7.9 Packages & Subscriptions

**`GET /api/v1/admin/statistics/packages?limit=10`**

Returns comprehensive package statistics including subscription trends and most popular packages.

```json
{
  "packages": {
    "total": 15, "active": 12,
    "individual_type": 8, "team_type": 7
  },
  "subscriptions": {
    "total": 45, "active": 28, "completed": 12, "cancelled": 5,
    "success_rate": 88.9
  },
  "revenue": {
    "paid_revenue": 18750.00
  },
  "orders": {
    "total_remaining_orders": 142, "total_orders_purchased": 280,
    "orders_utilization_pct": 49.3
  },
  "popular_packages": [
    {
      "package_data": {
        "id": 3, "name": "باقة الفرق المتقدمة", "package_type": "team", "price": 500.00
      },
      "total_subscriptions": 15, "active_subscriptions": 8, "total_revenue": 7500.00
    }
  ],
  "subscription_trend": [
    {
      "period": "2026-08",
      "total_subscriptions": 12, "active_subscriptions": 8, "completed_subscriptions": 3,
      "cancelled_subscriptions": 1, "revenue": 5600.00
    }
  ]
}
```

**Field explanations:**
- `success_rate` — (active + completed) / total subscriptions — percentage of subscriptions that resulted in payment
- `orders_utilization_pct` — (purchased - remaining) / purchased — how much of purchased orders are consumed
- `popular_packages` — top 5 packages by total subscription count
- `subscription_trend` — 12 months of subscription data by period

> **Note**: Only paid subscriptions (active/completed status) are included in invoice-related statistics. Removed redundant fields like inactive counts, averages, and client targeting details for cleaner responses.

---

### Delta Object (`this_month`) Structure

All `this_month` financial fields return:

```json
{ "value": 9200.00, "previous": 7800.00, "change": 1400.00, "change_pct": 17.9, "trend": "up" }
```

`trend`: `"up"`, `"down"`, or `"flat"` — use for trend arrows and color coding in the UI.
