# توثيق الربط مع معمل الجُماعي كاد كام (Partner API)

الرابط الأساسي: `https://cadcam.lovable.app/api/public/partner`
التوثيق المنسّق: https://cadcam.algomaei.com/docs/integration
OpenAPI: https://cadcam.algomaei.com/api/public/partner/openapi.json

التوثيق بالعربية، وأسماء الحقول والردود بالإنجليزية كما هي في الواجهة.

## ٠. الربط بضغطة زر (بدون لصق مفتاح)

الطريقة المفضّلة: صاحب العيادة يضغط «اربط مع المعمل» داخل نظامه، فيُفتح له في المعمل صفحة موافقة، يسجّل دخوله بحسابه في المعمل ويوافق — ثم يعود إلى نظامه مرتبطاً. المفتاح لا يمر عبر متصفحه أبداً.

- يسجّل المعمل نظامك مرة واحدة ويعطيك: `app_id` و`app_secret` ونطاق العودة المسموح (نطاق موقع نظامك).
- لكل مستخدم: وجّهه إلى صفحة الموافقة بـ `app_id` و`redirect_uri` و`state` من عندك.
- بعد الموافقة يعود المتصفح إلى `redirect_uri?code=…&state=…` — الرمز صالح ٥ دقائق ولمرة واحدة.
- خادمك يستبدل الرمز بالمفتاح عبر `POST /connect/exchange` ويحفظ المفتاح مشفَّراً لهذا المستخدم.
- الرفض يعود بـ `redirect_uri?error=access_denied`.
- حساب غير معتمد في المعمل يرى «حسابك بانتظار اعتماد المعمل» ولا يُنشأ له ربط.

**١) صفحة الموافقة (وجّه متصفح صاحب العيادة إليها)**

```text
https://cadcam.algomaei.com/connect?app_id=<APP_ID>&redirect_uri=https%3A%2F%2Fclinic-hub.example.com%2Flab%2Freturn&state=<RANDOM>
```

**٢) استبدال الرمز بالمفتاح (من خادمك فقط)**

```bash
curl -X POST https://cadcam.lovable.app/api/public/partner/connect/exchange \
  -H "x-app-id: <APP_ID>" -H "x-app-secret: <APP_SECRET>" \
  -H "Content-Type: application/json" \
  -d '{ "code": "<CODE_FROM_REDIRECT>" }'

# { "api_key": "lab_live_…", "account": { "id": "…", "name": "عيادة النور", "type": "clinic" } }
```

**٣) فصل الربط (بالمفتاح نفسه)**

