المُسْتَشَار أوتو ALMOSTASHAR AUTO ERP جهاز محلي

تحميل البيانات...
🛍️ السوق

👤 1. بيانات العميل واختيار السيارة (CRM & Deal Engine)

🚗 إضافة سيارة جديدة لمخزون المعرض

(البيانات السرية مثل سعر الشاسيه وتكلفة الشراء تبقى محجوبة داخل المحاسبة فقط)

📦 مخزون وسيارات المعرض والوساطات

📋 سجل العقود والتحصيل الآلي للأقساط

متابعة تحصيل الأقساط الشهرية، تنبيهات التأخير، وإرسال إيصالات الواتساب.

👥 إضافة عميل محتمل جديد (New Lead)

📊 قمع المبيعات ومتابعة العملاء (Sales Pipeline)

📢 مركز الإرسال الجماعي عبر واتساب (WhatsApp Multi-Send)

إرسال متعدد باسم المعرض
إرسال رسائل واتساب موحدة ومخصصة لمجموعات العملاء باسم المعرض ورقم الواتساب المعتمد:
المعرض: معرض السيارات | واتساب: 01012345678

📖 شجرة الحسابات المحاسبية (Chart of Accounts)

دليل الحسابات والأرصدة الدفترية اللحظية بالأستاذ العام:
1010 - النقدية والبنوك
0 ج.م
1020 - مديونيات التقسيط المباشر
0 ج.م
1030 - مخصص مخاطر التعثر
0 ج.م
4010 + 4020 - إجمالي الإيرادات
0 ج.م
رقم الحساب اسم الحساب المحاسبي التصنيف الرصيد الدفتري الحالي
1010 النقدية بالخزينة والحسابات البنكية أصول متداولة 0 ج.م
1020 مديونيات ومستحقات عقود التقسيط المباشر أصول متداولة 0 ج.م
1030 مخصص مخاطر الديون المتعثرة (ECL/PD) خصم أصول متداولة 0 ج.م
4010 إيرادات مبيعات السيارات وعمولات البنوك إيرادات نشاط 0 ج.م
4020 إيرادات فوائد ومصاريف إدارية للتقسيط المباشر إيرادات تمويل 0 ج.م

📝 دفتر قيود اليومية الآلية (General Journal Entries)

توليد القيود المحاسبية الآلية المزدوجة لكل عملية بيع أو تحصيل قسط:

📈 تحليل المحفظة الائتمانية والتدفق النقدي (Credit & Cash Flow)

إجمالي المحفظة الممولة
0 ج.م
إجمالي النقدية المحصلة فعلياً
0 ج.م
المتبقي للتحصيل في السوق
0 ج.م
نسبة التعثر المتوقعة المحجوزة
0 ج.م

📅 جدول التدفقات النقدية الشهرية القادمة (Projected Cash Inflow)

إجمالي الأقساط المجدول تحصيلها شهرياً بناءً على العقود النشطة:

🛡️ شركات التأمين وإدارة وثائق السيارات

🏦 إضافة بنك جديد للنظام

البنوك المعتمدة المسجلة

📱 💻 إعداد وتكامل الأجهزة (Mobile + Laptop)

نوع هذا الجهاز الحالي:
معرّف الجهاز (UUID):
حالة تفعيل الجهاز: غير مفعل
يدعم النظام جهازين مصرحين معًا: لابتوب واحد وهاتف محمول واحد مع مزامنة كاملة لقاعدة البيانات.

🏢 بيانات وهوية الشركة والمعرض (WhatsApp & Company Profile)

الاسم الرسمي للرسائل
تُستخدم هذه البيانات رسمياً في ترويسة وتوقيع رسائل الواتساب، عروض الأسعار، وإيصالات السداد لكافة العملاء:

📦 حزمة الترخيص للإرسال عبر واتساب (WhatsApp ZIP Package)

نسخة مرخصة ومحمية
تم تجهيز ملف ZIP مضغوط يحتوي على كامل المنظومة. يمكنك إرساله للعميل/الفرع عبر واتساب، وهو محمي برمز التفعيل ولا يعمل إلا على هاتف واحد وجهاز لابتوب واحد فقط:
📥 تنزيل حزمة الـ ZIP للكمبيوتر

☁️ المزامنة مع Google Sheets

📁 قاعدة بيانات Excel المستقلة

تصدير واستيراد قاعدة البيانات والأستاذ العام ومخزون السيارات كملف Excel (.xlsx):

⚙️ الافتراضات المالية ونموذج المخاطر

📚 كتالوج الاستخدام ومزايا منظومة مستشارك (System Catalog & Benefits)

دليل الإمكانيات والفوائد المباشرة لصاحب المعرض، المحاسب، وفريق المبيعات والعملاء
🛍️ تصفح كتالوج السوق
🚗
كتالوج السيارات والمركبات
تصفح الأسعار، المواصفات، وكروت واتساب
📖
دليل القيود والأستاذ العام
5 أمثلة عملية لقيود الشراء والبيع والعمولة
📢
شريط الإعلانات المجدول
بث أخبار وتحذيرات بتوقيت وروابط نصية
📊
ميزان المراجعة والتقارير
تطابق الحسابات 100% وطباعة A4 وصور PNG
💼

1. مزايا وفوائد نظام المحاسبة والإدارة المالية (Accounting ERP)

للمدير والمحاسب
  • القيد المزدوج الآلي الفوري (Double-Entry): يتم إنشاء قيود اليومية المحاسبية المعقدة تلقائياً بمجرد إتمام أي عملية بيع، شراء، أو تحصيل قسط، دون الحاجة لخبرة محاسبية مسبقة.
  • فصل دقيق بين سيارات المعرض وسيارات الأمانة/الوساطة: يحمي رأس مال المعرض من خلال عدم تحميل سيارات الأمانة على مخزون المعرض، وقيد عمولة المعرض فقط كإيراد صافٍ، مع إثبات حق المالك الخارجي كالتزام أمانة.
  • محرك تقسيط مالي متكامل: حساب دقيق لفوائد الأقساط (ثابتة أو متناقصة)، جدولة مواعيد الاستحقاق، التنبيه التلقائي بالمتأخرات، وإصدار وطباعة سندات قبض فورية.
  • إدارة علاقات العملاء (CRM) وفحص سياسات البنوك: فحص فوري لعبء الدين (DBR) والحد الأدنى للرواتب ومطابقتها مع شروط كبرى البنوك المصرية (الأهلي، مصر، CIB، QNB).
  • تقارير مالية جاهزة للطباعة والمشاركة: جرد وتقييم المخزون، ميزان المراجعة الشامل، تقرير المتأخرات، مع إمكانية أخذ لقطة شاشة صورة PNG للمشاركة المباشرة عبر الواتساب.
🛍️

2. مزايا وفوائد تطبيق سوق ومعرض السيارات (Marketplace App)

لمسؤولي المبيعات والعملاء
  • كتالوج بصري جذاب للمركبات: يدعم السيارات، الموتوسيكلات، السكوتر، والشاحنات، مع فلترة سريعة بالماركة والموديل والحالة والمدينة.
  • كروت واتساب التسويقية بضغطة زر (📸 كارت صورة): توليد صورة احترافية لكل سيارة تشمل المواصفات والسعر والمقدم وقسط البنك لمشاركتها فوراً على حالات ومجموعات الواتساب.
  • حاسبة أقساط تفاعلية أمام العميل: تمكن موظف المبيعات أو الزائر من تجربة مختلف الدفعات المقدمة وعدد الأشهر ومعرفة القسط الشهري فورياً.
  • نظام حجز إلكتروني سريع مع إشعار للمالك: عندما يطلب العميل حجز سيارة، يصدر لابتوب المعرض نغمة رنين فورية مع إشعار بالاسم ورقم الواتساب ونوع الحجز.
  • توضيح ملكية وموقع المعاينة: بيان واضح عما إذا كانت السيارة متواجدة بالمعرض الرئيسي، بالتوكيل، مع المالك، أو بمستودع خارجي لراحة العميل.
📢

3. مزايا وفوائد شريط الإعلانات والتنبيهات المجدول (Ribbon System)

للتسويق وإدارة الأمان
  • جدولة زمنية ذاتية (Per Timer): ضبط وقت البدء والانتهاء بالساعة والدقيقة، بحيث ينتهي ظهور العرض تلقائياً دون تدخل يدوي.
  • إمكانية الاستهداف الخاص أو العام: بث إعلانات عامة لكافة زوار السوق، أو إرسال إشعار سري وخاص يظهر فقط لعميل محدد برقم هاتفه المسجل.
  • روابط نصية ذكية داخل الكلمات (Word Links): دعم صيغة [اضغط هنا](رابط) لتحويل الكلمات لروابط تفاعلية تحول الزائر لواتساب الإدارة فوراً.
  • تصنيف لوني ونفسي للرسائل: أخبار خضراء 📢، إشعارات زرقاء 🔔، ورسائل تحذير حمراء عاجلة ⚠️ لمنع التعاملات غير المعتمدة وحماية أموال الزبائن.
🔄

4. مزايا المعمارية المزدوجة (محلي مستقل Standalone أو سحابي متصل)

حرية تامة وأمان 100%
  • العمل بدون إنترنت وبدون خادم (Offline Standalone): يعمل ملف المحاسبة وملف السوق كملفات مستقلة ذاتياً بنسبة 100%، ويتم حفظ كافة العمليات بأمان على جهاز المستخدم.
  • مزامنة لحظية بين النوافذ (BroadcastChannel): إذا فتح المستخدم كلا الملفين في نفس المتصفح، تتدفق طلبات الشراء وتحديثات الأشرطة فورياً بينهما بدون شبكة وبدون سيرفر!
  • السحابة المركزية والربط عبر الشبكة (Connected Mode): عند الرغبة في ربط أجهزة المعرض عبر الواي فاي أو السحابة المركزية، يتم تفعيل المزامنة مع سحابة مستشارك (تحت إشراف autofyon@gmail.com وهاتف +201222597304).
  • التوافق مع كافة الأجهزة وأنظمة التشغيل: يعمل بسلاسة على لابتوب وكمبيوتر ويندوز، ماك، هواتف أندرويد، وآيفون وآيباد، مع إمكانية التثبيت كتطبيق أصلي (PWA).

📲 طلبات الشراء والحجز الواردة من تطبيق السوق (Marketplace)

تصلك طلبات حجز وشراء السيارات والموتوسيكل والسكوتر من تطبيق الهاتف مباشرة هنا مع إشعار فوري للمالك. عند اعتماد الطلب، يتم إنشاء العقد وتحديث المخزون وقيد العملية في الحسابات تلقائياً.
🟢 المراقبة الحية نشطة 0 طلب وارد

