# البحث الشامل للمتجر

API عام للاقتراحات المجمّعة للويب أو الموبايل. لا يحتاج تسجيل دخول. مرتبط باقتراحات البحث في واجهات 3lcost وkalemat وyaboow وalmudaires وpropixel؛ راجع [اختبارات الواجهات وتعليمات نشر Propixel](../../front/tests/README.md).

## الطلب

```http
GET /api/v1/search?q=نجيب&limit=5
Accept: application/json
X-Client-Id: YOUR_TENANT_CODE
X-Store-Code: kw
X-Store-ID: 24
Accept-Language: ar
```

`X-Client-Id` و`X-Store-Code` مطلوبان. عند إرسال `X-Store-ID` يجب أن يطابق المتجر المحدد. رقم `24` مثال الكويت المحلي فقط؛ استخدم رقم المتجر الفعلي لكل عميل.

المسار البديل المتوافق مع بقية الكتالوج:

```http
GET /api/v1/stores/kw/search?q=نجيب&limit=5
X-Client-Id: YOUR_TENANT_CODE
Accept-Language: ar
```

| المعامل | الاستخدام |
| --- | --- |
| `q` | مطلوب؛ من حرفين إلى 120 حرفًا، وبحد أقصى 8 كلمات. تُرفض الكلمات الفارغة بعد إزالة التشكيل والتطويل. |
| `limit` | اختياري؛ الافتراضي 5، والحد الأقصى 10. يطبّق بشكل مستقل على كل مجموعة، وعلى خيارات/قيم كل attribute. |
| `lang` | اختياري؛ بديل لترويسة `Accept-Language`، بنفس اختيار اللغة المستخدم في بقية الـAPI. |

لا توجد pagination أو إجماليات ثقيلة في هذا الطلب. `has_more` يوضح وجود نتائج إضافية باستخدام صف إضافي فقط. لا تُطبَّق معاملات تصفية المنتجات على هذه الاقتراحات؛ استخدم واجهات الكتالوج عند فتح مجموعة أو اختيار فلتر.

## الاستجابة

مثال توضيحي مختصر لبحث عن اسم مؤلف؛ المعرفات أمثلة وليست بيانات ثابتة:

```json
{
  "success": true,
  "message": "OK",
  "data": {
    "products": [
      {
        "id": 501,
        "slug": "example-book",
        "name": "كتاب تجريبي",
        "image": "https://api.example.com/media/CLIENT/products/book.webp",
        "attributes": [
          {
            "id": 12,
            "code": "author",
            "name": "المؤلف",
            "type": "select",
            "option_id": 91,
            "value": "نجيب محفوظ"
          }
        ]
      }
    ],
    "categories": [],
    "sub_categories": [],
    "departments": [],
    "brands": [],
    "attributes": [
      {
        "id": 12,
        "code": "author",
        "name": "المؤلف",
        "type": "select",
        "filterable": true,
        "options": [
          {"id": 91, "name": "نجيب محفوظ", "image": null}
        ],
        "values": [],
        "has_more": false
      }
    ]
  },
  "meta": {
    "query": "نجيب",
    "limit": 5,
    "has_more": {
      "products": false,
      "categories": false,
      "sub_categories": false,
      "departments": false,
      "brands": false,
      "attributes": false
    }
  }
}
```

- `products`: ملخصات خفيفة: `id`, `slug`, `name`, `image` وattributes القابلة للبحث. تفاصيل الأسعار والخيارات الكاملة موجودة في endpoints المنتجات الحالية.
- `categories`, `sub_categories`, `departments`, `brands`: عناصر بالشكل `{id, slug, name, image}`. الصورة رابط وسائط عام للتينانت الحالي أو `null`.
- `attributes`: مجموعات عامة حسب `code`؛ لا توجد أسماء ثابتة مثل `authors` أو `publishers`. أي attribute نشط ومحدد «قابل للبحث» يدخل تلقائيًا، سواء كان قابلًا للفلترة أم لا.
- `select`: القيم المطابقة في `options`، وكل خيار يحمل `id`, `name`, `image`.
- `text`: النصوص المطابقة في `values` بالشكل `{id, name}`، مع دمج النصوص المكررة بنفس بيانات الترجمة. `id` هنا مرجع لسجل قيمة نصية، وليس معرّف خيار فلترة.
- كل المجموعات ترجع arrays حتى عند عدم وجود نتائج. لا يتم تحويل المجموعات الفارغة إلى `null`.