```bash
curl -X POST https://cadcam.lovable.app/api/public/partner/connect/revoke -H "Authorization: Bearer lab_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

> تحقّق من مطابقة `state` عند العودة، ولا ترسل `app_secret` ولا المفتاح إلى المتصفح. إن أردت الطريقة اليدوية القديمة (مفتاح يعطيه لك المعمل مباشرة) فهي تعمل كما هي وبقية التوثيق ينطبق عليها.

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

واجهة الربط تسمح لأي نظام إدارة عيادات (مثل clinic-hub) بالعمل مع معمل الجُماعي كاد كام مباشرة: قراءة كتالوج الأعمال بأسعار حساب العيادة نفسها، إرسال الحالة من ملف المريض بضغطة واحدة، إرفاق ملفات السكان والتصميم، متابعة مراحل التنفيذ، وقراءة كشف الحساب.

- الحالة المُرسَلة تُنشأ داخل المعمل كأي حالة عادية: رقم رسمي، تسعير من كتالوج المعمل، مراحل إنتاج، وإشعار للاستقبال.
- كل مفتاح ربط مرتبط بحساب عيادة واحد، ولا يرى أو يكتب أي بيانات لحساب آخر.
- الأسعار والخصومات وحالات الإنتاج لا يمكن تغييرها من الخارج — التسعير من المعمل دائماً.
- كل نداء يُسجَّل في المعمل (الوقت، النداء، النتيجة) ويظهر للمدير.

> المفتاح يصدره مدير المعمل من: لوحة النظام ← «الربط مع الأنظمة» ← «مفتاح ربط جديد». يظهر مرة واحدة فقط، فاحفظه في متغيرات البيئة لنظام العيادة (مثل LAB_API_KEY).

## ٢. التوثيق والرابط الأساسي

الرابط الأساسي لكل النقاط:

```text
https://cadcam.lovable.app/api/public/partner
```

كل طلب يحمل المفتاح في ترويسة `Authorization` (أو `x-lab-key` إن كان العميل لا يسمح بتعديل Authorization):

```http
Authorization: Bearer lab_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
```

- شكل المفتاح: `lab_live_` + ٤٨ حرفاً ستّ عشرياً.
- المفتاح سرّي — يُستخدم من خادم نظام العيادة فقط، لا من المتصفح.
- بدون مفتاح صالح أو مع مفتاح معطّل: الرد `401` مع `{ "error": "unauthorized" }`.
- كل النقاط تدعم `OPTIONS` (CORS) وترد بترويسات `Access-Control-Allow-*`.
- كل الردود بصيغة JSON بترميز UTF-8 وبلا تخزين مؤقت.

## ٣. البداية السريعة (٣ خطوات)

- اختبر المفتاح بـ `GET /ping` — يعيد اسم الحساب ونوعه.
- اجلب `GET /catalog` واحفظ `work_types[].id` لعرضها في نموذج الإرسال داخل العيادة.
- أرسل الحالة بـ `POST /cases` مع `external_ref` من رقم الزيارة عندك، ثم تابعها بـ `GET /cases/{code}`.

**اختبار الاتصال**

```bash
curl -H "Authorization: Bearer $LAB_API_KEY" https://cadcam.lovable.app/api/public/partner/ping
```

**الردّ**

```json
{
  "ok": true,
  "lab": "معمل الجُماعي كاد كام",
  "account": { "id": "7f0c…", "name": "عيادة النور", "type": "clinic" }
}
```

## ٤. كتالوج الأعمال والأسعار — GET /catalog

يعيد مجموعات الأعمال وأنواعها وحقولها الإضافية، مع **سعر حساب هذه العيادة تحديداً**: حساب نوعه `lab` يأخذ سعر المعمل، وغيره يأخذ سعر الطبيب.

```bash
curl -H "Authorization: Bearer $LAB_API_KEY" https://cadcam.lovable.app/api/public/partner/catalog
```

**الردّ (مختصر)**

```json
{
  "groups": [{ "id": "…", "name": "الزيركون", "description": null, "sort_order": 1 }],
  "work_types": [
    {
      "id": "8c1f…",
      "group_id": "…",
      "name": "تاج زيركون",
      "case_type": "digital",
      "unit": "قطعة",
      "price": 9000,
      "currency": "YER",
      "fields": [
        { "id": "…", "label": "نوع الحافة", "field_type": "select", "required": true, "options": ["كتف", "شامفر"] }
      ]
    }
  ]
}
```

> اجلب الكتالوج عند فتح شاشة الإرسال (أو خزّنه مؤقتاً لساعة). الأسعار قد تتغير من المعمل في أي وقت، ولا تُحسب من عندك — الحساب النهائي يتم في المعمل.

## ٥. إرسال حالة — POST /cases

حقول الجسم:

| الحقل | النوع | إلزامي | الوصف |
| --- | --- | --- | --- |
| external_ref | string ≤120 | مستحسن جداً | معرّفك عندك (رقم الزيارة/العمل). يمنع التكرار عند إعادة الإرسال. |
| patient_name | string ≤160 | نعم | اسم المريض. |
| patient_age | integer 0–130 | لا | العمر. |
| patient_gender | "male" \| "female" | لا | الجنس. |
| case_type | "digital" \| "traditional" | لا | رقمية (سكان) أو تقليدية (طبعة). الافتراضي: رقمية. |
| priority | "normal" \| "urgent" | لا | الأولوية. |
| due_date | YYYY-MM-DD | لا | تاريخ التسليم المطلوب. |
| notes | string ≤2000 | لا | ملاحظات الطبيب. |
| items | array 1–30 | نعم | بنود العمل (انظر الجدول التالي). |

كل بند داخل `items`:

| الحقل | النوع | إلزامي | الوصف |
| --- | --- | --- | --- |
| work_type_id | uuid | نعم | من `/catalog`. |
| teeth | array من أرقام 11–85 | لا | الأسنان بنظام FDI (11 = الأيمن العلوي الأمامي). |
| bridges | array of arrays | لا | مجموعات الجسور، مثل `[[11,12,13]]`. |
| quantity | integer 1–200 | لا | عدد القطع. الافتراضي: عدد الأسنان أو 1. |
| shade | string ≤40 | لا | اللون، مثل A2. |
| notes | string ≤1000 | لا | ملاحظة البند. |
| field_values | object | لا | قيم الحقول الإضافية للعمل من `fields` في الكتالوج. |

```bash
curl -X POST https://cadcam.lovable.app/api/public/partner/cases \
  -H "Authorization: Bearer $LAB_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "external_ref": "clinic-visit-8841",
    "patient_name": "أحمد محمد",
    "patient_age": 34,
    "patient_gender": "male",
    "case_type": "digital",
    "priority": "normal",
    "due_date": "2026-10-05",
    "notes": "اللون A2 — تسليم قبل الخميس",
    "items": [
      { "work_type_id": "8c1f…", "teeth": [11, 12], "shade": "A2" },
      { "work_type_id": "8c1f…", "bridges": [[14, 15, 16]], "quantity": 3 }
    ]
  }'