🛒 نظام شراء وتوريد مركبة جديدة للمخزون (Purchase System)

تسجيل فاتورة شراء سيارة/مركبة جديدة من المورد أو المالك الأصلي وإضافتها فورياً للمخزون مع توليد القيد المحاسبي المزدوج وإشعار المالك.

🔐 حجز ترخيص نظام مستشارك (Mostasharak)

إقرار واعتماد شروط الاستخدام وحقوق الملكية التجارية
إصدار 2.5 معتمد
مرحباً بك في نظام مستشارك (Mostasharak) لإدارة معارض السيارات والحسابات العامة. لربط نسختك وحجز قاعدة بياناتك السحابية المعزولة، يرجى تسجيل بيانات المعرض والاطلاع على شروط الاستخدام والضغط على زر التأكيد:

📜 وثيقة شروط الاستخدام وحقوق الترخيص التجاري:

1. الترخيص الحصري: هذه النسخة وقواعد بياناتها مرخصة ومحجوزة حصرياً لاسم المعرض ورقم الهاتف المسجلين أعلاه، ولا يحق نقلها أو التنازل عنها لأي طرف ثالث.
2. حد الأجهزة المسموح به: يسمح الترخيص بربط وتفعيل جهازين فقط (1 لابتوب رئيسي + 1 هاتف محمول) للمنشأة الواحدة.
3. حفظ البيانات والإشراف السحابي: يتم تشفير وحفظ السجلات المالية وحركة المخزون والعقود بسحابة المنظومة المركزية تحت حساب الإدارة: autofyon@gmail.com وهاتف/واتساب الدعم الفني: +201222597304.
4. المسؤولية المحاسبية والضريبية: المعرض هو المسؤول القانوني الأول والأخير عن صحة القيود والأسعار والمبالغ المقيدة، والبرنامج يقدم المعالجة والتدقيق المحاسبي المزدوج والتنبيهات.
5. الحماية والملكية الفكرية: جميع الحقوق البرمجية والعلامة التجارية (Mostasharak - مستشارك) محفوظة بالكامل، وأي نسخ أو تلاعب يوقع صاحبه تحت طائلة المسؤولية القانونية.

📖 دليل المحاسبة والتوجيه المالي لأصحاب معارض السيارات

منظومة مستشارك (Mostasharak) — شرح عملي مبسط مع قيود اليومية وأمثلة واقعية
💡 رسالة لصاحب المعرض والمحاسب: صُمم نظام "مستشارك" ليقوم بكافة القيود المحاسبية المعقدة تلقائياً بنظام القيد المزدوج المعتمد عالمياً (Double-Entry). هذا الدليل يوضح لك كيف تسير أموالك بين المخزون، الخزينة، أصحاب السيارات، والبنوك خطوة بخطوة.

📌 1. دليل الحسابات الرئيسي (Chart of Accounts)

  • 1010 - النقدية بالخزينة (Cash): المبالغ الفعلية المتوفرة في درج المعرض.
  • 1020 - الحسابات البنكية (Bank): أرصدة المعرض بالبنوك (الأهلي، مصر، CIB...).
  • 1040 - مخزون السيارات (Inventory Asset): السيارات المملوكة بالكامل للمعرض وتُسجل بسعر تكلفتها الأصلي.
  • 1050 - مدينو أقساط السيارات (Installments Receivable): إجمالي الأقساط المستحقة على العملاء بالمستقبل.
  • 1060 - فوائد أقساط مؤجلة غير مكتسبة: أرباح التقسيط المستقبلية التي تتحول لإيراد تدريجياً مع كل قسط يتم تحصيله.
  • 2020 - الموردون والدائنون (Payables): مستحقات تجار السيارات والشركات التي نشتري منها.
  • 2030 - مستحقات أصحاب سيارات الأمانة والوساطة: حق مالك السيارة الخارجي التي بيعت عبر المعرض.
  • 4010 - إيرادات مبيعات سيارات المعرض: قيمة بيع سيارة ملك المعرض.
  • 4030 - إيرادات عمولات ووساطة: عمولة المعرض عند بيع سيارة بالوساطة/الأمانة.
  • 5010 - تكلفة مبيعات السيارات (COGS): التكلفة الشرائية الأصلية للسيارة المباعة.

🚗 2. أمثلة عملية توضيحية من واقع سوق السيارات

المثال 1: شراء سيارة لحساب مخزون المعرض (نقداً أو آجل)
اشتريت سيارة (تويوتا كورولا 2023) بسعر 500,000 ج.م من أحد الموردين ودفعتها نقداً:
من حـ/ 1040 - مخزون السيارات: 500,000 ج.م (مدين - زاد المخزون)
    إلى حـ/ 1010 - النقدية بالخزينة: 500,000 ج.م (دائن - خرجت النقدية)
النتيجة: رأس مالك ثابت، تحولت النقدية إلى أصل بضاعة (سيارة في المعرض).
المثال 2: بيع سيارة ملك المعرض كاش وتحقيق ربح
تم بيع السيارة أعلاه (تكلفتها 500,000 ج.م) للعميل نقداً بمبلغ 560,000 ج.م:
قيد التحصيل والمبيعات:
من حـ/ 1010 - النقدية: 560,000 ج.م (مدين)
    إلى حـ/ 4010 - إيرادات مبيعات السيارات: 560,000 ج.م (دائن)

قيد خروج السيارة وإثبات التكلفة:
من حـ/ 5010 - تكلفة مبيعات السيارات: 500,000 ج.م (مدين - تكلفة)
    إلى حـ/ 1040 - مخزون السيارات: 500,000 ج.م (دائن - خرجت السيارة)
النتيجة: إيراد 560,000 ج.م ناقص تكلفة 500,000 ج.م = صافي ربح المعرض 60,000 ج.م.
المثال 3: بيع سيارة أمانة / وساطة (Consignment - سيارة شخص آخر)
أودع عميل سيارته بالمعرض لبيعها بـ 400,000 ج.م، واتفقت معه على عمولة للمعرض 15,000 ج.م، وتم بيعها بـ 415,000 ج.م:
من حـ/ 1010 - النقدية: 415,000 ج.م (مدين - استلمنا المبلغ كاملاً من المشتري)
    إلى حـ/ 2030 - مستحقات مالك السيارة: 400,000 ج.م (دائن - أمانة للمالك)
    إلى حـ/ 4030 - إيرادات عمولة الوساطة: 15,000 ج.م (دائن - ربح خالص للمعرض)
لاحظ جيداً: لم يقترب القيد من حساب (1040 المخزون)، لأن السيارة ليست أصل ملك المعرض! بل أمانة تم تسويقها.
المثال 4: تحصيل قسط شهري وإثبات الإيراد
جاء العميل لسداد قسطه الشهري البالغ 7,000 ج.م (منها 5,500 أصل سيارة + 1,500 فائدة):
من حـ/ 1010 - النقدية: 7,000 ج.م (مدين - دخلت الخزينة)
    إلى حـ/ 1050 - مدينو الأقساط: 7,000 ج.م (دائن - انخفض دين العميل)

قيد الاعتراف بربح الفائدة:
من حـ/ 1060 - فوائد مؤجلة: 1,500 ج.م (مدين)
    إلى حـ/ 4020 - إيرادات تمويل محققة: 1,500 ج.م (دائن)
تقوم شاشة العقود بطباعة سند قبض رسمي فوراً وإرساله للعميل على الواتساب.
المثال 5: كيف تقرأ ميزان المراجعة (Trial Balance)؟
عند فتح مركز التقارير 📊 ➔ ميزان المراجعة، ستجد جدولاً من عمودين متساويين دائماً:
- مجموع المدين (Debit) = مجموع الدائن (Credit).
- إذا كانا متساويين، فحساباتك دقيقة 100% ولا يوجد أي قرش ضائع بين الدرج والمخزون والعملاء.

📢 إدارة شريط أخبار وتنبيهات السوق (Mostasharak Ribbon)

بث رسائل عامة أو خاصة ومجدولة بوقت بدء وانتهاء مع روابط نصية
📋 الرسائل المسجلة والمجدولة في النظام:

📄 مركز طباعة المستندات والفواتير الرسمية (A4 Document Center)

طباعة فواتير البيع والشراء · محاضر وأذون تسليم المركبات · سندات الصرف والقبض المعتمدة
💡 اختر عقداً لطباعة فاتورة بيع سيارة رسمية A4 تتضمن بيانات المعرض، المشتري، مواصفات المركبة، والمقدم والأقساط والشروط والتواقيع الرسمية:

🔐 تفعيل ترخيص الجهاز (1 Mobile + 1 Laptop)

أدخل كود التفعيل المكون من 6 أرقام لتفعيل هذا الجهاز. يسمح النظام بجهازين معتمدين فقط (لابتوب + هاتف).

📊 مركز التقارير والتحليلات الشاملة

طباعة عالية الدقة A4 · لقطات شاشة PNG للمشاركة بالواتساب · وتصدير Excel
`); win.document.close(); } // ------------------------------------------------------------- // UNIVERSAL SCREEN CAPTURE & PRINTING ENGINE // ------------------------------------------------------------- function printCurrentView() { window.print(); } async function captureCurrentScreen() { toast('جاري التقاط الشاشة وحفظها كـ صورة عالية الدقة...'); try { const curTab = document.querySelector('.tab.on') || document.querySelector('main') || document.body; if (window.html2canvas) { const canvas = await html2canvas(curTab, { scale: 2, useCORS: true, backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark' ? '#14201a' : '#ffffff' }); const dataUrl = canvas.toDataURL('image/png'); const a = document.createElement('a'); const comp = (db.settings.companyName || 'ShowroomOS').replace(/[^\w\u0600-\u06FF]/g,'_'); const activeTabName = curTab.id || 'View'; a.download = `${comp}_${activeTabName}_${iso(new Date())}.png`; a.href = dataUrl; document.body.appendChild(a); a.click(); a.remove(); toast('✅ تم حفظ لقطة الشاشة كـ صورة PNG عالية الدقة جاهزة للإرسال بالواتساب'); } else { window.print(); } } catch(e) { toast('تعذر التقاط الصورة، جاري فتح الطباعة...'); window.print(); } } // ------------------------------------------------------------- // REPORTS CENTER (مركز التقارير الشامل) // ------------------------------------------------------------- let currentActiveReport = 'inventory'; function openReportsModal() { $('reportsModal').classList.add('on'); switchReportTab(currentActiveReport, document.querySelector(`.report-subnav button.on`) || $('repBtn_inventory')); } function closeReportsModal() { $('reportsModal').classList.remove('on'); } function switchReportTab(repType, btn) { currentActiveReport = repType; document.querySelectorAll('.report-subnav button').forEach(b => b.classList.remove('on')); if (btn) btn.classList.add('on'); const container = $('reportPrintArea'); const comp = db.settings.companyName || 'معرض السيارات'; const nowStr = new Date().toLocaleString('ar-EG'); $('reportGeneratedTime').textContent = `تاريخ التقرير: ${nowStr}`; if (repType === 'inventory') { container.innerHTML = generateInventoryReportHtml(comp, nowStr); } else if (repType === 'collections') { container.innerHTML = generateCollectionsReportHtml(comp, nowStr); } else if (repType === 'trialbalance') { container.innerHTML = generateTrialBalanceReportHtml(comp, nowStr); } else if (repType === 'contracts') { container.innerHTML = generateContractsReportHtml(comp, nowStr); } } function generateInventoryReportHtml(comp, nowStr) { const cars = db.cars || []; let totalCost = 0; let totalPrice = 0; cars.forEach(c => { totalCost += (c.cost || 0); totalPrice += (c.price || 0); }); const totalMargin = totalPrice - totalCost; return `