## فتح النتائج

- المنتج: `GET /api/v1/products/id/501` أو `GET /api/v1/products/example-book`.
- تصنيف: `GET /api/v1/products?category_id=ID`.
- تصنيف فرعي: `GET /api/v1/products?sub_category_id=ID`.
- قسم: `GET /api/v1/products?department_id=ID`.
- ماركة: `GET /api/v1/products?brand_id=ID`.
- خيار attribute مع `filterable=true`: `GET /api/v1/products?attribute_option_ids[]=91`. استخدم `options[].id`، وليس معرّف الـattribute نفسه.
- attribute نصي أو غير قابل للفلترة: استخدم النص في `GET /api/v1/products/search?q=TEXT`. لا ترسل معرّف القيمة النصية ضمن `attribute_option_ids`.

## قواعد البحث والعزل

- المنتجات تستخدم محرك البحث الموجود: الاسم، SKU والباركود، بيانات التصنيف والماركة، والـattributes المفعّل لها البحث، مع ترتيب الصلة والتسامح الحالي مع الأخطاء البسيطة.
- مجموعات التصنيفات والماركات تطابق كل كلمات البحث في الأسماء المترجمة أو الـslug. مجموعات الـattributes تطابق **القيم**، ولا تعرض كل الخيارات لمجرد تطابق اسم نوع الـattribute.
- تطبيع الحروف العربية وإزالة التشكيل والتطويل متوافق مع محرك المنتجات. تظل الاستعلامات مربوطة بمعاملات SQL؛ `%` و`_` لا يحولان بحث التصنيفات والـattributes إلى wildcard مفتوح.
- تعاد التسميات بلغة الطلب مع نفس fallback الترجمة المستخدم في بقية الكتالوج، ويمكن البحث بالترجمة العربية أو الإنجليزية.
- المنتجات المحذوفة أو غير المفعّلة لا تظهر. حالة نفاد المخزون تتبع الإعداد `catalog_show_out_of_stock` الموجود.
- التصنيفات والأقسام والماركات يجب أن تكون نشطة وغير محذوفة؛ يجوز ظهور تصنيف نشط حتى لو لم يحتوِ منتجات، مثل قوائم الكتالوج الحالية.
- قيم الـattributes يجب أن تكون مرتبطة بمنتجات ظاهرة من نفس المتجر، وخيارات `select` يجب أن تكون نشطة وتخص نفس الـattribute. القيم غير المستخدمة أو الروابط غير المتسقة بين المتاجر لا تظهر.
- الاقتراحات لا تسجل حدث بحث لكل ضغطة حرف. طلب `/products/search` الحالي يظل مسؤولًا عن تسجيل البحث الفعلي.
- لا يحتاج هذا التعديل migration أو seeder. التينانت القديم دون عمود `searchable` يستمر في إرجاع بقية المجموعات، وتكون `attributes` فارغة.

## الرفض والحدود

- `422`: كلمة أو limit غير صالحين، أو هوية تينانت/متجر ناقصة، أو ترويسات متجر تشير إلى متجرين مختلفين.
- `404`: المتجر غير موجود أو لا يتبع التينانت المطلوب.
- `403`: المتجر غير مفعّل، حسب سياسة الـAPI الحالية.
- `429`: تجاوز `throttle:search`، مع `Retry-After`. الافتراضي 60 طلبًا في الدقيقة لكل تينانت/IP، قابل للضبط عبر `ecommerce.rate_limits.search_per_minute`. المساران يشتركان في العداد نفسه.

## التحقق

```bash
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:$PATH"
env -u APP_ENV -u APP_DEBUG -u DB_CONNECTION -u DB_DATABASE -u DB_URL \
  -u DB_CENTRAL_DATABASE -u DB_CENTRAL_CONNECTION -u CACHE_STORE \
  -u QUEUE_CONNECTION -u SESSION_DRIVER \
  ./vendor/bin/phpunit tests/Feature/Api/V1/Catalog/UnifiedSearchApiTest.php
```