```

**الردّ 201**

```json
{ "id": "b2d9…", "code": "25-1043", "duplicate": false }
```

- `code` هو رقم الحالة الرسمي في المعمل — اعرضه للطبيب واستخدمه في المتابعة.
- إعادة إرسال نفس `external_ref` تعيد نفس الحالة مع `duplicate: true` والرمز `200` — آمن لإعادة المحاولة بعد انقطاع الشبكة.
- المعمل يمنع أيضاً تكرار نفس المريض + نفس نوع الحالة خلال دقيقتين، ويرد بخطأ واضح.
- الأسعار تُحسب في المعمل من الكتالوج؛ أي سعر ترسله يُتجاهل.

## ٦. إرفاق ملف — POST /files

يُرفق ملف سكان أو تصميم أو صورة بحالة قائمة لنفس الحساب: STL / PLY / OBJ / HTML / صور / PDF. الحد الأقصى ٢٥ ميجابايت للملف الواحد، ويُرسل مُرمّزاً Base64.

| الحقل | النوع | إلزامي | الوصف |
| --- | --- | --- | --- |
| case | string | نعم | رقم الحالة (`code`) أو معرّفها (`id`). |
| name | string ≤200 | نعم | اسم الملف مع الامتداد، مثل `upper.stl`. |
| content_base64 | string | نعم | محتوى الملف Base64 (يُقبل أيضاً `data:…;base64,` كبادئة). |
| content_type | string ≤120 | لا | نوع المحتوى إن عرفته. |

```bash
curl -X POST https://cadcam.lovable.app/api/public/partner/files \
  -H "Authorization: Bearer $LAB_API_KEY" -H "Content-Type: application/json" \
  -d "{\"case\":\"25-1043\",\"name\":\"upper.stl\",\"content_base64\":\"$(base64 -w0 upper.stl)\"}"