🚗 ${esc(comp)}

تقرير جرد وتقييم مخزون المركبات

تاريخ الاستخراج: ${nowStr} | عدد المركبات: ${cars.length} مركبة
إجمالي رأس المال بالمخزون (التكلفة)
${fmt(totalCost)} ج.م
القيمة البيعية الإجمالية
${fmt(totalPrice)} ج.م
إجمالي الأرباح المتوقعة
${fmt(totalMargin)} ج.م
${cars.map((c, idx) => ` `).join('')}
# الماركة والموديل السنة اللون رقم الشاسيه (VIN) التكلفة (ج.م) سعر البيع (ج.م) الحالة
${idx + 1} ${esc(c.brand)} ${esc(c.model)} ${c.year} ${esc(c.color || '—')} ${esc(c.chassisNo || c.vin || '—')} ${fmt(c.cost)} ${fmt(c.price)} ${c.status === 'Available' ? 'متاح' : (c.status === 'Reserved' ? 'محجوز' : 'مباع')}
مسؤول المخزن والحركة:

_________________
اعتماد الإدارة المالية والختم:

_________________
`; } function generateCollectionsReportHtml(comp, nowStr) { const contracts = db.contracts || []; let totalFin = 0; let totalPaid = 0; let totalLate = 0; const lateList = []; const today = iso(new Date()); contracts.forEach(c => { (c.installments || []).forEach(i => { totalFin += i.amount; if (i.paid) { totalPaid += i.amount; } else { if (i.due < today) { totalLate += i.amount; const days = Math.floor((new Date() - new Date(i.due)) / (1000 * 60 * 60 * 24)); lateList.push({ contractId: c.id, customer: c.customerName, phone: c.customerPhone, n: i.n, due: i.due, amount: i.amount, days }); } } }); }); return `

🚗 ${esc(comp)}

تقرير تحصيل الأقساط ومتابعة المديونيات والمتأخرات

تاريخ الاستخراج: ${nowStr}
إجمالي المحفظة التمويلية
${fmt(totalFin)} ج.م
المسدد فعلياً بالخزينة
${fmt(totalPaid)} ج.م
المتأخرات مستحقة السداد
${fmt(totalLate)} ج.م

⚠️ قائمة الأقساط المتأخرة المستحقة فورياً:

${lateList.length ? lateList.map(item => ` `).join('') : ''}
رقم العقد اسم العميل رقم الهاتف رقم القسط تاريخ الاستحقاق مبلغ القسط التأخير
CTR-${item.contractId} ${esc(item.customer)} ${esc(item.phone || '—')} قسط #${item.n} ${item.due} ${fmt(item.amount)} ج.م ${item.days} يوم
✅ لا توجد أقساط متأخرة حالياً — المحفظة منتظمة تماماً.
`; } function generateTrialBalanceReportHtml(comp, nowStr) { const b = computeLedgerBalances(); return `

🚗 ${esc(comp)}

ميزان المراجعة وقائمة المركز المالي العام

تاريخ الاستخراج: ${nowStr} | القيد المزدوج المعتمد
رقم الحساب اسم الحساب المحاسبي الرصيد المدين (ج.م) الرصيد الدائن (ج.م)
1010 النقدية بالخزينة والبنوك ${fmt(b.cash)} —
1020 مدينو أقساط سيارات (حسابات العملاء) ${fmt(b.receivables)} —
1030 مخصص مخاطر ائتمانية متوقعة (PD/LGD) — ${fmt(b.provisionPD)}
1040 مخزون المركبات (سيارات / موتو / سكوتر) ${fmt(b.inventory || 0)} —
2020 الموردون وأوراق الدفع (التوريدات الآجلة) — ${fmt(b.payables || 0)}
4010 إيرادات مبيعات المركبات — ${fmt(b.revSales)}
4020 إيرادات الفوائد والتمويل — ${fmt(b.revInterest)}
`; } function generateContractsReportHtml(comp, nowStr) { const contracts = db.contracts || []; return `

🚗 ${esc(comp)}

سجل عقود البيع والتقسيط المعتمدة

