---
feature: Joshen finder management
slug: joshan
surface: admin
consumer: react
version: 1.0.0
status: ready
brd_version: "1.3"
brd_ids: [ADMIN-012]
business_rules: [BR-02, BR-05, BR-06, BR-08, BR-09, BR-10]
decisions: [DEC-02, TD-12, TD-19, TD-22, TD-27, TD-42, TD-112, TD-114, TD-115, TD-116, TD-117, TD-118, TD-119, TD-120]
schema_tables: [app_configs, occasions, occasion_translations, services, service_occasions, packages, package_occasions, finder_searches, orders]
related_docs: [services.md, orders.md, ../client/joshan.md, ../API_CONVENTIONS.md]
last_updated: 2026-10-26
---

# Joshen finder management — React Dashboard Integration Guide

> **Base URL:** `{HOST}/api/v1/admin` · Shared envelope, pagination and errors: [API_CONVENTIONS](../API_CONVENTIONS.md).

## Version history

| Version | Date | Type | Summary | Dashboard action required |
|---------|------|------|---------|---------------------------|
| 1.0.0 | 2026-10-26 | Initial | Adds finder settings, occasion CRUD, and read-only search/conversion monitoring. | Add a Services section finder settings page, occasion management, and search monitor. |

## Changelog

### 1.0.0 — 2026-10-26
- **Added:** Joshen visibility/copy/input/bracket settings, occasion CRUD and paginated search monitor with a summary endpoint.
- **Changed:** Category service types derive from active categories; packages have one admin switch. Occasion tags on services are managed on the existing service form.
- **Fixed:** N/A.
- **Removed / Deprecated:** No admin request routing or special-request workflow exists (DEC-02).
- **Breaking:** No.
- **Endpoints affected:** `GET|PUT /joshan/config`, `/joshan/occasions` CRUD, `GET /joshan/searches`, `GET /joshan/searches/summary`, and `PUT /services/{uuid}` occasion tags.
- **Dashboard action required:** Add the route permissions listed below; protect search/customer information from display beyond UUIDs and aggregate counts.
- **BRD / decisions:** ADMIN-012, AC-ADMIN-012, BR-02/05/06/08/09/10, SCR-11, TD-114…120.

---

## 1. Business summary

Admins control whether the finder is visible, its customer-facing translated copy, visible inputs, package availability, budget brackets, and the active occasion list. Service types are derived from active catalog categories and the package switch. Search monitoring is read-only: it reports search criteria, zero-result counts, and orders linked by `finder_search_id` without exposing guest device ids, customer names, or phone numbers.

## 2. Pages and permissions

Every route belongs to RBAC section `services` (TD-27). The summary route has an explicit `routes` map entry in `config/rbac.php`.

| Page / component | Endpoint | Route name | Action |
|------------------|----------|------------|--------|
| Finder settings | `GET /joshan/config` | `admin.joshan.config.show` | view |
| Save finder settings | `PUT /joshan/config` | `admin.joshan.config.update` | update |
| Occasion list | `GET /joshan/occasions` | `admin.joshan.occasions.index` | view |
| Occasion details | `GET /joshan/occasions/{uuid}` | `admin.joshan.occasions.show` | view |
| Create occasion | `POST /joshan/occasions` | `admin.joshan.occasions.store` | create |
| Edit occasion | `PUT|PATCH /joshan/occasions/{uuid}` | `admin.joshan.occasions.update` | update |
| Delete occasion | `DELETE /joshan/occasions/{uuid}` | `admin.joshan.occasions.destroy` | delete |
| Search monitor | `GET /joshan/searches` | `admin.joshan.searches.index` | view |
| Search summary | `GET /joshan/searches/summary` | `admin.joshan.searches.summary` | view |
| Tag type-1 services | `PUT|PATCH /services/{uuid}` (`occasion_ids`) | `admin.services.update` | update |

