# العملاء

> إنشاء عميل فرد أو شركة، والحقول المطلوبة لكل نوع، وأنواع الهوية، والبحث عن العميل برقم الجوال أو بمعرّفك الخاص، وقواعد التعديل.

كل فاتورة مبيعات ودفعة تُنشأ عن طريق الـ API تحدد عميلاً. والعميل إما فرد أو شركة، ونوعه يحدد نوع الفاتورة: الشركة تصدر لها فاتورة ضريبية تعتمدها هيئة الزكاة والضريبة والجمارك، والفرد تصدر له فاتورة مبسطة. التفاصيل في [ابدأ من هنا](https://docs.kosoor.com.sa/ar/start-here.md#standard-and-simplified).

| الـ Endpoint | الصلاحية |
|---|---|
| `POST /v1/customers` | `customers:write` |
| `GET /v1/customers` | `customers:read` |
| `GET /v1/customers/{id}` | `customers:read` |
| `PATCH /v1/customers/{id}` | `customers:write` |

## إنشاء عميل فرد

العميل الفرد يحتاج `type` و `phone`. ورقم الجوال بصيغة E.164: علامة `+` ثم رمز الدولة ثم الرقم، مثل `+966501234567`.

```bash
curl https://api.kosoor.com.sa/v1/customers \
  -H "Authorization: Bearer ksr_test_..." \
  -H "Idempotency-Key: 6f1d2c3b-4a5e-4f60-9b7a-8c9d0e1f2a3b" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "person",
    "firstName": "Sara",
    "lastName": "Al-Harbi",
    "phone": "+966501234567",
    "email": "sara@example.com",
    "referenceId": "crm-88412"
  }'
```

الرد `201 Created`:

```json
{
  "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
  "object": "customer",
  "type": "person",
  "referenceId": "crm-88412",
  "firstName": "Sara",
  "lastName": "Al-Harbi",
  "phone": "+966501234567",
  "email": "sara@example.com",
  "isActive": true,
  "branchId": null,
  "company": null,
  "address": {
    "street": null,
    "buildingNumber": null,
    "postalCode": null,
    "district": null,
    "city": null,
    "country": null
  },
  "metadata": {},
  "standardInvoiceMissing": null,
  "createdAt": "2026-11-07T09:30:12.512Z"
}
```

ويمكن للعميل الفرد أن يرسل أيضاً `address.street` و `address.district` و `address.city` و `address.country`. أما `buildingNumber` و `postalCode` والكائن `company` فللشركات فقط، وكذلك الاستغناء عن رقم الجوال.

## إنشاء عميل من نوع شركة

الشركة تحتاج `type` و `company.name` و `address.country`. والدولة رمز من حرفين حسب ISO 3166، مثل `SA`. ولا تحتاج الشركة رقم جوال، ولا تقبل `firstName` ولا `lastName`، فاسمها في `company.name`.

```bash
curl https://api.kosoor.com.sa/v1/customers \
  -H "Authorization: Bearer ksr_test_..." \
  -H "Idempotency-Key: 2b8e4f9a-1c3d-4e5f-8a6b-7c8d9e0f1a2b" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "company",
    "phone": "+966112345678",
    "company": {
      "name": "Al Noor Logistics",
      "vatNumber": "310123456700003"
    },
    "address": {
      "street": "Prince Sultan Road",
      "buildingNumber": "7421",
      "postalCode": "23521",
      "district": "Al Rawdah",
      "city": "Jeddah",
      "country": "SA"
    },
    "referenceId": "crm-90017"
  }'
```

الرد `201 Created`:

```json
{
  "id": "3f1c2a6e-8b4d-4e2a-9c1f-5d7e8a9b0c12",
  "object": "customer",
  "type": "company",
  "referenceId": "crm-90017",
  "firstName": null,
  "lastName": null,
  "phone": "+966112345678",
  "email": null,
  "isActive": true,
  "branchId": null,
  "company": {
    "name": "Al Noor Logistics",
    "vatNumber": "310123456700003",
    "idType": null,
    "idValue": null,
    "creditLimit": 0,
    "creditPeriodDays": null
  },
  "address": {
    "street": "Prince Sultan Road",
    "buildingNumber": "7421",
    "postalCode": "23521",
    "district": "Al Rawdah",
    "city": "Jeddah",
    "country": "SA"
  },
  "metadata": {},
  "standardInvoiceMissing": [],
  "createdAt": "2026-11-07T09:31:05.208Z"
}
```

### ما تحتاجه الفاتورة الضريبية

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

| المطلوب | متى |
|---|---|
| `company.vatNumber`، أو `company.idType` مع `company.idValue` | دائماً |
| `address.street` و `address.city` و `address.country` | دائماً |
| `address.buildingNumber` و `address.postalCode` و `address.district` | إذا كانت الدولة `SA` |

والفاتورة لشركة ما زال ينقصها أي منها ترجع `422 customer_address_incomplete`، والقائمة نفسها في `missing`. تحقق من `standardInvoiceMissing` قبل إصدار الفاتورة، وأكمل الناقص بطلب `PATCH`.

### قواعد الشركة

| الحقل | القاعدة |
|---|---|
| `company.vatNumber` | 15 رقماً تبدأ بـ 3 وتنتهي بـ 3 (`400 invalid_vat_number`)، ولا يكون لعميل آخر (`409 vat_number_taken`) |
| `company.idType` و `company.idValue` | يُرسلان معاً، إما كلاهما بقيمة أو كلاهما `null`، وقيمة `idValue` من 1 إلى 50 حرفاً ورقماً |
| `address.buildingNumber` | 4 أرقام إذا كانت الدولة `SA`، وحتى 20 حرفاً في غيرها |
| `address.postalCode` | 5 أرقام إذا كانت الدولة `SA`، وحتى 20 حرفاً في غيرها |
| `company.creditLimit` و `company.creditPeriodDays` | في الرد فقط، ويحددهما التاجر من لوحة التحكم. حد الائتمان 0 يعني بلا حد. |

### أنواع الهوية

إذا لم يكن للشركة رقم ضريبي، أو إلى جانبه، يحدد `idType` نوع الرقم الموجود في `idValue`. وهذه رموز الهيئة لهوية المشتري:

| `idType` | الرقم |
|---|---|
| `CRN` | السجل التجاري |
| `MOM` | ترخيص وزارة الشؤون البلدية والقروية |
| `MLS` | ترخيص وزارة الموارد البشرية |
| `700` | الرقم الموحد 700 |
| `SAG` | ترخيص الاستثمار (الهيئة العامة للاستثمار سابقاً، وزارة الاستثمار حالياً) |
| `NAT` | الهوية الوطنية |
| `GCC` | هوية خليجية |
| `IQA` | الإقامة |
| `PAS` | جواز السفر |
| `OTH` | أي هوية أخرى |

## الحقول المشتركة لكل عميل

| الحقل | القاعدة |
|---|---|
| `type` | `person` أو `company`، مطلوب عند الإنشاء ولا يتغير أبداً |
| `phone` | بصيغة E.164، ولا يكون لعميل آخر (`409 phone_taken`) |
| `firstName` و `lastName` | اسم العميل الفرد، حتى 100 حرف لكل منهما |
| `email` | بريد إلكتروني صحيح، حتى 256 حرفاً |
| `referenceId` | معرّفك الخاص للعميل، حتى 100 حرف، ولا يكون لعميل آخر (`409 reference_id_taken`)، ويُحدَّد مرة واحدة فقط |
| `branchId` | الفرع الذي يتبعه العميل، ويجب أن يكون فرعاً يملك مفتاحك صلاحية عليه (`403 branch_not_allowed`) |
| `isActive` | قيمته `true` ما لم ترسل `false`، والعميل المعطّل ترجع فواتيره ودفعاته `422 customer_inactive` |
| `metadata` | حتى 20 مفتاحاً، طول كل مفتاح حتى 40 حرفاً، وكل قيمة حتى 500 حرف |

عندما تكون القيمة مستخدمة، يحمل الحقل `existingId` في الخطأ معرّف العميل الذي يستخدمها. استخدمه بدل أن تنشئ عميلاً ثانياً:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "phone_taken",
    "message": "رقم الجوال مسجّل لعميل آخر.",
    "param": "phone",
    "docUrl": "https://docs.kosoor.com.sa/ar/errors#phone_taken",
    "existingId": "0f8fad5b-d9cb-469f-a165-70867728950e"
  }
}
```

## البحث عن عميل

يقبل `GET /v1/customers` فلاتر تطابق القيمة تماماً، وتُجمع معاً بـ AND:

| الـ Parameter | يطابق |
|---|---|
| `phone` | رقم الجوال بصيغة E.164 |
| `vatNumber` | `company.vatNumber` |
| `idValue` | `company.idValue` |
| `referenceId` | معرّفك الخاص |

اكتب علامة `+` في رقم الجوال بالشكل `%2B`، لأن `+` في الـ Query String تُقرأ مسافة.

```bash
curl "https://api.kosoor.com.sa/v1/customers?phone=%2B966501234567" \
  -H "Authorization: Bearer ksr_test_..."