إجمالي العقود: ${contracts.length} عقد | تاريخ الاستخراج: ${nowStr}
${contracts.map(c => { const paidC = (c.installments || []).filter(x => x.paid).length; return ` `; }).join('')}
العقد العميل المركبة السعر الكلي المقدم القسط الشهري المدة المسدد
CTR-${c.id} ${esc(c.customerName)} ${esc(c.carModel || 'مركبة معتمدة')} ${fmt(c.price)} ${fmt(c.downPayment)} ${fmt(c.monthly)} ${c.months} شهر ${paidC} من ${(c.installments || []).length}
`; } function printActiveReport() { const content = $('reportPrintArea').innerHTML; const titles = { inventory: 'تقرير_جرد_وتقييم_المخزون', collections: 'تقرير_التحصيل_والمتأخرات', trialbalance: 'ميزان_المراجعة_والمركز_المالي', contracts: 'سجل_عقود_المبيعات' }; openPrintWindow(content, titles[currentActiveReport] || 'تقرير_معرض_السيارات'); } async function captureReportBox() { toast('جاري التقاط صورة التقرير بدقة عالية للواتساب...'); try { const area = $('reportPrintArea'); if (window.html2canvas) { const canvas = await html2canvas(area, { scale: 2, backgroundColor: '#ffffff' }); const a = document.createElement('a'); a.download = `Report_${currentActiveReport}_${iso(new Date())}.png`; a.href = canvas.toDataURL('image/png'); document.body.appendChild(a); a.click(); a.remove(); toast('✅ تم تحميل صورة التقرير PNG بنجاح — جاهزة للإرسال في واتساب!'); } else { printActiveReport(); } } catch(e) { toast('تعذر التقاط الصورة، جاري فتح الطباعة...'); printActiveReport(); } } function exportActiveReportExcel() { exportXlsx(false); } function pay(cid, n, keepOpen) { const c = db.contracts.find(x => x.id === cid); const i = c.installments.find(x => x.n === n); i.paid = true; i.paidDate = iso(new Date()); db.journal.push({ id: nid(db.journal), date: i.paidDate, contractId: cid, desc: `تحصيل قسط #${n} من العميل: ${c.customerName}`, debitAccount: '1010 - النقدية والبنوك', creditAccount: '1020 - مديونيات التقسيط المباشر', amount: i.amount }); saveLocal(); renderContracts(); if (keepOpen) toggleIns(cid); toast('تم تسجيل سداد القسط وتحديث دفتر الأستاذ العام'); stat(); } function delContract(id) { if (confirm('هل أنت متأكد من حذف هذا العقد وكافة سجلات أقساطه؟')) { db.contracts = db.contracts.filter(c => c.id !== id); db.journal = db.journal.filter(j => j.contractId !== id); saveLocal(); renderContracts(); stat(); } } // ------------------------------------------------------------- // GENERAL LEDGER & CHART OF ACCOUNTS // ------------------------------------------------------------- function computeLedgerBalances() { let cash = 0; let receivables = 0; let inventory = 0; let payables = 0; let revSales = 0; let revInterest = 0; (db.journal || []).forEach(j => { const amt = j.amount || 0; if (j.debitAccount.includes('1010') || j.debitAccount.includes('1015')) cash += amt; if (j.creditAccount.includes('1010') || j.creditAccount.includes('1015')) cash -= amt; if (j.debitAccount.includes('1020')) receivables += amt; if (j.creditAccount.includes('1020')) receivables -= amt; if (j.debitAccount.includes('1040')) inventory += amt; if (j.creditAccount.includes('1040')) inventory -= amt; if (j.debitAccount.includes('2020')) payables -= amt; if (j.creditAccount.includes('2020')) payables += amt; if (j.creditAccount.includes('4010')) revSales += amt; if (j.creditAccount.includes('4020')) revInterest += amt; }); const provisionPD = Math.round(receivables * (db.settings.pd || 0.03) * (db.settings.lgd || 0.50)); return { cash, receivables, inventory, payables, provisionPD, revSales, revInterest, totalRev: revSales + revInterest }; } function renderGeneralLedger() { const b = computeLedgerBalances(); $('bal_1010').textContent = fmt(b.cash) + ' ج.م'; $('bal_1020').textContent = fmt(b.receivables) + ' ج.م'; $('bal_1030').textContent = '-' + fmt(b.provisionPD) + ' ج.م'; $('bal_4000').textContent = fmt(b.totalRev) + ' ج.م'; $('row_1010').textContent = fmt(b.cash) + ' ج.م'; $('row_1020').textContent = fmt(b.receivables) + ' ج.م'; $('row_1030').textContent = '(' + fmt(b.provisionPD) + ' ج.م)'; $('row_4010').textContent = fmt(b.revSales) + ' ج.م'; $('row_4020').textContent = fmt(b.revInterest) + ' ج.م'; const jList = $('journalEntriesList'); if (!db.journal || !db.journal.length) { jList.innerHTML = '
لا توجد قيود يومية مسجلة بعد. عند اعتماد أي عقد أو تحصيل قسط، ستظهر القيود آلياً.
'; return; } jList.innerHTML = db.journal.slice().reverse().map(j => `
${esc(j.desc)}
التاريخ: ${j.date} | قيد آلي رقم #${j.id}
مدين: ${esc(j.debitAccount)} ⬅️ دائن: ${esc(j.creditAccount)}
${fmt(j.amount)} ج.م
`).join(''); } // ------------------------------------------------------------- // CREDIT FLOW & CASH FLOW ENGINE // ------------------------------------------------------------- function renderCreditFlow() { let totalFin = 0; let totalCollected = 0; let totalRemaining = 0; const monthlyFlow = {}; db.contracts.forEach(c => { (c.installments || []).forEach(i => { totalFin += i.amount; if (i.paid) { totalCollected += i.amount; } else { totalRemaining += i.amount; const ym = (i.due || '').substring(0, 7); if (ym) { monthlyFlow[ym] = (monthlyFlow[ym] || 0) + i.amount; } } }); }); const riskReserve = Math.round(totalRemaining * (db.settings.pd || 0.03) * (db.settings.lgd || 0.50)); $('cf_totalFin').textContent = fmt(totalFin) + ' ج.م'; $('cf_totalCollected').textContent = fmt(totalCollected) + ' ج.م'; $('cf_totalRemaining').textContent = fmt(totalRemaining) + ' ج.م'; $('cf_riskReserve').textContent = fmt(riskReserve) + ' ج.م'; const mDiv = $('monthlyInflowList'); const sortedMonths = Object.keys(monthlyFlow).sort(); if (!sortedMonths.length) { mDiv.innerHTML = '
لا توجد أقساط مستقبلية مستحقة التحصيل حالياً.
'; } else { mDiv.innerHTML = sortedMonths.slice(0, 12).map(m => `
شهر ${m} ${fmt(monthlyFlow[m])} ج.م
`).join(''); } const insDiv = $('insuranceList'); insDiv.innerHTML = (db.insurance || []).map(ins => `
${esc(ins.name)}
${esc(ins.coverage)}
${(ins.rate * 100).toFixed(1)}% سنوياً
`).join(''); } function renderBanks() { $('blist').innerHTML = db.banks.map(b => `
${esc(b.name)}
أقل راتب: ${fmt(b.minSalary)} ج.م · DBR: ${Math.round(b.maxDBR * 100)}% · مقدم جديد/مستعمل: ${Math.round(b.minDPNew * 100)}% / ${Math.round(b.minDPUsed * 100)}% · عمولة: ${(b.commission * 100).toFixed(1)}%
`).join('') || '
لا توجد بنوك مسجلة.
'; } function addBank() { const v = id => parseFloat($(id).value); const name = $('bn').value.trim(); if (!name || [v('bs'), v('bd'), v('bpn'), v('bpu'), v('bc'), v('bm')].some(isNaN)) { toast('يرجى تعبئة جميع حقول البنك'); return; } db.banks.push({ id: nid(db.banks), name, minSalary: v('bs'), maxDBR: v('bd'), sectors: 'Government,Private_Approved,Business_Owner', minDPNew: v('bpn'), minDPUsed: v('bpu'), maxMonths: v('bm'), commission: v('bc') }); saveLocal(); ['bn', 'bs', 'bd', 'bpn', 'bpu', 'bc'].forEach(i => $(i).value = ''); renderBanks(); stat(); toast('تم حفظ البنك في النظام'); } function delBank(id) { if (confirm('هل أنت متأكد من إزالة هذا البنك؟')) { db.banks = db.banks.filter(b => b.id !== id); saveLocal(); renderBanks(); stat(); } } function resetDefaultBanks() { if (confirm('استعادة قائمة البنوك المصرية الافتراضية؟')) { db.banks = JSON.parse(JSON.stringify(DEF_BANKS)); saveLocal(); renderBanks(); stat(); toast('تمت استعادة البنوك الافتراضية'); } } // ------------------------------------------------------------- // MARKET ORDERS & PURCHASE SYSTEM ENGINE // ------------------------------------------------------------- let knownOrderIds = new Set(); let lastOrderPollCount = 0; function playAlertChime() { try { const ctx = new (window.AudioContext || window.webkitAudioContext)(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.type = 'sine'; osc.frequency.setValueAtTime(587.33, ctx.currentTime); osc.frequency.setValueAtTime(880, ctx.currentTime + 0.15); gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.5); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.5); } catch(e){} } function showOwnerAlert(title, text) { playAlertChime(); const alertBox = document.createElement('div'); alertBox.style.cssText = 'position:fixed;top:15px;right:15px;left:15px;max-width:500px;margin:auto;z-index:99999;background:#0f6b5c;color:#fff;border-radius:12px;padding:16px;box-shadow:0 8px 30px rgba(0,0,0,0.35);font-family:Cairo,sans-serif;direction:rtl;border:2px solid #25D366;animation:slideDown 0.3s ease'; alertBox.innerHTML = `
🔔 ${esc(title)}
✕
${esc(text)}
`; document.body.appendChild(alertBox); setTimeout(() => { if (alertBox.parentElement) alertBox.remove(); }, 12000); } // 1. PURCHASE ENGINE: Buy vehicle from supplier into Inventory & General Ledger function executeCarPurchase() { const supplier = $('p_supplier').value.trim(); const suppPhone = $('p_supp_phone').value.trim(); const cat = $('p_cat').value; const brand = $('p_brand').value.trim(); const model = $('p_model').value.trim(); const year = parseInt($('p_year').value) || 0; const color = $('p_color').value.trim() || 'أبيض'; const chassis = $('p_chassis').value.trim(); const cost = parseFloat($('p_cost').value) || 0; const price = parseFloat($('p_price').value) || 0; const payMethod = $('p_pay_method').value; const notes = $('p_notes').value.trim(); if (!supplier || !brand || !model || !year || !cost || !price || !chassis) { toast('⚠️ يرجى ملء الحقول الإلزامية لشراء وتوريد المركبة'); return; } // 1. Add to Car Inventory const newCar = { id: nid(db.cars), brand, model, year, color, chassisNo: chassis, cost, price, condition: year >= new Date().getFullYear() ? 'New' : 'Used', status: 'Available', supplier, supplierPhone: suppPhone, category: cat, notes, dateAdded: iso(new Date()) }; db.cars.unshift(newCar); // 2. Generate Double-Entry Accounting Journal Entry const creditAcc = payMethod === 'credit' ? '2020 - الموردون وأوراق الدفع (آجل)' : (payMethod === 'bank' ? '1015 - الحسابات البنكية للمنشأة' : '1010 - النقدية بالخزينة'); const jId = nid(db.journal); db.journal.push({ id: jId, date: iso(new Date()), desc: `فاتورة شراء وتوريد مركبة: ${brand} ${model} (${year}) - شاسيه: ${chassis} من المورد: ${supplier}`, debitAccount: '1040 - مخزون المركبات', creditAccount: creditAcc, amount: cost }); saveLocal(); stat(); renderCars(); renderGeneralLedger(); // Clear inputs $('p_supplier').value = ''; $('p_supp_phone').value = ''; $('p_brand').value = ''; $('p_model').value = ''; $('p_year').value = ''; $('p_color').value = ''; $('p_chassis').value = ''; $('p_cost').value = ''; $('p_price').value = ''; $('p_notes').value = ''; showOwnerAlert('تم تسجيل شراء وتوريد مركبة جديدة بنجاح', `تم إضافة [${brand} ${model} ${year}] للمخزون برقم شاسيه (${chassis}) وتوليد القيد المحاسبي بالمديونية بقيمة ${fmt(cost)} ج.م.`); toast('✅ تم إضافة المركبة للمخزون وترحيل قيد الشراء للأستاذ العام'); } // 2. MARKET ORDERS API: Fetch, Alert Owner & Approve Sales async function loadMarketOrders() { const listDiv = $('marketOrdersList'); if (!listDiv) return; try { const res = await fetch('/api/orders'); if (!res.ok) throw new Error('API server unavailable'); const json = await res.json(); const orders = json.orders || []; // Check for new orders let hasNew = false; orders.forEach(o => { if (!knownOrderIds.has(o.id)) { knownOrderIds.add(o.id); if (lastOrderPollCount > 0 && o.status === 'pending') { hasNew = true; showOwnerAlert('طلب شراء / حجز وارد من تطبيق السوق!', `ورد طلب جديد من العميل [${o.buyerName}] لشراء/حجز [${o.productName}] بسعر ${fmt(o.price)} ج.م.`); } } }); lastOrderPollCount = orders.length; const pendingCount = orders.filter(o => o.status === 'pending').length; const countBadge = $('marketOrderCount'); if (countBadge) { countBadge.textContent = `${pendingCount} طلب بانتظار الاعتماد`; countBadge.className = pendingCount > 0 ? 'badge badge-warn' : 'badge badge-ok'; } if (!orders.length) { listDiv.innerHTML = '
لا توجد طلبات واردة من تطبيق السوق حالياً. كل طلب حجز أو شراء من تطبيق الموبايل سيظهر هنا فوراً.
'; return; } listDiv.innerHTML = orders.map(o => { const isPending = o.status === 'pending'; const isApproved = o.status === 'approved'; const statusBadge = isApproved ? '✅ معتمد ومرحل للمحاسبة' : (isPending ? '⏳ بانتظار اعتماد المالك' : '❌ مرفوض'); return `
${esc(o.productName)} ${statusBadge}
👤 العميل: ${esc(o.buyerName)} · 📞 واتساب: ${esc(o.buyerPhone)}
💰 السعر: ${fmt(o.price)} ج.م · نوع الطلب: ${esc(o.orderType)} · التوقيت: ${o.received ? o.received.substring(0,16).replace('T',' ') : ''} ${o.notes ? `
📝 ملاحظات: ${esc(o.notes)}` : ''}
${isPending ? ` ` : ''} 📲 محادثة واتساب
`; }).join(''); } catch(e) { if (listDiv) listDiv.innerHTML = '
خادم المزامنة المحلي متوقف أو غير متصل. اضغط تحديث عند تشغيل السيرفر.
'; } } // 3. APPROVE MARKET ORDER → UPDATE INVENTORY & POST ACCOUNTING async function approveMarketOrder(orderId) { try { const res = await fetch('/api/orders/action', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ orderId, action: 'approved' }) }); const json = await res.json(); if (!json.ok) throw new Error(json.error || 'Failed'); const o = json.order; // 1. Update vehicle inventory status to 'Sold' let matchedCar = (db.cars || []).find(c => c.status === 'Available' && (o.productName || '').includes(c.model)); if (matchedCar) { matchedCar.status = 'Sold'; } // 2. Post Sales Accounting Double-Entry (Distinguishes Our Stock vs Brokerage Consignment) const price = o.price || 0; const isOur = !matchedCar || matchedCar.isOurStock !== false; const cost = matchedCar ? (matchedCar.cost || 0) : Math.round(price * 0.85); if (isOur) { // Case A: Showroom Asset Sale db.journal.push({ id: nid(db.journal), date: iso(new Date()), desc: `اعتماد بيع مركبة (ملك المعرض): ${o.productName} للعميل: ${o.buyerName}`, debitAccount: '1010 - النقدية بالخزينة والبنوك', creditAccount: '4010 - إيرادات مبيعات المركبات', amount: price }); if (cost > 0) { db.journal.push({ id: nid(db.journal), date: iso(new Date()), desc: `تكلفة البضاعة المباعة للمركبة: ${o.productName}`, debitAccount: '5010 - تكلفة المركبات المباعة (COGS)', creditAccount: '1040 - مخزون المركبات', amount: cost }); } showOwnerAlert('تم اعتماد بيع مركبة (ملك المعرض)', `تم تسجيل إيراد البيع بقيمة ${fmt(price)} ج.م وإخراجها من المخزون بنجاح.`); } else { // Case B: Third-Party Brokerage / Consignment Sale const commission = (matchedCar && matchedCar.commission) ? matchedCar.commission : Math.round(price * 0.025); const netToOwner = price - commission; const owner = (matchedCar && matchedCar.ownerName) ? matchedCar.ownerName : 'المالك الأصلي'; db.journal.push({ id: nid(db.journal), date: iso(new Date()), desc: `تحصيل قيمة بيع سيارة وساطة (${o.productName}) - المالك: ${owner}`, debitAccount: '1010 - النقدية بالخزينة والبنوك', creditAccount: '2030 - مستحقات وأمانات أصحاب السيارات', amount: price }); db.journal.push({ id: nid(db.journal), date: iso(new Date()), desc: `عمولة المعرض عن وساطة بيع مركبة (${o.productName}) من المالك: ${owner}`, debitAccount: '2030 - مستحقات وأمانات أصحاب السيارات', creditAccount: '4030 - إيرادات عمولات وساطة وتسويق سيارات', amount: commission }); showOwnerAlert('تم اعتماد بيع سيارة وساطة خارجية!', `تم قيد عمولة المعرض ${fmt(commission)} ج.م كإيراد وساطة، وتثبيت صافي مستحقات المالك [${owner}] بقيمة ${fmt(netToOwner)} ج.م.`); } saveLocal(); stat(); renderCars(); renderGeneralLedger(); loadMarketOrders(); toast('🎉 تم اعتماد البيع وتحديث المخزون وقيد العملية في المحاسبة بنجاح!'); } catch(e) { toast('❌ تعذر اعتماد الطلب: ' + e.message); } } async function rejectMarketOrder(orderId) { if (!confirm('هل أنت متأكد من رفض هذا الطلب؟')) return; try { await fetch('/api/orders/action', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ orderId, action: 'rejected' }) }); toast('تم رفض الطلب'); loadMarketOrders(); } catch(e) { toast('تعذر الرفض'); } } function fillSettings() { const s = db.settings; $('s_cost').value = s.cost; $('s_rate').value = s.rate; $('s_bank').value = s.bank; $('s_pd').value = s.pd; $('s_lgd').value = s.lgd; $('s_cap').value = s.cap; $('s_fee').value = s.fee; $('s_method').value = s.method; $('s_comp_name').value = s.companyName || 'معرض السيارات الحديث'; $('s_comp_phone').value = s.companyPhone || '01012345678'; $('s_comp_addr').value = s.companyAddress || 'القاهرة - مصر'; $('apiUrlInput').value = getApiUrl(); $('myDevId').textContent = devId(); $('actStatusBadge').textContent = activated() ? 'مفعل ومقترن ✅' : 'غير مفعل ⚠️'; $('actStatusBadge').className = activated() ? 'badge badge-ok' : 'badge badge-warn'; $('deviceTypeSelect').value = getDeviceType(); } function saveSettings() { const n = id => parseFloat($(id).value); const s = { cost: n('s_cost'), rate: n('s_rate'), bank: n('s_bank'), pd: n('s_pd'), lgd: n('s_lgd'), cap: n('s_cap'), fee: n('s_fee'), method: $('s_method').value }; if (Object.values(s).some(x => typeof x === 'number' && isNaN(x))) { toast('قيمة غير صحيحة'); return; } db.settings = s; saveLocal(); toast('تم حفظ الافتراضات المالية'); } function wipe() { if (confirm('تحذير: سيتم مسح كافة البيانات من هذا الجهاز وإعادة الضبط للافتراضي. متابعة؟')) { db = { banks: JSON.parse(JSON.stringify(DEF_BANKS)), insurance: JSON.parse(JSON.stringify(DEF_INSURANCE)), cars: JSON.parse(JSON.stringify(DEF_CARS)), leads: JSON.parse(JSON.stringify(DEF_LEADS)), customers: [], contracts: [], journal: [], settings: {...DEF_SET} }; saveLocal(); fillSettings(); stat(); toast('تم مسح البيانات وإعادة الضبط'); } } // Excel SheetJS Export / Import function buildWb() { const wb = XLSX.utils.book_new(), S = toSheets(); Object.keys(S).forEach(n => XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(S[n]), n)); return wb; } async function exportXlsx(share) { try { const name = 'ShowroomERP_' + iso(new Date()) + '.xlsx'; const out = XLSX.write(buildWb(), {bookType: 'xlsx', type: 'array'}); const blob = new Blob([out], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); const file = new File([blob], name, {type: blob.type}); if (share && navigator.canShare && navigator.canShare({files: [file]})) { await navigator.share({files: [file], title: name}); return; } if (share) toast('المشاركة المباشرة غير مدعومة، جاري التنزيل...'); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = name; document.body.appendChild(a); a.click(); a.remove(); toast('تم تصدير ملف Excel الشامل'); } catch (e) { if (e.name !== 'AbortError') toast('تعذّر تصدير الملف'); } } $('imp').addEventListener('change', async e => { const f = e.target.files[0]; if (!f) return; try { const wb = XLSX.read(await f.arrayBuffer(), {type: 'array'}); const rows = n => wb.Sheets[n] ? XLSX.utils.sheet_to_json(wb.Sheets[n]) : []; if (!wb.Sheets.Banks && !wb.Sheets.Contracts) throw new Error('invalid format'); if (!confirm('سيتم استبدال البيانات الحالية بمحتوى ملف Excel. متابعة؟')) { e.target.value = ''; return; } db = dbFromSheets({ Banks: rows('Banks'), Insurance: rows('Insurance'), Cars: rows('Cars'), Leads: rows('Leads'), Customers: rows('Customers'), Contracts: rows('Contracts'), Installments: rows('Installments'), Journal: rows('Journal'), Settings: rows('Settings') }); saveLocal(); fillSettings(); stat(); toast('تم استيراد قاعدة البيانات والأستاذ العام والمخزون بنجاح'); } catch (err) { toast('ملف Excel غير صالح. استخدم ملفًا صُدّر من التطبيق'); } e.target.value = ''; }); // Google Sheets Sync & Dual-Device Lock Logic const API_URL_KEY = 'showroom_api_url'; const DEV_K = 'showroom_device_v4'; const DEV_TYPE_K = 'showroom_device_type'; const ACT_K = 'showroom_activated_v4'; const DIRTY_K = 'showroom_dirty_v4'; function getApiUrl() { return localStorage.getItem(API_URL_KEY) || ''; } function saveApiUrl() { const val = $('apiUrlInput').value.trim(); localStorage.setItem(API_URL_KEY, val); toast('تم حفظ رابط Apps Script'); syncState = syncCfg() ? 'pending' : 'off'; setSync(syncState); if (syncCfg()) initialSync(); } const syncCfg = () => /^https:\/\/script\.google\.com\//.test(getApiUrl()); const activated = () => localStorage.getItem(ACT_K) === '1'; const syncOn = () => syncCfg() && activated(); let rev = 0, pushing = false, pushTimer = null, syncState = syncCfg() ? 'pending' : 'off'; function devId() { let d = localStorage.getItem(DEV_K); if (!d) { d = crypto.randomUUID ? crypto.randomUUID() : [...crypto.getRandomValues(new Uint8Array(16))].map(x => x.toString(16).padStart(2, '0')).join(''); localStorage.setItem(DEV_K, d); } return d; } function getDeviceType() { let t = localStorage.getItem(DEV_TYPE_K); if (!t) { t = /Mobi|Android|iPhone/i.test(navigator.userAgent) ? 'Mobile' : 'Laptop'; localStorage.setItem(DEV_TYPE_K, t); } return t; } function changeDeviceType(newType) { localStorage.setItem(DEV_TYPE_K, newType); updateDeviceBadge(); toast('تم تحديد الجهاز كـ ' + (newType === 'Laptop' ? 'كمبيوتر محمول' : 'هاتف محمول')); } function updateDeviceBadge() { const t = getDeviceType(); const badge = $('devRoleBadge'); if (badge) { badge.textContent = t === 'Laptop' ? '💻 كمبيوتر محمول' : '📱 هاتف محمول'; } } const SYNC_MSG = { off: 'المزامنة غير مفعّلة (يرجى إدخال رابط Apps Script Web App من إعدادات Google Sheets).', ok: 'البيانات متزامنة تمامًا مع Google Sheet السحابي ومتاحة على الجهازين.', pending: 'توجد تعديلات جديدة جاري إرسالها إلى Google Sheet تلقائيًا...', err: 'تعذّرت المزامنة مؤقتًا (تحقق من الإنترنت). بياناتك محفوظة على هذا الجهاز وسيُعاد الإرسال تلقائيًا.' }; function syncLbl() { return {off: '', ok: ' · ☁ متزامن', pending: ' · ⏳ جاري المزامنة', err: ' · ⚠️ غير متزامن'}[syncState] || ''; } function setSync(s) { syncState = s; stat(); const m = $('syncmsg'); if (m) m.textContent = SYNC_MSG[s] || ''; } async function api(action, extra = {}) { const ctl = new AbortController(); const t = setTimeout(() => ctl.abort(), 25000); try { const r = await fetch(getApiUrl(), { method: 'POST', headers: {'Content-Type': 'text/plain;charset=utf-8'}, body: JSON.stringify({ action, deviceId: devId(), deviceType: getDeviceType(), companyName: db.settings.companyName || 'Auto One Egypt', companyPhone: db.settings.companyPhone || '01222597304', ...extra }), signal: ctl.signal }); return await r.json(); } finally { clearTimeout(t); } } function showLockModal(msg) { const m = $('lmsg'); m.textContent = msg || ''; m.style.display = msg ? 'block' : 'none'; $('modalDeviceType').value = getDeviceType(); $('lock').classList.add('on'); } function fail(e) { const m = String(e && e.message || e); if (/unauthorized/.test(m)) { localStorage.removeItem(ACT_K); showLockModal('هذا الجهاز غير مسجّل أو انتهت صلاحيته. اطلب كود تفعيل جديد من لوحة Apps Script.'); } else { setSync('err'); } } async function activateDevice() { const code = $('lc').value.trim(); if (!code) return; const b = $('lbtn'); b.disabled = true; const selType = $('modalDeviceType').value; localStorage.setItem(DEV_TYPE_K, selType); try { const r = await api('activate', {code, deviceType: selType}); if (!r.ok) throw new Error(r.error); localStorage.setItem(ACT_K, '1'); $('lock').classList.remove('on'); $('lc').value = ''; updateDeviceBadge(); fillSettings(); toast(`تم تفعيل الجهاز بنجاح كـ (${selType === 'Laptop' ? 'كمبيوتر محمول' : 'هاتف محمول'})`); initialSync(); } catch (e) { const m = String(e && e.message || e); showLockModal(/bad code/.test(m) ? 'كود التفعيل غير صحيح' : /already reached/i.test(m) ? 'تم الوصول للحد الأقصى للأجهزة (لابتوب + هاتف). أعد ضبط الكود من Google Sheets.' : 'تعذّر الاتصال بخادم Google. تحقق من الرابط والإنترنت.'); } b.disabled = false; } async function push() { if (!syncOn() || pushing) return; if (!navigator.onLine) { setSync('err'); return; } pushing = true; const mine = rev; try { const r = await api('push', {data: toSheets()}); if (!r.ok) throw new Error(r.error); if (mine === rev) { localStorage.removeItem(DIRTY_K); setSync('ok'); } else { pushing = false; return push(); } } catch (e) { fail(e); } finally { pushing = false; } } async function initialSync(manual) { if (!syncOn()) return; try { if (localStorage.getItem(DIRTY_K)) { await push(); if (manual) toast('تمت المزامنة بنجاح'); return; } setSync('pending'); const r = await api('pull'); if (!r.ok) throw new Error(r.error); const S = r.data || {}; if (!(S.Banks && S.Banks.length) && !(S.Customers && S.Customers.length) && !(S.Contracts && S.Contracts.length)) { rev++; localStorage.setItem(DIRTY_K, '1'); await push(); } else { db = dbFromSheets(S); localStorage.setItem(STORAGE_KEY, JSON.stringify(db)); setSync('ok'); fillSettings(); const curTab = document.querySelector('.tab.on'); if (curTab) tab(curTab.id); } if (manual) toast('تم استلام أحدث البيانات من Google Sheet'); } catch (e) { fail(e); } } const retrySync = () => { if (syncOn() && localStorage.getItem(DIRTY_K)) push(); }; window.addEventListener('online', retrySync); setInterval(retrySync, 60000); document.addEventListener('visibilitychange', () => { if (!document.hidden && syncOn()) initialSync(); }); // ══════════════════════════════════════════════════════════ // MOSTASHARAK EULA & LICENSE 1-CLICK RESERVATION LOGIC // ══════════════════════════════════════════════════════════ function checkEulaOnBoot() { const accepted = localStorage.getItem('mostasharak_eula_accepted'); if (!accepted) { $('eula_compName').value = db.settings.companyName || 'Auto One Egypt'; $('eula_compPhone').value = db.settings.companyPhone || '01222597304'; $('eula_deviceType').value = getDeviceType(); $('eulaModal').classList.add('on'); return false; } return true; } async function confirmEulaRegistration() { const compName = $('eula_compName').value.trim(); const compPhone = $('eula_compPhone').value.trim(); const devType = $('eula_deviceType').value; const agreed = $('eula_agree_check').checked; const errEl = $('eula_error_msg'); if (!compName || !compPhone) { errEl.textContent = '⚠️ يرجى إدخال اسم المعرض ورقم الهاتف كاملاً'; errEl.style.display = 'block'; return; } if (!agreed) { errEl.textContent = '⚠️ يرجى الموافقة على شروط الاستخدام وحقوق الترخيص بالضغط على مربع الإقرار'; errEl.style.display = 'block'; return; } errEl.style.display = 'none'; // Save to settings db.settings.companyName = compName; db.settings.companyPhone = compPhone; saveLocal(); updateCompanyBranding(); localStorage.setItem('mostasharak_eula_accepted', '1'); localStorage.setItem(ACT_K, '1'); localStorage.setItem(DEV_TYPE_K, devType); // Attempt registration against server or Google Sheets try { if (syncCfg()) { await api('register_and_accept', { companyName: compName, companyPhone: compPhone, deviceType: devType, deviceId: devId(), notes: 'اعتماد الشروط وحجز النسخة بنجاح' }); } } catch(e) { console.log('Local acceptance saved, cloud sync will resume when online'); } $('eulaModal').classList.remove('on'); toast('🎉 تم حجز النسخة واعتماد شروط نظام مستشارك بنجاح!'); updateDeviceBadge(); stat(); } // ══════════════════════════════════════════════════════════ // ACCOUNTING GUIDE MODAL LOGIC // ══════════════════════════════════════════════════════════ function openAccountingGuideModal() { $('accountingGuideModal').classList.add('on'); } function closeAccountingGuideModal() { $('accountingGuideModal').classList.remove('on'); } function printAccountingGuide() { const content = $('accountingGuideContent').innerHTML; const win = window.open('', '_blank'); win.document.write(` دليل المحاسبة والتوجيه المالي - نظام مستشارك

