# توثيق API الطلب الصوتي — تطبيق Flutter الكيوسك

**الإصدار:** 1.0  
**القاعدة:** `https://erp.sayedkhattab.com/api/voice-order`  
**آخر اختبار:** 2026-06-26 — ناجح

---

## 1. نظرة عامة

خدمة الطلب الصوتي تفهم قائمة الطعام (513+ صنف مفهرس)، تتحدث مع العميل بالعربية، وتبني السلة تلقائياً.  
تطبيق Flutter مسؤول عن:

- التقاط الصوت (STT) وتحويله لنص
- إرسال النص للـ API
- عرض رد المساعد وقراءته (TTS)
- مزامنة السلة مع واجهة الطلب الحالية
- إتمام الطلب عبر **POS API** الموجود (نفس `submitOrder`)

```
┌─────────────┐    نص العميل     ┌──────────────────┐
│ Flutter STT │ ───────────────► │ voice-order API  │
└─────────────┘                  │  (Gemini + RAG)  │
       ▲                         └────────┬─────────┘
       │ TTS                              │ cart.lines
       └──────────────────────────────────┘
                                          ▼
                                 ┌──────────────────┐
                                 │ POS submitOrder  │
                                 └──────────────────┘
```

---

## 2. الحصول على عنوان API

### من bootstrap نقطة البيع (مُفضّل)

بعد تسجيل دخول الكيوسك:

```
GET /api/pos/bootstrap
Authorization: Bearer {pos_jwt}
```

الحقل الجديد في الاستجابة:

```json
{
  "franchise_id": 1,
  "menu_api_url": "https://erp.sayedkhattab.com/api/menu/public/pos",
  "voice_order_api_url": "https://erp.sayedkhattab.com/api/voice-order/public",
  "branch": { "id": 1, "code": "..." }
}
```

استخدم `voice_order_api_url` كقاعدة لجميع طلبات الجلسة.

---

## 3. تفعيل الميزة (لوحة الأدمن)

| الإعداد | المسار |
|---------|--------|
| لوحة التحكم | `https://erp.sayedkhattab.com/admin/voice-order` |
| القائمة الجانبية | **المبيعات** → **الطلب الصوتي** |

تبويبات الإدارة:

1. **الإعدادات** — تفعيل/تعطيل، رسالة الترحيب، نموذج Gemini
2. **فهرسة القائمة** — إعادة فهرسة + اختبار بحث
3. **المرادفات** — مرادفات عربية (شوارما → شاورما)

> إذا أعاد الـ API `403` عند بدء الجلسة، الميزة غير مفعّلة في الإعدادات.

---

## 4. مسار المحادثة (Kiosk)

### 4.1 بدء جلسة

```http
POST /api/voice-order/public/sessions
Content-Type: application/json

{
  "franchise_id": 1,
  "branch_id": 1,
  "kiosk_device_id": "kiosk-uuid-optional"
}
```

**استجابة 201:**

```json
{
  "session_id": "5b8501c8-b2c8-4ba7-b53c-474be1d63fc6",
  "greeting": "أهلاً، كيف أقدر أساعدك في طلبك؟",
  "greeting_ar": "أهلاً، كيف أقدر أساعدك في طلبك؟",
  "voice_locale": "ar-SA",
  "settings": {
    "is_enabled": true,
    "voice_locale": "ar-SA"
  }
}
```

اعرض `greeting_ar` واقرأه بـ TTS فوراً.

**أخطاء:**

| HTTP | المعنى |
|------|--------|
| 403 | الطلب الصوتي معطّل |
| 500 | خطأ خادم |

---

### 4.2 إرسال رسالة (نص من STT أو لوحة المفاتيح)

```http
POST /api/voice-order/public/sessions/{session_id}/chat
Content-Type: application/json

{
  "message": "شاورما دجاج"
}
```

**استجابة 200:**

