Invare • توثيق نظام الآجل

نظام الآجل (Revolving Credit / Pay by Debt)

هذا المستند يشرح نظام الآجل المدمج مع الطلبات (Orders) وطلبات الخدمات (Service Orders). يتيح للمشتري الشراء ضمن حد ائتماني معتمَد من الإدارة، بينما تقوم المنصة بدفع البائع/مقدم الخدمة فورًا عبر Mint Points.

المفاهيم الأساسية

وحدة التحويل
1 ريال = 100 نقطة
هدف المنصة
دفع البائع فورًا + متابعة دين المشتري
مبدأ محوري: الدفع بالآجل لا يعني انتظار البائع — المنصة تموّل العملية مباشرة بالنقاط، ثم يتم تتبع الدين على حساب الآجل للمشتري.

الكيانات (Entities)

المسارات داخل المشروع:

/src/modules/credit/
  credit-account.entity.ts
  credit-request.entity.ts
  credit-ledger-entry.entity.ts

CreditAccount

يمثّل حساب الآجل (لـ User أو Company) ويحتوي على الحد المستخدم والمتاح وحالة الحساب والمدة.

limitSar / limitPoints
usedSar / usedPoints
platformFeePointsPerKg (اختياري)
termStartAt / termDueAt
status: active | frozen | disabled
reachedFullLimitInTerm: boolean
عمولة منصة مخصصة للآجل: يمكن للإدارة تحديد platformFeePointsPerKg لكل حساب آجل عند الموافقة. عند الشراء بالآجل سيتم احتساب رسوم المنصة لكل KG بهذه القيمة بدل العمولة الافتراضية.

CreditRequest

طلب تقديم آجل يتم مراجعته من الإدارة (قبول/رفض) مع تخزين القرار والحد والمدة.

CreditLedgerEntry

سجل حركات الآجل: purchase / repayment / freeze / unfreeze

تدفقات الاستخدام (Flows)

1) تدفق طلب الآجل + المراجعة

مستخدم/شركة
  |
  |  POST /credit/requests
  v
CreditRequest: pending
  |
  |  (Admin)
  |  POST /credit/admin/requests/:id/approve  أو  /reject
  v
CreditAccount: active + حد + مدة

2) الشراء بالآجل (Orders)

Buyer
  |
  | POST /orders/:id/checkout/credit
  | - تحقق من CreditAccount (active + limit)
  | - احتساب عمولة المنصة لكل KG (platformFeePointsPerKg إن وُجد)
  | - زيادة usedPoints/usedSar + ledger purchase
  | - Mint نقاط للبائع فورًا
  | - تسجيل رسوم المنصة (fees)
  v
Order: awaiting_fulfillment (paymentMethod=credit)

3) الشراء بالآجل (Service Orders)

Buyer
  |
  | POST /service-orders/:id/pay-credit
  | - تحقق من CreditAccount (active + limit)
  | - زيادة usedPoints/usedSar + ledger purchase
  | - Mint basePoints للمقدّم فورًا
  | - تسجيل ربح المنصة (profit)
  v
ServiceOrder: paid

4) السداد التلقائي (من المبيعات)

عند اكتمال الطلب (Order COMPLETED)
  |
  | net = total - (buyerFee + sellerFee)
  | ملاحظة: عند الدفع بالآجل يتم احتساب صافي السداد بناءً على الرسوم الفعلية التي تم خصمها وقت الشراء
  | (باستخدام order.totalPoints لضمان التطابق حتى لو تغيّرت إعدادات العمولة لاحقًا)
  | applyRepaymentFromSale(net)
  v
CreditAccount.usedPoints ينقص + ledger repayment

ملاحظة: السداد هنا idempotent (لن يتكرر لنفس orderId).

5) التجميد عند نهاية المدة (Scheduler)

كل ساعة (Cron)
  |
  | إذا termDueAt انتهت و reachedFullLimitInTerm=false و usedPoints<limitPoints
  v
CreditAccount.status = frozen

أمثلة الحالات (كما طلبت)