نظام مستشارك (Mostasharak) لإدارة معارض السيارات

دليل المحاسبة والتوجيه المالي لأصحاب المعارض والمديرين

${content} `); win.document.close(); setTimeout(() => { win.print(); }, 500); } // ══════════════════════════════════════════════════════════ // PC RIBBON MANAGER LOGIC // ══════════════════════════════════════════════════════════ let PC_RIBBONS = []; function openRibbonManagerModal() { loadPcRibbons(); resetPcRibbonForm(); $('pcRibbonManagerModal').classList.add('on'); } function closeRibbonManagerModal() { $('pcRibbonManagerModal').classList.remove('on'); } function togglePcRibbonAudience() { const aud = $('pc_rb_audience').value; $('pc_rb_private_box').style.display = aud === 'private' ? 'block' : 'none'; } function insertPcRibbonLinkSample() { const ta = $('pc_rb_content'); const sample = '[اضغط هنا لمعرفة التفاصيل](https://wa.me/201222597304)'; ta.value += (ta.value ? ' ' : '') + sample; ta.focus(); } function resetPcRibbonForm() { $('pc_rb_id').value = ''; $('pc_rb_type').value = 'news'; $('pc_rb_audience').value = 'public'; $('pc_rb_private_box').style.display = 'none'; $('pc_rb_targetPhone').value = ''; $('pc_rb_title').value = ''; $('pc_rb_content').value = ''; $('pc_rb_active').checked = true; const now = new Date(); const plus7 = new Date(Date.now() + 7 * 86400000); $('pc_rb_fromDateTime').value = now.toISOString().slice(0, 16); $('pc_rb_toDateTime').value = plus7.toISOString().slice(0, 16); } async function loadPcRibbons() { try { const res = await fetch('/api/ribbons'); if (res.ok) { const data = await res.json(); if (data && data.ribbons) { PC_RIBBONS = data.ribbons; } } } catch(e) { const cached = localStorage.getItem('mostasharak_ribbons_cache'); if (cached) { try { PC_RIBBONS = JSON.parse(cached); } catch(err){} } } renderPcRibbonTable(); } function renderPcRibbonTable() { const container = $('pc-ribbon-list-table'); if (!container) return; if (!PC_RIBBONS.length) { container.innerHTML = '
لا توجد رسائل مجدولة حالياً
'; return; } container.innerHTML = PC_RIBBONS.map(r => { const audBadge = r.audience === 'private' ? `🔒 خاص: ${esc(r.targetPhone || '')}` : '🌐 عام للجميع'; const typeLabel = r.type === 'warning' ? '⚠️ تحذير' : r.type === 'notice' ? '🔔 إشعار' : '📢 خبر'; return `
${typeLabel}: ${esc(r.title)}
${audBadge} · من ${(r.fromDateTime||'').slice(0,10)} إلى ${(r.toDateTime||'').slice(0,10)}
`; }).join(''); } function editPcRibbon(id) { const r = PC_RIBBONS.find(item => item.id === id); if (!r) return; $('pc_rb_id').value = r.id; $('pc_rb_type').value = r.type || 'news'; $('pc_rb_audience').value = r.audience || 'public'; togglePcRibbonAudience(); $('pc_rb_targetPhone').value = r.targetPhone || ''; $('pc_rb_title').value = r.title || ''; $('pc_rb_content').value = r.content || ''; $('pc_rb_fromDateTime').value = (r.fromDateTime || '').slice(0, 16); $('pc_rb_toDateTime').value = (r.toDateTime || '').slice(0, 16); $('pc_rb_active').checked = r.active !== false; } async function savePcRibbon() { const title = $('pc_rb_title').value.trim(); const content = $('pc_rb_content').value.trim(); const type = $('pc_rb_type').value; const audience = $('pc_rb_audience').value; const targetPhone = $('pc_rb_targetPhone').value.trim(); const fromDateTime = $('pc_rb_fromDateTime').value; const toDateTime = $('pc_rb_toDateTime').value; const active = $('pc_rb_active').checked; const id = $('pc_rb_id').value || ('ribbon-' + Date.now()); if (!title || !content) { toast('⚠️ يرجى إدخال العنوان ونص الرسالة'); return; } if (!fromDateTime || !toDateTime) { toast('⚠️ يرجى تحديد وقت البدء والانتهاء'); return; } const ribbonObj = { id, type, audience, targetPhone, title, content, fromDateTime, toDateTime, active, updatedAt: new Date().toISOString() }; const idx = PC_RIBBONS.findIndex(r => r.id === id); if (idx >= 0) PC_RIBBONS[idx] = ribbonObj; else PC_RIBBONS.unshift(ribbonObj); localStorage.setItem('mostasharak_ribbons_cache', JSON.stringify(PC_RIBBONS)); try { await fetch('/api/ribbons', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(ribbonObj) }); } catch(e){} toast('✅ تم حفظ وبث شريط التنبيهات إلى السوق بنجاح!'); renderPcRibbonTable(); resetPcRibbonForm(); } async function deletePcRibbon(id) { if (!confirm('هل تريد حذف هذه الرسالة من شريط السوق؟')) return; PC_RIBBONS = PC_RIBBONS.filter(r => r.id !== id); localStorage.setItem('mostasharak_ribbons_cache', JSON.stringify(PC_RIBBONS)); try { await fetch('/api/ribbons/delete', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({id}) }); } catch(e){} renderPcRibbonTable(); toast('🗑 تم حذف الرسالة'); } // ══════════════════════════════════════════════════════════ // OFFICIAL DOCUMENT PRINTING CENTER & TEMPLATES (A4) // ══════════════════════════════════════════════════════════ function openDocPrintCenter() { switchDocTab('invoices', $('docTab_invoices')); renderDocInvoicesList(); $('docPrintCenterModal').classList.add('on'); } function closeDocPrintCenter() { $('docPrintCenterModal').classList.remove('on'); } function switchDocTab(tab, btn) { document.querySelectorAll('.doc-sec').forEach(e => e.style.display = 'none'); document.querySelectorAll('.report-subnav button').forEach(b => b.classList.remove('on')); if (btn) btn.classList.add('on'); const sec = $('docSec_' + tab); if (sec) sec.style.display = 'block'; if (tab === 'invoices') renderDocInvoicesList(); else if (tab === 'delivery') renderDocDeliveryList(); else if (tab === 'purchase') renderDocPurchaseList(); else if (tab === 'vouchers') renderDocVouchersList(); } function renderDocInvoicesList() { const container = $('docInvoicesList'); if (!container) return; if (!db.contracts || !db.contracts.length) { container.innerHTML = '
لا توجد عقود مسجلة بعد لطباعة فواتيرها
'; return; } container.innerHTML = db.contracts.map(c => `
عقد #${c.id} — ${esc(c.customerName)}
المركبة: ${esc(c.carModel || 'سيارة')} | القيمة: ${fmt(c.price)} ج.م | التاريخ: ${c.date}
`).join(''); } function renderDocDeliveryList() { const container = $('docDeliveryList'); if (!container) return; const list = db.contracts && db.contracts.length ? db.contracts : []; if (!list.length && (!db.cars || !db.cars.length)) { container.innerHTML = '
لا توجد سيارات أو عقود مسجلة بعد
'; return; } let out = list.map(c => `
تسليم للمشتري: ${esc(c.customerName)}
السيارة: ${esc(c.carModel || 'مركبة')} | شاسيه: ${esc(c.carChassis || '---')}
`).join(''); container.innerHTML = out || '
اختر من العقود أعلاه
'; } function renderDocPurchaseList() { const container = $('docPurchaseList'); if (!container) return; const cars = (db.cars || []).filter(c => c.cost > 0); if (!cars.length) { container.innerHTML = '
لا توجد مركبات مسجلة بتكلفة شراء
'; return; } container.innerHTML = cars.map(c => `
${esc(c.brand)} ${esc(c.model)} (${c.year})
سعر الشراء: ${fmt(c.cost)} ج.م | شاسيه: ${esc(c.chassis || '---')} | المالك/المورد: ${esc(c.ownerName || 'المورد')}
`).join(''); } function renderDocVouchersList() { const container = $('docJournalVouchersList'); if (!container) return; const journal = db.journal || []; if (!journal.length) { container.innerHTML = '
لا توجد قيود مسجلة بعد
'; return; } container.innerHTML = journal.slice(0, 15).map(j => `
قيد #${j.id} — ${esc(j.desc || 'عملية مالية')}
المبلغ: ${fmt(j.amount)} ج.م | من ${j.debit} إلى ${j.credit} | التاريخ: ${j.date}
`).join(''); } // ────────────────────────────────────────────────────────── // 1. OFFICIAL CAR SALES INVOICE (فاتورة بيع سيارة معتمدة A4) // ────────────────────────────────────────────────────────── function printSalesInvoice(cid) { const c = db.contracts.find(x => x.id === cid); if (!c) return; const comp = db.settings.companyName || 'معرض السيارات'; const phone = db.settings.companyPhone || '01222597304'; const addr = db.settings.companyAddress || 'القاهرة - جمهورية مصر العربية'; const html = `

