/* Bonyan ERP — Inventory / warehouse */ function InventoryScreen({ user }) { const I = window.BnyIcon; const D = window.BnyData; const { Card, Button, Money, Badge, Select, Input } = window.BonyanDesignSystem_ad3ca0; const isMobile = window.useBnyMobile(); const canCost = window.BnyCan(user, "purchase_price"); // رؤية سعر الشراء const canEdit = window.BnyCan(user, "inventory_edit"); const [wh, setWh] = React.useState("all"); // بحث معلّق من المساعد الصوتي (اسم مادة) — يُستهلك مرة واحدة const [q, setQ] = React.useState(() => { const s = window.BnyPendingInvSearch; if (s) { window.BnyPendingInvSearch = null; return s; } return ""; }); // all | low | out | reorder — قد يصل مطلوبًا من لوحة التحكم (عرض الكل) const [filter, setFilter] = React.useState(() => { var f = window.BnyPendingInvFilter; if (f) { window.BnyPendingInvFilter = null; return f; } return "all"; }); const [, forceRender] = React.useReducer((x) => x + 1, 0); const [editMat, setEditMat] = React.useState(null); // null | {} جديدة | {مادة} تعديل const [transferMat, setTransferMat] = React.useState(null); // مادة قيد النقل بين المخازن const [catF, setCatF] = React.useState("all"); // فلتر الفئة const saveMat = async (m) => { await window.BnyOps.saveMaterial(m); setEditMat(null); forceRender(); }; // مدة الركود (متغيّر ديناميكي يُضبط هنا، افتراضي 30) — تعتمده لوحة التحكم const [stagDays, setStagDays] = React.useState(D.stagnantDays || 30); const onStagDays = (v) => setStagDays(window.BnySetStagnantDays(v)); const stagnantSkus = React.useMemo( () => new Set(window.BnyStagnantMaterials(stagDays).map((x) => x.m.sku)), [stagDays, D.materials, D.invoices] ); // computed inventory health (from data, not hardcoded) const lowCount = D.materials.filter((m) => m.stock > 0 && m.stock <= m.low).length; const outCount = D.materials.filter((m) => m.stock === 0).length; const totalValue = D.materials.reduce((s, m) => s + m.price * m.stock, 0); let rows = D.materials; if (wh !== "all") rows = rows.filter((m) => m.wh === wh); if (q) rows = rows.filter((m) => (m.name + m.sku + m.cat).includes(q)); if (catF !== "all") rows = rows.filter((m) => m.cat === catF); // فلترة حسب الفئة if (filter === "low") rows = rows.filter((m) => m.stock > 0 && m.stock <= m.low); if (filter === "out") rows = rows.filter((m) => m.stock === 0); if (filter === "reorder") rows = rows.filter((m) => m.stock <= m.low); // نافدة + ستنفد قريبًا const stockBadge = (m) => { const base = m.stock === 0 ? نفد : m.stock <= m.low ? منخفض : متوفر; if (!stagnantSkus.has(m.sku)) return base; return {base}راكد; }; return (
} label="إجمالي قيمة المخزون" value={totalValue.toLocaleString("en-US")} unit="د.ع" active={filter === "all"} onClick={() => setFilter("all")} /> } label="مواد منخفضة المخزون" value={String(lowCount)} sub="قاربت حد الطلب" tone="warning" active={filter === "low"} onClick={() => setFilter("low")} /> } label="مواد نافدة (رصيد صفر)" value={String(outCount)} sub="بحاجة لإعادة طلب" tone="danger" active={filter === "out"} onClick={() => setFilter("out")} />
} onClick={() => setEditMat({})}>إضافة مادة : null}>
setQ(e.target.value)} placeholder="ابحث عن مادة أو رمز أو فئة" icon={} />
{(filter !== "all" || catF !== "all") && ( )} {canEdit && (
مدة الركود (يوم) onStagDays(e.target.value.replace(/[^\d]/g, ""))} style={{ width: 64, height: 36, textAlign: "center", fontFamily: "var(--font-mono)" }} />
)}
{isMobile ? (
{rows.map((m) => (
setEditMat({ ...m }) : undefined}>
{m.name}
{canEdit && ( )} {stockBadge(m)}
{m.sku} · {m.cat} · {m.wh}{m.hasMultiUnits ? ` · ${m.packUnit}=${m.subUnitCount}${m.unit}` : ""}
{m.hasMultiUnits ? {window.BnyStock(m)} ({m.stock.toLocaleString("en-US")} {m.unit}) : {m.stock.toLocaleString("en-US")} {m.unit}}
{canCost &&
شراء:
}
))} {rows.length === 0 &&
لا توجد مواد مطابقة
}
) : (
{["المادة", "الرمز", "الفئة", "المخزن", "الحالة", "الرصيد", ...(canCost ? ["سعر الشراء"] : []), "السعر", ...(canEdit ? [""] : [])].map((h, hi) => ( ))} {rows.map((m) => ( {canCost && } {canEdit && ( )} ))} {rows.length === 0 && ( )}
{h}
{m.name} {m.hasMultiUnits && ( {m.packUnit} = {m.subUnitCount} {m.unit} )} {m.sku} {m.cat} {m.wh} {stockBadge(m)} {m.hasMultiUnits ? (
{window.BnyStock(m)}
{m.stock.toLocaleString("en-US")} {m.unit}
) : ( {m.stock.toLocaleString("en-US")} {m.unit} )}
لا توجد مواد مطابقة
)}
{editMat && setEditMat(null)} />} {transferMat && { setTransferMat(null); forceRender(); }} onClose={() => setTransferMat(null)} />}
); } /* نافذة نقل مادة بين المخازن */ function TransferModal({ m, onDone, onClose }) { const I = window.BnyIcon; const D = window.BnyData; const { Button, Select } = window.BonyanDesignSystem_ad3ca0; const others = (D.warehouses || []).filter((w) => w.name !== m.wh); const [target, setTarget] = React.useState(others.length ? others[0].name : ""); const [qty, setQty] = React.useState(""); const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(""); const val = Number(String(qty).replace(/[^\d.]/g, "")) || 0; const lbl = { fontSize: 12.5, fontWeight: 600, color: "var(--text-strong)", display: "block", marginBottom: 5 }; const doTransfer = async () => { if (busy) return; setBusy(true); setErr(""); try { const res = await window.BnyOps.transferStock(m, target, val); onDone(res); } catch (e) { setErr(String((e && e.message) || e)); } finally { setBusy(false); } }; return (
e.stopPropagation()} className="bny-card bny-card--pad" style={{ width: "100%", maxWidth: 400 }}>

نقل بين المخازن

{m.name} · الرصيد الحالي: {m.stock} {m.unit}
setQty(e.target.value.replace(/[^\d.]/g, ""))} placeholder={"بحد أقصى " + m.stock} style={{ width: "100%", height: 42, fontSize: 15, fontWeight: 700, textAlign: "center" }} aria-label="الكمية المنقولة" />
{others.length === 0 &&
لا يوجد مخزن آخر — أضف مخزنًا من «إدارة المخازن» أولًا.
} {err &&
{err}
}
); } /* نافذة إضافة/تعديل مادة */ function MaterialModal({ m, onSave, onClose }) { const I = window.BnyIcon; const D = window.BnyData; const { Button, Input, Select } = window.BonyanDesignSystem_ad3ca0; const isNew = !m.sku; const cats = D.categories || []; const whs = D.warehouses || []; const [f, setF] = React.useState(() => ({ sku: m.sku || "", name: m.name || "", cat: m.cat || "", unit: m.unit || "قطعة", hasMultiUnits: !!m.hasMultiUnits, packUnit: m.packUnit || "", subUnitCount: m.subUnitCount || 1, price: m.price || 0, priceUsd: m.priceUsd || 0, cost: m.cost || 0, stock: m.stock || 0, low: m.low || 0, wh: m.wh || (whs[0] && whs[0].name) || "الرئيسي", })); const [busy, setBusy] = React.useState(false); const set = (k, v) => setF((s) => ({ ...s, [k]: v })); const num = (v) => Number(String(v).replace(/[^\d.]/g, "")) || 0; const lbl = { fontSize: 12.5, fontWeight: 600, color: "var(--text-strong)", display: "block", marginBottom: 5 }; // الفئة إلزامية ويجب أن تطابق الفئات المُدارة (إدارة المخازن) const catValid = f.cat.trim() !== "" && cats.some((c) => c.name === f.cat); const canSave = f.name.trim() !== "" && f.unit.trim() !== "" && catValid && (!f.hasMultiUnits || (f.packUnit.trim() && f.subUnitCount > 1)); const save = async () => { if (!canSave || busy) return; setBusy(true); try { const sku = f.sku.trim() || ("BNY-" + Date.now().toString(36).toUpperCase()); await onSave({ sku, name: f.name.trim(), cat: f.cat.trim(), unit: f.unit.trim(), hasMultiUnits: f.hasMultiUnits, packUnit: f.hasMultiUnits ? f.packUnit.trim() : undefined, subUnitCount: f.hasMultiUnits ? Math.max(2, num(f.subUnitCount)) : 1, price: num(f.price), priceUsd: Number(f.priceUsd) || 0, cost: num(f.cost), stock: num(f.stock), low: num(f.low), wh: f.wh.trim() || "الرئيسي", }); } catch (e) { console.error(e); } finally { setBusy(false); } }; const numInput = (key, ph) => ( set(key, e.target.value.replace(/[^\d.]/g, ""))} placeholder={ph || "0"} style={{ width: "100%", height: 38 }} aria-label={ph} /> ); return (
e.stopPropagation()} className="bny-card" style={{ width: "100%", maxWidth: 520, padding: 0, display: "flex", flexDirection: "column", maxHeight: "90vh" }}>

