# ربط فلتر المتجر مع بحث وفلاتر تطبيق Kapitano

هذه الوثيقة هي تعليمات التنفيذ لمبرمج تطبيق الهاتف. التعديل الخلفي يضيف فلترًا اختياريًا موحدًا باسم `shop_id` إلى بحث المنتجات وخيارات الفلاتر السياقية.

## العقد المعتمد

- `shop_id` يعني حصرًا `shops.id` الذي يرجع من API المتاجر.
- لا ترسل `users.id` ولا `seller_id` ولا `vendor_id` مكانه.
- السيرفر يحول `shops.id` داخليًا إلى حساب البائع `shops.user_id`.
- السيرفر يعيد فقط منتجات هذا البائع التي يكون `products.added_by = seller`.
- البرامتر اختياري. عدم إرساله يبقي البحث العام الحالي كما هو.
- لا يحتاج التطبيق إلى فلترة المنتجات محليًا.

## مصدر الرقم داخل التطبيق

استخدم الحقل `id` من موديل المتجر الحالي. مثال مبسط:

```json
{
  "id": 18,
  "user_id": 742,
  "name": "اسم المتجر",
  "slug": "shop-slug"
}
```

القيمة الصحيحة التي تُرسل هي `18` وليست `742`.

## 1. جلب منتجات متجر مع البحث والفلاتر

```http
GET /api/v2/products/search?shop_id=18&page=1
```

يمكن دمج `shop_id` مع كل برامترات البحث الحالية:

```http
GET /api/v2/products/search?shop_id=18&name=anker&categories=12,15&brands=4&min=100000&max=900000&sizes=M,L&colors=%23000000&sort_key=price_low_to_high&page=1
```

البرامترات:

| البرامتر | الشكل | الملاحظة |
|---|---|---|
| `shop_id` | integer | `shops.id`، اختياري |
| `name` | string | كلمة البحث، اختياري |
| `categories` | CSV | أرقام التصنيفات مثل `12,15` |
| `brands` | CSV | أرقام الماركات مثل `4,9` |
| `min`, `max` | number | السعر الظاهر بالليرة السورية SYP |
| `sizes` | CSV | القيم كما ترجع من `filter/options` |
| `colors` | CSV | أكواد الألوان، ويجب URL encode للرمز `#` |
| `sort_key` | string | `default`, `price_low_to_high`, `price_high_to_low`, `new_arrival`, `popularity`, `top_rated` |
| `page` | integer | صفحة النتائج؛ عند تغيير أي فلتر أعدها إلى `1` |

شكل الاستجابة لم يتغير، ويستمر التطبيق باستخدام موديل وقائمة المنتجات الحاليين.

## 2. جلب خيارات الفلاتر الخاصة بالمتجر

يجب إرسال `shop_id` نفسه عند طلب خيارات الفلاتر:

```http
GET /api/v2/filter/options?shop_id=18&name=anker&categories=12&brands=4&min=100000&max=900000
```

مثال الاستجابة:

```json
{
  "data": {
    "categories": [],
    "brands": [],
    "sizes": [],
    "colors": [],
    "price": { "min": 0, "max": 0 }
  },
  "success": true,
  "status": 200
}
```

هذه القوائم محسوبة من منتجات المتجر المطابقة للسياق الحالي. لا تستخدم `/filter/categories` و`/filter/brands` لشاشة نتائج المتجر لأنهما قائمتان عامتان.

## 3. الإكمال التلقائي داخل المتجر

أرسل `shop_id` مع كل طلب اقتراحات أثناء الكتابة:

```http
GET /api/v2/search/suggestions?q=anker&shop_id=18
```

عند وجود `shop_id` يحصر السيرفر جميع أجزاء الإكمال التلقائي بالمتجر:

- `products`: منتجات المتجر المطابقة فقط.
- `categories`: تصنيفات موجودة ضمن منتجات المتجر فقط.
- `brands`: ماركات موجودة ضمن منتجات المتجر فقط.
- `keywords`: مصفوفة فارغة داخل المتجر لتقليل كلفة الطلب؛ الكلمات الرائجة بيانات عامة وليست مملوكة لمتجر.

