المفاهيم الأساسية
الكيانات (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
CreditRequest
طلب تقديم آجل يتم مراجعته من الإدارة (قبول/رفض) مع تخزين القرار والحد والمدة.
CreditLedgerEntry
سجل حركات الآجل: purchase / repayment / admin_adjustment / freeze / unfreeze / disable / reenable
تدفقات الاستخدام (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) حظر الدفع على الحسابات المجمّدة
عند محاولة الدفع بالآجل
|
| إذا كان status = frozen
v
403 Forbidden: "Your credit account is frozen. Please contact the admin to unfreeze it."
إذا كان status = disabled
v
400 Bad Request: "Credit account is not active"
إذا كان status = active فقط يُسمح بالمتابعة
6) التجميد عند نهاية المدة (Scheduler)
كل ساعة (Cron)
|
| إذا termDueAt انتهت و reachedFullLimitInTerm=false و usedPoints<limitPoints
v
CreditAccount.status = frozen
أمثلة الحالات (كما طلبت)
النتيجة: يعود له رصيد متاح 4000 فورًا، ويتم تمديد المدة لشهر جديد من تاريخ السداد، ولا يتم إيقاف الحساب.
النتيجة: عند نهاية المدة يتم تجميد حسابه الآجل ويحتاج طلب آجل جديد لإعادة تقييم احتياجه.
الإشعارات (Notifications)
النظام يرسل إشعارات إلى كل الـAdmins وإلى صاحب الطلب/الحساب لكل الأحداث المتعلقة بالآجل.
NotificationType:
credit_request_created
credit_request_approved
credit_request_rejected
credit_account_frozen
credit_account_unfrozen
credit_account_disabled
credit_account_enabled
يتم الإرسال عبر جدول notifications + Push Notification (Firebase topic لكل مستخدم) + WhatsApp إن كان مفعّل.
نقاط النهاية (API)
جميع المسارات التالية تتطلب إرسال هيدر: Authorization: Bearer <token> ما لم يُذكر خلاف ذلك.
Customer APIs
إنشاء طلب آجل (User أو Company). يتم إرسال إشعارات للـAdmins ولصاحب الطلب.
{
"ownerType": "user", // "user" | "company"
"ownerCompanyId": null, // مطلوب فقط عند ownerType=company
"requestedLimitSar": "5000",
"companyName": "",
"phone": "+9665...",
"email": "customer@example.com",
"commercialRegisterNumber": "",
"notes": "احتاج حد للشراء الشهري"
}
عرض حساب/حسابات الآجل للمستخدم (قد يعيد حساب مستخدم + حساب شركة إن كان مالك شركة).
Admin APIs
جلب حسابات الآجل مع بيانات المستخدم/الشركة المرتبطة بها.
الفلاتر المتاحة:
status (active | frozen | disabled) •
ownerType (user | company) •
search (اسم المستخدم الأول/الأخير أو اسم الشركة) •
page •
limit (max 100)
// Response
{
"items": [
{
"id": "uuid",
"ownerType": "user",
"ownerUser": { "id": "...", "firstName": "...", "lastName": "...", "email": "..." },
"ownerCompany": null,
"status": "active",
"limitSar": "5000.00",
"limitPoints": "500000",
"usedSar": "1200.00",
"usedPoints": "120000",
"termStartAt": "2026-04-01T00:00:00.000Z",
"termDueAt": "2026-05-01T00:00:00.000Z",
"freezeReason": null,
...
}
],
"total": 42,
"page": 1,
"limit": 20
}
جلب طلبات الآجل حسب الحالة: pending/approved/rejected.
قبول الطلب وتحديد الحد والمدة. يقوم النظام بإنشاء/تحديث CreditAccount.
{
"limitSar": "5000",
"termDueAtIso": "2026-05-02T00:00:00.000Z",
"platformFeePointsPerKg": 1
}
رفض الطلب مع سبب اختياري.
{
"reason": "لا يوجد سجل مشتريات كافي"
}
تجميد حساب الآجل يدويًا (مع سبب اختياري).
{
"reason": "مطلوب تحديث بيانات العميل"
}
فك تجميد حساب الآجل يدويًا (يعيد status إلى active).
{
"reason": "تم تحديث البيانات"
}
إعادة تفعيل حساب آجل مجمَّد أو معطَّل مع تحديد حد جديد ومدة جديدة (يبدأ العداد من جديد، usedPoints/usedSar تُصفَّر).
يُسجَّل حدث reenable في CreditLedgerEntry ويُرسَل إشعار للمالك والـAdmins.
خطأ 400 إذا كان الحساب فعّالاً مسبقًا.
{
"limitSar": "8000", // مطلوب — الحد الجديد
"termDurationDays": 30, // أو
"termDueAtIso": "2026-09-01T00:00:00Z", // أحدهما مطلوب
"platformFeePointsPerKg": 1, // اختياري
"note": "تجديد بعد السداد الكامل" // اختياري
}
// Response: كائن CreditAccount بالحالة المحدّثة (status=active)
تعطيل حساب الآجل نهائيًا (status = disabled). لا يمكن الدفع بالآجل من حساب معطّل.
يُسجَّل حدث disable في CreditLedgerEntry ويُرسَل إشعار للمالك والـAdmins.
خطأ 400 إذا كان الحساب معطّلاً مسبقًا.
{
"reason": "إنهاء التعاقد" // اختياري
}
// Response: كائن CreditAccount بالحالة المحدّثة
Checkout with Credit
دفع طلب منتجات بالآجل. يقوم النظام بالتالي داخل Transaction: زيادة استخدام الآجل + إنشاء Ledger purchase + Mint للبائع + تسجيل رسوم المنصة.
{
"ownerType": "user", // اختياري
"ownerCompanyId": null // اختياري
}
دفع طلب خدمة بالآجل. يقوم النظام بزيادة استخدام الآجل + Mint للمقدّم + تسجيل ربح المنصة.