${esc(comp)}

ALMOSTASHAR AUTO • المُسْتَشَار أوتو
${esc(addr)}
هاتف وواتساب: ${esc(phone)} | س.ت: 48921 | ب.ض: 621-893-410
فاتورة بيع سيارة
رقم: INV-${c.id}-${(c.date||'').slice(0,4)}
التاريخ: ${c.date || iso(new Date())}
👤 بيانات المشتري (العميل)
اسم المشتري: ${esc(c.customerName)}رقم الهاتف: ${esc(c.customerPhone || '---')}
الرقم القومي: ${esc(c.customerIdNumber || '2940815xxxxxxxx')}العنوان: ${esc(c.customerAddress || 'جمهورية مصر العربية')}
🚗 مواصفات المركبة المباعة
الماركة والموديل: ${esc(c.carModel || 'سيارة')} سنة الصنع: ${esc(c.carYear || '---')} اللون: ${esc(c.carColor || 'أبيض')}
رقم الشاسيه (VIN): ${esc(c.carChassis || '---')} رقم الموتور: ${esc(c.carEngine || '---')}
البيان الماليالمبلغ (جنيه مصري)
سعر البيع الإجمالي المتفق عليه:${fmt(c.price)} ج.م
الدفعة المقدمة المسددة نقداً / بنكياً:${fmt(c.downPayment)} ج.م
المتبقي المقسط على العميل:${fmt(c.price - c.downPayment)} ج.م
نظام السداد ومدة الأقساط:${c.months ? `${c.months} شهر × ${fmt(c.monthly)} ج.م` : 'سداد كاش بالكامل'}
إقرار وشروط الفاتورة: تقر إدارة المعرض بخلو المركبة المذكورة من أي موانع بيع قانونية أو مستحقات مالية حتى تاريخ وساعة تحرير هذه الفاتورة، ويتعهد المشتري بسداد الأقساط والالتزامات وفق جدول الاستحقاق المحدد.
توقيع المشتري المقر:

