# DZBuild POS

DZBuild POS هو برنامج صندوق مجاني على ويندوز للمحلات التي تبيع أيضاً عبر الإنترنت. يعمل دون اتصال، وبعد ربطه يبقى متزامناً مع متجر DZBuild واحد أو أكثر. تذكر هذه الصفحة النداءات التي يرسلها الصندوق المرتبط، ليعرف فريق الدعم والشركاء ما يستطيع الصندوق فعله وما لا يستطيعه.

لا تجيب هذه النقاط إلا الرموز الصادرة للصندوق. مفتاح API الشخصي أو رمز التطبيق يتلقى `403` على المسارات الخاصة بالصندوق، ورمز الصندوق يتلقى `403` على كل مسار خارج قائمته.

## لمن هو[​](#لمن-هو "رابط مباشر إلى لمن هو")

* لأصحاب المتاجر الذين يبيعون في محل وفي متجرهم على DZBuild. صاحب المتجر وحده يستطيع ربط صندوق؛ أعضاء الفريق لا يستطيعون.
* لكل الخطط. الصندوق ليس مفتاح API، فلا يحتاج خطة Enterprise ولا يأخذ مكان مفتاح.
* بعد تسجيل أول صندوق تظهر إضافة **DZBuild POS** في لوحة التحكم مع الصناديق المرتبطة وزر الفصل وآخر المبيعات والمرتجعات وإغلاقات Z. الرابط `https://dzbuild.com/dashboard/connected-devices` يفتح هذه الصفحة.

## كيف يرتبط الصندوق[​](#كيف-يرتبط-الصندوق "رابط مباشر إلى كيف يرتبط الصندوق")

