# تسجيل عملية بيع

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

عملية البيع عن طريق الـ API أربع خطوات. وكل خطوة في الأسفل تعرض الطلب كاملاً والرد كاملاً، بمفتاح اختبار على بيئة الاختبار.

1. اقرأ الكتالوج: الفرع والأصناف وطريقة الدفع.
2. ابحث عن العميل، أو أنشئه.
3. أصدر الفاتورة، مع الدفع أو بدونه.
4. سجّل الدفعة لاحقاً إذا لم تُدفع الفاتورة عند الإصدار.

يحتاج المفتاح الصلاحيات `branches:read` و `items:read` و `payment_methods:read` و `customers:write` و `sales_invoices:write`، و `payments:write` للخطوة 4.

المعرّفات في الأمثلة من [البيانات الجاهزة في بيئة الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md#fixtures) متى وُجدت. أما قيم `number` للفواتير وسندات القبض فمن تأليفنا، لأن كل تاجر يحدد ترقيمه بنفسه.

## 1. اقرأ الكتالوج

تحدد الفاتورة فرعها، وصنف كل بند، وطريقة الدفع، بمعرّفاتها. خذ هذه المعرّفات من الكتالوج، ولا تخمّنها أبداً:

| الـ Endpoint | الصلاحية | ماذا يعطيك |
|---|---|---|
| `GET /v1/branches` | `branches:read` | الفروع التي يعمل فيها مفتاحك: `branchId` |
| `GET /v1/items` | `items:read` | الأصناف التي يبيعها التاجر: `lines[].itemId` |
| `GET /v1/payment-methods` | `payment_methods:read` | طرق الدفع لدى التاجر: `payment.paymentMethodId` |

تحتفظ بيئة الاختبار بالمعرّفات الفعلية للفروع والأصناف وطرق الدفع التي نسختها، فالمعرّفات التي تربطها في بيئة الاختبار صالحة على البيانات الفعلية.

## 2. ابحث عن العميل أو أنشئه

كل فاتورة تحدد عميلاً، فلا يدعم الـ API البيع لعميل نقدي غير مسجّل. ابحث عن العميل برقم جواله، أو بقيمة `referenceId` التي أعطيته إياها:

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

إذا كانت `data` فارغة فأنشئ العميل بـ `POST /v1/customers`. وإذا أُرسل طلبان منك في الوقت نفسه وسُجّل الرقم بينهما، يرجع طلب الإنشاء `409 phone_taken`، ويحمل `existingId` معرّف العميل الذي تستخدمه. الطلبان وكل القواعد في صفحة [العملاء](https://docs.kosoor.com.sa/ar/customers.md).

تستخدم الأمثلة التالية عميلين جاهزين في بيئة الاختبار: **Sandbox Person** (`...0001`) وهو فرد، و **Sandbox Standard Co** (`...0002`) وهو شركة.

## 3. أصدر الفاتورة

يُنشئ `POST /v1/sales-invoices` الفاتورة ويصدرها في طلب واحد، بلا مسودة. وكل طلب `POST` يحمل `Idempotency-Key`: إذا ضاع الرد فأرسل الطلب نفسه بالمفتاح نفسه، فيرجع لك الرد على الفاتورة نفسها بدل أن تُنشأ فاتورة ثانية. التفاصيل في [منع تكرار الطلبات](https://docs.kosoor.com.sa/ar/idempotency.md).

### عميل فرد يدفع المبلغ كاملاً

العميل فرد، فالفاتورة مبسطة. و `payment` بدون `amount` يدفع الفاتورة كلها. والسعر هنا شامل الضريبة، لذلك قيمة `pricesIncludeVat` هي `true`.

```bash
curl https://api.kosoor.com.sa/v1/sales-invoices \
  -H "Authorization: Bearer ksr_test_..." \
  -H "Idempotency-Key: 4e9f2a7c-1b3d-4c5e-8f6a-9b0c1d2e3f4a" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "5a4db0c5-0000-4000-8000-000000000001",
    "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
    "pricesIncludeVat": true,
    "lines": [
      { "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "quantity": 1, "unitPrice": 115 }
    ],
    "payment": { "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9" },
    "referenceId": "order-20417"
  }'
```

الرد `201 Created`:

```json
{
  "id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
  "object": "salesInvoice",
  "number": "INV-1042",
  "issueDate": "2026-11-07",
  "supplyDate": null,
  "dueDate": null,
  "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
  "customerId": "5a4db0c5-0000-4000-8000-000000000001",
  "currency": "SAR",
  "subtotal": 100,
  "discountTotal": 0,
  "vatTotal": 15,
  "total": 115,
  "amountPaid": 115,
  "amountDue": 0,
  "lines": [
    {
      "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
      "name": "Full wash",
      "description": null,
      "quantity": 1,
      "unitPrice": 100,
      "unitDiscount": 0,
      "vatRate": 15,
      "vatAmount": 15,
      "lineTotal": 115
    }
  ],
  "notes": null,
  "costCentreId": null,
  "referenceId": "order-20417",
  "metadata": {},
  "createdVia": "api",
  "zatca": {
    "kind": "simplified",
    "status": "pending",
    "uuid": null,
    "counter": null,
    "hash": null,
    "qr": null,
    "submittedAt": null,
    "clearedAt": null,
    "reportedAt": null,
    "errors": []
  }
}
```

اقرأ من الرد:

- **أسعار البنود قبل الضريبة.** الـ 115 التي أرسلتها شاملة الضريبة تظهر `unitPrice: 100` مع `vatAmount: 15`.
- **`total` هو ما يدفعه العميل.** قارنه بما يتوقعه نظامك: الخصم الذي ينزل بالسعر عن الحد الأدنى للصنف يُخفَّض، والسعر الشامل للضريبة قد يغيّر الإجمالي بهللة. التفاصيل في [فواتير المبيعات](https://docs.kosoor.com.sa/ar/sales-invoices.md).
- **قيمة `zatca.status` هي `pending`.** الفاتورة المبسطة تُبلَّغ بها هيئة الزكاة والضريبة والجمارك بعد الرد، ثم تصبح `reported`. وتبقى حقول المستند `null` حتى توقّعه كسور. التفاصيل في [نتائج الهيئة](https://docs.kosoor.com.sa/ar/zatca.md).

ويبقى المبلغ على الفاتورة نفسها (`amountPaid`)، فالبيع لعميل فرد مدفوعاً عند الإصدار لا تُنشأ له دفعة منفصلة.

### شركة بالآجل

العميل شركة، فالفاتورة ضريبية، وتعتمدها الهيئة قبل أن ترد كسور. وبدون `payment` تُسجَّل الفاتورة كلها على حساب الشركة. والأسعار هنا قبل الضريبة.

```bash
curl https://api.kosoor.com.sa/v1/sales-invoices \
  -H "Authorization: Bearer ksr_test_..." \
  -H "Idempotency-Key: 0c5b7d3e-9f2a-4b1c-8d6e-3a4f5b6c7d8e" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "5a4db0c5-0000-4000-8000-000000000002",
    "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
    "pricesIncludeVat": false,
    "lines": [
      { "itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a", "quantity": 2, "unitPrice": 100, "description": "Plates ABC 1234 and XYZ 5678" }
    ],
    "dueDate": "2026-12-07",
    "notes": "Fleet contract 2026-14",
    "referenceId": "order-20418",
    "metadata": { "fleetContract": "2026-14" }
  }'
```

الرد `201 Created`:

```json
{
  "id": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
  "object": "salesInvoice",
  "number": "INV-1043",
  "issueDate": "2026-11-07",
  "supplyDate": null,
  "dueDate": "2026-12-07",
  "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
  "customerId": "5a4db0c5-0000-4000-8000-000000000002",
  "currency": "SAR",
  "subtotal": 200,
  "discountTotal": 0,
  "vatTotal": 30,
  "total": 230,
  "amountPaid": 0,
  "amountDue": 230,
  "lines": [
    {
      "itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a",
      "name": "Fleet wash",
      "description": "Plates ABC 1234 and XYZ 5678",
      "quantity": 2,
      "unitPrice": 100,
      "unitDiscount": 0,
      "vatRate": 15,
      "vatAmount": 30,
      "lineTotal": 230
    }
  ],
  "notes": "Fleet contract 2026-14",
  "costCentreId": null,
  "referenceId": "order-20418",
  "metadata": { "fleetContract": "2026-14" },
  "createdVia": "api",
  "zatca": {
    "kind": "standard",
    "status": "cleared",
    "uuid": "e7f8a9b0-c1d2-4e3f-a4b5-c6d7e8f9a0b1",
    "counter": 18,
    "hash": "zXab+32ksuccBzrDiE27f65AmOvbZ2ixbnSSRnoRKzA=",
    "qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDdUMDk6NDE6MjJaBAYyMzAuMDAFBTMwLjAw",
    "submittedAt": "2026-11-07T09:41:21.884Z",
    "clearedAt": "2026-11-07T09:41:22.517Z",
    "reportedAt": null,
    "errors": []
  }
}
```

قيمة `zatca.status` هي `cleared`، ويحمل `zatca.qr` رمز QR الذي أرجعته الهيئة. وإذا لم ترد الهيئة في الوقت المحدد فالرد يبقى `201` لكن بـ `status: "pending"`، وتعتمدها كسور لاحقاً. وإذا رفضتها الهيئة فلا يُحفظ شيء، ويرجع الطلب `422 zatca_rejected`. الحالتان موضحتان في [نتائج الهيئة](https://docs.kosoor.com.sa/ar/zatca.md).

البيع بالآجل للشركات فقط. والفاتورة لعميل فرد بدون `payment`، أو مع `payment.amount` أقل من الإجمالي، ترجع `422 on_account_not_allowed`.

### استلام جزء من المبلغ عند الإصدار

إذا كانت `payment.amount` أقل من الإجمالي، يُدفع هذا المبلغ عند الإصدار، ويبقى الباقي على حساب الشركة:

```json
{
  "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
  "amount": 100
}
```

والمبلغ الأكبر من الإجمالي يرجع `400 invalid_parameter`. ولدفع الفاتورة كاملة لا ترسل `amount`، بدل أن ترسل الإجمالي الذي حسبته بنفسك.

## 4. سجّل الدفعة لاحقاً

عندما تدفع الشركة، سجّل المبلغ بـ `POST /v1/payments` وخصّصه للفاتورة:

```bash
curl https://api.kosoor.com.sa/v1/payments \
  -H "Authorization: Bearer ksr_test_..." \
  -H "Idempotency-Key: 7a1b2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "5a4db0c5-0000-4000-8000-000000000002",
    "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
    "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "amount": 230,
    "date": "2026-11-09",
    "allocations": [
      { "invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "amount": 230 }
    ],
    "referenceId": "bank-tx-55190"
  }'
```

الرد `201 Created`:

```json
{
  "id": "d3e4f5a6-b7c8-4d9e-8f0a-2b3c4d5e6f7a",
  "object": "payment",
  "number": "RCT-0031",
  "date": "2026-11-09",
  "customerId": "5a4db0c5-0000-4000-8000-000000000002",
  "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
  "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
  "amount": 230,
  "discount": 0,
  "allocations": [
    { "invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "amount": 230 }
  ],
  "unallocatedAmount": 0,
  "notes": null,
  "referenceId": "bank-tx-55190",
  "metadata": {},
  "createdVia": "api"
}
```

بعدها يعرض `GET /v1/sales-invoices/c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f` القيمتين `amountPaid: 230` و `amountDue: 0`. والمبلغ الذي لا تخصصه يبقى رصيداً دائناً للعميل، ويظهر في `unallocatedAmount`. كل القواعد في صفحة [المدفوعات](https://docs.kosoor.com.sa/ar/payments.md).

## 5. أعطِ العميل الفاتورة

يرجع `GET /v1/sales-invoices/{id}/pdf` الفاتورة بمقاس A4. ملف PDF للفاتورة المبسطة جاهز فوراً، أما الفاتورة الضريبية فيرجع ملفها `409 invoice_not_cleared` حتى تعتمدها الهيئة.

## ما الذي قد يحدث

| الخطأ | المعنى | ماذا تفعل |
|---|---|---|
| `422 customer_address_incomplete` | الفاتورة الضريبية تحتاج عنوان الشركة كاملاً مع الرقم الضريبي أو رقم الهوية، والحقل `missing` يوضح الناقص | أكمل الناقص بـ `PATCH /v1/customers/{id}`، ثم أرسل الفاتورة مرة أخرى |
| `422 on_account_not_allowed` | عميل فرد لم يدفع المبلغ كاملاً | أرسل `payment` بدون `amount` |
| `422 credit_limit_exceeded` | حساب الشركة سيتجاوز حد الائتمان | استلم المبلغ عند الإصدار، أو راجع التاجر |
| `422 customer_inactive` | عطّل التاجر هذا العميل | راجع التاجر |
| `422 item_not_sellable` | الصنف اشتراك، أو غير معروض للبيع | استخدم صنفاً آخر |
| `400 missing_parameter` على `lines[i].unitPrice` | الصنف مسعّر حسب حجم السيارة | أرسل `unitPrice` |
| `403 branch_not_allowed` | المفتاح مقصور على فروع أخرى | استخدم فرعاً من `GET /v1/branches` |
| `422 branch_not_onboarded` | الفرع غير مربوط بالهيئة، فلا يستطيع إصدار فاتورة ضريبية | راجع التاجر |
| `422 zatca_rejected` | رفضت الهيئة الفاتورة الضريبية، ولم يُحفظ شيء | اقرأ `zatcaErrors`، وصحّح البيانات، ثم أرسلها مرة أخرى بـ `Idempotency-Key` جديد |

كل الأكواد في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md).

المصدر: https://docs.kosoor.com.sa/ar/record-a-sale