____________________
المحاسب المالي:

____________________
المدير العام والاعتماد:

____________________
خاتم المعرض الرسمي:

____________________
`; openPrintWindow(html, `فاتورة_بيع_${c.customerName}_INV_${c.id}`); } // ────────────────────────────────────────────────────────── // 2. VEHICLE HANDOVER PROTOCOL (محضر وإذن تسليم واستلام مركبة A4) // ────────────────────────────────────────────────────────── function printDeliveryNote(cid) { const c = db.contracts.find(x => x.id === cid); if (!c) return; const comp = db.settings.companyName || 'معرض السيارات'; const phone = db.settings.companyPhone || '01222597304'; const html = `

🚗 ${esc(comp)}

إدارة المبيعات والتسليم | هاتف: ${esc(phone)}
محضر تسليم واستلام مركبة
رقم الإذن: DEL-${c.id}-${(c.date||'').slice(0,4)}
تاريخ ووقت التسليم: ${iso(new Date())}
إنه في يوم ${new Date().toLocaleDateString('ar-EG', {weekday:'long', year:'numeric', month:'long', day:'numeric'})}، تم تسليم واستلام المركبة الموضحة أدناه بين كل من:
الطرف الأول (المُسَلِّم): إدارة ${esc(comp)}.
الطرف الثاني (المُسْتَلِم): السيد / ${esc(c.customerName)} — بطاقة رقم قومي: ${esc(c.customerIdNumber || '2940815xxxxxxxx')} — هاتف: ${esc(c.customerPhone || '---')}.
🚗 مواصفات المركبة المسلمة
النوع والموديل: ${esc(c.carModel || 'مركبة')} سنة الصنع: ${esc(c.carYear || '---')} اللون الخارجي: ${esc(c.carColor || 'أبيض')}
رقم الشاسيه: ${esc(c.carChassis || '---')} رقم الموتور: ${esc(c.carEngine || '---')} قراءة العداد الحالية: ${c.carKm || 0} كم
📋 قائمة الفحص ومشتملات الاستلام (Checklist):
☑️ أصل رخصة التسيير / تصريح المرور / الإفراج الجمركي☑️ المفتاح الأصلي + المفتاح الاحتياطي المشفر
☑️ الإطار الاحتياطي (الاستبن) + الكوريك ومفتاح العجل☑️ طفاية الحريق وحقيبة الإسعافات والمثلث العاكس
☑️ سلامة الصالون الداخلي والفرش وشاشة التحكم والتابلوه☑️ سلامة الزجاج والفوانيس والهيكل الخارجي
إقرار استلام ومعاينة: أقر أنا الطرف الثاني المستلم بأنني قمت بمعاينة وفحص وتجربة المركبة المذكورة أعلاه المعاينة التامة النافية للجهالة شرعاً وقانوناً، واستلمتها بحالة ممتازة ومطابقة لكافة المواصفات مع كافة أوراقها ومشتملاتها، وأصبحت السيارة في حيازتي وتحت مسؤوليتي المدنية والجنائية الكاملة من تاريخ وساعة هذا المحضر.
توقيع المستلم (الطرف الثاني):

____________________
مسؤول التسليم بالمعرض:

____________________
اعتماد الإدارة العامة:

____________________
الختم الرسمي:

____________________
`; openPrintWindow(html, `محضر_تسليم_${c.customerName}_DEL_${c.id}`); } function printCarDeliveryNoteById(carId) { const car = (db.cars || []).find(x => x.id === carId); if (!car) return; const comp = db.settings.companyName || 'معرض السيارات'; const phone = db.settings.companyPhone || '01222597304'; const html = `

🚗 ${esc(comp)} — إذن ومحضر تسليم مركبة

رقم الإذن: DEL-CAR-${car.id} | التاريخ: ${iso(new Date())}
المركبة: ${esc(car.brand)} ${esc(car.model)} (${car.year})اللون: ${esc(car.color || '---')}
رقم الشاسيه: ${esc(car.chassis || '---')}مكان التسليم: ${esc(car.locationName || 'المعرض الرئيسي')}
تم تسليم المركبة المذكورة أعلاه بحالة فنية ممتازة مع مفاتيحها وأوراقها القانونية.
توقيع المستلم:

____________________
توقيع مسؤول المعرض:

____________________
الختم الرسمي:

____________________
`; openPrintWindow(html, `إذن_تسليم_${car.brand}_${car.model}`); } // ────────────────────────────────────────────────────────── // 3. CAR INWARD PURCHASE INVOICE (فاتورة شراء وتوريد مركبة A4) // ────────────────────────────────────────────────────────── function printPurchaseInvoice(carId) { const car = (db.cars || []).find(x => x.id === carId); if (!car) return; const comp = db.settings.companyName || 'معرض السيارات'; const phone = db.settings.companyPhone || '01222597304'; const html = `

🛒 ${esc(comp)}

إدارة المشتريات والمخازن | هاتف: ${esc(phone)}
فاتورة شراء وتوريد مركبة
رقم الفاتورة: PUR-${car.id}-${new Date().getFullYear()}
التاريخ: ${iso(new Date())}
🏢 بيانات المورد / البائع الأصلي
اسم المورد: ${esc(car.ownerName || 'شركة توريد معتمدة')}الهاتف: ${esc(car.supplierPhone || '01xxxxxxxxx')}
📦 مواصفات المركبة الموردة للمخزون
الماركة والموديل: ${esc(car.brand)} ${esc(car.model)} سنة الصنع: ${car.year} اللون: ${esc(car.color || 'أبيض')}
رقم الشاسيه: ${esc(car.chassis || '---')} سعر الشراء والتكلفة: ${fmt(car.cost)} ج.م سعر البيع المقترح: ${fmt(car.price)} ج.م
القيد المحاسبي الآلي المولد:
من حـ/ 1040 - مخزون السيارات: ${fmt(car.cost)} ج.م (مدين)
    إلى حـ/ 1010 النقدية بالخزينة / 2020 الموردين: ${fmt(car.cost)} ج.م (دائن)