```json
{
  "reply": "تمام شاورما دجاج. اختر النوع: عادي، سبايسي",
  "reply_ar": "تمام شاورما دجاج. اختر النوع: عادي، سبايسي",
  "cart": {
    "lines": [],
    "item_count": 0,
    "subtotal": 0
  },
  "candidates": [
    {
      "product_id": 51,
      "name_ar": "شاورما دجاج",
      "base_price_hint": 7,
      "confidence": 0.99
    }
  ],
  "actions": [],
  "pending_product": {
    "product_id": 51,
    "name_ar": "شاورما دجاج"
  }
}
```

**حقول مهمة:**

| الحقل | الاستخدام في Flutter |
|-------|---------------------|
| `reply_ar` | عرض + TTS |
| `cart.lines` | **استبدل السلة المحلية** بهذه المصفوفة عند كل رد |
| `pending_product` | منتج قيد إعداد الخيارات — اعرض مؤشر تقدم |
| `actions` | انظر §6 |
| `candidates` | اختياري: عرض بدائل عند الغموض |

---

### 4.3 جلب السلة الحالية

```http
POST /api/voice-order/public/sessions/{session_id}/cart/apply
```

```json
{
  "session_id": "...",
  "cart": [ /* نفس بنية lines */ ]
}
```

---

### 4.4 حالة الجلسة

```http
GET /api/voice-order/public/sessions/{session_id}
```

---

## 5. بنية سطر السلة (`cart.lines[]`)

متوافقة مع POS الحالي — انسخها مباشرة لـ `submitOrder`:

```json
{
  "key": "51#",
  "product_id": 51,
  "category_id": 6,
  "product_name": "Chicken Shawarma",
  "product_name_ar": "شاورما دجاج",
  "product_image_url": "https://erp.sayedkhattab.com/api/menu/...",
  "unit_price": 7,
  "quantity": 1,
  "notes": "",
  "additions": [],
  "variants": [],
  "variations_display": ""
}
```

مع خيارات:

```json
{
  "variants": [
    { "group_id": 12, "option_id": 45, "name": "عادي", "price": 0 }
  ],
  "additions": [
    { "id": 45, "name": "Regular", "name_ar": "عادي", "price": 0 }
  ],
  "variations_display": "عادي",
  "unit_price": 7
}
```

---

## 6. الإجراءات (`actions[]`)

| `type` | متى | ماذا يفعل Flutter |
|--------|-----|-------------------|
| `cart_updated` | بعد إضافة صنف | حدّث UI السلة + `cart` في الاستجابة |
| `ready_for_checkout` | العميل أنهى الطلب صوتياً | انتقل لشاشة الدفع / استدعِ `submitOrder` |

مثال `ready_for_checkout`: العميل يقول «خلاص» أو «تمام».

---

## 7. سيناريو محادثة كامل (مُختبر)

```
1. POST /sessions                          → session_id
2. POST /chat  "شاورما دجاج"              → يسأل عن النوع (عادي/سبايسي)
3. POST /chat  "عادي"                     → يطلب التأكيد
4. POST /chat  "نعم أضف"                  → cart_updated، سعر 7.00 ر.س
5. POST /chat  "كم صار الحساب"            → ملخص السلة
6. submitOrder مع cart.lines              → طلب POS عادي
```

**نتيجة الاختبار الفعلي (2026-06-26):**

- بحث «شاورما» → منتج #51 بثقة 99%
- إضافة للسلة → `unit_price: 7`، `product_id: 51`
- Gateway production يعمل: `https://erp.sayedkhattab.com/api/voice-order/health`

---

## 8. إتمام الطلب (POS — بدون تغيير)

```http
POST /api/pos/orders
Authorization: Bearer {pos_jwt}
Content-Type: application/json

{
  "franchise_id": 1,
  "order_type_id": 1,
  "payment_method": "cash",
  "client_ref": "{uuid}",
  "items": [ /* من cart.lines */ ]
}
```

كل عنصر في `items`:

```json
{
  "product_id": 51,
  "category_id": 6,
  "product_name": "Chicken Shawarma",
  "product_name_ar": "شاورما دجاج",
  "product_image_url": "...",
  "quantity": 1,
  "unit_price": 7,
  "total_price": 7,
  "notes": "",
  "variants": [],
  "additions": []
}
```

