Tally
أداة لإصدار الفواتير فيها عملاء وملفات PDF وبريد إلكتروني خلف تسجيل دخول حقيقي، حيث تعيش قاعدة «لا ترى إلا فواتيرك أنت» في قاعدة البيانات لا في الواجهة.
شاهد العرض المباشر ←- المنصة
- Lovable
- التقنيات
- Lovable, Supabase (Postgres, Auth, Row Level Security)
ما هي
Tally أداة بسيطة لإصدار الفواتير للعاملين المستقلين، لكن النموذج الأولي يدور في الحقيقة حول كل ما يجب أن يعمل تحت منتج كهذا. هناك صفحة هبوط، وتسجيل اشتراك يكتب الناس في قاعدة بيانات، وحسابات وتسجيلات دخول حقيقية، ولوحة تحكم لا يرى فيها كل شخص إلا فواتيره هو.
وإصدار الفواتير يمضي إلى نهايته. تحتفظ بقائمة عملاء فلا تعيد كتابة بياناتهم مع كل عمل؛ وتُرقَّم الفواتير تسلسليًا؛ وتقبل كل فاتورة ما يلزم العمل من بنود، مع إضافة الضريبة والخصومات فوقها. والفاتورة الجاهزة تُنزَّل بصيغة PDF من المتصفح مباشرة، أو تصل إلى العميل عبر بريد إلكتروني يحمل هوية العلامة.
ملاحظة بصراحة: جانب المال ليس فعليًا. تحصيل المدفوعات مبني كطبقة Stripe مُعطَّلة عمدًا (stub)، فالمسار موجود لكنه غير موصول بمدفوعات حقيقية.
لماذا بنيتها بهذه الطريقة
صفحة الهبوط وحدها لا تُظهر سوى قدرتي على تنسيق صفحة. أردت النصف الأصعب في المشروع نفسه: الحسابات، وتسجيلات الدخول، والقواعد التي تحدد من يُسمح له برؤية ماذا.
الجزء الذي تطلّب تفكيرًا حقيقيًا
كل ما في عبارة «تسجيل الدخول متعامَل معه بشكل صحيح» يختزل في سؤال واحد: حين تكون مسجَّل الدخول، هل تمنعك قاعدة البيانات فعلًا من قراءة بيانات مستخدم آخر، أم أن التطبيق لا يعرضها لك فحسب؟
نسختي الأولى كانت الحالة الثانية. كانت تُصفّي الفواتير داخل المتصفح، بينما كان الجدول تحتها ما يزال يعيد كل صف لأي مستخدم مسجَّل الدخول. ولم يكن هناك ما يبدو خاطئًا: كل حساب يرى فواتيره هو فقط على الشاشة، والبيانات في المقابل متاحة لمن يعرف كيف يطلبها.
لذلك نقلت الفرض إلى قاعدة البيانات نفسها، على شكل سياسات أمان على مستوى الصف (row-level security) تربط كل صف بمالكه وترفض الطلب من أصله، بدل أن تعيد صفوفًا تخفيها الواجهة بعد ذلك. والأدوار مثل صلاحية المشرف وضعتها في مكان لا يستطيع المستخدم تعديله، لا حقلًا في ملفه الشخصي. ثم تحققت من ذلك بالطريقة التي قد يسلكها شخص يحاول الدخول: حسابان مفتوحان جنبًا إلى جنب، مع الاستعلام المباشر عن سجلات الحساب الآخر، والتأكد من أن ما يعود فارغ لا مخفيّ.