توقيع المورد/البائع:

____________________
أمين مخزن المعرض:

____________________
المحاسب المالي:

____________________
اعتماد الإدارة والختم:

____________________
`; openPrintWindow(html, `فاتورة_شراء_${car.brand}_${car.model}_PUR_${car.id}`); } // ────────────────────────────────────────────────────────── // 4. CASH VOUCHERS (سند صرف نقدية وسند قبض نقدية A4) // ────────────────────────────────────────────────────────── function printCustomVoucher() { const type = $('v_type').value; const amount = parseFloat($('v_amount').value) || 0; const party = $('v_party').value.trim(); const desc = $('v_desc').value.trim(); if (!amount || !party) { alert('يرجى إدخال المبلغ واسم الطرف المعني'); return; } const isReceipt = type === 'receipt'; const comp = db.settings.companyName || 'معرض السيارات'; const phone = db.settings.companyPhone || '01222597304'; const serial = (isReceipt ? 'REC-' : 'PAY-') + Date.now().toString().slice(-6); const html = `

${esc(comp)}

الإدارة المالية والحسابات العامة | ت: ${esc(phone)}
${isReceipt ? 'سند قبض نقدية (Receipt)' : 'سند صرف نقدية (Payment)'}
رقم السند: ${serial}
التاريخ: ${iso(new Date())}
المبلغ المطلوب: ${fmt(amount)} جنيه مصري فقط لا غير
${isReceipt ? 'وصلنا من السيد / السادة:' : 'يُصرف للسيد / السادة:'}${esc(party)}
وذلك عن قيمة (البيان):${esc(desc || 'سداد نقدية مسجلة')}
طريقة السداد:نقداً من الخزينة الرئيسية (حـ/ 1010 النقدية)
توقيع المستلم:

____________________
أمين الخزينة:

____________________
المحاسب المالي:

____________________
اعتماد الإدارة والختم:

____________________
`; openPrintWindow(html, `سند_${isReceipt ? 'قبض' : 'صرف'}_${serial}`); } function printJournalVoucher(jid) { const j = (db.journal || []).find(x => x.id === jid); if (!j) return; const isReceipt = j.debit === 1010; const comp = db.settings.companyName || 'معرض السيارات'; const html = `

${esc(comp)} — سند قيد محاسبي

رقم القيد: JRN-${j.id} | التاريخ: ${j.date}
${fmt(j.amount)} ج.م
الحساب المدين: حـ/ ${j.debit}الحساب الدائن: حـ/ ${j.credit}
البيان: ${esc(j.desc || 'قيد أوتوماتيكي')}
إعداد المحاسب:

____________________
مراجعة الحسابات:

____________________
اعتماد المدير المالي:

____________________
`; openPrintWindow(html, `سند_قيد_JRN_${j.id}`); } function boot() { if (window.SHOWROOM_LICENSE && window.SHOWROOM_LICENSE.companyName) { db.settings.companyName = window.SHOWROOM_LICENSE.companyName; if (window.SHOWROOM_LICENSE.companyPhone) db.settings.companyPhone = window.SHOWROOM_LICENSE.companyPhone; if (window.SHOWROOM_LICENSE.companyAddress) db.settings.companyAddress = window.SHOWROOM_LICENSE.companyAddress; saveLocal(); // Show update banner if version info available checkForUpdateNotice(); } updateCompanyBranding(); updateDeviceBadge(); if (!checkEulaOnBoot()) return; const savedTheme = localStorage.getItem('showroom_theme'); if (savedTheme) document.documentElement.setAttribute('data-theme', savedTheme); renderCarSelectDropdown(); // Start Market Orders Background Listener (Auto Owner Indication) setInterval(loadMarketOrders, 5000); setTimeout(loadMarketOrders, 1000); if (!syncCfg()) { setSync('off'); return; } if (!activated()) { showLockModal(); return; } initialSync(); } function checkForUpdateNotice() { const lic = window.SHOWROOM_LICENSE; if (!lic) return; const storedVer = localStorage.getItem('showroom_installed_version'); const currentVer = lic.version || '2.0.0'; const updateDate = lic.updateDate || lic.issueDate || ''; if (storedVer && storedVer !== currentVer) { // New version detected — show a banner const banner = document.createElement('div'); banner.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:9999;background:#0f6b5c;color:#fff;text-align:center;padding:10px 16px;font-family:Cairo,sans-serif;font-size:14px;direction:rtl;'; banner.innerHTML = `✅ تم تثبيت تحديث جديد للنظام (الإصدار ${currentVer}) بتاريخ ${updateDate} لـ ${esc(lic.companyName)}. `; document.body.prepend(banner); setTimeout(() => banner.remove(), 8000); } localStorage.setItem('showroom_installed_version', currentVer); localStorage.setItem('showroom_client_id', lic.clientId || ''); } function toSheets() { return { Banks: db.banks, Insurance: db.insurance || [], Cars: db.cars || [], Leads: db.leads || [], Customers: db.customers, Contracts: db.contracts.map(({installments, ...c}) => c), Installments: db.contracts.flatMap(c => (c.installments || []).map(i => ({contractId: c.id, ...i}))), Journal: db.journal || [], Settings: Object.entries(db.settings).map(([key, value]) => ({key, value})) }; } function dbFromSheets(S) { const N = x => x === '' || x == null ? 0 : +x; const T = x => x === true || x === 'TRUE' || x === 'true'; const ins = S.Installments || []; const set = {...DEF_SET}; (S.Settings || []).forEach(r => { if (r.key) { if (['method', 'companyName', 'companyPhone', 'companyAddress'].includes(r.key)) { set[r.key] = String(r.value); } else { set[r.key] = +r.value; } } }); return { banks: (S.Banks || []).map(b => ({ ...b, id: N(b.id), minSalary: N(b.minSalary), maxDBR: N(b.maxDBR), minDPNew: N(b.minDPNew), minDPUsed: N(b.minDPUsed), maxMonths: N(b.maxMonths), commission: N(b.commission), sectors: String(b.sectors || '') })), insurance: (S.Insurance || DEF_INSURANCE).map(i => ({ ...i, id: N(i.id), rate: N(i.rate) })), cars: (S.Cars || DEF_CARS).map(c => ({ ...c, id: N(c.id), cost: N(c.cost), price: N(c.price), year: N(c.year) })), leads: (S.Leads || DEF_LEADS).map(l => ({ ...l, id: N(l.id), budget: N(l.budget) })), customers: (S.Customers || []).map(c => ({ ...c, id: N(c.id), income: N(c.income), phone: String(c.phone || '') })), contracts: (S.Contracts || []).map(c => ({ ...c, id: N(c.id), customerId: N(c.customerId), price: N(c.price), downPayment: N(c.downPayment), months: N(c.months), monthly: N(c.monthly), date: String(c.date || ''), installments: ins.filter(i => N(i.contractId) === N(c.id)).map(i => ({ n: N(i.n), due: String(i.due), amount: N(i.amount), paid: T(i.paid), paidDate: i.paidDate ? String(i.paidDate) : '', whatsappSent: T(i.whatsappSent), whatsappSentDate: i.whatsappSentDate ? String(i.whatsappSentDate) : '', whatsappOverdueSent: T(i.whatsappOverdueSent), whatsappOverdueSentDate: i.whatsappOverdueSentDate ? String(i.whatsappOverdueSentDate) : '' })) })), journal: (S.Journal || []).map(j => ({ ...j, id: N(j.id), contractId: N(j.contractId), amount: N(j.amount) })), settings: set }; } // ══════════════════════════════════════════════════════════ // MOSTASHARAK DUAL-MODE SYNC & BROADCAST ENGINE // ══════════════════════════════════════════════════════════ let mostasharakBus = null; try { if (typeof BroadcastChannel !== 'undefined') { mostasharakBus = new BroadcastChannel('mostasharak_bus'); mostasharakBus.onmessage = function(ev) { if (!ev.data) return; if (ev.data.type === 'MARKET_ORDER_PLACED') { console.log('📡 Order received via BroadcastChannel:', ev.data.order); onIncomingMarketOrder(ev.data.order); } else if (ev.data.type === 'SYNC_REQUEST') { broadcastStateToMarket(); } }; } } catch(e) { console.log('BroadcastChannel not supported, falling back to localStorage bridge'); } // Window Storage Event Fallback for Safari & older webviews window.addEventListener('storage', function(e) { if (e.key === 'mostasharak_new_order_event' && e.newValue) { try { const order = JSON.parse(e.newValue); onIncomingMarketOrder(order); } catch(err){} } }); function onIncomingMarketOrder(order) { playOrderChime(); loadMarketOrders(); toast('📲 طلب شراء جديد ورد من السوق: ' + (order.productName || 'مركبة') + ' من ' + (order.buyerName || 'عميل')); } function broadcastStateToMarket() { if (!mostasharakBus) return; try { mostasharakBus.postMessage({ type: 'STATE_BROADCAST', cars: db.cars || [], ribbons: PC_RIBBONS || [], companyName: db.settings.companyName, companyPhone: db.settings.companyPhone }); } catch(e){} } function toggleSystemSyncMode() { const cur = localStorage.getItem('mostasharak_sync_mode') || 'connected'; const next = cur === 'connected' ? 'standalone' : 'connected'; localStorage.setItem('mostasharak_sync_mode', next); updateSystemModeUI(); toast(next === 'standalone' ? '🟡 تم التبديل إلى: نمط العمل المحلي المستقل (Offline Standalone)' : '🟢 تم التبديل إلى: نمط المزامنة السحابية / الشبكية (Connected)'); } function updateSystemModeUI() { const mode = localStorage.getItem('mostasharak_sync_mode') || 'connected'; const dot = $('systemModeDot'); const txt = $('systemModeText'); const btn = $('systemModeBtn'); if (!dot || !txt || !btn) return; if (mode === 'standalone') { dot.textContent = '🟡'; txt.textContent = 'مستقل (أوفلاين)'; btn.style.background = '#854d0e'; btn.title = 'النظام يعمل بوضع مستقل كامل بدون الحاجة لأي خادم أو اتصال'; } else { dot.textContent = '🟢'; txt.textContent = 'متصل ومزامن'; btn.style.background = '#0f766e'; btn.title = 'النظام متصل بالسيرفر المحلي أو السحابة المركزية'; } } // Hook into initial sync and boot setTimeout(updateSystemModeUI, 500); // Initialize Application loadLocal(); stat(); boot();