---

## 9. Google Cloud TTS / STT (جودة صوت عربي سعودي)

> **مهم:** مفتاح Gemini AI Studio (`GEMINI_API_KEY`) **للنص والذكاء فقط** — لا يعمل مع Text-to-Speech و Speech-to-Text.  
> Google Cloud TTS/STT يتطلب **OAuth2** عبر **Service Account** (ليس API key عادي).

### إعداد السيرفر (`.env`)

```env
GEMINI_API_KEY=...              # للذكاء واللهجة السعودية فقط
GEMINI_MODEL=gemini-3.5-flash
GOOGLE_SERVICE_ACCOUNT_PATH=/path/to/service-account.json
GOOGLE_TTS_VOICE=ar-XA-Chirp3-HD-Kore
GOOGLE_STT_LANGUAGE=ar-SA
```

**في Google Cloud Console:**
1. تفعيل **Cloud Text-to-Speech API**
2. تفعيل **Cloud Speech-to-Text API**
3. إنشاء Service Account بصلاحية استخدام هذين الـ API
4. تحميل ملف JSON وربطه في `.env` ثم `pm2 restart erp-voice-order`

### التحقق من الجاهزية

عند بدء الجلسة (`POST /public/sessions`):

```json
{
  "tts_available": false,
  "stt_available": false
}
```

إذا كانا `false` → التطبيق يستخدم صوت الجهاز الاحتياطي (`flutter_tts`) حتى يُضبط Service Account.

`GET /health`:

```json
{
  "gemini": "configured",
  "google_tts_stt": "requires_service_account",
  "google_auth_mode": "none"
}
```

بعد الإعداد الصحيح: `google_tts_stt: "ready"` و `google_auth_mode: "service_account"`.

### تحويل رد المساعد لصوت (TTS)

```http
POST /api/voice-order/public/sessions/{session_id}/tts
Content-Type: application/json

{ "text": "أبشر، تبغى شاورما دجاج؟" }
```

**استجابة:**

```json
{
  "audio_base64": "//...",
  "mime_type": "audio/mpeg",
  "voice": "ar-XA-Chirp3-HD-Kore"
}
```

أو استخدم `reply_audio_base64` المرفق مباشرة في استجابة `/chat`.

### تحويل صوت العميل لنص (STT — لهجة سعودية)

```http
POST /api/voice-order/public/sessions/{session_id}/stt
Content-Type: application/json

{
  "audio_base64": "...",
  "encoding": "OGG_OPUS",
  "sample_rate": 48000
}
```

> **Flutter:** إذا كان التسجيل بصيغة Opus داخل Ogg (شائع على أندرويد)، استخدم `encoding: "OGG_OPUS"` وليس `WEBM_OPUS`.

**استجابة:**

```json
{
  "transcript": "ابي شاورما دجاج",
  "confidence": 0.92,
  "language_code": "ar-SA"
}
```

### أصوات Google الموصى بها للعربية

| الصوت | الوصف |
|-------|--------|
| `ar-XA-Chirp3-HD-Kore` | الأحدث — جودة عالية |
| `ar-XA-Neural2-B` | بديل ممتاز |
| `ar-XA-Wavenet-B` | بديل ثالث |

### اللهجة السعودية في النص

- النموذج: `gemini-3.5-flash` مع تعليمات لهجة سعودية
- يفهم: «ابي»، «ابغى»، «ودي»، «زود»، «خلاص»، «بكم»
- يُعيد صياغة الردود بأسلوب: «أبشر»، «تبغى»، «حاضر»، «تمام»

---

## 10. الصوت في Flutter (مسؤولية التطبيق)

| المكوّن | الدور |
|---------|--------|
| **Gemini 3.5 Flash** | فهم الكلام + كتابة الرد (نص) |
| **Google Cloud TTS** (`/tts` أو `reply_audio_base64`) | صوت المساعد — Chirp3 HD سعودي |
| **`flutter_tts`** (احتياطي) | عند `tts_available: false` أو فشل `/tts` |