```

```json
{
  "data": [
    {
      "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
      "object": "customer",
      "type": "person",
      "referenceId": "crm-88412",
      "firstName": "Sara",
      "lastName": "Al-Harbi",
      "phone": "+966501234567",
      "email": "sara@example.com",
      "isActive": true,
      "branchId": null,
      "company": null,
      "address": {
        "street": null,
        "buildingNumber": null,
        "postalCode": null,
        "district": null,
        "city": null,
        "country": null
      },
      "metadata": {},
      "standardInvoiceMissing": null,
      "createdAt": "2026-11-07T09:30:12.512Z"
    }
  ],
  "hasMore": false
}
```

إذا كانت `data` فارغة فلا يوجد عميل مطابق. وبدون فلاتر تعرض القائمة كل العملاء صفحة بعد صفحة، الأحدث أولاً. التفاصيل في [تقسيم النتائج](https://docs.kosoor.com.sa/ar/pagination.md).

## تعديل عميل

يغيّر `PATCH /v1/customers/{id}` الحقول الموجودة في الـ Body فقط، وتبقى الحقول الأخرى على قيمها. وكذلك داخل `company` و `address`: لا يتغير إلا ما ترسله.

```bash
curl -X PATCH https://api.kosoor.com.sa/v1/customers/3f1c2a6e-8b4d-4e2a-9c1f-5d7e8a9b0c12 \
  -H "Authorization: Bearer ksr_test_..." \
  -H "Idempotency-Key: 8d7c6b5a-4f3e-4d2c-9b1a-0f9e8d7c6b5a" \
  -H "Content-Type: application/json" \
  -d '{ "email": "accounts@alnoor.example", "address": { "street": "King Abdulaziz Road" } }'
```

والرد هو العميل كاملاً، كما يرجعه `GET`.

| ما ترسله | النتيجة |
|---|---|
| حقل بقيمة | يأخذ الحقل القيمة الجديدة |
| `email` أو `phone` بقيمة `null`، أو `firstName` أو `lastName` للعميل الفرد | يُمسح الحقل، ولا يمكن مسح رقم جوال العميل الفرد |
| `metadata` | تستبدل القائمة كلها، و `{}` أو `null` تفرغها |
| `type` | يجب أن يساوي النوع الحالي، فالعميل لا يتغير نوعه |
| `referenceId` | يُقبل مرة واحدة فقط ما دام العميل بلا `referenceId`، وبعدها ترجع أي قيمة مختلفة `400 invalid_parameter` |
| `company` أو `address` بقيمة `null` | `400 invalid_parameter`، فأرسل الحقول التي تريد تغييرها بدلاً من ذلك |
| `isActive` بقيمة `null` | `400 invalid_parameter`، فأرسل `true` أو `false` |

وإذا خالف الطلب أي قاعدة فلا يتغير شيء.

المصدر: https://docs.kosoor.com.sa/ar/customers
