# م11 (نهاية اليوم والتقارير) — عقد Fable + ليدجر (2026-07-09)

> عقد Fable المُلزِم. **أخطر مرحلة مالية للقراءة** (تقارير من الـGL). **صفر migrations/settings/permissions** — طبقة تقارير read-only فوق الدفاتر. WP1+WP2 = full kit (native+Codex+Fable).

## قرارات Fable
- **D1 تقرير اليوم من الـGL:** group posted JE lines by `account_id` على مدى تاريخ، 3 entry types (`clinic_receipt`+`clinic_receipt_void`+`clinic_credit_note`). تحصيل/تندر = net movement per tender account (labels من `CashierRoutingService`، غير المعروف→«other» بلا حذف). refund = leg الدفع في credit-note. billed = revenue movement. **deferred = AR movement (=billed−collected)**، ممكن سالب (repayment). **الخصم مش من الـGL** (الـJE بيقيّد patient_amount الصافي) → من `reception_receipts.discount_total` (غير voided)، labeled `source:operational`. new_patients = count. BCMath. scope=clinic فقط (عشان cash account مشترك مع LIS مايسربش).
- **D2 debts:** per-patient من `patient_ledger_entries` + buckets 30/60/90/120 (helper منسوخ من AgingReportService) + header فيه `gl_ar_balance` (getAccountBalance) + delta ظاهر (payer/claim AR بيظهر في الـdelta = الصدق). مش AgingReportService as-is (partners مالهمش ar مستقل).
- **D3 العمولات: تأجيل رسمي تاني** (تعارض نموذج الاعتراف من م9 — commission at service-time vs revenue at collection). تقرير read-only accrued-parties من snapshots (`EncounterServiceParty`، بلا JE، **ماتلمسش EventServiceProvider/ServiceRendered**). التفعيل مستقبلًا = wiring بس.
- **D4 payable-account picker يُشحن** (فجوة نصف-موصّلة: model+API موجود، HTML control ناقص). options = `AccountService.listAll()` filter `account_type==='detail'` (نمط clinic-settings).
- **D5 النطاق:** MUST=WP1(day-close)+WP2(debts)+WP4(FE+CSV)+WP5(payable UI)+WP7(docs/verify). SHOULD=WP3(accrued-parties)+WP6(CSV hardening)+تنظيف rank1-2. exports=CSV بس (Excel/PDF مؤجّل). الداشبورد=verification بس (م8 عملها صح).

## 📊 ليدجر
| WP | المحتوى | يعتمد | مراجعة | الحالة |
|----|---------|-------|--------|--------|
| WP1 | ✅ `ee2463a0f` (Fable CORRECT) 🔴 **BE تقرير اليوم من الـGL** `ClinicReportService::dayClose` + controller + `GET reports/day-close` (perm reports.view). payload: tenders[]/other_accounts/totals(collected/refunded/net)/billed_net/deferred_net_ar_change/discount(operational)/new_patients/reconciliation/scope. BCMath، posted+date. **تستات:** full/partial/repayment/void/refund/unmapped-tender/discount-excludes-voided/identity billed−collected==deferred/date-boundary. تجنّب frozen-midnight. | — | **full kit** | ⬜ |
| WP2 | ✅ `a1942bc05` 🔴 **BE debts** `patientDebts(companyId,asOf)` + `GET reports/patient-debts`. per-patient من patient_ledger_entries + buckets + header gl_ar_balance+delta. تستات balances/buckets/delta. | — | **full kit** | ⬜ |
| WP3 | ✅ `df98f2909` **BE accrued-parties read-only** `accruedParties` + `GET reports/accrued-parties` من EncounterServiceParty (group by party، Σresolved_amount، payable account، status). **صفر JE، ماتلمسش EventServiceProvider.** + سجل التأجيل الرسمي للعمولات. | — | native+Codex | ⬜ |
| WP4 | ✅ BE `049b505aa`+FE `db748a5` **FE شاشات التقارير + CSV** day-close (tender table+KPIs+reconciliation) + debts (aging+GL delta header) + accrued-parties. clinic-report.service +3. + BE export CSV للتقارير الـ3 (reports.export). i18n ثنائي English-first. | WP1,WP2,WP3 | single+build | ⬜ |
| WP5 | ✅ `a2f4bf94` **FE payable-account picker** على revenue-split party rows (bound لـpayableAccountId الموجود، AccountService.listAll detail، nullable). صفر BE. | — | light | ⬜ |
| WP6 | ✅ `73be4aa78` **CSV hardening** `csvSafe()` (prefix `'` لأي خلية تبدأ =+-@/tab/CR — OWASP) على الـ4 قديمة + 3 جديدة. تست injection. | WP4 | native+Codex | ⬜ |
| WP7 | ✅ BE `a747ebe88`+FE `bc0c594` **تنظيف+verify+docs:** (1) توحيد dashboard services (نقل queue()، حذف summary() المهجورة + اليتيم) (2) vitals TODO (3) commission_type help-text. + verification test (resolveCollected==dayClose net_collected). + CHANGELOG م11 ثنائي + KB + سكربت تست المالك (يوم كامل → reports/day-close → قارن بالـGL trial balance). | WP1,WP4 | light | ⬜ |

