feat: P0/P3/P5 — دفترا المستأجر ورسم العملية وتوجيه المال
الأساس المالي حسب docs/24: فصل ما نملكه عمّا نحتفظ به لغيرنا. - جدولان منفصلان (`tenant_revenue_ledger` · `tenant_pending_ledger`) لا جدول واحد بعمود نوع: استعلامٌ ينسى الشرط كان يجعل المالك يسحب من مال الركّاب. مضافان فقط، والرصيد مشتقّ لا حقل يُحدَّث. - فهرس فريد (tenant_id, ref) = حارس التسوية المزدوجة. التصادم يُلتقط من القاعدة لا بالفحص المسبق وحده — نداءان متزامنان يمرّان معاً قبل أي كتابة. - رسم العملية من مصدر واحد (35 ل.س · 5 ج.م · 0.20 د.أ) قابل للتجاوز من إعداد المستأجر، مع قصّه عند المبلغ حتى لا يخرج المستخدم بصافٍ سالب. - توجيه الدفع: شحن السائق ← إيراد + رصيده التشغيلي · شحن الراكب ← أمانة · الرسم ← إيراد. وفُتح مسار شحن السائق الذي لم يكن له مدخل إطلاقاً. - ثغرة سُدّت: `purpose` يصل من الجسم، فراكب كان يستطيع إرسال `credit_topup` فيُسجَّل مالُه إيراداً ويُشحن حساب سائق لا يملكه. الدور الآن من التوكن. - `/admin/overview`: الإيراد من الدفتر، وعمولة الرحلات حقل منفصل عنه. - `/admin/wallet/summary` و`/revenue-by-reason` لأدمن المستأجر. 25 اختباراً جديداً (188 إجمالاً، كلها خضراء). أحدها أمسك عطلاً فعلياً: صافٍ صفري كان ينادي الدفتر بصفر فيرمي خطأً بعد قيد الرسم — عملية نصف مطبَّقة. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4ca932dd1f
commit
b1a060c5ed
@@ -62,8 +62,8 @@
|
||||
<div class="card">
|
||||
<h2>المستأجرون</h2>
|
||||
<table>
|
||||
<thead><tr><th>الاسم</th><th>slug</th><th>الدولة</th><th>الباقة</th><th>الحالة</th><th>رحلات</th><th>GMV</th><th>إيراد المنصة</th><th></th></tr></thead>
|
||||
<tbody id="tenantRows"><tr><td colspan="9" class="muted">…</td></tr></tbody>
|
||||
<thead><tr><th>الاسم</th><th>slug</th><th>الدولة</th><th>الباقة</th><th>الحالة</th><th>رحلات</th><th>GMV</th><th>عمولة الرحلات</th><th>الإيراد الكلي</th><th></th></tr></thead>
|
||||
<tbody id="tenantRows"><tr><td colspan="10" class="muted">…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -170,17 +170,18 @@
|
||||
document.getElementById('ovTotals').innerHTML =
|
||||
`مستأجرون: <b>${t0.tenants ?? 0}</b> (فعّال: ${t0.active_tenants ?? 0}) ·
|
||||
رحلات: <b>${t0.trips ?? 0}</b> (مكتملة: ${t0.completed_trips ?? 0}) ·
|
||||
GMV: <b>${money(t0.gmv)}</b> · إيراد المنصة: <b>${money(t0.revenue)}</b>`;
|
||||
GMV: <b>${money(t0.gmv)}</b> · عمولة رحلات: <b>${money(t0.trip_commission)}</b> · الإيراد الكلي: <b>${money(t0.revenue)}</b>`;
|
||||
|
||||
const tenants = ov.tenants || [];
|
||||
rows.innerHTML = tenants.length ? '' : '<tr><td colspan="9" class="muted">لا مستأجرين بعد</td></tr>';
|
||||
rows.innerHTML = tenants.length ? '' : '<tr><td colspan="10" class="muted">لا مستأجرين بعد</td></tr>';
|
||||
for (const t of tenants) {
|
||||
const suspended = t.status !== 'active';
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `<td>${esc(t.name)}</td><td class="muted">${esc(t.slug)}</td><td>${esc(t.countryPack || '-')}</td>
|
||||
<td>${esc(t.plan)}</td>
|
||||
<td><span class="badge ${suspended ? '' : 'active'}">${esc(t.status)}</span></td>
|
||||
<td>${t.completed_trips}/${t.trips}</td><td>${money(t.gmv)}</td><td>${money(t.revenue)}</td>
|
||||
<td>${t.completed_trips}/${t.trips}</td><td>${money(t.gmv)}</td>
|
||||
<td class="muted">${money(t.trip_commission)}</td><td>${money(t.revenue)}</td>
|
||||
<td><button class="ghost" data-act="manifest">مانيفست</button>
|
||||
<button class="ghost" data-act="admin">تعيين أدمن</button>
|
||||
<button class="ghost" data-act="logo">اللوغو</button>
|
||||
@@ -233,7 +234,7 @@
|
||||
};
|
||||
rows.appendChild(tr);
|
||||
}
|
||||
} catch (e) { if (e.message !== 'unauthorized') rows.innerHTML = `<tr><td colspan="9" class="muted">${esc(e.message)}</td></tr>`; }
|
||||
} catch (e) { if (e.message !== 'unauthorized') rows.innerHTML = `<tr><td colspan="10" class="muted">${esc(e.message)}</td></tr>`; }
|
||||
}
|
||||
document.getElementById('ovDays').onchange = loadTenants;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user