{isNew ? "إضافة مادة جديدة" : "تعديل: " + m.name}

set("name", e.target.value)} placeholder="مثال: حديد تسليح ١٦ ملم" autoFocus={isNew} />
set("sku", e.target.value)} placeholder="يُولَّد تلقائيًا إن تُرك" disabled={!isNew} />
{!catValid && f.cat === "" &&
إلزامية — تُدار الفئات من «إدارة المخازن»
}
set("unit", e.target.value)} placeholder="قطعة / كيس / طن" />
وحدات متعددة (كرتون/قطعة)
set("hasMultiUnits", !f.hasMultiUnits)}>
{f.hasMultiUnits && (
set("packUnit", e.target.value)} placeholder="كرتون" />
{numInput("subUnitCount", "12")}
)}
{numInput("price")}
set("priceUsd", e.target.value)} style={{ width: "100%", height: 38 }} aria-label="سعر البيع بالدولار" />
{numInput("cost")}
{numInput("stock")}
{numInput("low")}
); } function SummaryTile({ icon, label, value, unit, sub, tone, active, onClick }) { const color = tone === "danger" ? "var(--danger)" : tone === "warning" ? "var(--warning)" : "var(--brand)"; const soft = tone === "danger" ? "var(--danger-soft)" : tone === "warning" ? "var(--warning-soft)" : "var(--brand-soft)"; return ( ); } window.InventoryScreen = InventoryScreen;