**DAG:** WP1‖WP2‖WP3‖WP5 → WP4 → WP6 → WP7.

## مؤجّل رسميًا
تفعيل commission JE (تعارض الاعتراف) · الخصم في الـGL (يعيد كتابة JE م8) · Excel/PDF exports · branch-scoped day-close · payer/claims aging في debts · استهلاك commission_type للعيادة.

## سجل التنفيذ
- WP1 (day-close) ✅ `ee2463a0f` — **Fable: CORRECT (بلا بلوكرات).** المنفّذ اصطاد خطأ في spec Fable الأصلي: `billed−collected==deferred` تفشل مع الاسترداد → صحّحها لـ`billed−net_collected(collected−refunded)==deferred` (**ثابت جبري مُثبَت** عبر partition شامل + double-entry متوازن). 9 تست DC + fix بق تاريخ sqlite (DATE()). new_patients من lab_patients. **متبقّي (Fable [SHOULD]):** تستَي عرض — multi-tender day + refund-only-day (algebra مضمونة، بس صفوف العرض مش مثبّتة) → أضيفهم بعد WP2. **مؤجّل (Fable [NOTE]):** branch-scoped day-close (لا branch_id على JEs) · draft-JE warning badge (auto-post فشل=under-count) · discount sqlite float-drift (بلا أثر prod).
- WP5 (payable-account picker) ✅ `a2f4bf94`.

## ⚠️ حادثة تزامن (2026-07-09) — درس
وكلاء المنفّذين بيعلّقوا على «background wait» بعد ما يخلّصوا الشغل ويكوميتوا، لكن **بيفضلوا أحياء**. أنا كنت بعامل التعليق كـ«خلص» وأكوميت شغلهم بنفسي وأمشي — فوكيل WP2 فضل حي 50 دقيقة، أعاد بناء WP2 (byte-identical للمكوميت)، وعمل commit غلط لقط شغل WP4 غير المكوميت (`8ee3b43b2`) ثم `git reset HEAD~1` (صحّح نفسه، صفر ضرر — HEAD رجع df98f2909، شغل WP4 سليم). **القاعدة الجديدة: استنى إشعار الإكمال الحقيقي (بالكوميت SHA) قبل ما ألمس الريبو؛ ماكوميتش شغل وكيل وهو لسه شغّال؛ وكيل واحد كاتب لكل ريبو في اللحظة (مؤكَّد).**

## ✅ م11 مكتملة (2026-07-09) — كل الـ7 WP
WP1 day-close (Fable CORRECT، identity مُثبَت) · WP2 debts+GL delta · WP3 accrued-parties (read-only، commission مؤجّل) · WP4 3 شاشات FE (tabs) + CSV · WP5 payable-account picker · WP6 CSV injection hardening · WP7 **verification test يربط داشبورد م8 بالـday-close (collected_today == net_collected، يتطابقوا بالظبط — تشغيلي مقابل GL)** + help-text + vitals TODO. صفر migrations/settings/permissions. مؤجّل: توحيد dashboard services (tech-debt) · commission JE activation · Excel/PDF · branch-scoped day-close · discount في الـGL.

## 🧪 سكربت تست المالك (م11)
يوم كامل: احجز مرضى → حصّل بعضهم بالكامل، بعضهم جزئي → اعمل استرداد (refund) لواحد → void إيصال واحد. المسا: افتح تقرير **reports/day-close** لليوم → قارن كل رقم بـ**GeneralLedgerService trial balance** (المحصّل بالتندر = حركة حسابات الدفع، المفوتر = حركة الإيراد، الآجل = حركة الذمم) → لازم يتطابق. شريط المطابقة لازم يعرض ✓. **الخصم** مصدره جدول الإيصالات (بعلامة «تشغيلي») بالتصميم — مش من الـGL. جرّب **patient-debts** → قارن `gl.ar_balance` بإجمالي المرضى (الفرق = AR الدافع/المطالبة، بيظهر). جرّب **accrued-parties** (للقراءة فقط).