الحالة 1: زبون حدّه 5000 لمدة شهر، استخدمها (وصل الحد بالكامل)، وبعد 15 يوم سدّد 4000.
النتيجة: يعود له رصيد متاح 4000 فورًا، ويتم تمديد المدة لشهر جديد من تاريخ السداد، ولا يتم إيقاف الحساب.
الحالة 2: زبون وافقوا له 5000 لمدة 30 يوم ولم يستخدمها كاملة.
النتيجة: عند نهاية المدة يتم تجميد حسابه الآجل ويحتاج طلب آجل جديد لإعادة تقييم احتياجه.

الإشعارات (Notifications)

النظام يرسل إشعارات إلى كل الـAdmins وإلى صاحب الطلب/الحساب لكل الأحداث المتعلقة بالآجل.

NotificationType:
  credit_request_created
  credit_request_approved
  credit_request_rejected
  credit_account_frozen
  credit_account_unfrozen

يتم الإرسال عبر جدول notifications + Push Notification (Firebase topic لكل مستخدم) + WhatsApp إن كان مفعّل.

نقاط النهاية (API)

جميع المسارات التالية تتطلب إرسال هيدر: Authorization: Bearer <token> ما لم يُذكر خلاف ذلك.

Customer APIs

POST /credit/requests

إنشاء طلب آجل (User أو Company). يتم إرسال إشعارات للـAdmins ولصاحب الطلب.

{
  "ownerType": "user",            // "user" | "company"
  "ownerCompanyId": null,         // مطلوب فقط عند ownerType=company
  "requestedLimitSar": "5000",
  "companyName": "",
  "phone": "+9665...",
  "email": "customer@example.com",
  "commercialRegisterNumber": "",
  "notes": "احتاج حد للشراء الشهري"
}
GET /credit/accounts/my

عرض حساب/حسابات الآجل للمستخدم (قد يعيد حساب مستخدم + حساب شركة إن كان مالك شركة).

Admin APIs

صلاحيات: هذه المسارات محمية بـ @AdminOnly().
GET /credit/admin/requests?status=pending&page=1&limit=20

جلب طلبات الآجل حسب الحالة: pending/approved/rejected.

POST /credit/admin/requests/:id/approve

قبول الطلب وتحديد الحد والمدة. يقوم النظام بإنشاء/تحديث CreditAccount.

{
  "limitSar": "5000",
  "termDueAtIso": "2026-05-02T00:00:00.000Z",
  "platformFeePointsPerKg": 1
}
platformFeePointsPerKg: عمولة المنصة لكل KG عند الشراء بالآجل لهذا الحساب. إذا لم تُرسل، يستخدم النظام العمولة الافتراضية.
POST /credit/admin/requests/:id/reject

رفض الطلب مع سبب اختياري.

{
  "reason": "لا يوجد سجل مشتريات كافي"
}
POST /credit/admin/accounts/:accountId/freeze

تجميد حساب الآجل يدويًا (مع سبب اختياري).

{
  "reason": "مطلوب تحديث بيانات العميل"
}
POST /credit/admin/accounts/:accountId/unfreeze

فك تجميد حساب الآجل يدويًا.

{
  "reason": "تم تحديث البيانات"
}

Checkout with Credit

POST /orders/:id/checkout/credit

دفع طلب منتجات بالآجل. يقوم النظام بالتالي داخل Transaction: زيادة استخدام الآجل + إنشاء Ledger purchase + Mint للبائع + تسجيل رسوم المنصة.

{
  "ownerType": "user",            // اختياري
  "ownerCompanyId": null           // اختياري
}
POST /service-orders/:id/pay-credit

دفع طلب خدمة بالآجل. يقوم النظام بزيادة استخدام الآجل + Mint للمقدّم + تسجيل ربح المنصة.

ملاحظة حول السداد: حاليًا السداد يتم تلقائيًا عند اكتمال بيع (Order COMPLETED) عبر applyRepaymentFromSale.

ملاحظات فنية

Atomicity: عمليات الشراء بالآجل (زيادة استخدام الآجل + mint للبائع + تسجيل أرباح) تتم داخل Transaction.
Idempotency: السداد التلقائي يتأكد من عدم تكرار Ledger repayment لنفس Order.