```

**الردّ 201**

```json
{
  "file": { "id": "…", "url": "/api/public/media/cases/partner/…stl", "name": "upper.stl", "kind": "partner", "created_at": "…" },
  "case": { "id": "b2d9…", "code": "25-1043" }
}
```

## ٧. المتابعة — GET /cases و GET /cases/{code}

`GET /cases` يعيد حالات هذا الحساب فقط. معاملات الاستعلام: `status` (حالة الإنتاج)، `search` (رقم الحالة أو اسم المريض)، `limit` (١–٢٠٠، الافتراضي ٥٠).

```bash
curl -H "Authorization: Bearer $LAB_API_KEY" "https://cadcam.lovable.app/api/public/partner/cases?limit=20&search=أحمد"
curl -H "Authorization: Bearer $LAB_API_KEY" https://cadcam.lovable.app/api/public/partner/cases/25-1043
```

`GET /cases/{code_or_id}` يعيد التفاصيل الكاملة: البنود، مراحل الإنتاج وحالتها، الملفات، طلبات موافقة التصميم، الدفعات، والرصيد المتبقي على الحالة.

**الردّ (مختصر)**

```json
{
  "case": { "id": "b2d9…", "code": "25-1043", "external_ref": "clinic-visit-8841", "status": "in_progress",
            "delivery_status": "pending", "due_date": "2026-10-05", "total_price": 18000, "discount": 0, "currency": "YER" },
  "items":  [{ "work_type_name": "تاج زيركون", "teeth": [11, 12], "quantity": 2, "unit_price": 9000 }],
  "stages": [{ "name": "التصميم", "department": "cad", "status": "done", "completed_at": "…" },
             { "name": "القص", "department": "milling", "status": "in_progress" }],
  "files":   [{ "name": "upper.stl", "url": "/api/public/media/…" }],
  "reviews": [{ "id": "…", "image_url": "…", "message": "يرجى الموافقة على التصميم", "status": "pending" }],
  "payments": [{ "amount": 10000, "currency": "YER", "receipt_no": "R-221" }],
  "balance": 8000
}
```

> إذا كان في `reviews` عنصر بحالة `pending` فهناك تصميم ينتظر موافقة الطبيب — اعرضه كتنبيه في نظام العيادة.

## ٨. كشف الحساب — GET /statement

إجماليات مفصولة بالعملة (رصيد افتتاحي + مستحق − مدفوع = الرصيد)، مع قائمة الحالات والدفعات. معاملات اختيارية: `from` و`to` بصيغة تاريخ ISO.

```bash
curl -H "Authorization: Bearer $LAB_API_KEY" "https://cadcam.lovable.app/api/public/partner/statement?from=2026-09-01"
```

**الردّ (مختصر)**

```json
{
  "totals": { "YER": { "opening": 0, "billed": 480000, "paid": 300000, "balance": 180000 } },
  "cases": [{ "code": "25-1043", "patient_name": "أحمد محمد", "total_price": 18000, "currency": "YER" }],
  "payments": [{ "amount": 10000, "currency": "YER", "receipt_no": "R-221", "created_at": "…" }]
}
```

## ٩. الإشعار العكسي (Webhook)

يضيف مدير المعمل عنوان نظام العيادة (https فقط) لكل مفتاح، فيصل إليه إشعار فوري عند كل تغيّر مهم — بدون انتظار أو استعلام متكرر.

| الحدث | متى يُرسل | أهم الحقول |
| --- | --- | --- |
| case.updated | تغيّر حالة الإنتاج أو حالة التسليم | id, code, external_ref, status, delivery_status, patient_name, due_date, delivered_at |
| payment.recorded | تسجيل دفعة على حالة | order_id, code, amount, currency, method, receipt_no, created_at |
| review.requested | طلب موافقة على التصميم | order_id, code, review_id, image_url, message, status |

**شكل الحمولة**

```json
{
  "event": "case.updated",
  "sent_at": "2026-09-19T08:14:22.104Z",
  "data": { "id": "b2d9…", "code": "25-1043", "external_ref": "clinic-visit-8841",
            "status": "ready", "delivery_status": "pending", "patient_name": "أحمد محمد" }
}
```

- الترويسات: `X-Lab-Event` باسم الحدث، و`X-Lab-Signature` = HMAC SHA-256 (hex) لنص الجسم الخام بسر التوقيع.
- سر التوقيع يظهر لمدير المعمل في صفحة «الربط مع الأنظمة» بجانب العنوان.
- تحقّق من التوقيع دائماً على **النص الخام** قبل تحليل JSON، بمقارنة ثابتة الزمن.
- ردّ بسرعة بـ 200؛ المعالجة الطويلة تكون بعد الرد. عالج الإشعار بشكل مُتحمّل للتكرار (اعتمد `code` + `status`).

**التحقق من التوقيع (Node / Web Crypto)**

```ts
import { createHmac, timingSafeEqual } from "crypto";

