نظام إدارة العمليات
جمعية الدعوة والإرشاد وتوعية الجاليات بالمجمعة
نظام داخلي مبني حول الخطة التشغيلية للجمعية. كل قسم يسجّل منجزه في صفحته الخاصة، والإدارة ترى المستهدف مقابل المنجز في لوحة واحدة، ومعها المالية والمانحون والخطابات الرسمية وأرشيف الشواهد.
- الواجهة
- React 19 · Vite · Tailwind 4 · PWA
- الخادم
- Google Apps Script بنقطة دخول واحدة
- البيانات
- Google Sheets، والشواهد على Google Drive
- الاستضافة
- Firebase Hosting برؤوس أمان صارمة
- الحماية
- تشفير ChaCha20 وتجزئة PBKDF2، مكتوبان بدون مكتبات
- دوري
- النظام كاملاً، بمفردي
01المشكلة
ستة أقسام تعمل على خطة سنوية واحدة. كل قسم يحتاج أن يسجّل عمله دون أن يرى بيانات غيره أو يعدّلها، والإدارة تحتاج صورة واحدة تقارن المستهدف بالمنجز.
ومجلس الإدارة والمانحون يحتاجون اطلاعاً محدوداً، دون أن تُفتح لهم حسابات كاملة.
02كيف يعمل النظام
الواجهة ترسل ما تغيّر فقط، والخادم يقرر من يقرأ ومن يكتب قبل أي حفظ.
03ما الذي يديره النظام
- الخطة والتشغيلالخطة الربعية، وعمليات كل قسم، ولوحة تنفيذية للمستهدف مقابل المنجز.
- الماليةالفواتير وأوامر الصرف وسندات القبض والحسابات البنكية ودليل الحسابات.
- المانحونإدارة المانحين وعروضهم، وبوابة يرى فيها كل مانح مشاريعه فقط.
- المراسلاتخطابات بمسار اعتماد وختم رسمي، وتعاميم، وإعلانات بتأكيد قراءة، ومحادثات.
- الدعاة والسجلاتملف لكل داعية مع متابعة انتهاء الإقامة، وسجلات مشفّرة.
- الشواهد والتقاريرأرشيف للشواهد على Drive، وتقارير تنفيذية تُصدَّر إلى Excel بعدة أوراق.
04ثلاثة قرارات هندسية
القسم لا يتجاوز حدوده
الصلاحيات تُفحص على الخادم في كل كتابة: الموظف يكتب في قسمه فقط، ولا يستطيع نقل عنصر إلى قسم آخر. والخطاب بعد اعتماده لا يعدّله قسمه، ولا يختمه إلا المدير أو المفوَّض.
if (!isWholeOrg_(ctx)) {
if (oldItem && !ownsDept_(ctx, p.depts(oldItem))) return 'OUT_OF_SCOPE';
if (newItem && !ownsDept_(ctx, p.depts(newItem))) return 'OUT_OF_SCOPE';
// لا يجوز نقل عنصر إلى قسم آخر
if (oldItem && newItem &&
JSON.stringify(p.depts(oldItem)) !== JSON.stringify(p.depts(newItem)))
return 'OUT_OF_SCOPE';
}لا يُكتب فوق عنصر لقسم آخر
إذا وصل عنصر جديد برقم مستخدم من قبل، أو برقم عنصر لا يملك المستخدم رؤيته، يعيد الخادم تسميته بدل أن يكتب فوقه، ويخبر الواجهة بالاسم الجديد.
if (existing && (isNew || !oldItem || !canReadItem_(ctx, collection, oldItem))) {
var oldId = item.id;
do { item.id = oldId + '-' + hex_(randomBytes_(2)); }
while (dataGet_(collection, item.id));
result.renamed.push({ from: oldId, to: item.id });
}تشفير بدون مكتبات
Apps Script لا يوفّر مكتبة تشفير كاملة، فكتبت ChaCha20 وPBKDF2 يدوياً، واختبرت التشفير على متجهات الاختبار الرسمية في RFC 8439. وتسجيل الدخول يأخذ نفس الوقت سواء كان الرقم مسجلاً أم لا، حتى لا يكشف التوقيت الأرقام المسجلة.
function pbkdf2_(passwordBytes, saltBytes, iterations) {
var u = hmac_(passwordBytes, saltBytes.concat([0, 0, 0, 1]));
var t = u.slice();
for (var i = 1; i < iterations; i++) {
u = hmac_(passwordBytes, u);
for (var j = 0; j < t.length; j++) t[j] ^= u[j];
}
return t;
}