**تدفق مقترح:**

```dart
final session = await voiceApi.startSession(franchiseId: 1, branchId: 1);

// إذا tts_available == true → شغّل reply_audio_base64 أو POST /tts
// إذا false → flutter_tts مع أفضل صوت عربي على الجهاز + رسالة «صوت احتياطي»

final text = session.stt_available
    ? await voiceApi.stt(session.sessionId, audioBase64)
    : await deviceStt.listen(locale: 'ar-SA');

final response = await voiceApi.chat(session.sessionId, text);

if (response.ttsAvailable && response.replyAudioBase64 != null) {
  await playMp3Base64(response.replyAudioBase64);
} else {
  await fallbackTts.speak(response.replyAr); // جودة أقل
}

cartController.replaceAll(response.cart.lines);
```

> لا تكرر طلبات `/tts` بعد خطأ `503` مع `code: GOOGLE_SERVICE_ACCOUNT_REQUIRED`.

---

## 11. Health Check

```http
GET /api/voice-order/health
```

```json
{
  "status": "ok",
  "service": "voice-order",
  "database": "connected",
  "gemini": "configured",
  "google_tts_stt": "ready",
  "google_auth_mode": "service_account"
}
```

`google_tts_stt` قيم محتملة: `ready` | `requires_service_account`

---

## 12. رموز الأخطاء الشائعة

| الرسالة | السبب | الحل |
|---------|--------|------|
| `الطلب الصوتي غير مفعّل` | `is_enabled = false` | فعّل من `/admin/voice-order` |
| `GOOGLE_STT_API_DISABLED` | Speech-to-Text API غير مفعّل | فعّل API من `help_url` في الرد |
| `GOOGLE_TTS_API_DISABLED` | Text-to-Speech API غير مفعّل | فعّل API من Google Cloud Console |
| HTTP 401/503 على `/tts` | مفتاح Gemini لا يكفي للصوت | Service Account + تفعيل TTS API |
| `الجلسة غير موجودة` | `session_id` خاطئ | ابدأ جلسة جديدة |
| `انتهت الجلسة` | timeout | ابدأ جلسة جديدة |
| `الرسالة مطلوبة` | body فارغ | أرسل `message` |

---

## 13. ملاحظات للمطور

1. **لا تعتمد على `base_price_hint` في البحث** — السعر النهائي دائماً في `cart.lines[].unit_price` بعد الإضافة.
2. **استبدل السلة كاملة** من `cart` في كل رد (ليس merge جزئي) لتجنب التعارض.
3. **`session_id` واحد لكل جلسة كيوسك** — عند الخروج أو إلغاء الطلب ابدأ جلسة جديدة.
4. **الخيارات الإلزامية**: عند وجود `pending_product`، اعرض للعميل أنه في خطوة اختيار خيارات.
5. **بدون مصادقة** على `/public/*` — مناسب للكيوسك؛ اربط `franchise_id` و`branch_id` من bootstrap.
6. **فهرسة القائمة** تتحدث تلقائياً عند تعديل المنتجات في `/admin/menu`.

---

## 14. نقاط النهاية المرجعية

| Method | Path | الوصف |
|--------|------|--------|
| POST | `/public/sessions` | بدء جلسة |
| POST | `/public/sessions/:id/chat` | رسالة محادثة |
| GET | `/public/sessions/:id` | حالة الجلسة |
| POST | `/public/sessions/:id/tts` | تحويل نص لصوت Google |
| POST | `/public/sessions/:id/stt` | تحويل صوت لنص (ar-SA) |
| GET | `/health` | صحة الخدمة |

**Admin (JWT مطلوب):**

| Method | Path |
|--------|------|
| GET/PUT | `/settings` |
| GET/POST/PUT/DELETE | `/synonyms` |
| GET | `/index/stats` |
| POST | `/index/reindex` |
| POST | `/index/search-test` |
| GET | `/stats` |

---

## 15. اتصال

للأسئلة التقنية: فريق ERP — `erp.sayedkhattab.com`
