1. 12 Sep, 2026 31 commits
    • DevPilot's avatar
      feat(accounting): الحسابات الناقصة من ملف المعالجة المحاسبية بتاع الأستاذ حسيني · 2c6ab082
      DevPilot authored
      مراجعة الملف حساب بحساب على دليل الحسابات: ٥٩ حساب منصوص عليه في الملف،
      منهم ٤٥ كانوا موجودين فعلاً و١٤ ناقصين. الناقصين اتعملوا.
      
      اللي كان ناقص وأهمه الحسابات الوسيطة — والملف كله مبني عليها: البيع
      بيتقيّد على حساب وسيط والتحصيل بيقفله، ومن غيرها مفيش طريقة تفصل
      «العضوية اتباعت» عن «الفلوس اتحصّلت»:
        120415  حساب وسيط — عضويات نقدية
        120416  حساب وسيط — عضويات بالتقسيط
        120417  حساب وسيط — نشاط رياضي
        120418  حساب وسيط — تجديد الاشتراك السنوي
        120419  حساب وسيط — نشاط اجتماعي
        120414  عهدة استمارات العضوية
      
      وبنود إيراد ومستحقات منصوص عليها بالاسم ومكانش ليها حساب:
        410541  إيراد التنازل عن العضوية
        410542  اشتراك الجيم
        410543  الاشتراك الإضافي
        410544  جيم إضافات
        410545  رسوم استخراج الكارنيهات
        410546  عمولة شيكات محصلة
        230822  عمولة شيكات غير محصلة (البيع بفائدة)
        23080602 رسم التنمية
      
      كلها حسابات جديدة — ولا صف قائم اتغيّر ولا قيد اتأثر. الميزان لسه متوازن
      (512,971,418.03 مدين = دائن)، ومفيش حساب بأب مفقود.
      
      ملاحظتين للأستاذ حسيني (محتاجين قراره، مش باجّات):
      - الدمغة العادية والإضافية: في الملف بندين، وفي الدليل حساب واحد
        (23080405 الدمغة). ينفع يفضل واحد أو نفصله لاتنين.
      - ضريبة القيمة المضافة: الملف بيفصلها حسب مصدر الإيراد (استمارات/عضويات/
        كارنيهات/إيجارات ١٪/مصاريف)، والدليل فيه حساب واحد (23080404).
        التفرقة موجودة في ملفات الضرائب (revenue_tax_profiles) فالتقارير تقدر
        تفصل من غير ما نكسر الحساب.
      2c6ab082
    • DevPilot's avatar
      feat(accounting): فلترة بمستوى شجرة الحسابات في ميزان المراجعة والحركات اليومية · 4bd3ad0b
      DevPilot authored
      ميزان المراجعة: فلتر «مستوى الشجرة» فوق الميزان. القيود بتترحّل على
      الحسابات الطرفية بس، فالحساب الأب لو اتعرض لوحده رصيده صفر — عشان كده
      الفلتر بيجمّع أرصدة الأبناء على أقرب أب عند المستوى المطلوب.
      
      اتأكدت إن الإجماليات ما بتتغيّرش ولا بيختل التوازن على أي مستوى:
        بدون فلتر   مدين=513,110,587.03  دائن=513,110,587.03  متوازن
        مستوى ≤1      4 حسابات — نفس الإجمالي، متوازن
        مستوى ≤2     15 حساب   — نفس الإجمالي، متوازن
        مستوى ≤3    120 حساب   — نفس الإجمالي، متوازن
        مستوى ≤4    486 حساب   — نفس الإجمالي، متوازن
        مستوى ≤5    733 حساب   — نفس الإجمالي، متوازن
      والفلتر بينتقل مع تصدير CSV كمان.
      
      الحركات اليومية: نفس الفكرة بس بشكل مناسب للشاشة — تختار المستوى،
      وتختار حساب من المستوى ده، فيعرض حركات الحساب وكل الحسابات اللي تحته
      في الشجرة.
      
      اتضافت helpers مشتركة في LedgerService (accountLevels / accountsAtLevel /
      descendantAccountIds) عشان أي شاشة تانية تستخدم نفس الفلترة من غير تكرار.
      4bd3ad0b
    • DevPilot's avatar
      fix(accounting): غرامة الشيك المرتد ما كانتش بتتطبّق + إعدادات ناقصة من ملف المعالجة المحاسبية · 516d57b6
      DevPilot authored
      مراجعة النظام على ملف «المعالجة المحاسبية للعمليات المالية» بتاع الأستاذ
      حسيني — اللي اتنفّذ واللي كان ناقص:
      
      الإصلاح:
      - فورم ارتداد الشيك كان بيبدأ بغرامة 0.00 دايماً، فالـ50 ج المظبوطة في
        إعدادات رسوم الفروع لشيراتون ما كانتش بتتطبّق غير لو الموظف كتبها
        بإيده وهو فاكرها. دلوقتي الفورم بيجيب الغرامة المعتمدة لفرع الشيك
        تلقائياً وبيوضّح إنها الغرامة المعتمدة للفرع.
      
      الإعدادات اللي اتضافت (منصوص عليها بالأرقام في الملف):
      - عمولة الفيزا لفرع أكتوبر: صفر حتى 10,000 ثم 2% على الزيادة. كانت
        متسجّلة لشيراتون بس، فأي تحصيل بالفيزا في أكتوبر ما كانش بيتحمّل عمولة
        خالص رغم إن الملف بيحدّدها بالظبط.
      - ملف ضريبة القيمة المضافة على الإيجارات 1% — بند «إيراد إيجار المنافذ».
      516d57b6
    • DevPilot's avatar
      feat(accounting): شاشة متابعة خطابات الضمان — سجل تجديدات كامل + تنبيه 45 يوم · 46b84589
      DevPilot authored
      التجديد كان بيكتب فوق تاريخ الانتهاء القديم ويسيب renewal_date واحد، يعني
      تاريخ الخطاب بيضيع: مش عارف اتجدّد كام مرة ولا التواريخ السابقة كانت إيه.
      
      المطلوب اتنفّذ بالكامل:
      - جدول letter_of_guarantee_renewals: كل تجديد صف مستقل فيه رقم التجديد
        وتاريخه وتاريخ الانتهاء السابق والجديد وعمولة التجديد ومين سجّله.
      - الخطاب نفسه ما بيتغيّرش: نفس الرقم ونفس المورد ونفس المشروع. اللي
        بيتحدّث هو تاريخ الانتهاء الحالي وعدّاد التجديدات بس.
      - أعمدة جديدة: supplier_id (المورد/الشركة)، purpose (الغرض)،
        project_name (المشروع)، renewal_count، original_expiry_date.
      - الحالة بقت محسوبة من تاريخ الانتهاء الحالي مش متخزّنة: ساري / قريب
        الانتهاء / منتهي / مُجدد — فـ«قريب الانتهاء» بتتحدّث لوحدها، ولو الخطاب
        اتجدّد التنبيه بيتقاس على التاريخ الجديد تلقائياً.
      - تنبيه قبل الانتهاء بـ45 يوم: بانر في شاشة الخطابات (وكمان بانر أحمر
        للخطابات اللي فات ميعادها ولسه مفتوحة)، وقاعدة في محرك التنبيهات
        (guarantee_expiry) عشان تطلع في الإشعارات كمان.
      - updateStatus بقى يرفض حالة 'renewed' ويوجّه لزر التجديد، عشان محدش
        يغيّر تاريخ الانتهاء من غير ما يتسجّل في السجل.
      
      وباجّات كانت في نفس الشاشة:
      - فلاتر الحالة والنوع كانت بقيم مش موجودة في الـ ENUM أصلاً
        (claimed/cancelled/bid_bond/performance_bond/retention) — يعني الفلترة
        ما كانتش بترجّع حاجة أبداً. اتظبطت على القيم الحقيقية.
      - عمود البنك كان بيقرا issuing_bank والكويري بترجّع bank_name، فكان
        بيطلع «—» دايماً.
      - زر «خطاب ضمان جديد» كان بيتحكم بصلاحية accounting.instruments.manage
        بينما الـ route محتاج accounting.guarantee.manage.
      
      الـ migration اتطبّقت على قاعدة البيانات الحية واتسجّلت (batch 130).
      46b84589
    • DevPilot's avatar
      fix(accounting): إيداع الشيك بالبنك ما كانش بيرحّل قيد + أداة تصفية رصيد شيكات تحت التحصيل · fa934c04
      DevPilot authored
      العميل قال إن رصيد «شيكات تحت التحصيل» طلع ناقص بعد ما عمل قيد التحصيل،
      وإن فيه «رسم تحصيل» اتخصم. السبب الحقيقي مش رسوم — ده باج في الترحيل:
      
      InstrumentPostingService::STATUS_MAP كانت بتسمع لحالة 'under_collection'
      بس، لكن شاشة «إيداع بالبنك» بتحط الحالة 'deposited'. يعني إيداع الشيك
      في البنك ما كانش بيولّد أي قيد خالص (اتأكدت من الداتا: حركة الإيداع
      للشيك TSTD-B1-NR بـ journal_entry_id = NULL).
      
      فالحساب كان بيتقفل بالدائن وقت التحصيل (52,000) من غير ما يكون اتدين
      وقت الإيداع — فيطلع رصيده بالسالب. والرقم اللي العميل شافه «104» هو
      في الحقيقة 104,000 = مجموع الدائن (52,000 مرتين، لأن القيد المجمّع
      اليومي كرّر نفس الحركة وبعدين اتعكس).
      
      الإصلاح:
      - 'deposited' اتضافت لـ STATUS_MAP، فالإيداع بقى يرحّل رجله:
        من ح/ شيكات تحت التحصيل إلى ح/ أوراق قبض.
      - حارس منع تكرار: لو الورقة عدّت على 'deposited' وبعدين 'under_collection'
        (والاتنين نفس الحدث المحاسبي) القيد بيتعمل مرة واحدة بس.
      
      وأداة جديدة في شاشة سد الفجوات — تصفية رصيد شيكات تحت التحصيل:
      السيناريو ده طبيعي جداً: نادي داخل على النظام وعنده رصيد مرحّل من السنة
      اللي فاتت، رقم مجمّع من غير أرقام شيكات ولا أسماء، ولازم يتقفل وإلا
      هيفضل ظاهر للأبد كأن فيه شيكات معلّقة. الشاشة بتوريه:
      - رصيده الحالي، وكام منه شيكات متسجّلة فعلاً وكام رصيد مرحّل من غير شيكات
      - وبيختار الرصيد راح فين: اتحصّل ودخل البنك / ارتد ورجع دين / تسوية رصيد قديم
      - وبيدوّر على الحساب الطرف التاني بالاسم، والنظام بيعمل القيد في الاتجاه
        الصح (سواء الرصيد مدين أو دائن) ومش بيسمح بتصفية أكتر من الرصيد الموجود.
      fa934c04
    • DevPilot's avatar
    • DevPilot's avatar
      fix(accounting): «برسم التحصيل» كانت متخزّنة في الداتابيز مش في الكود بس · f14c5841
      DevPilot authored
      العميل كان محقّ إن الكلمة ظاهرة قدامه — أنا اللي كنت ناقص في الفحص.
      أول مرة دوّرت في الكود بس، فلقيت موضع واحد وغيّرته وقلت خلاص. لكن النص
      الحقيقي اللي المستخدم بيشوفه متخزّن في الداتابيز في ٣ أماكن:
      
      - posting_chain_steps.name_ar = 'إيداع الشيك برسم التحصيل'
        (دي الخطوة رقم ٢ في «دورة الشيكات المستلمة» — يعني بالظبط الشاشة اللي
         أي حد بيدوّر على «القاعدة» هيروحلها)
      - posting_chains.description_ar فيها 'يتودّع برسم التحصيل'
      - instrument_movements.notes = 'إيداع الشيك بالبنك برسم التحصيل'
        (ودي على نفس الشيك اللي العميل بيتكلم عنه — TSTD-B1-NR)
      
      النص اتغيّر لـ «تسليم الشيك للبنك للتحصيل»، واتضافت جملة صريحة على وصف
      الخطوة: «مفيش أي رسوم بتتخصم في الخطوة دي — قيمة الشيك بتتنقل كاملة».
      
      تأكيد إن مفيش رسم فعلاً (ده اللي كان صح من الأول):
      قيد JE-2026-001095 = من ح/ البنك 52,000 إلى ح/ شيكات تحت التحصيل 52,000.
      ولا سطر قيد واحد في النظام كله بقيمة رسم تحصيل، ولا الورقة عليها
      bank_charge أو fee_charged (كلهم صفر)، ولا فيه كود بيخصم رسم عند التحصيل.
      
      الـ migration اتطبّقت على قاعدة البيانات الحية واتسجّلت (batch 129)،
      والـ seed اتظبط عشان أي تركيب جديد يطلع بالصياغة الصحيحة.
      f14c5841
    • DevPilot's avatar
      docs+tools: ملحق الدليل لأداة تكرار الرقم القومي وتوضيح «برسم التحصيل» · 29874daf
      DevPilot authored
      - الدليل بقى 75 صفحة: اتضاف ملحق بيشرح أداة تكرار الرقم القومي (إزاي
        تستخدمها، وإمتى تدمج وإمتى تفرّغ الرقم بدل ما تدمج)، وملحق تاني بيوضّح
        إن «برسم التحصيل» مصطلح بنكي معناه «بغرض التحصيل» — مفيش رسم بيتخصم،
        وقيد تحصيل الشيك بيكون بقيمته كاملة.
      
      - tools/pdf/fonts: خطوط Amiri اتحطّت جوّه المستودع. كانت في /tmp واتمسحت
        مع تنضيف السيرفر، فالدليل ما كانش ينفع يتبني تاني من غيرها.
      
      - tools/pdf/build_addendum.py: بيولّد صفحات ملحق بنفس شكل الدليل ويلزقها
        في آخره — عشان أي أداة جديدة تتوثّق من غير ما نعيد تصوير الـ67 شاشة.
      
      - tools/fix_player_duplicates.py: بينضّف التكرار من خلال نفس الـ routes
        بتاعة الشاشة (نفس CSRF ونفس الكنترولر)، وبيدمج بس المجموعات اللي كل
        الأسماء فيها متطابقة ويسيب الباقي للموظف.
      29874daf
    • DevPilot's avatar
      feat(sa): أداة تكرار الرقم القومي للاعبين + منع التكرار من أصله · 5b2dbb02
      DevPilot authored
      المشكلة اللي اتبلّغت: نفس اللاعب متسجّل مرتين بنفس الرقم القومي — مرة
      كعضو ومرة كغير عضو. اتأكدت من الداتا الحية: فعلاً فيه 7 أرقام قومية
      مكررة، واحد منهم متكرر 4 مرات. شاشة تسجيل اللاعبين ما كانش فيها أي فحص
      للتكرار، والـ index على العمود non-unique.
      
      المنع:
      - PlayerController::store() و ::update() بقوا يرفضوا أي رقم قومي مسجّل
        للاعب تاني، ويقولوا اسم اللاعب ورقمه عشان الموظف يفتح ملفه بدل ما
        يسجّله من جديد.
      
      التنضيف (شاشة جديدة /sa/players/duplicates):
      - بتعرض كل مجموعة مكررة وجوّاها اللاعبين، وقدّام كل نسخة عدد السجلات
        المرتبطة بيها (اشتراكات، حضور، بطاقات، مستندات...) عشان الموظف يعرف
        النسخة اللي عليها شغل فعلي.
      - بتفرّق بين «نفس الاسم» و«أسماء مختلفة» وبتحذّر: مش كل تكرار هو نفس
        الشخص — أحياناً حد كتب رقم قومي غلط لشخص تاني.
      - إجراءين: دمج (بينقل كل السجلات للنسخة المختارة ويأرشف الباقي)، أو
        تفريغ الرقم القومي لو اتضح إنهم أشخاص مختلفين.
      - الدمج كله في transaction، ومفيش حذف نهائي — النسخ المدموجة بتتأرشف
        بملاحظة توضّح اتدمجت في مين.
      
      ملاحظة مهمة في التنفيذ: في الداتابيز 45 عمود اسمه player_id، لكن كتير
      منهم بيشاور على جدول players بتاع «شؤون اللاعبين» — جدول تاني خالص.
      فالدمج بيشتغل على قايمة جداول مكتوبة بإيد ومتأكد منها، مش على اسم العمود،
      عشان ما نخربش بيانات موديول تاني.
      
      كمان: مسمّى «إيداع ورقة قبض برسم التحصيل» في قيود الأوراق التجارية اتغيّر
      لـ «تسليم ورقة قبض للبنك للتحصيل» — «برسم التحصيل» مصطلح بنكي معناه
      «عشان يتحصّل»، بس بتتقري كأن فيه رسم اتخصم.
      5b2dbb02
    • Mahmoud Aglan's avatar
      fix(captain): pie charts showed their title in quotes, and their labels too small · 1031b8c0
      Mahmoud Aglan authored
      mermaid keeps a pie title's quotes as literal text (other chart types strip
      them), so the widget unquotes pie titles before rendering. Pie title, legend
      and slice labels are sized up, since the chart is drawn wide and scaled down to
      the panel.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      1031b8c0
    • Mahmoud Aglan's avatar
      fix(captain): an answer lost everything after its chart · 55830813
      Mahmoud Aglan authored
      The "drop an unclosed code block" cleanup matched a chart's closing fence
      whenever no backtick followed it, and deleted that fence and the rest of the
      answer — the table note, the «ملحوظة» line. It now cuts only when the number of
      fences is odd, and never a chart that is still being written. Found on the
      first live end-to-end answer.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      55830813
    • Mahmoud Aglan's avatar
      feat(captain): الكابتن — a read-only AI assistant for managers, on every screen · 354d0675
      Mahmoud Aglan authored
      A floating assistant for the super admin and senior managers (captain.use:
      super_admin, board_member, general_manager, membership_director,
      sports_director). It answers in Egyptian Arabic: step-by-step guides with the
      exact on-screen labels and a link to the page, numbers from the live data with
      tables and mermaid charts, and explanations of the page the user has open. It
      never talks about code, and it cannot change anything.
      
      The model runs on the DevPilot box under a restricted client token — Haiku at
      low effort, only Read/Grep/Glob, confined to its own checkout (clubphp-captain),
      enforced by DevPilot from the token. It never touches the database: it asks for
      SELECTs in a [[QUERY]] block and QueryGuard runs them on a separate read-only
      connection (READ ONLY transaction, one statement, 5 s cap), refusing other
      schemas, credential tables and columns, and HR/accounting/treasury data the
      user has no permission for; contact data is masked and every query is logged.
      Verified against the live database before shipping, including a write with the
      text filter bypassed, which MySQL itself refused (1792).
      
      KnowledgeSync keeps .captain/schema.txt (live schema with the values actually
      stored) and .captain/menu.txt (every sidebar path) current in that checkout.
      Config sits in captain_settings, not system_config, so the token never appears
      on the Settings screen; the widget only shows once a token is configured.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      354d0675
    • Mahmoud Aglan's avatar
      fix(proposal): keep tech terms in English instead of forced Arabic · a86c9429
      Mahmoud Aglan authored
      Terms the team says in English had been rendered in formal Arabic, which
      read as machine translation — "معالج مُرشد" for wizard, "ماسح" for scanner,
      "تشغيل تجريبي" for dry run, "الخادم" for server. They are now written as
      used: Wizard, Scanner, Dry Run, Offline, Override, PIN, Push, SMS, Branding,
      Audit Log, Mockups, Server. Two calques go too: "لا يُكتب شيء" → "لا يُحفظ
      شيء", and "شاشات مُعادة الرسم" → "Mockups". The club's own terms and the
      portal's screen names are untouched.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      a86c9429
    • Mahmoud Aglan's avatar
      feat(proposal): Pro Max screenshots, the full feature list, and a real A4 print · de9ab004
      Mahmoud Aglan authored
      The app screenshots came from a 16:9 emulator, so every phone in the
      proposal looked squat. They are recaptured on a 1320x2868 @3x emulator — the
      iPhone 16 Pro Max's own 440x956pt screen — so the frame now reads as a
      modern phone.
      
      The scope section listed what was out of scope, which read as the offer not
      covering the club. It is replaced by an inventory of what is built and
      working today, checked against both repos: 52 member-app features and 45
      across the portal and the gate scanner, grouped into cards, under the four
      pillars and the counts (51 app screens, 34 portal sections, 33 wizards,
      6 roles). Items the guide implies but the app has no screen for — uploading
      a transfer proof, requesting a plan, autopay — are left out.
      
      Print is now true A4. One print stylesheet serves both the browser's Print
      and generate-pdf.mjs: printer-safe margins, a cover page, dark bands printed
      light, grids held at their desktop columns, every app and portal screenshot
      in captioned galleries built into the page, page numbers in the footer, and
      breaks that keep sections whole. 17 pages.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      de9ab004
    • Mahmoud Aglan's avatar
      feat(proposal): show the real app and portal, and drop the phases · 969b2048
      Mahmoud Aglan authored
      The Sayd proposal carried a hand-built imitation of the app and a mocked
      portal. Both are replaced with screenshots of the real thing: 17 screens
      captured from the Flutter app (sayd-mobile-app-main) driving the live demo
      API on an emulator, and 19 portal screens (sayd-mobile-portal-main) wearing
      the club's own branding.
      
      The phase framing is gone — no "first/second phase", no week-by-week plan.
      Scope now lists what the system actually does (installments, fines and
      appeals, credit balance, transfer proofs, medical certificates, attendance
      and evaluations, events, audit log). Captions reuse the proposal's own
      Arabic and the Arabic user guide's wording.
      
      The PDF export prints both screenshot sets as captioned grids.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      969b2048
    • DevPilot's avatar
      docs: دليل PDF للمحاسبة والأصول الثابتة والخزينة (67 شاشة بلقطات حقيقية) · f4f4b150
      DevPilot authored
      دليل عملي واحد يغطي التلات مجالات المطلوبة بس:
      - المحاسبة: الإعداد، دليل الحسابات، القيود، البنوك والشيكات، الاعتمادات
        والضمانات والقروض، وكل التقارير والقوائم المالية.
      - الأصول الثابتة: السجل، الإضافة، الصيانة، العهدة، الموردين.
      - الخزينة والكاشير: الورديات، التحصيل، التسويات، العهدة، الإيداعات البنكية.
      
      كل شاشة ليها لقطة حقيقية من النظام الحي + شرح إيه اللي بتعمله وإزاي
      تستخدمها والقواعد المهمة فيها.
      
      أدوات التوليد اتحفظت تحت tools/pdf عشان الدليل يتبني تاني بعد أي تعديل.
      اللقطات بتتفحص إنها مش صفحة تسجيل دخول قبل ما تتقبل — أول محاولة طلعت
      65 لقطة من 67 صفحة لوجن من غير ما السكريبت ياخد باله.
      f4f4b150
    • DevPilot's avatar
    • DevPilot's avatar
      fix(core-wide): every edit form in the app was rendering blank — old() never returns null · 41217a9f
      DevPilot authored
      السبب اللي خلّى شاشة تعديل اللاعب تبان كأنها «شاشة إضافة جديدة»:
      old() في app/Core/Helpers.php مُعرّفة إنها ترجع (string) دايماً، يعني
      بترجع '' مش null لما مفيش إدخال سابق. وكل الفورمات مكتوبة بالشكل ده:
      
          old('full_name_ar') ?? $player['full_name_ar']
      
      و ?? بيشتغل على null بس — و'' مش null — فالنتيجة دايماً '' مهما كانت
      البيانات المحفوظة. يعني كل خانات كل شاشات التعديل في النظام كانت بتطلع
      فاضية، والمستخدم يفتكرها شاشة إضافة.
      
      اتصلّح 345 موضع في 37 ملف بتحويل ?? إلى ?: في نداءات old() تحديداً،
      فالقيمة المحفوظة بتظهر لما ما يكونش فيه إدخال سابق.
      ده بيمس: HR (كل الفورمات)، SportsActivity، TrainingGroups، Coaches،
      PlayerAffairs، Tournaments، PoolManagement، Achievements، Waiver،
      Installments.
      
      كمان فورمات بتبعت على routes مش موجودة (الحفظ كان بيدي 404 من غير ما
      الشاشة تشتكي):
      - /hr/overtime/store -> الصح /hr/overtime
      - /hr/shifts/assign  -> الصح /hr/shifts/assignments
      
      وحذف MirrorDisplayController وشاشتينه — مفيش أي route بيوصّلهم من ساعة
      ما الميرور اتنقل لموديول FacilityGrids.
      
      أدوات جديدة عشان النوع ده من الأعطال ما يوصلش للمستخدم تاني:
      - tools/form_action_check.py: بيتأكد إن كل فورم POST بيبعت على route
        موجود فعلاً (692 route اتفحصوا). الفحص ده كان هيمسك بَجّي الـ /update.
      - tools/ui_verify.js: فحص بالمتصفح بيدوس الأزرار زي المستخدم بالظبط
        ويفشل على أي استثناء في الصفحة — مش مجرد طلب GET على الـ route.
      41217a9f
    • DevPilot's avatar
      fix(accounting, sa): missing LC/LG show screens, debtors report repeating每... · 2a77ae3a
      DevPilot authored
      fix(accounting, sa): missing LC/LG show screens, debtors report repeating每 debtor, player link fix screen
      
      - شاشتي عرض الاعتماد المستندي وخطاب الضمان مكانوش موجودين أصلاً (View not
        found) — أي حد يدوس «عرض» كان بياخد استثناء. اتعملوا كاملين: البيانات
        والغطاء والعمولة والمستندات، وكمان فورم تحديث الحالة اللي كان متعرّف في
        الكنترولر من غير أي واجهة توصّله (التجديد بيطلب تاريخ انتهاء جديد).
      
      - تقرير المدينين كان بيطلع سطر لكل مطالبة، فالعضو الواحد يتكرر 53 مرة
        (1219 سطر لكل المدينين). بقى سطر واحد لكل مدين بإجمالي مديونيته وتوزيع
        التقادم، وبالضغط عليه بتتفتح تفاصيل مطالباته — من غير ما نخسر أي تفصيلة.
      
      - «صلّح الربط» في شاشة الفجوات كان بيفتح فورم فيه خانة رقم مجرّدة، والأسوأ
        إن الفورم أصلاً بيبعت على /sa/players/{id}/update واللي مش موجود كـ
        route — يعني الحفظ ما كانش بيشتغل خالص. دلوقتي:
        • الفورم بيبعت على المسار الصح.
        • خانة «العضو المرتبط» بقت بحث بالاسم/رقم العضوية وبتختار من النتايج.
        • بتوضّح العضو المربوط حالياً، ولو الرقم المسجّل مش عضو حقيقي بتقول كده
          صراحة — وده بالظبط الحالة اللي الفجوة بتبلّغ عنها.
        • الكنترولر بقى يرفض أي member_id مش رقم عضو موجود، في الإضافة والتعديل —
          عشان الفجوة دي ما تتكررش من أصلها.
      
      - tools/route_smoke.py: كان بيحط id=1 دايماً، والكنترولر بيعمل redirect لما
        الصف مش موجود، فالشاشة ما بتتفتحش والفحص بيعدّي وهو فاضي — وده اللي خلّى
        شاشات ناقصة view تعدّي الفحص وتقع عند المستخدم. بقى بيجيب ID حقيقي من
        قاعدة البيانات لكل نوع، وبيبلّغ عن أي شاشة ما اتفتحتش بدل ما يعتبرها نجاح.
      2a77ae3a
    • DevPilot's avatar
      fix(payments, cashier, treasury): void-type gaps, unpaid fines via queue,... · 366460fd
      DevPilot authored
      fix(payments, cashier, treasury): void-type gaps, unpaid fines via queue, invisible guest payments, missing permission checks
      
      Payments/Installments:
      - PaymentLifecycleService::onPaymentVoided() ما كانتش بترجع حالة العضو
        إلا لـ membership_fee/down_payment بس — رغم إن foreign_membership_fee
        وsports_membership_fee وseasonal_fee كلهم بينشّطوا العضو، إلغاء أي واحدة
        منهم كان بيسيب العضو "فعال" للأبد من غير أي غطاء دفع حقيقي.
        MembershipPaymentGuard::deactivateMember() نفسها كانت أصلًا عارفة تتعامل
        مع الخمس أنواع صح — المشكلة كانت في القايمة اللي بتقرر تنادي عليها.
      - غرامة بتترسل لطابور الدفع (fine) كانت بتتحصّل وتتطبع إيصال، لكن صف
        الغرامة في جدول fines فضل زي ما هو "غير مسدد" للأبد — مفيش حد كان بيسمع
        الحدث ده أصلًا. أي فحص رصيد أو أهلية كارنيه كان هيفضل يقول إن الغرامة
        لسه مستحقة حتى بعد سدادها فعليًا. اتضاف مستمع بيقفل الغرامة المحددة.
      - GET /api/v1/payments كان مكسور تمامًا — بيفلتر بعمود is_archived مش
        موجود أصلًا في جدول payments (الصح is_voided).
      - شاشة كل المدفوعات والتقرير اليومي كانوا بيستخدموا INNER JOIN مع
        members، فأي دفعة لعميل زائر (member_id فاضي — زي تسجيل رياضي لغير
        عضو) كانت تختفي تمامًا من الشاشتين دول رغم إنها محصّلة فعليًا
        (23 دفعة بحوالي 41,800 جنيه في البيانات الحية). اتحول لـ LEFT JOIN.
      - زرار إلغاء الدفعة كان بيتحكم بصلاحية payment.void_receipt بينما الـ
        route بتاعه محتاج payment.void — محدش في الأدوار الحية عنده الصلاحية
        التانية، فكان عمليًا محدش غير super_admin يقدر يلغي دفعة من الشاشة دي.
      - عضوية موسمية بتتفعّل من كاشير مباشرة من غير ما تعدّي على
        activateIncludedDependents — يعني زوجة/أبناء العضو الموسمي المرفقين في
        نفس الرسم ما كانوش بيتفعّلوا معاه.
      - حذف DefaultChecker.php الميتة تمامًا (صفر استدعاء) — نفس منطق فحص
        التعثر بالظبط موجود وشغّال فعليًا في cron/jobs/InstallmentDefaultJob.php.
      
      Treasury/Cashier:
      - شاشات موديول Treasury بالكامل كانت من غير أي فحص can() على الإطلاق —
        كل الأزرار بتظهر لأي حد شايف الشاشة بغض النظر عن صلاحياته الحقيقية.
        اتأكد إن ده مش نظري: أدوار حية زي auditor وtreasury_manager وmain_cashier
        عندهم جزء من صلاحيات الخزنة بس مش كلها، فكانوا بياخدوا 403 على أزرار
        شايفينها. كل زرار دلوقتي بيتحقق من نفس صلاحية الـ route بتاعه.
        نفس الحاجة اتصلحت في زرار فتح/قفل وردية كاشير، وزرار طباعة الإيصال اللي
        كان بيتحكم بصلاحية receipt.print بدل payment.view الحقيقية.
      - عداد "طلبات معلقة" في لوحة الخزنة الفرعية كان بيستخدم قايمة أنواع دفع
        أقدم من القايمة الحقيقية المستخدمة في الطابور نفسه — ناقصة رسوم عضوية
        أجانب/رياضية وبعض أنواع الأنشطة الرياضية.
      
      Plus extreme-detail reference docs for Payments/Installments, Cashier, and Treasury.
      366460fd
    • DevPilot's avatar
      fix(facility-grids, members): cross-grid delete auth gap, blank names, wrong... · d3f2e87e
      DevPilot authored
      fix(facility-grids, members): cross-grid delete auth gap, blank names, wrong permissions, dead crons, FY string mismatch
      
      FacilityGrids:
      - ZoneTraineeController::remove()/clearZone()/move() ما كانوش بيتأكدوا إن
        المتدرب/المنطقة فعلاً تابعين لنفس الشبكة في الـ URL — أي حد عنده
        facility_grid.manage على أي شبكة كان يقدر يمسح متدرب أو يفرغ منطقة تابعة
        لشبكة تانية تمامًا لو خمّن الـ ID. دلوقتي بيتأكدوا من الملكية الأول.
      - FacilityAttendanceService::sendPaymentReminders() والـ view بتاع شاشة
        الحضور كانوا لسه بيقروا name_ar بعد ما الكويري اتصلح لـ full_name_ar —
        فكل تذكير دفع كان بيتبعت باسم فاضي، وشاشة تسجيل الحضور كانت بتطلع كل
        أسماء المدربين/المتدربين فاضية وبتحفظ entity_name_cache فاضي في القاعدة.
      - MonthlyPlanController::index() كان بيتحقق من صلاحية مختلفة عن اللي مسجلة
        على الـ route بتاعه (manage بدل view) — يعني حتى لو عندك صلاحية العرض
        بس، تاخد 403 فور ما تدخل شاشة الخطط الشهرية.
      - زرار "إنشاء شبكة" و"خطة جديدة" كانوا بيتحكموا بصلاحية facility.manage
        (بتاعة موديول Facilities المختلف) بدل facility_grid.manage بتاع
        الموديول نفسه.
      - زرار "تصدير PDF" في لوحة المالية كان بيسمي نفسه غلط — الملف اللي بينزل
        فعليًا HTML مش PDF.
      
      Members:
      - AutoFreezeService::freezeTemporaryAtAgeLimit() كانت شرطه بيدور على أكواد
        فئات (sisters_under_25 إلخ) مش موجودة في القاعدة أصلًا — الفئات الحقيقية
        المخزنة هي sister/stepchild/orphan، فالكرون كان ميت فعليًا ومفيش شقيقة
        ولا ابن زوج ولا يتيم بيتقفل تلقائيًا بعد سن 25.
      - حالة permanently_dropped (اللي بيكتبها كرون إسقاط الأعضاء) ما كانتش في
        قائمة تسميات الحالات — فكانت بتظهر بالإنجليزي الخام في أي شاشة تعرض حالة
        العضو. اتضافت هي وterminated اللي كانت ناقصة في نفس القائمة.
      - MemberController::show() كان بيبني السنة المالية الحالية بصيغة فيها شرطة
        (2025-2026) بينما كل مكان تاني في الكود بيكتبها بشرطة مايلة (2025/2026)
        زي ما فعليًا متخزن في subscriptions — فكانت شاشة بروفايل أي عضو دايمًا
        بتقول إن مفيش اشتراك للسنة الحالية حتى لو مدفوع فعلًا.
      
      Plus extreme-detail reference docs for both modules.
      d3f2e87e
    • DevPilot's avatar
      docs: extreme-detail reference for PlayerAffairs · 00d84740
      DevPilot authored
      يوثق كل شاشات الموديول (تقييمات، لياقة، إصابات، الملفات) وأهم نقطة فيه:
      جدول players هنا منفصل تمامًا عن sa_players بتاع SportsActivity — مفيش
      مفتاح مشترك ولا مزامنة. بيوثق كمان الإصلاحات اللي اتعملت قبل كده في نفس
      الجلسة لصلاحيات الأزرار، وموضوع تضارب شاشتي اعتماد الشهادات الطبية.
      00d84740
    • DevPilot's avatar
      fix(player-affairs): buttons gated by a different module's permission, and one... · ea7b4757
      DevPilot authored
      fix(player-affairs): buttons gated by a different module's permission, and one by a permission marked deprecated
      
      كل شاشات الموديول تقريبًا كانت بتتحكم في ظهور أزرار الإضافة/التعديل بصلاحية
      sa.player.manage اللي أصلًا مسجّلة في SportsActivity مش هنا — فموظف عنده
      صلاحياته الصحيحة (player.register، player.edit، player.evaluate،
      player.manage_injuries، player.manage_fitness) ما كانش بيشوف زراره خالص.
      
      وشاشة اعتماد الشهادات الطبية كانت بتتحكم بصلاحية sa.medical.approve
      اللي متسجّلة في كود SportsActivity نفسه بعلامة '(ملغي) استخدم
      medical.board.approve' — يعني حتى المصلحة الأصلية بتقول إنها ملغاة،
      والصلاحية الحقيقية للـ route هي player.approve_medical. كل الأزرار
      بقت بتتحكم بنفس الصلاحية اللي الـ route فعليًا محتاجها.
      ea7b4757
    • DevPilot's avatar
      docs: extreme-detail reference for SA core structure · a77f34ec
      DevPilot authored
      Disciplines, academies/contracts, programs, groups (enroll vs force-enroll,
      the three disagreeing pause implementations, medical grace-period sweep),
      coaches, facilities/units, pricing resolution algorithm, attendance,
      player cards, and both SA reports. Documents the coach-cost fix's real
      impact (9 of 25 active coaches previously cost 0 in the finance report)
      and flags the card-renewal duration bug as a deliberate fix, not a
      quick patch, since it touches both fee calculation and payment payload.
      a77f34ec
    • DevPilot's avatar
      docs: extreme-detail reference for SA bookings, passes, scheduling, waitlist, transfers · 9c54a882
      DevPilot authored
      Classic hourly booking form, booking passes (confirmed orphaned gate-scan
      API), daily/weekly schedule, blackout dates, conflict detection scope,
      locker rentals, group schedule/waitlist/transfer (including the separate
      TrainingGroups parallel system), makeup sessions, and Service Desk. Flags
      a consistent cash-collected-outside-the-system pattern across three
      screens for a deliberate finance decision rather than three silent fixes.
      9c54a882
    • DevPilot's avatar
      fix(sports-activity): 4 real bugs found while documenting core screens · 28ea8431
      DevPilot authored
      - تقرير الماليات (SaFinanceReportService): الـ switch بتاع تكلفة المدربين
        كان بيقارن بقيم زي 'monthly'/'hourly' مش موجودة في enum الحقيقي
        (monthly_fixed/per_session/per_player/hybrid/salary) — يعني أي مدرب
        مش per_session كان بيتحسب تكلفته صفر، وصافي الربح المعروض في التقرير
        كان مبالغ فيه بشكل منهجي. اتصلحت لتغطي الخمس نماذج كلهم، بنفس صيغة
        الحساب المستخدمة فعلًا في Coaches\Services\CoachPaymentService
        (مدرب الأكاديمية 'salary' متعمّد يفضل صفر — أجره بيتحسب من خلال تسوية
        عقد الأكاديمية مش كبند تكلفة مباشر).
      - FacilityUnit::$fillable ما كانش فيه expected_capacity، فالحقل كان
        بيتقبل في الفورم ويتفلتر بصمت قبل ما يوصل لقاعدة البيانات.
      - EnrollmentService::forceEnroll() كان بيثبّت is_full=1 دايمًا بدل ما
        يحسبه — تجاوز واحد في مجموعة سعتها 20 كان بيقفل التسجيل العادي على
        الـ 19 مكان الفاضيين اللي بعده.
      - زرار سحب اللاعب من المجموعة كان بيتفعّل بصلاحية sa.group.enroll بينما
        الـ route نفسه محتاج sa.group.manage — تضارب بين الاتنين حسب الدور.
      28ea8431
    • DevPilot's avatar
      docs: extreme-detail reference for the rest of Accounting · 5d693197
      DevPilot authored
      Chart of accounts, cost centers/budgets, dimensions, revenue mapping
      manual screens, posting chains, accruals, gaps, opening entries/
      reclassification, bank reconciliation, loans/documentary credits/
      guarantees, settlements/billing, fiscal years/journal types/branch
      fees, and the exact calculation logic behind every report screen
      including all 5 financial statements — plus a status table for every
      defect found and fixed or flagged along the way.
      5d693197
    • DevPilot's avatar
      fix(accounting): guarantee_type dropdown offered values not in the live ENUM · e6e92996
      DevPilot authored
      نفس فئة الغلط بتاعة نوع القرض: القائمة كانت بتعرض initial/final/advance
      بينما الـ ENUM الحقيقي في القاعدة tender/performance/advance_payment/
      maintenance/customs/other — فأي اختيار غير maintenance كان يفشل الحفظ
      بـ Data truncated for column 'guarantee_type'.
      e6e92996
    • DevPilot's avatar
      fix(accounting): two more submit-blockers + rate columns too narrow for real values · 26a4ff88
      DevPilot authored
      بعد إصلاح أسماء الحقول، اختبرت الشاشات الثلاثة على السيرفر الحي ولقيت:
      
      - إنشاء اعتماد مستندي: تاريخ الانتهاء NOT NULL في القاعدة بس مش required
        في الفورم — الحفظ كان بيقع بـ 500. بقى required.
      - إنشاء خطاب ضمان: حقل رقم الخطاب كان required في الـ HTML بس الكنترولر
        بيتجاهله ويولّد رقمه لوحده — يعني المتصفح نفسه كان بيمنع الحفظ من غير
        ما توصل السيرفر أصلًا. بقى للعرض بس زي رقم القرض.
      - bank_loans.interest_rate و letters_of_guarantee.commission_rate كانوا
        DECIMAL(5,4) — أقصى قيمة 9.9999%. أي سعر فايدة حقيقي (15%، 22%...
        معدلات مصرية عادية) كان يفشل بـ Out of range. اتوسّعوا لـ DECIMAL(7,4).
      26a4ff88
    • DevPilot's avatar
      fix(accounting): 3 forms crash or silently discard data on submit · 3479e8e6
      DevPilot authored
      اكتشفت أثناء توثيق الشاشات المتبقية إن تلات فورمات معطلة فعليًا:
      
      - إنشاء اعتماد مستندي: أسماء الحقول (credit_number، supplier_id،
        bank_account_id، currency) ما تطابقش اللي الكنترولر بيقراه
        (issuing_bank_id، beneficiary_supplier_id، currency_code)، ومفيش حقل
        أصلًا لـ beneficiary_name رغم إنه NOT NULL في القاعدة — يعني أي محاولة
        حفظ كانت بتطلّع 500 من قاعدة البيانات.
      - خطاب ضمان جديد: نفس المشكلة بالظبط — beneficiary بدل beneficiary_name
        NOT NULL، فأي حفظ كان بيقع.
      - قرض بنكي جديد: خانة الضمان اسمها collateral بس الكنترولر بيقرا
        collateral_description، فأي نص كان بيتكتب فيها بيتفقد بصمت. وقائمة
        نوع القرض فيها قيم (term_loan، equipment) مش موجودة في enum القاعدة
        أصلًا (term، revolving، overdraft، mortgage) — اختيارها كان هيفشل
        الحفظ أو يبوّظ العمود. رقم القرض واسم البنك كانا حقول قابلة للتعديل
        بالمصادفة بس الكنترولر بيتجاهلهم دايمًا — بقوا معروضين للقراءة بس.
      3479e8e6
    • DevPilot's avatar
      docs: extreme-detail reference for all 5 system wizards · 05a6e6f8
      DevPilot authored
      Member retroactive entry, SA player registration, SA hourly booking,
      swimming pool reservations, and the accounting revenue-mapping wizard.
      Every field, validation rule, permission key, button, DB write, and
      error message, sourced from reading the actual controller/service code
      rather than the UI — plus a list of real defects found along the way
      (transaction leaks, dead endpoints, schema mismatches, silent failures)
      flagged for a deliberate fix pass rather than folded in unannounced.
      05a6e6f8
  2. 11 Sep, 2026 9 commits
    • DevPilot's avatar
      docs: الدليل النهائي — ٨٧ صفحة، ١٦ فصل، ١٤٥ خطوة · 9236b6a1
      DevPilot authored
      اتضافت خطوة البحث في دليل الحسابات (٧٣٣ حساب) اللي بتوضّح إن البحث
      بيشتغل من غير همزة وبالأرقام العربية ومع الأخطاء المطبعية.
      9236b6a1
    • DevPilot's avatar
      fix(ui): نواة البحث بتتحمّل في الـ head · 8f83d048
      DevPilot authored
      سكربتات الشاشات بتشتغل وقت تحميل الصفحة، يعني قبل سكربتات آخرها. فدليل
      الحسابات كان بيقع على الطريقة القديمة: «إيراد» بترجّع ١٢ و«ايراد» ٤،
      و«١٢٠٦» بالأرقام العربية صفر. النواة دلوقتي في الـ head قبل أي شاشة.
      8f83d048
    • DevPilot's avatar
      fix(ui): كل بحث في النظام بقى بيستخدم نواة التطبيع العربي · be4bf013
      DevPilot authored
      سبع شاشات كانت بتعمل بحث بـ toLowerCase().indexOf — يعني «احمد» ما
      بتلاقيش «أحمد» و«المطابقه» ما بتلاقيش «المطابقة». أهمهم دليل الحسابات
      (٧٣٣ حساب) والمدينون والدائنون والحسابات البنكية ومراكز التكلفة.
      
      دلوقتي كلهم على ArabicSearch: تطبيع الهمزات والتشكيل والأرقام العربية،
      ومطابقة تقريبية بتتحمّل الأخطاء المطبعية. ودليل الحسابات بيطبّع نص كل
      صف مرة واحدة بدل كل ضغطة زرار.
      be4bf013
    • DevPilot's avatar
      chore(accounting): تنضيف بقايا شاشات كشف الحساب القديمة + ضبط دقة البحث · d7e46851
      DevPilot authored
      - شاشتين وميثودين وداليتين قراءة بقوا ميتين بعد توحيد كشوف الحسابات.
        AccountStatementService فضلت لتسجيل الحركات (١٦ مكان بيستخدمها).
      - البحث بالحروف المتتابعة كان فضفاض: «recon» كانت بتلاقي «شئون العضوية»
        لأن حروفها متفرقة في النص. اتضاف شرط تقارب — الحروف لازم تكون في مدى
        ضعف طول اللي اتكتب.
      d7e46851
    • DevPilot's avatar
      docs: تحديث الدليل — ٨٧ صفحة، ١٦ فصل، ١٤٤ خطوة · 7783bd1e
      DevPilot authored
      الفصل الجديد بيغطي تنظيم قائمة المالية الجديد، كشوف الحسابات الموحّدة
      بالمطابقة، البحث والترتيب في الجداول، وفحص جاهزية الإقفال. وفصل البنوك
      اتشال منه لقطة القائمة القديمة بعد ما التنظيم اتغيّر.
      7783bd1e
    • DevPilot's avatar
      fix(accounting): use statement مكانش ينفع جوه if — واتضاف فحص صياغة شامل · b2c3414b
      DevPilot authored
      الـ view اتدفع وفيه خطأ صياغة لأن git commit ما بيلنتش. tools/php_lint.sh
      بيفحص الـ ٢٣٩٠ ملف PHP في ثواني، وده اللي كان لازم يتشغّل قبل الدفع.
      b2c3414b
    • DevPilot's avatar
      feat(accounting): فحص جاهزية قبل إقفال الشهر · d1cdb878
      DevPilot authored
      الإقفال كان بيتحقق من حاجة واحدة (القيود المسودة) وبعدين يقفل. أسوأ حاجة
      في الإقفال إن المحاسب يقفل ويطلّع القوائم وبعدين يكتشف ناقص ويضطر يفتح تاني.
      
      الشاشة دلوقتي بتعمل سبع فحوصات قبل الإقفال، كل واحد بلينك يوصّل للشاشة
      اللي تحلّه: القيود المسودة، توازن الميزان، الشهر السابق، العمليات غير
      المقيّدة، الفلوس الواقفة في حسابات وسيطة، الشيكات المستحقة، والمطابقة
      البنكية.
      
      المسودة وعدم التوازن موانع بتوقف الإقفال — الباقي تنبيهات والقرار للمحاسب.
      والفحص بيتعاد في الكنترولر مش بس في الشاشة عشان حد ما يعديه بـ POST مباشر.
      d1cdb878
    • DevPilot's avatar
    • DevPilot's avatar
      fix(accounting): المطابقة تقارن حساب الذمم مش إجمالي الدفتر · 9245247e
      DevPilot authored
      أول ما شغّلت الشاشة على عضو حقيقي طلّعت «فرق ٣٦٣ ألف» على عضو حسابه سليم.
      السبب: القيد بيتعلّم برقم العضو على الطرفين (مدين الصندوق / دائن الإيراد)،
      فجمع كل سطوره بيطلّع صفر دايمًا والفرق مالوش معنى.
      
      المطابقة الصح بتقارن رصيد العضو في حسابات الذمم (١٢٠٣/١٢٠٤ للعملاء،
      ٢٣٠٦ للموردين) بالحساب المساعد. والعضو اللي بيدفع نقدي وقت العملية
      مالوش حساب ذمم أصلًا، فبقت الشاشة بتقول كده صريح بدل تحذير غلط.
      9245247e