Hide the menu without `admin.joshan.searches.index`; gate each action by its route permission. Sidebar: **Services → Joshen finder**.

## 3. Search monitor

`GET /api/v1/admin/joshan/searches` · response list key `data.searches`; pagination metadata is in `data.meta`.

Supported query keys: `page`, `per_page` (1–100), and the filters below.

| Filter | Type | Meaning |
|--------|------|---------|
| `filters.date_from`, `filters.date_to` | `YYYY-MM-DD` | Inclusive search creation date range. |
| `filters.category_id` | UUID | Selected service category; package searches have no category filter unless stored. |
| `filters.occasion_id` | UUID | Selected occasion. |
| `filters.customer_id` | UUID | Customer profile; guest searches have no customer id. |
| `filters.has_results` | boolean | `results_count > 0` or `results_count = 0`. |
| `filters.converted` | boolean | At least one order references the search. |

The row contains search/customer/category/occasion/district UUIDs, package flag, desired date/time, exact budget strings, `results_count`, `has_results`, `converted`, `orders_count`, and `created_at`. It omits customer names/phones and guest device identifiers. **Real response:** Postman `admin.joshan.searches.index` → “Finder Search Monitoring List”.

```json
{
  "status": "success",
  "message": "Joshen finder searches",
  "data": {
    "searches": [{
      "id": "01a17883-9815-730c-ad29-24ef1f018958",
      "customer_id": null,
      "category_id": null,
      "wants_package": false,
      "occasion_id": null,
      "district_id": null,
      "desired_date": null,
      "desired_time": null,
      "budget_min": null,
      "budget_max": null,
      "results_count": 0,
      "has_results": false,
      "converted": false,
      "orders_count": 0,
      "created_at": "2026-10-26 14:00:52"
    }],
    "links": {"first": "http://localhost/api/v1/admin/joshan/searches?per_page=10&page=1", "last": "http://localhost/api/v1/admin/joshan/searches?per_page=10&page=1", "prev": null, "next": null},
    "meta": {"current_page": 1, "from": 1, "last_page": 1, "per_page": 10, "to": 1, "total": 1}
  }
}
```

### 3.1 Summary

`GET /api/v1/admin/joshan/searches/summary` · accepts the same filters as the list.

```json
{
  "status": "success",
  "message": "Joshen finder summary",
  "data": {"searches": 1, "zero_result_searches": 1, "converted_searches": 0, "orders_created": 0, "completed_orders": 0}
}
```

`converted_searches` counts distinct search rows with at least one linked order. `orders_created` and `completed_orders` count orders, so one search can contribute more than one order. Real response: Postman `admin.joshan.searches.summary` → “Finder Search Monitoring Summary”.

## 4. Settings form

`GET /api/v1/admin/joshan/config` and partial `PUT /api/v1/admin/joshan/config`.

| Field | Type | Rules |
|-------|------|-------|
| `enabled` | boolean | Finder availability switch. |
| `name.ar`, `description.ar` | string | Required Arabic copy when the object is updated (TD-12). |
| `name.en`, `description.en` | string or null | Optional English copy. |
| `input_fields.{service_type,occasion,address,date,time,budget}` | boolean | Address must remain true; attempts to hide it fail with 422 `JOSHEN_CONFIG_UNAVAILABLE`. |
| `package_enabled` | boolean | Adds/removes the literal `package` entry from derived service types. |
| `budget_brackets[]` | array | 2–20 entries; unique keys, required Arabic label, exact two-decimal min/max. First min is `0.00`; bounds ascend contiguously; final max is null. |

The same `JoshenSettings::assertUpdate` validator runs for this endpoint and the generic admin settings endpoint. Config rows live in `app_configs` group `joshan`; migration and seeder insert missing rows without overwriting edits. Seeded Arabic name and description are neutral placeholders marked `product_copy_pending` for product review.

**Real config response:** Postman `admin.joshan.config.show` → “Finder Config Shown”. **Real update response:** Postman `admin.joshan.config.update` → “Finder Config Updated”.