export async function handleLabWebhook(request: Request) {
  const raw = await request.text();                       // النص الخام أولاً
  const sent = request.headers.get("x-lab-signature") ?? "";
  const expected = createHmac("sha256", process.env.LAB_WEBHOOK_SECRET!)
    .update(raw)
    .digest("hex");

  const a = Buffer.from(sent, "utf8");
  const b = Buffer.from(expected, "utf8");
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return new Response("invalid signature", { status: 401 });
  }

  const { event, data } = JSON.parse(raw);
  // حدّث العمل عندك بحسب data.external_ref أو data.code
  return new Response("ok");
}
```

## ١٠. الأخطاء ومعالجتها

| الرمز | error | المعنى وما تفعله |
| --- | --- | --- |
| 401 | unauthorized | المفتاح ناقص أو خاطئ أو معطّل — راجع الترويسة، أو اطلب مفتاحاً جديداً من المعمل. |
| 400 | invalid_json | الجسم ليس JSON صالحاً. |
| 400 | invalid_body | حقول ناقصة أو غير صحيحة — التفاصيل في `issues[]` (مسار الحقل والسبب). |
| 400 | create_failed | رفض من المعمل (مثل نوع عمل غير موجود أو حالة مكررة) — الرسالة في `message` بالعربية. |
| 404 | not_found | لا توجد حالة بهذا الرقم لهذا الحساب. |
| 413 | file_too_large | الملف يتجاوز ٢٥ ميجابايت — قسّمه أو اضغطه. |
| 500 | read_failed / upload_failed / attach_failed | خطأ مؤقت — أعد المحاولة بتأخير متزايد. |

- أعد المحاولة عند 429/500/503 بتأخير متزايد (1s، 4s، 15s).
- لا تُعد إنشاء الحالة بعد انقطاع دون `external_ref` — معه الإعادة آمنة تماماً.
- سجّل رمز الحالة و`message` عندك لتشخيص أسرع؛ المدير يرى نفس النداءات في المعمل.

## ١١. للمساعد الذكي (AI agent)

إن كنت تبني الربط داخل نظام العيادات بمساعدة وكيل ذكي، أعطه هذه الروابط مباشرة:

```text
التوثيق كنص خام (Markdown): https://cadcam.algomaei.com/docs/integration.md
وصف الواجهة (OpenAPI 3.1): https://cadcam.algomaei.com/api/public/partner/openapi.json
```

وهذه المهمة الجاهزة للصق في الوكيل:

```text
اقرأ https://cadcam.algomaei.com/docs/integration.md ثم أضف في هذا المشروع:
1) شاشة «إعدادات الربط بالمعمل»: حفظ مفتاح lab_live_ في أسرار الخادم + زر اختبار الاتصال عبر GET /ping.
2) في ملف المريض زر «إرسال للمعمل»: يجلب /catalog ويعرض أنواع الأعمال وأسعارها، يختار الأسنان بنظام FDI،
   ثم POST /cases مع external_ref = معرّف الزيارة، ثم POST /files لكل ملف سكان.
3) شاشة «أعمالي في المعمل»: GET /cases و GET /cases/{code} مع تنبيه عند وجود طلب موافقة تصميم، و GET /statement للرصيد.
4) نقطة استقبال إشعارات: تتحقق من X-Lab-Signature (HMAC SHA-256 على النص الخام) قبل التحديث.
كل النداءات من الخادم فقط، والمفتاح لا يظهر في المتصفح.
```

## ١٢. قواعد وحدود

- المفتاح يقرأ ويكتب بيانات حسابه فقط — لا وصول لأي حالة أو حساب آخر.
- لا يمكن تعديل الأسعار ولا الخصومات ولا مراحل الإنتاج من الخارج.
- تعطيل المفتاح من المعمل يوقف الربط فوراً دون أي أثر على الحالات السابقة.
- الأسنان بنظام FDI؛ الجسور مجموعات أرقام متصلة.
- العملات كما هي في المعمل (YER / USD) ولا تُحوَّل تلقائياً.
- حد الملف ٢٥ ميجابايت؛ حد بنود الحالة ٣٠ بنداً.
- الملفات تُخزَّن في المعمل وتظهر للفنيين مع الحالة.
