/* Bonyan ERP — التقارير والتحليلات (كل الأرقام مشتقّة من الفواتير الفعلية) */ function ReportsScreen() { const I = window.BnyIcon; const D = window.BnyData; const { Card, StatCard, Money, Badge } = window.BonyanDesignSystem_ad3ca0; const isMobile = window.useBnyMobile(); window.useBnyInvoices(); // إعادة الرسم عند أي تعديل فاتورة/دفعة const fmt = (n) => Math.round(Number(n) || 0).toLocaleString("en-US"); const abbr = (n) => { n = Math.round(Number(n) || 0); const a = Math.abs(n); if (a >= 1e6) return (n / 1e6).toFixed(a >= 1e7 ? 0 : 1) + "م"; if (a >= 1e3) return Math.round(n / 1e3) + "ألف"; return String(n); }; // فترة التقرير: الكل / هذه السنة / هذا الشهر const [period, setPeriod] = React.useState("all"); const nowIso = new Date().toISOString().slice(0, 10); const usdRate = D.usdRate || 1500; const inPeriod = (d) => period === "all" ? true : period === "year" ? (d || "").slice(0, 4) === nowIso.slice(0, 4) : (d || "").slice(0, 7) === nowIso.slice(0, 7); const periodLabel = period === "all" ? "كل الفترات" : period === "year" ? "هذه السنة" : "هذا الشهر"; // ---- الأساس: فواتير البيع + دوال التحويل والتكلفة (بالرمز أولًا) ---- const allSales = (D.invoices || []).filter((i) => i.type === "sale"); const sales = allSales.filter((i) => inPeriod(i.date)); const invIqd = (inv, amt) => (inv.cur === "usd" ? (Number(amt) || 0) * usdRate : (Number(amt) || 0)); const bySku = {}, byName = {}; (D.materials || []).forEach((m) => { bySku[m.sku] = m; byName[(m.name || "").trim()] = m; }); const matOf = (it) => (it.sku && bySku[it.sku]) || byName[(it.name || "").trim()] || null; const costOf = (it) => { const m = matOf(it); return m ? (Number(m.cost) || 0) : 0; }; const invProfit = (inv) => (inv.items || []).reduce((x, it) => x + (invIqd(inv, it.price) - costOf(it)) * (Number(it.qty) || 0), 0); // ---- مؤشرات رئيسية ---- const totalSales = sales.reduce((s, i) => s + invIqd(i, i.total), 0); const totalProfit = sales.reduce((s, i) => s + invProfit(i), 0); const totalDebts = (D.customers || []).reduce((s, c) => s + window.BnyToIqd(c.balance, c.balanceUsd), 0); const invCount = sales.length; const margin = totalSales > 0 ? Math.round(totalProfit / totalSales * 100) : 0; const avg = invCount ? totalSales / invCount : 0; // ---- الاتجاه الشهري: آخر ٦ أشهر (مبيعات + صافي ربح حقيقي) ---- const AR = ["كانون٢", "شباط", "آذار", "نيسان", "أيار", "حزيران", "تموز", "آب", "أيلول", "تشرين١", "تشرين٢", "كانون١"]; const months = []; { const d = new Date(); for (let k = 5; k >= 0; k--) { const dt = new Date(d.getFullYear(), d.getMonth() - k, 1); months.push({ ym: dt.getFullYear() + "-" + String(dt.getMonth() + 1).padStart(2, "0"), label: AR[dt.getMonth()] }); } } const trend = months.map((m) => { const ms = allSales.filter((i) => (i.date || "").slice(0, 7) === m.ym); return { label: m.label, values: { sales: ms.reduce((s, i) => s + invIqd(i, i.total), 0), profit: ms.reduce((s, i) => s + invProfit(i), 0) } }; }); const trendHasData = trend.some((g) => g.values.sales > 0); // ---- المبيعات حسب الفئة (ضمن الفترة) ---- const catAgg = {}; sales.forEach((i) => (i.items || []).forEach((it) => { const m = matOf(it); const c = (m && m.cat) || "أخرى"; catAgg[c] = (catAgg[c] || 0) + (Number(it.qty) || 0) * invIqd(i, it.price); })); const catTotal = Object.values(catAgg).reduce((s, v) => s + v, 0) || 1; const catColors = ["var(--brand)", "var(--amber-500)", "var(--blue-400)", "var(--success)", "var(--concrete-400)", "var(--concrete-300)"]; const categories = Object.keys(catAgg).map((name, i) => ({ name, val: catAgg[name], pct: Math.round(catAgg[name] / catTotal * 100), color: catColors[i % catColors.length] })).sort((a, b) => b.val - a.val).slice(0, 6); // ---- أعلى العملاء (ضمن الفترة، بالمعرّف) ---- // يُستثنى الزبون المستطرق من ترتيب العملاء (مبيعاته محسوبة في الإجماليات لكنه ليس عميلًا مسجّلًا) const walkin = (D.customers || []).find((c) => c.walkin) || { id: 0, name: "زبون مستطرق" }; const isWalkSale = (i) => i.custId === 0 || i.custId === walkin.id || (i.custId == null && (i.party || "") === walkin.name); const custAgg = {}; sales.forEach((i) => { if (isWalkSale(i)) return; // لا يُدرج ضمن أعلى العملاء const key = i.custId != null ? "id:" + i.custId : "nm:" + (i.party || "—"); const c = custAgg[key] || (custAgg[key] = { name: i.party || "—", orders: 0, total: 0 }); c.orders++; c.total += invIqd(i, i.total); }); const topCustomers = Object.values(custAgg).sort((a, b) => b.total - a.total).slice(0, 5); // ---- أعلى المواد ربحًا (ضمن الفترة، بالرمز) ---- const matAgg = {}; sales.forEach((i) => (i.items || []).forEach((it) => { const m = matOf(it); const k = (m && m.sku) || it.sku || (it.name || "").trim(); if (!k) return; const a = matAgg[k] || (matAgg[k] = { name: (m && m.name) || it.name, sold: 0, unit: (m && m.unit) || it.unit || "", profit: 0 }); a.sold += Number(it.qty) || 0; a.profit += (invIqd(i, it.price) - costOf(it)) * (Number(it.qty) || 0); })); const topMaterials = Object.values(matAgg).sort((a, b) => b.profit - a.profit).slice(0, 5); const gap = isMobile ? "var(--space-4)" : "var(--space-6)"; return (
{/* فلتر الفترة */}
الفترة
({invCount} فاتورة · الدولار محوَّل بسعر {usdRate.toLocaleString("en-US")})
{/* مؤشرات رئيسية */}
} tone="brand" label={"المبيعات · " + periodLabel} value={fmt(totalSales)} unit="د.ع" /> } tone="success" label="صافي الربح التقديري" value={fmt(totalProfit)} unit="د.ع" sub={"هامش " + margin + "%"} /> } tone="amber" label="عدد الفواتير" value={fmt(invCount)} sub={"متوسط " + abbr(avg) + " د.ع"} /> } tone="danger" label="إجمالي الديون" value={fmt(totalDebts)} unit="د.ع" />
{/* الاتجاه الشهري — مخطط أعمدة بسيط */} {trendHasData ? ( abbr(g.values.sales)} /> ) : (
لا مبيعات في آخر ٦ أشهر بعد
)}
{/* الفئات + أعلى العملاء */}
{categories.length === 0 && } {categories.map((c) => (
{c.name} {abbr(c.val)} · {c.pct}%
))} {topCustomers.length === 0 && } {(() => { const max = Math.max(1, ...topCustomers.map((x) => x.total)); return topCustomers.map((it, i) => ( } pct={Math.round(it.total / max * 100)} color="var(--blue-400)" /> )); })()}
{/* أعلى المواد ربحًا */} {topMaterials.length === 0 ? : (() => { const max = Math.max(1, ...topMaterials.map((x) => x.profit)); return topMaterials.map((it, i) => ( } pct={Math.round(it.profit / max * 100)} color="var(--success)" /> )); })()}
); } /* بطاقة مؤشر مبسّطة بأيقونة ملوّنة */ function Kpi({ icon, tone, label, value, unit, sub }) { const bg = { brand: "var(--brand-soft)", success: "var(--success-soft, #e6f7ef)", amber: "var(--amber-soft, #fdf2d9)", danger: "var(--danger-soft)" }[tone] || "var(--surface-sunken)"; const fg = { brand: "var(--brand-strong)", success: "var(--success)", amber: "var(--amber-600, #b8790a)", danger: "var(--danger)" }[tone] || "var(--text-muted)"; return (
{icon} {label}
{value} {unit && {unit}}
{sub &&
{sub}
}
); } function Empty() { return
لا بيانات في هذه الفترة
; } function RankRow({ rank, name, sub, value, pct, color }) { return (
{rank} {name} {value}
{sub &&
{sub}
}
); } window.ReportsScreen = ReportsScreen;