/* Bonyan ERP — Invoices register (سجل الفواتير) كل الفواتير (بيع + شراء) مرتّبة من الأحدث للأقدم، مع فلاتر: النوع (بيع/شراء) · المدى الزمني (رينج) · رقم الفاتورة (رينج) · الحالة. النقر على فاتورة يفتح النافذة الموحّدة (أصناف + تعديل + حذف). */ function InvoicesScreen({ onNav }) { const I = window.BnyIcon; const cur = window.BnyCur; const fmtDate = window.BnyDate; const status = window.BnyInvStatus; const { Card, Button, Money, Badge } = window.BonyanDesignSystem_ad3ca0; const isMobile = window.useBnyMobile(); const list = window.useBnyInvoices(); // يتحدّث تلقائيًا بعد أي تعديل/حذف const D = window.BnyData; const orgs = D.organizations || []; // مؤسسة العميل المرتبط بالفاتورة (بمطابقة الاسم) — للعرض والبحث const orgOfInvoice = (inv) => { const c = (inv.custId != null ? (D.customers || []).find((x) => x.id === inv.custId) : null) || (D.customers || []).find((x) => x.name === inv.party); if (!c || !c.org) return ""; const o = orgs.find((x) => x.id === c.org); return o ? o.name : ""; }; const [textQ, setTextQ] = React.useState(""); // بحث بالعميل/المؤسسة const [dupTarget, setDupTarget] = React.useState(null); // فاتورة قيد تأكيد النسخ const [typeF, setTypeF] = React.useState("all"); // all | sale | purchase const [statusF, setStatusF] = React.useState("all"); // all | paid | partial | unpaid const [dateFrom, setDateFrom] = React.useState(""); const [dateTo, setDateTo] = React.useState(""); const [numFrom, setNumFrom] = React.useState(""); const [numTo, setNumTo] = React.useState(""); // نسخ فاتورة: توليد رقم جديد ثم إنشاء نسخة مطابقة كمسودة غير مسددة const nextNoFor = (type) => { const cfg = (D.numbering || {})[type] || { prefix: type === "purchase" ? "PINV-" : "" }; const maxNum = list.filter((i) => i.type === type).reduce((m, i) => Math.max(m, i.num || 0), 0) + 1; return (cfg.prefix || "") + maxNum; }; const duplicate = (inv) => { const no = nextNoFor(inv.type); const clone = { ...inv, no, num: parseInt(String(no).replace(/\D/g, ""), 10) || 0, paid: 0, date: new Date().toISOString().slice(0, 10), items: (inv.items || []).map((it) => ({ ...it })) }; window.BnyInvoices.add(clone, { applyEffects: true }); // النسخة فاتورة فعلية: تؤثر على المخزون والأرصدة if (window.BnyOps) window.BnyOps.log("invoice_create", "نسخ الفاتورة #" + inv.no + " إلى مسودة جديدة #" + no, no); setDupTarget(null); window.BnyOpenInvoice(no); // افتحها كمسودة للتعديل }; const remaining = (inv) => Math.max(0, (inv.total || 0) - (inv.paid || 0)); const openInvoice = (inv) => window.BnyOpenInvoice(inv.no); const rows = list .filter((inv) => { if (typeF !== "all" && inv.type !== typeF) return false; if (statusF !== "all" && status(inv).key !== statusF) return false; if (dateFrom && inv.date < dateFrom) return false; if (dateTo && inv.date > dateTo) return false; if (numFrom !== "" && inv.num < Number(numFrom)) return false; if (numTo !== "" && inv.num > Number(numTo)) return false; if (textQ) { const on = orgOfInvoice(inv); if (!((inv.party || "").includes(textQ) || on.includes(textQ))) return false; } return true; }) .slice() .sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : b.num - a.num)); const hasFilter = textQ || typeF !== "all" || statusF !== "all" || dateFrom || dateTo || numFrom !== "" || numTo !== ""; const reset = () => { setTextQ(""); setTypeF("all"); setStatusF("all"); setDateFrom(""); setDateTo(""); setNumFrom(""); setNumTo(""); }; const typeBadge = (t) => t === "sale" ? بيع : شراء; const addBtn = ( ); const rangeInput = { height: 36, width: isMobile ? "100%" : 92, fontSize: 13, padding: "0 8px" }; const dateInput = { height: 36, width: isMobile ? "100%" : 150, fontSize: 13, padding: "0 8px", fontFamily: "var(--font-mono)" }; const filters = (
setTextQ(e.target.value)} placeholder="اسم العميل أو المؤسسة" aria-label="بحث بالعميل أو المؤسسة" style={{ height: 36, width: isMobile ? "100%" : 210, fontSize: 13, padding: "0 10px" }} />
setDateFrom(e.target.value)} aria-label="من تاريخ" style={dateInput} /> setDateTo(e.target.value)} aria-label="إلى تاريخ" style={dateInput} />
setNumFrom(e.target.value)} aria-label="من رقم" style={rangeInput} /> setNumTo(e.target.value)} aria-label="إلى رقم" style={rangeInput} />
{hasFilter && ( )}
); const empty = (
لا توجد فواتير مطابقة
جرّب تعديل الفلاتر أو امسحها لعرض كل الفواتير.
); const rowHover = (on) => (e) => { e.currentTarget.style.background = on ? "var(--surface-hover)" : "transparent"; }; return (
{isMobile &&
{addBtn}
} {filters}
{rows.length === 0 ? empty : isMobile ? (
{rows.map((inv) => { const st = status(inv); const rem = remaining(inv); return (
openInvoice(inv)} style={{ padding: "13px 4px", borderTop: "1px solid var(--border-subtle)", cursor: "pointer", borderRadius: "var(--radius-md)" }}>
{inv.no} {typeBadge(inv.type)}
{st.label}
{inv.party}
{fmtDate(inv.date)} · {(inv.items || []).length} صنف
{rem > 0 ? المتبقي: {rem.toLocaleString("en-US")} {cur(inv.cur)} : لا يوجد متبقٍ}
); })}
) : (
{rows.map((inv) => { const st = status(inv); const rem = remaining(inv); return ( openInvoice(inv)} onMouseEnter={rowHover(true)} onMouseLeave={rowHover(false)} style={{ borderTop: "1px solid var(--border-subtle)", cursor: "pointer", transition: "background var(--dur-fast,160ms) ease" }}> ); })}
رقم الفاتورة النوع اسم العميل المؤسسة التاريخ المبلغ الكلي الحالة المتبقي خيارات
{inv.no} {typeBadge(inv.type)} {inv.party} {orgOfInvoice(inv) || "—"} {fmtDate(inv.date)} {st.label} {rem > 0 ? : }
)}
{dupTarget && (
setDupTarget(null)} style={{ position: "fixed", inset: 0, zIndex: 1000, background: "rgba(0,0,0,.45)", display: "flex", alignItems: "center", justifyContent: "center", padding: "var(--space-5)" }}>
e.stopPropagation()} className="bny-card bny-card--pad" style={{ width: "100%", maxWidth: 410 }}>

نسخ الفاتورة #{dupTarget.no}

سيتم إنشاء فاتورة جديدة مطابقة تمامًا كمسودة (غير مسدّدة) برقم جديد، ثم فتحها للتعديل. هل تريد المتابعة؟

)}
); } const th = { fontWeight: 600, padding: "16px 12px 14px" }; const td = { padding: "14px 12px" }; /* حقل فلترة: عنوان صغير + عنصر التحكم. مُعرّف خارج المكوّن حتى لا تُعاد تهيئة الحقول (وتفقد تركيز الإدخال) مع كل إعادة رسم. */ function Field({ label, children }) { return (
{label} {children}
); } window.InvoicesScreen = InvoicesScreen;