* الاكتشاف: `GET https://dzbuild.com/.well-known/oauth-authorization-server` (RFC 8414).
* الصندوق عميل OAuth 2.0 عام اسمه `dzbuild-pos-windows`، بلا سرّ. يُقبل `S256` وحده في PKCE، والتحويل يكون إلى `http://127.0.0.1:PORT/oauth/callback` على أي منفذ.
* يدخل صاحب المتجر من متصفح النظام ويختار المتاجر التي يستعملها الصندوق، حتى 10 متاجر. ويمكن ربط الصندوق برمز أيضاً: يعرض الصندوق رمزاً، ويكتبه صاحب المتجر في `https://dzbuild.com/device` من هاتفه.
* رمز الوصول يبدأ بـ `dzpos_` ويدوم 15 دقيقة. رمز التحديث يُستبدل عند كل استعمال وينتهي بعد 30 يوماً دون استعمال أو 180 يوماً في المجموع. إرسال رمز تحديث قديم من جديد ينهي الربط.
* كل نداء يحمل `X-DZ-Store` مع رقم المتجر، ما عدا `GET /v1/me`. المتجر الذي لم يختره صاحبه يُرجع `403`.
* كل `POST` و`PATCH` و`DELETE` يحمل `Idempotency-Key`، بالقواعد المذكورة في [التكرار الآمن](https://dzbuild.com/ar/ar/api-docs/idempotency.md).
* الحدود: 120 طلباً في الدقيقة لكل صندوق و600 لكل متجر. حصة الواجهة البرمجية الشهرية لا تنطبق على الصناديق.
* فصل الصندوق من لوحة التحكم ينهي الربط: التحديث التالي يُرجع `400 invalid_grant` ونبضة الاتصال التالية `410 device_revoked`.

## الصلاحيات الـ 19[​](#الصلاحيات-الـ-19 "رابط مباشر إلى الصلاحيات الـ 19")

يطلب الصندوق الصلاحيات الـ 19 دائماً، وتمنحها DZBuild كلها دائماً.

| الصلاحيات                             | ما يستطيعه الصندوق                                      |
| ------------------------------------- | ------------------------------------------------------- |
| `openid` و`profile` و`offline_access` | معرفة من ربطه والبقاء مرتبطاً                           |
| `store:read`                          | قراءة متاجر الربط ولغتها وخطتها وحدّ منتجاتها           |
| `products:read` و`products:write`     | قراءة المنتجات وإنشاؤها وتعديلها على دفعات وإضافة الصور |
| `inventory:read` و`inventory:write`   | قراءة مخزون المنتجات وتعديله                            |
| `orders:read` و`orders:write`         | استقبال طلبات المتجر وأخذ طلب وتحريكه وإلغاؤه           |
| `customers:read`                      | التحقق من رقم هاتف قبل البيع                            |
| `pos:sales:read` و`pos:sales:write`   | تسجيل المبيعات والمرتجعات وإغلاقات Z                    |
| `locations:read` و`locations:write`   | تسجيل المحل الذي يوجد فيه الصندوق                       |
| `backups:read` و`backups:write`       | محجوزة: النسخ الاحتياطية غير متاحة                      |
| `devices:self`                        | تسجيل الصندوق وإرسال نبضات الاتصال وفك الربط            |
| `events:read`                         | قراءة تدفق التغييرات                                    |

## النقاط[​](#النقاط "رابط مباشر إلى النقاط")

| الطريقة والمسار                                                                       | ما تفعله                                                                           |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `GET /v1/me`                                                                          | صاحب المتجر ومتاجر الربط                                                           |
| `GET /v1/store`                                                                       | المتجر المختار: الاسم واللغة والعملة `DZD` وتوقيت خصم المخزون والخطة وحدّ المنتجات |
| `POST /v1/devices` و`GET /v1/devices`                                                 | تسجيل الصندوق (سطر واحد لكل صندوق ومتجر) وعرض الصناديق                             |
| `POST /v1/devices/{id}/heartbeat`                                                     | كل 15 دقيقة: الإصدار والإرسالات المنتظرة والفاشلة                                  |
| `DELETE /v1/devices/{id}`                                                             | فك ربط هذا الصندوق                                                                 |
| `GET /v1/locations` و`POST /v1/locations`                                             | المحل الذي يوجد فيه الصندوق                                                        |
| `POST /v1/products/batch`                                                             | إنشاء أو تعديل حتى 100 منتج                                                        |
| `GET /v1/products` و`GET /v1/products/{id}`                                           | المنتجات المعدّلة منذ تاريخ معيّن، ومنتج واحد                                      |
| `POST /v1/media/uploads` و`POST /v1/products/{id}/images`                             | تذكرة الصورة، ثم ربط الصورة المرفوعة                                               |
| `POST /v1/inventory/adjustments/batch`                                                | حتى 500 تثبيت أو تغيير للمخزون                                                     |
| `POST /v1/pos/sales` و`POST /v1/pos/sales/{sale_id}/refunds` و`POST /v1/pos/closures` | المبيعات والمرتجعات وإغلاقات Z                                                     |
| `GET /v1/orders` و`GET /v1/orders/{id}`                                               | طلبات المتجر بصيغة الصندوق                                                         |
| `POST /v1/orders/{id}/claim` و`PATCH /v1/orders/{id}` و`POST /v1/orders/{id}/cancel`  | أخذ طلب وتحريكه وإلغاؤه                                                            |
| `GET /v1/customers?phone=`                                                            | مؤشرات رقم هاتف واحد                                                               |
| `GET /v1/events`                                                                      | تدفق التغييرات                                                                     |

بعض المسارات مشتركة مع مفاتيح API (المنتجات والطلبات والزبائن). الصندوق يتلقى الصيغ المذكورة في هذه الصفحة، ومفتاح API يحتفظ بالصيغ المذكورة في قسم الموارد.

## دفعة المنتجات[​](#دفعة-المنتجات "رابط مباشر إلى دفعة المنتجات")

تأخذ `POST /v1/products/batch` الحقل `items`، حتى 100 عنصر. لكل عنصر `external_id` الخاص بالصندوق و`name` و`sku` و`barcode` اختياريان و`pricing.price` كنص بخانتين عشريتين (`"4500.00"`) و`inventory.track_stock` و`status` (`active` أو `draft` أو `archived`) و`category` اختيارية لها `external_id` و`name` خاصان بها.

* يعدّل العنصر المنتج المرتبط مسبقاً بـ `external_id` الخاص به. وإن لم يوجد، يتبنّى منتجاً بلا متغيرات له نفس `sku` وغير مرتبط بعد. وإن لم يوجد، ينشئ منتجاً بمخزون 0.
* يُعثر على الفئة بـ `external_id` الخاص بها، أو تُنشأ من اسمها.
* تذكر الاستجابة كل عنصر: `external_id` و`id` و`status` (`created` أو `updated`) و`error: null`. العنصر المرفوض يحمل كائن `error` ولا يحمل `id` ولا `status`.
* حدّ الخطة: عندما يملك المتجر عدد المنتجات النشطة الذي تسمح به خطته، فإن الدفعة التي تنشئ منتجاً، مسودة كان أو لا، تُرجع `402 product_limit_reached`. العناصر التي سبقته تبقى مكتوبة. وتحويل منتج موجود إلى `active` بعد الحدّ خطأ على ذلك العنصر وحده.
* الدفعة لا تضبط المخزون أبداً. المخزون يمرّ عبر نداء التعديلات.

## الصور[​](#الصور "رابط مباشر إلى الصور")

1. `POST /v1/media/uploads` مع `filename` و`content_type` (`image/jpeg` أو `image/png` أو `image/webp`) و`size` (حتى 8 ميغابايت) و`sha256`. تحمل الاستجابة `media_id` ورابطاً موقّعاً `upload_url` صالحاً 10 دقائق.
2. يرسل الصندوق البايتات الخام بـ `PUT` إلى `upload_url`، دون ترويسات DZBuild.
3. `POST /v1/products/{id}/images` مع `media_id` و`position` يربط الصورة. يجب أن يطابق الحجم و`sha256` التذكرة، وإلا يُرجع النداء `422 media_mismatch`. التذكرة المجهولة أو المنتهية تُرجع `404 media_not_found`. يحمل المنتج حتى 20 صورة.

## المخزون[​](#المخزون "رابط مباشر إلى المخزون")

تأخذ `POST /v1/inventory/adjustments/batch` حتى 500 عنصر. يذكر كل عنصر `product_external_id` و`target: "product"` وإما `set` (قطع كاملة) وإما `delta`، و`reason` (`pos_sale` أو `pos_return` أو `restock` أو `count` أو `loss`) و`ref` فريداً.

* لا يُعدَّل إلا مخزون المنتجات التي تتابع المخزون على مستوى المنتج. المنتج الذي له مخزون لكل متغير يُرجع خطأ العنصر `variant_product`، والذي لا يتابع المخزون `not_tracked`، والمنتج الذي لم يعد موجوداً `unknown_product`.
* تذكر الاستجابة كل عنصر بـ `ref` مع `status` إما `ok` وإما `error`.
* تغييرات مخزون الصندوق لا تظهر أبداً في سجل تغييرات لوحة التحكم ولا تعرض تراجعاً.

## وثائق الصندوق[​](#وثائق-الصندوق "رابط مباشر إلى وثائق الصندوق")

تسجّل `POST /v1/pos/sales` تذكرة أو فاتورة أو وصل تسليم، و`POST /v1/pos/sales/{sale_id}/refunds` وصل إرجاع أو إشعاراً دائناً على ذلك البيع، و`POST /v1/pos/closures` إغلاق Z بمجاميعه وبصماته.

* تُحفظ الوثائق كما أُرسلت ولا تتغير أبداً: لا يوجد مسار تعديل أو حذف.
* يُرجع البيع `201` مع `{"id": 99120, "stock_applied": false}`. المخزون يمرّ عبر نداء التعديلات، ولا يمرّ أبداً عبر وثيقة.
* وثائق الصندوق منفصلة عن الطلبات. لا تُحسب في حدّ الطلبات الشهري للمتجر، ولا تُرسل أي إشعار، ولا تصل أبداً إلى Google Sheets ولا إلى webhooks.
* المبالغ نصوص بخانتين عشريتين، والكميات أرقام بثلاث خانات عشرية على الأكثر، مع حتى 500 سطر و20 دفعة في الوثيقة.
* إرسال وثيقة سُجّل `external_id` الخاص بها من قبل يُرجع `409 already_exists` مع الرقم المسجّل في `error.details.id`. يعدّ الصندوق ذلك نجاحاً.
* المرتجع على بيع متجر آخر يُرجع `404 not_found`.

## الطلبات[​](#الطلبات "رابط مباشر إلى الطلبات")

* تعطي `GET /v1/orders?updated_since=...` و`GET /v1/orders/{id}` طلبات المتجر بصيغة الصندوق مع منتجاتها. `order_number` هو الرقم القصير للمتجر إن وُجد.
* `POST /v1/orders/{id}/claim` مع `device_id` و`terminal`: أول صندوق يأخذ الطلب. الصندوق نفسه إذا أخذه من جديد يتلقى `200`، وأي صندوق آخر يتلقى `409 order_claimed`.
* `PATCH /v1/orders/{id}` مع `status` يتطلب أخذ الطلب (`409 claim_required` في غير ذلك). الانتقالات المسموحة: من `pending` أو `confirmed` إلى `processing` أو `shipped` أو `delivered`، ومن `processing` إلى `shipped` أو `delivered`، ومن `shipped` إلى `delivered`. أي انتقال آخر يُرجع `409 transition_not_allowed`.
* تأخذ `POST /v1/orders/{id}/cancel` الحقل `reason` (`out_of_stock` أو `customer_unreachable` أو `duplicate` أو `other`) و`note` اختيارية حتى 500 حرف. يعود المخزون حسب قواعد مخزون المتجر. إذا كان صندوق آخر قد أخذ الطلب، يُرجع الإلغاء `409 order_claimed`.
* تغيير الحالة من الصندوق يشغّل نفس الخطوات التي يشغّلها تغيير من لوحة التحكم، ومنها الإشعارات وتحديث Google Sheets.

## الزبائن[​](#الزبائن "رابط مباشر إلى الزبائن")

تُرجع `GET /v1/customers?phone=0550123456` صفحة فيها عنصر واحد على الأكثر: `id` و`is_banned` و`fraud_score`. تبحث في زبائن المتجر وحدهم، وتقبل الرقم مع `+213` أو بدونه، ولا تعطي اسماً ولا عنواناً ولا سجلاً. الرقم المجهول يعطي صفحة فارغة.

## الأحداث[​](#الأحداث "رابط مباشر إلى الأحداث")

تُرجع `GET /v1/events?wait=25&limit=200&cursor=...` الحقول `items` و`next_cursor` و`has_more`. الحقل `next_cursor` موجود دائماً، حتى في صفحة فارغة، ويعيده الصندوق في النداء التالي.

* تحتفظ الحافة بالنداء حتى 25 ثانية وتجيب فور حدوث تغيير.
* النداء الأول، دون مؤشر، يبدأ بحدث `order.updated` لكل طلب في حالة `pending` أو `confirmed` أو `processing`.
* الأنواع: `order.created` و`order.updated` و`product.updated` و`product.deleted` و`inventory.level_changed` و`customer.updated` و`device.revoked`. لكل حدث `id` ثابت، فيستطيع الصندوق تجاهل التكرار.
* يحمل `inventory.level_changed` الحقول `old` و`new` و`delta` و`source`: `order` عندما يحرّك طلبٌ المخزون (مع رقم الطلب)، و`dashboard` لأي تغيير آخر حدث خارج الصندوق، و`pos` عندما يغيّره صندوق آخر في المتجر نفسه (يتجاهل الصندوق هذا المصدر).
* كتابات الصندوق نفسه على المنتجات والمخزون لا تُرسل إليه من جديد.
* يحمل `device.revoked` الحقل `device_id` كنص، مرة واحدة، بعد فصل الصندوق.
* المؤشر الذي لم تُصدره هذه الواجهة يُرجع `400 bad_request`.

## النسخ الاحتياطية[​](#النسخ-الاحتياطية "رابط مباشر إلى النسخ الاحتياطية")

لا تحفظ DZBuild النسخ الاحتياطية للصناديق. تُرجع `GET /v1/backups` و`POST /v1/backups` و`POST /v1/backups/{id}/complete` و`GET /v1/backups/{id}/download` دائماً `501 not_implemented`، ويحتفظ الصندوق بنسخه على الحاسوب.

## رموز الأخطاء[​](#رموز-الأخطاء "رابط مباشر إلى رموز الأخطاء")

| الحالة | `code`                   | متى                                                    |
| ------ | ------------------------ | ------------------------------------------------------ |
| 400    | `bad_request`            | `updated_since` خاطئ أو مؤشر لم تُصدره هذه الواجهة     |
| 401    | `unauthorized`           | رمز مفقود أو خاطئ أو منتهٍ، أو ربط منتهٍ               |
| 402    | `product_limit_reached`  | دفعة تنشئ منتجاً بعد حدّ الخطة                         |
| 403    | `forbidden`              | مسار خارج قائمة الصندوق، أو متجر لم يختره صاحبه        |
| 404    | `not_found`              | منتج أو بيع أو طلب ليس في هذا المتجر                   |
| 404    | `device_not_found`       | رقم صندوق ليس هذا الصندوق في هذا المتجر                |
| 404    | `media_not_found`        | تذكرة صورة مجهولة أو منتهية                            |
| 409    | `already_exists`         | وثيقة بهذا `external_id` مسجّلة؛ رقمها في `details.id` |
| 409    | `order_claimed`          | صندوق آخر أخذ الطلب                                    |
| 409    | `claim_required`         | تحريك طلب لم يأخذه هذا الصندوق                         |
| 409    | `transition_not_allowed` | الانتقال غير مسموح من حالة الطلب                       |
| 410    | `device_revoked`         | فُصل الصندوق                                           |
| 413    | `payload_too_large`      | جسم أكبر من 1 ميغابايت                                 |
| 422    | `validation_error`       | حقل خاطئ؛ يذكره `details.field`                        |
| 422    | `media_mismatch`         | الصورة المرفوعة لا تطابق تذكرتها                       |
| 422    | `idempotency_key_reuse`  | نفس `Idempotency-Key` مع جسم مختلف                     |
| 429    | `rate_limited`           | تجاوز حدّ؛ انتظر `Retry-After`                         |
| 501    | `not_implemented`        | مسارات النسخ الاحتياطية                                |
| 503    | `storage_unavailable`    | تخزين الصور غير متاح؛ أعد المحاولة لاحقاً              |
