# TASKS.md — خطة تنفيذ SMM Panel للوكيل البرمجي > نفّذ المهام بالترتيب. كل مرحلة لازم "تشتغل وتُختبر" قبل ما تنتقل للي بعدها. > المرجع الكامل: `AGENTS.md` (السباق العام) و`WALLET.md` (تفاصيل الحساب والمحفظة — إلزامي قراءته قبل مرحلة 2 و3) > و`FRONTEND.md` (شرح الواجهة الحالية + مواصفات دقيقة للمراحل 9 و10 — إلزامي قراءته قبلهن). > علّم كل مهمة [x] بعد ما تخلص وتتأكد إنها تشتغل فعلياً (شغّلها، ما تفترض). --- ## المرحلة 0 — الإعداد - [x] إنشاء مشروع Node.js + Express، هيكلة المجلدات حسب `AGENTS.md` § 3. - [x] إعداد الاتصال بقاعدة بيانات MySQL/MariaDB (connection pool، ليس اتصال مفتوح لكل طلب). - [x] إعداد نظام migrations (Knex / Prisma / أي أداة) — ممنوع تعديل السكيمة يدوياً بدون migration. - [x] إنشاء ملف `.env.example` بكل المتغيرات المذكورة في `AGENTS.md` § 9. - [x] إعداد error handler مركزي (middleware) يرجّع رسائل موحدة وما يسرّب stack trace بالإنتاج. - [x] إعداد logging أساسي (بدون تسجيل باسوردات أو مفاتيح API إطلاقاً). **معيار القبول:** السيرفر يشتغل، يتصل بقاعدة البيانات، ويرجّع 200 على `GET /health`. --- ## المرحلة 1 — قاعدة البيانات - [x] إنشاء جدول `users` حسب `AGENTS.md` § 4 (بما فيها `balance DECIMAL(12,4) DEFAULT 0`). - [x] إنشاء جدول `services`. - [x] إنشاء جدول `orders`. - [x] إنشاء جدول `transactions` — مع `UNIQUE` على (`user_id`, `reference`, `type`) لمنع التكرار (`WALLET.md` § 2.2). - [x] إنشاء جدول `deposits` — مع `UNIQUE` على `tx_ref` (`WALLET.md` § 2.3-أ). - [x] (اختياري لكن موصى) جدول `idempotency_keys` حسب `WALLET.md` § 3. - [x] (اختياري) جدول `refresh_tokens` لإدارة الجلسات حسب `WALLET.md` § 1.2. **معيار القبول:** كل الجداول موجودة، الـ FKs والـ UNIQUE constraints فعّالة (جرّب إدخال قيمة مكررة وتأكد إنها تُرفض). --- ## المرحلة 2 — نظام الحساب (Auth) - [x] `POST /auth/register` — تشفير الباسورد بـ bcrypt، تحقق من تفرّد username/email. - [x] `POST /auth/login` — إصدار JWT قصير المدة + refresh token، رسالة خطأ موحدة (`WALLET.md` § 1.2). - [x] Rate limiting على `/auth/login` و`/auth/register` (مثلاً 5 محاولات/15 دقيقة). - [x] Middleware `auth` (تحقق JWT) و`requireActive` (يرفض المستخدم المحظور) و`admin` (تحقق role). - [x] `POST /auth/logout` — إبطال الـ refresh token. - [x] `POST /auth/forgot-password` + `POST /auth/reset-password` (token عشوائي مؤقت، يبطل الجلسات القديمة عند إعادة التعيين). - [x] `GET /me` — يرجّع بيانات الحساب + الرصيد الحالي. **معيار القبول:** - تسجيل حساب جديد ثم دخول فيه يرجّع JWT صالح. - محاولة دخول بباسورد غلط 6 مرات متتالية تُحظر مؤقتاً. - طلب لمسار محمي بدون توكن يرجّع 401. --- ## المرحلة 3 — المحفظة (Wallet) — أهم مرحلة، لا تستعجل فيها اقرأ `WALLET.md` كامل قبل البدء هون. - [x] بناء `wallet.service.js` — نقطة الدخول الوحيدة لأي تعديل على `balance`. لا كود ثاني يلمس عمود `balance` مباشرة. - [x] دالة `chargeBalance(userId, amount, reference, note)` — قفل الصف (`FOR UPDATE`) + تحقق الرصيد + خصم + تسجيل transaction، الكل بـ DB transaction واحدة. - [x] دالة `creditBalance(userId, amount, reference, note)` — نفس الآلية للإضافة (شحن/استرجاع). - [x] `GET /transactions` — كشف حساب المستخدم مرتب زمنياً مع `balance_after` لكل حركة. - [x] `POST /deposits` — تقديم طلب شحن (amount, method, tx_ref)، رفض فوري لو `tx_ref` مكرر. - [x] `GET /deposits` — طلبات الشحن الخاصة بالمستخدم وحالتها. - [x] `PATCH /admin/deposits/:id` — موافقة/رفض، بقفل الصف ومنع الموافقة المزدوجة (`WALLET.md` § 2.3-ب). - [x] `PATCH /admin/users/:id` (تعديل رصيد يدوي) — يمر حصراً عبر `wallet.service.js`. - [x] **اختبارات إلزامية** (`WALLET.md` § 4): - [x] خصم أكبر من الرصيد يُرفض ولا يغيّر الرصيد. - [x] عمليتان متزامنتان (parallel) على نفس الرصيد → وحدة بس تنجح. - [x] موافقتان متزامنتان على نفس طلب شحن → إضافة وحدة بس. - [x] `tx_ref` مكرر → رفض بدون لمس الرصيد. - [x] بعد سلسلة عمليات عشوائية: `SUM(transactions.amount) == users.balance`. - [x] job دوري (cron) للتسوية (Reconciliation) — يقارن `SUM(transactions)` مع `users.balance` ويرسل تنبيه عند أي فرق (`WALLET.md` § 2.4). **معيار القبول:** كل الاختبارات أعلاه خضراء فعلياً (شغّلها، شوف النتيجة)، ولا يوجد أي مسار كود يعدّل `balance` خارج `wallet.service.js` (تأكد بالبحث النصي بالمشروع). --- ## المرحلة 4 — الخدمات (Services) - [x] `provider.service.js` — طبقة موحّدة للتواصل مع API المزوّد (`services` / `add` / `status` / `balance`) حسب `AGENTS.md` § 5. - [x] `POST /admin/services/sync` — سحب الخدمات من المزوّد وتخزينها/تحديثها محلياً. - [x] منطق حساب `sell_price` من `provider_price` + الهامش (`DEFAULT_MARKUP_PERCENT` أو هامش لكل خدمة — حسب القرار المفتوح في `AGENTS.md` § 11). - [x] `GET /services` — قائمة الخدمات النشطة فقط، بسعر البيع (لا تُظهر `provider_price` للمستخدم العادي أبداً). - [x] `PATCH /admin/services/:id` — تعديل السعر/الهامش/التفعيل من لوحة الأدمن. **معيار القبول:** مزامنة الخدمات تعمل، والمستخدم العادي ما يقدر يشوف سعر المزوّد الحقيقي بأي استجابة API. --- ## المرحلة 5 — الطلبات (Orders) — الجزء اللي يربط المحفظة بالمزوّد - [x] `POST /orders` — تنفيذ التدفق الكامل خطوة بخطوة (`AGENTS.md` § 6): 1. تحقق الخدمة `is_active` والكمية ضمن `[min, max]`. 2. احسب `charge` **في الخادم فقط** (لا تثق بأي سعر من الواجهة). 3. `chargeBalance()` من المرحلة 3 (خصم + إنشاء order بحالة `pending` + transaction، الكل atomic). 4. أرسل الطلب لمزوّد الخدمة عبر `provider.service.js`. 5. نجاح → خزّن `provider_order_id`، حدّث `status=processing`. 6. فشل → استرجاع فوري عبر `creditBalance()` (type=`refund`)، حدّث `status=failed`. - [x] `GET /orders` و`GET /orders/:id` — طلبات المستخدم (تأكد إنه يشوف طلباته هو فقط، لا طلبات غيره). - [x] `GET /admin/orders` — كل الطلبات لعرض الأدمن. **معيار القبول:** طلب ناجح يخصم المبلغ الصحيح بالضبط ويوصل provider_order_id، وطلب فاشل (مزوّد down مثلاً) يرجع الرصيد تلقائياً بدون تدخل يدوي. --- ## المرحلة 6 — متابعة الحالات (Status Tracking) - [x] job دوري (مثلاً كل 5-10 دقائق) يمر على الطلبات بحالة `processing`/`in_progress` ويستعلم عن حالتها من المزوّد (`action: status`). - [x] تحديث `status` و`remains` بناءً على رد المزوّد. - [x] معالجة الحالات الجزئية (`partial`) — قرار: هل يُسترجع فرق المبلغ تلقائياً أم يحتاج مراجعة أدمن؟ (وضّح القرار بكود وتوثيق). **معيار القبول:** طلب تجريبي يتحدث تلقائياً لحالته الحقيقية عند المزوّد خلال دورة الـ job التالية. --- ## المرحلة 7 — لوحة الأدمن - [x] `GET /admin/users` + `PATCH /admin/users/:id` (حظر/رفع حظر، تعديل رصيد). - [x] `GET /admin/deposits` (المعلّقة أولاً) + الموافقة/الرفض. - [x] `GET /admin/stats` — إجمالي المبيعات، الأرباح (sell - provider cost)، عدد المستخدمين النشطين، طلبات معلّقة. - [x] كل مسارات هذا القسم خلف middleware `admin` بدون استثناء — تحقق فعلياً بمحاولة وصول بحساب عادي وتأكد من 403. **معيار القبول:** حساب `role=user` يحاول الوصول لأي مسار أدمن → 403، وحساب أدمن يشوف كل البيانات صح. --- ## المرحلة 8 — الواجهة (Frontend) - [x] صفحات: تسجيل / دخول / لوحة المستخدم (الرصيد + كشف حساب) / الخدمات / إنشاء طلب / طلباتي / شحن رصيد. - [x] لوحة أدمن منفصلة (خدمات، طلبات شحن، طلبات، مستخدمين، إحصائيات). - [x] عرض الرصيد بشكل بارز دائماً (Header) ويتحدث فور أي عملية. - [x] رسائل خطأ واضحة بالعربية (رصيد غير كافٍ، رابط غير صالح، إلخ). **معيار القبول:** مستخدم جديد يقدر يسوي التدفق الكامل من الواجهة: تسجيل → طلب شحن → (موافقة أدمن) → شراء خدمة → متابعة الحالة، بدون لمس الـ API مباشرة. --- ## المرحلة 9 — نظام الألوان والتصميم المتجاوب اقرأ `FRONTEND.md` § 3 كامل قبل البدء هون. - [x] وحّد متغيرات اللون الأساسي بـ `styles.css` تحت `--primary-h` / `--primary-s` / `--primary-l` واشتق منها `--primary` / `--primary-hover` / `--primary-soft` (استبدل كل استخدام لـ `--accent` / `--accent-strong` / `--blue` بالمتغيرات الجديدة). - [x] أضف زر Theme Picker بالـ `topbar`: 6 ألوان جاهزة (Presets) + خيار لون مخصص (``). - [x] عند اختيار لون: حوّل HEX إلى HSL بـ JS، حدّث المتغيرات الثلاث على `document.documentElement`، واحفظ الاختيار بـ `localStorage`. - [x] طبّق اللون المحفوظ فوراً بأول تنفيذ للسكربت (قبل أول رسم) لمنع أي وميض بلون افتراضي. - [x] **(تحسين مطلوب — راجع `FRONTEND.md` § 3.3 "نسخة منقّحة")** الزر الحالي دائرة صغيرة بدون تسمية وحسّه بدائي — بدّله بزر فيه أيقونة (SVG بسيط) + نص "المظهر" (يختفي على الموبايل، أيقونة بس). - [x] لوحة الألوان: كبّر الشبكة (44px بدل 34px) وأضف اسم تحت كل لون (تركواز/أزرق/بنفسجي/أخضر/برتقالي/وردي). - [x] بيّن اللون المفعّل حالياً بعلامة واضحة (حلقة حد + ✓) على الـ Preset المطابق. - [x] قسم اللون المخصص: زد حجم ``، أضف حقل نصي HEX مرتبط ثنائياً معه، وشريحة معاينة حيّة تتحدث أثناء السحب قبل التثبيت. - [x] اللوحة تنغلق بالضغط خارجها، بزر × صريح، أو بمفتاح Escape — مع انتقال فتح/إغلاق ناعم (`transition`). - [x] لا تغيّر آلية الحفظ/التطبيق بـ `localStorage` ولا الـ HEX→HSL — هذا الجزء شغّال صح، خلّيه زي ما هو. - [x] الألوان الدلالية (نجاح/تحذير/خطر لحالات الطلبات والحظر) تبقى ثابتة ومنفصلة عن اللون الأساسي — لا تتأثر بالثيمنج. - [x] أضف كسرين إضافيين للتجاوب (640px و1024px) بدل الكسر الواحد الحالي (900px). - [x] القائمة الجانبية: ثابتة ≥1024px، صف علوي بالتابلت، Drawer منزلق بزر ☰ تحت 640px مع خلفية قابلة للإغلاق. - [x] الجداول الكثيفة (الطلبات، كشف الحساب، طلبات الشحن) تتحول لعرض بطاقات (Card view) تحت 640px بدل التمرير الأفقي. - [x] `.inline-form` تتكدس تدريجياً (عمودين بالتابلت، عمود واحد بالموبايل). - [x] ارتفاع الأزرار وحقول الإدخال لا يقل عن 44px بالموبايل. - [x] `topbar` يلتف (`flex-wrap`) بدل ما يفيض خارج الشاشة على الموبايل. **معيار القبول:** غيّر اللون الأساسي وأعد تحميل الصفحة — نفس اللون يرجع فوراً بدون وميض. افتح الموقع بعرض موبايل (375px) وتابلت (768px) ودسكتوب — كل صفحة (بما فيها الجداول والفورمات ولوحة الأدمن) قابلة للاستخدام بدون تمرير أفقي مزعج أو عناصر متراكبة على بعض. --- ## المرحلة 10 — طرق شحن رصيد حقيقية (PayPal / شام كاش / USDT ...) اقرأ `FRONTEND.md` § 4 كامل قبل البدء هون. هذا تعديل على `WALLET.md` § 2.3 — يبقى نفس مبدأ "شحن يدوي بموافقة أدمن"، بس بقنوات محددة وتعليمات واضحة بدل حقل نص حر. - [x] Migration: جدول `payment_methods` (`id`, `code` UNIQUE, `label_ar`, `instructions`, `destination`, `is_active`, `sort_order`, `created_at`, `updated_at`). - [x] Migration/Seed: أضف قنوات ابتدائية على الأقل PayPal وشام كاش وUSDT (تعليمات placeholder، الأدمن يعبّي البيانات الحقيقية لاحقاً). - [x] `GET /payment-methods` — القنوات النشطة فقط (يتطلب تسجيل دخول، أي مستخدم). - [x] `GET /admin/payment-methods` — كل القنوات (نشطة وموقوفة). - [x] `POST /admin/payment-methods` — إضافة قناة جديدة. - [x] `PATCH /admin/payment-methods/:id` — تعديل/تفعيل/إيقاف قناة. - [x] عدّل تحقق `POST /deposits` (`zod`): `method` لازم يطابق `code` قناة **نشطة** فعلياً، وإلا رفض فوري — لا تقبل أي نص حر بعد اليوم. - [x] الواجهة: بدّل حقل "الطريقة" النصي بـ `