الكاش منفصل حسب اللغة والمتجر وكلمة البحث، لذلك لا يمكن أن تظهر اقتراحات متجر آخر من الكاش. عدم إرسال `shop_id` يبقي الإكمال التلقائي العام الحالي كما هو، بما فيه `keywords`.

## التعديل المطلوب في التطبيق

1. أضف `int? shopId` إلى طلب بحث المنتجات وطلب خيارات الفلاتر وطلب الإكمال التلقائي.
2. عند فتح البحث من صفحة متجر، مرر `shop.id` إلى حالة شاشة البحث.
3. خزّن `shopId` طوال عمر شاشة النتائج ولا تفقده عند البحث أو الفرز أو تغيير الصفحة.
4. أرسل `shop_id` في كل طلب إلى `/products/search`.
5. أرسل `shop_id` نفسه في كل طلب إلى `/filter/options`.
6. أرسل `shop_id` نفسه مع `q` في كل طلب إلى `/search/suggestions` أثناء الكتابة.
7. عند تغيير البحث أو التصنيف أو الماركة أو السعر أو المقاس أو اللون أو الفرز، ألغِ الطلب القديم، أعد `page = 1`، ثم اطلب النتائج مجددًا.
8. عند إزالة سياق المتجر والعودة إلى البحث العام، احذف `shop_id` من الطلب بدل إرسال `0` أو نص فارغ.
9. اعرض حالة فارغة عادية إذا كانت الاستجابة بلا منتجات؛ لا تعد إلى نتائج البحث العام.

مثال Dart لتهيئة query parameters:

```dart
final query = <String, dynamic>{
  if (shopId != null) 'shop_id': shopId,
  if (name.trim().isNotEmpty) 'name': name.trim(),
  if (categoryIds.isNotEmpty) 'categories': categoryIds.join(','),
  if (brandIds.isNotEmpty) 'brands': brandIds.join(','),
  if (minPrice != null) 'min': minPrice,
  if (maxPrice != null) 'max': maxPrice,
  if (sizes.isNotEmpty) 'sizes': sizes.join(','),
  if (colors.isNotEmpty) 'colors': colors.join(','),
  'sort_key': sortKey,
  'page': page,
};
```

لـ`/filter/options` ابنِ طلبًا منفصلًا يحتوي `shop_id`, `name`, `categories`, `brands`, `min`, `max` فقط.

## الأخطاء

إذا كان `shop_id` نصًا أو صفرًا أو رقم متجر غير موجود، يرجع السيرفر HTTP `422`:

```json
{
  "result": false,
  "message": "...",
  "errors": {
    "shop_id": ["..."]
  }
}
```

يجب أن يعامل التطبيق ذلك كخطأ في البيانات أو الرابط، ولا يعيد الطلب من دون `shop_id` لأن ذلك سيعرض منتجات كل المتاجر.

كل الطلبات تستخدم الهيدرات المعتادة في التطبيق، ومنها `System-Key` و`App-Language`.

## التوافق مع المسار القديم

المسار القديم التالي ما زال يعمل ولم يتغير:

```http
GET /api/v2/products/seller/{shop_id}
```

استخدم `/products/search?shop_id=...` للشاشة الجديدة لأنه يدعم التصنيفات والماركات والسعر والمقاسات والألوان والترتيب مع pagination. لا حاجة لتغيير المسار القديم في الشاشات القديمة ضمن هذا الربط.

## اختبارات القبول في التطبيق

- متجر A يعرض منتجاته فقط ولا يعرض منتجات متجر B.
- البحث داخل متجر A يبقى محصورًا بالمتجر.
- الإكمال التلقائي داخل متجر A لا يعرض منتجًا أو تصنيفًا أو ماركة تخص متجر B، ويرجع `keywords: []`.
- كل فلتر منفردًا، ثم عدة فلاتر معًا، تبقى محصورة بالمتجر.
- الفرز وتغيير الصفحة لا يفقدان `shop_id`.
- الرجوع للصفحة الأولى عند تغيير أي فلتر.
- متجر بلا نتائج يعرض الحالة الفارغة ولا يعرض نتائج عامة.
- البحث العام من خارج صفحة متجر لا يرسل `shop_id` ويبقى بسلوكه الحالي.
- `shop_id` غير صالح يظهر كخطأ ولا يتحول إلى بحث عام.