## 5. Occasion and service forms

Occasion CRUD uses `ar.name` (required), optional `en.name`, `sort_order`, and `is_active`. Names are unique per locale. Deleting an occasion referenced by a service, package, or finder search returns 409 `CATALOG_IN_USE`; deactivate it instead.

The existing service form gains `occasion_ids: string[]` containing occasion UUIDs. Only active occasions may be assigned, and only to a type-1 bookable service. Omit the key to leave tags unchanged; send `[]` to clear them. The `service_occasions` link has no UUID (TD-22).

## 6. Error behavior

| HTTP | `error_code` | When |
|------|--------------|------|
| 422 | `JOSHEN_CONFIG_UNAVAILABLE` | Settings are missing/invalid, bracket boundaries are malformed, or address visibility is disabled. |
| 422 | `INVALID_FINDER_OCCASION_TAGS` | Occasion tags contain inactive occasions or the service is not type 1. |
| 409 | `CATALOG_IN_USE` | An occasion has service/package/search associations. |
| 422 | validation | Invalid filters, names, or request shape. |

## Data model

| Table | Read / write | Notes |
|-------|-------------|-------|
| `app_configs` | Read/write | Joshen settings; both settings endpoints share validation. |
| `occasions`, `occasion_translations` | CRUD | Arabic required, English optional; unique names per locale. |
| `service_occasions`, `package_occasions` | Sync/read | Internal association tables; no public UUID. |
| `finder_searches`, `orders` | Read only | Monitor joins orders through `finder_search_id`; no admin action mutates either. |

## 7. TypeScript types

```ts
type BudgetBracket = { key: string; ar: string; en?: string; min: string; max: string | null };
type JoshenSettings = {
  enabled: boolean;
  name: { ar: string; en?: string };
  description: { ar: string; en?: string };
  input_fields: Record<'service_type' | 'occasion' | 'address' | 'date' | 'time' | 'budget', boolean>;
  package_enabled: boolean;
  budget_brackets: BudgetBracket[];
};
```

## 8. Business rules enforced by the backend

| Rule | Backend behavior | Admin behavior |
|------|------------------|----------------|
| BR-05/06 | Finder categories come from active catalog records; provider offers reuse existing completeness/eligibility queries. | No duplicate service-type catalog. |
| BR-08/09 | Coverage and eligibility run before featured ranking. | Monitoring does not reinterpret ranking as conversion. |
| BR-10 / TD-112 | Search monitor is read-only and omits direct PII/device ids. | Do not add name, phone, or device columns. |
| DEC-02 | No finder item is routed as a request. | No assign/forward action. |

## 9. Notifications triggered

None. Search creation and config changes do not send notifications.

## 10. Integration checklist

- [ ] Add all route names in §2 to dashboard permission gating.
- [ ] Render setting values without hard-coded budget brackets.
- [ ] Use the occasion service form field only for type-1 services.
- [ ] Show zero-result and converted counts without exposing customer PII.
- [ ] Treat summary as read-only and count searches separately from orders.

## 11. Acceptance scenarios

| AC ID | Scenario | Expected |
|-------|----------|----------|
| AC-ADMIN-012 | Admin changes visibility, copy, fields, lists and budget brackets. | Valid config is saved and returned with typed values. |
| AC-ADMIN-012 | Admin monitors searches and conversions. | Paginated filters and summary show search counts and linked orders. |
| AC-ADMIN-012 | Occasion is referenced by a catalog item or search. | Delete is refused with 409 `CATALOG_IN_USE`. |

## 12. Open decisions and known limitations

| ID | Impact | Current behavior |
|----|--------|------------------|
| TD-114 | Customer-facing name and description | Neutral Arabic seed copy is marked pending product content. |
| TD-119 | Rate limit numbers | Per-identity and per-IP defaults are configurable in `config/joshan_limits.php`. |
