يطلب منك المدير المالي أن تضع «مركز التكلفة» على طلب شراء السيرفرات، ويسألك المحاسب هل اشتراك الأدوات السنوي الذي دفعته الشهر الماضي «مصروف مدفوع مقدماً»، ويقول لك مدير المنتج إن الشركة «رابحة ولكن النقد قليل»، فتفهم الكلمات واحدة واحدة ولا تفهم الجملة. والمشكلة هنا ليست في صعوبة المالية، فأغلب ما يحتاجه المهندس منها قليل وواضح، وإنما في أن قسم المالية يتكلم لغة لها مصطلحات دقيقة، وكل مصطلح يعني شيئاً محدداً في الدفاتر، فإذا لم تعرفه أصبح كل طلب ميزانية وكل عقد اشتراك نقاشاً طويلاً لا يصل إلى نتيجة.
وهذا المقال هو الأول في سلسلة «المالية لغير الماليين» على عرب رووت، وهي سلسلة كتبناها للمهندسين ومديري تقنية المعلومات الذين لم يدرسوا المالية، وسوف نشرح فيها الأساسيات بأمثلة بالريال ومختبرات صغيرة تشغلها بنفسك.
المواضيع في السلسلة:
- المالية للتقنيين: اللغة التي يتحدث بها قسم المالية (الموضوع الحالي)
- كيف تبني ميزانية التقنية بين CapEx وOpEx؟
- دورة الشراء Procure-to-Pay من الطلب إلى الدفع
- حساب العائد ROI والقيمة الحالية NPV لمشروعك التقني
وسوف نناقش في هذا المقال ما يلي:
- الكلمات الأساسية: الإيراد والتكلفة والمصروف والربح والهامش.
- الفرق بين أساس النقد وأساس الاستحقاق، ولماذا لا تساوي الأرباح النقد.
- القوائم المالية الثلاث، وكيف تقرؤها على مثال واحد صغير.
- القيد المزدوج ودليل الحسابات ودفتر الأستاذ العام.
- مركز التكلفة ومركز الربح، والسنة المالية والإقفال الشهري.
- من يفعل ماذا في قسم المالية، وماذا يطلب كل منهم من فريق التقنية.
- مختبر: شهر كامل لشركة صغيرة في دفتر قيود مع DuckDB، نخرج منه القوائم الثلاث، ثم نكسره مرتين.
الكلمات التي تتكرر في كل اجتماع
أول ما تحتاجه هو أن تفرق بين خمس كلمات يستخدمها غير الماليين وكأنها شيء واحد، والجدول التالي يبين كل كلمة بمعناها الدقيق ومثال من شركة برمجيات صغيرة تبيع اشتراكات:
| المصطلح | المعنى | مثال بالريال |
|---|---|---|
| الإيراد Revenue | قيمة ما قدمته للعملاء في الفترة، سواء دفعوا أم لم يدفعوا بعد | اشتراكات أكتوبر 62,000 |
| تكلفة الإيرادات Cost of Revenue | ما تصرفه مباشرة لتقديم الخدمة نفسها، وتسمى في الشركات التجارية تكلفة البضاعة المباعة COGS | السحابة التي تشغل المنتج 4,500 |
| الربح الإجمالي Gross Profit | الإيراد ناقص تكلفة الإيرادات | 57,500 |
| المصروفات التشغيلية Operating Expenses | ما تصرفه لتشغيل الشركة ولا يرتبط مباشرة بكل عميل | الرواتب والإيجار والأدوات 46,400 |
| صافي الربح Net Profit | ما يبقى بعد كل المصروفات، وفي الواقع بعد الزكاة أو الضريبة أيضاً | 11,100 |
| الهامش Margin | الربح كنسبة من الإيراد، فالهامش الإجمالي Gross Margin هنا 92.7% وصافي الهامش Net Margin هنا 17.9% | 57,500 ÷ 62,000 |
ولاحظ أن «التكلفة Cost» و«المصروف Expense» ليستا كلمة واحدة عند المحاسب، فالتكلفة هي ما دفعته أو التزمت به مقابل شيء، والمصروف هو الجزء من التكلفة الذي يحسب على هذه الفترة في قائمة الدخل. فإذا اشتريت حاسوباً بستة آلاف ريال فهذه تكلفة، ولكن مصروف هذا الشهر منها جزء صغير فقط اسمه الإهلاك Depreciation، وسوف نشرحه بالتفصيل في مقال الميزانية وCapEx وOpEx.
ولماذا يهمك الهامش الإجمالي كمهندس؟ والسبب أن تكلفة السحابة في شركات البرمجيات تدخل غالباً في تكلفة الإيرادات، فكل ريال زائد في فاتورة السحابة ينقص الهامش الإجمالي مباشرة، وهذا الرقم هو أحد أول ما ينظر إليه المستثمر والإدارة، ولهذا يربط مقال FinOps التكلفة بالطلب الواحد أو بالعميل الواحد.
أساس النقد وأساس الاستحقاق
هناك طريقتان لتسجيل الإيراد والمصروف، والفرق بينهما هو سبب أغلب سوء الفهم بين التقنية والمالية:
- أساس النقد Cash Basis: تسجل الإيراد عندما يدخل المال إلى البنك، والمصروف عندما يخرج، وهي الطريقة التي يدير بها أغلبنا حسابه الشخصي.
- أساس الاستحقاق Accrual Basis: تسجل الإيراد عندما تقدم الخدمة أو تسلم البضاعة، والمصروف عندما تستهلك ما اشتريته، بغض النظر عن وقت الدفع.
والشركات تعد قوائمها المالية على أساس الاستحقاق، والسبب أن أساس النقد يجعل الشهر الذي دفع فيه عميل كبير اشتراك سنة كاملة يبدو شهراً عظيماً، ويجعل الأشهر الأحد عشر بعده تبدو أسوأ مما هي، رغم أن الشركة تقدم الخدمة نفسها كل شهر. والصورة التالية تبين عميلاً دفع 24,000 ريال مقدماً عن سنة كاملة:

وفي أساس الاستحقاق تظهر أربعة حسابات يحتاج كل مهندس أن يعرفها، لأنها تظهر كلما اشترى أو باع شيئاً لا يتزامن دفعه مع استخدامه:
| الحساب | متى يظهر | نوعه | مثال تقني |
|---|---|---|---|
| الإيراد المؤجل Deferred Revenue | دفع العميل قبل أن تقدم الخدمة | التزام Liability | اشتراك سنوي مدفوع مقدماً في منتجك |
| المصروف المدفوع مقدماً Prepaid Expense | دفعت قبل أن تستخدم الخدمة | أصل Asset | ترخيص أدوات التطوير لسنة كاملة |
| المصروف المستحق Accrued Expense | استخدمت الخدمة ولم تصل الفاتورة بعد | التزام Liability | استهلاك السحابة حتى آخر الشهر قبل صدور الفاتورة |
| الذمم المدينة Accounts Receivable | قدمت الخدمة وأرسلت الفاتورة ولم يدفع العميل | أصل Asset | فواتير العملاء الشهرية التي لم تحصل |
ولهذا يرسل إليك المحاسب في آخر كل شهر يسأل عن الخدمات التي استخدمتها ولم تصل فواتيرها، والسبب أنه يحتاج أن يسجلها كمصروف مستحق في الشهر الصحيح، وإلا ظهر الشهر أربح مما هو، ثم ظهر الشهر التالي أسوأ.
القوائم المالية الثلاث
كل شركة تخرج ثلاث قوائم أساسية من الدفاتر نفسها، وكل قائمة تجيب عن سؤال مختلف، والصورة التالية تبينها بأرقام مختبر هذا المقال وتبين كيف ترتبط ببعضها:

- قائمة الدخل Income Statement: وتسمى أيضاً قائمة الأرباح والخسائر Profit and Loss، واختصاراً P&L، وتجيب عن سؤال هل ربحت الشركة في هذه الفترة؟ وهي عن فترة، مثل شهر أو ربع سنة أو سنة.
- قائمة التدفقات النقدية Cash Flow Statement: وتجيب عن سؤال من أين جاء النقد وأين ذهب؟ وتقسمه إلى ثلاثة أقسام: التشغيل Operating، أي النشاط اليومي، والاستثمار Investing، أي شراء الأصول طويلة العمر مثل الأجهزة، والتمويل Financing، أي ما دفعه الملاك أو اقترضته الشركة.
- قائمة المركز المالي Balance Sheet: وتسمى أيضاً الميزانية العمومية، وتجيب عن سؤال ماذا تملك الشركة وماذا عليها في هذه اللحظة؟ وهي صورة في يوم واحد، مثل 31 أكتوبر.
وقائمة المركز المالي مبنية على معادلة واحدة لا تنكسر أبداً، وتسمى معادلة المحاسبة Accounting Equation:
الأصول Assets = الالتزامات Liabilities + حقوق الملكية Equity
والأصول هي ما تملكه الشركة وتتوقع منه منفعة، مثل النقد والأجهزة والمبالغ التي سيدفعها العملاء، والالتزامات هي ما عليها للآخرين، مثل فواتير الموردين والإيراد المؤجل، وحقوق الملكية هي ما يبقى للملاك، أي رأس المال الذي وضعوه مع الأرباح التي لم توزع. ولاحظ في الصورة أن صافي الربح لا يبقى في قائمة الدخل، وإنما ينتقل إلى حقوق الملكية باسم الأرباح المحتجزة Retained Earnings، وأن النقد في آخر قائمة التدفقات هو نفسه رصيد البنك في قائمة المركز المالي، وهذا هو سبب أن المحاسب يكتشف الخطأ بسرعة، فإذا لم تتطابق الأرقام بين القوائم فهناك قيد خاطئ في مكان ما.
وقد يتساءل البعض: إذا كانت قائمة الدخل تخبرني بالربح، فلماذا أحتاج قائمة التدفقات؟ والإجابة أن قائمة الدخل مبنية على الاستحقاق كما رأينا، فلا تخبرك هل في البنك ما يكفي لدفع رواتب الشهر القادم، أما قائمة التدفقات فتخبرك بذلك، ولهذا فإن السؤال الأول عند أي استثمار تقني كبير، مثل شراء سيرفرات بنصف مليون، ليس «هل هو مربح؟» فقط، وإنما «هل يتحمل النقد دفعه الآن؟».
القيد المزدوج بلغة المحاسب
كل عملية مالية تسجل كقيد Journal Entry له جانبان: مدين Debit ودائن Credit، ومجموع المدين يساوي مجموع الدائن في كل قيد، وهذا ما يجعل معادلة المحاسبة متوازنة دائماً. وقد شرحنا القيد المزدوج من زاوية المبرمج الذي يبني دفتر قيود لمحفظة رقمية في مقال المحفظة الرقمية، أما هنا فنحتاج القاعدة التي يستخدمها المحاسب ليعرف أي جانب يزيد كل حساب، وهي في الجدول التالي:
| نوع الحساب | يزيد في جانب | ينقص في جانب | مثال |
|---|---|---|---|
| الأصول Assets | المدين | الدائن | النقد، الأجهزة، العملاء |
| المصروفات Expenses | المدين | الدائن | الرواتب، السحابة |
| الالتزامات Liabilities | الدائن | المدين | الموردون، الإيراد المؤجل |
| حقوق الملكية Equity | الدائن | المدين | رأس المال |
| الإيرادات Revenue | الدائن | المدين | الاشتراكات |
ولنأخذ مثالاً: دفعت الشركة رواتب أكتوبر 40,000 ريال من البنك، فالمصروف زاد، والمصروف يزيد في المدين، والنقد نقص، والنقد أصل ينقص في الدائن، فيكون القيد:
- من حساب الرواتب Salaries (مدين) 40,000.
- إلى حساب النقد في البنك Cash at Bank (دائن) 40,000.
ولاحظ أن كلمة «دائن» هنا لا تعني أن أحداً يدين لك، وإنما هي اسم الجانب الأيمن في الدفتر فقط، وهذا أكثر ما يربك المهندسين في البداية، فلا تقم بترجمتها في ذهنك إلى معناها اليومي، وتعامل معها كاسم عمود.
دليل الحسابات ودفتر الأستاذ العام
دليل الحسابات Chart of Accounts هو قائمة كل الحسابات التي تستخدمها الشركة، لكل حساب رقم واسم ونوع، وهو مثل مخطط قاعدة البيانات Schema للدفاتر، فكل قيد يجب أن يشير إلى حساب موجود فيه. وأغلب الشركات ترقم الحسابات بحيث يدل أول رقم على النوع، كما في المثال التالي من مختبرنا:
| نطاق الأرقام | النوع | أمثلة من المختبر |
|---|---|---|
| 1000 إلى 1999 | الأصول | 1000 النقد، 1100 العملاء، 1200 مدفوع مقدماً، 1500 الأجهزة |
| 2000 إلى 2999 | الالتزامات | 2000 الموردون، 2100 الإيراد المؤجل |
| 3000 إلى 3999 | حقوق الملكية | 3000 رأس المال |
| 4000 إلى 4999 | الإيرادات | 4000 إيراد الاشتراكات |
| 5000 فما فوق | التكاليف والمصروفات | 5000 السحابة، 6000 الرواتب، 6300 الإهلاك |
وبعد الدليل تأتي ثلاثة سجلات تسمع أسماءها كثيراً:
- دفتر اليومية Journal: القيود بترتيب حدوثها، قيد بعد قيد، وكل قيد له تاريخ ووصف.
- دفتر الأستاذ العام General Ledger: القيود نفسها مجمعة حسب الحساب، فترى كل ما دخل حساب النقد وكل ما خرج منه، واختصاره GL، وهو الذي تقصده المالية عندما تقول «سجلناه في ال GL».
- ميزان المراجعة Trial Balance: قائمة بكل حساب ومجموع مدينه ودائنه، ومجموع المدين في الميزان كله يجب أن يساوي مجموع الدائن، وهذا أول فحص قبل إعداد القوائم.
وفي الأنظمة المحاسبية وأنظمة ERP توجد دفاتر فرعية Sub-ledgers تحمل التفاصيل، مثل دفتر الموردين لكل فاتورة مورد، ودفتر العملاء لكل فاتورة عميل، ودفتر الأصول لكل جهاز، ثم ترحل مجاميعها إلى الأستاذ العام. ولهذا عندما تشتري أجهزة يسجل كل جهاز في دفتر الأصول برقم أصل، وهذا هو الرابط بين المالية وسجل الأجهزة الذي شرحناه في دورة حياة أجهزة المؤسسة.
مركز التكلفة ومركز الربح
دليل الحسابات يخبرك بنوع الصرف، مثل «رواتب» أو «سحابة»، ولكنه لا يخبرك من صرف، ولهذا تضيف الشركات بعداً ثانياً لكل قيد، وأشهره ما يلي:
- مركز التكلفة Cost Centre: وحدة في الشركة تصرف ولا تحقق إيراداً مباشراً، مثل تقنية المعلومات والموارد البشرية والمالية نفسها، ويحاسب مديرها على الالتزام بالميزانية.
- مركز الربح Profit Centre: وحدة لها إيراد وتكلفة، مثل خط منتج أو فرع، ويحاسب مديرها على الربح وليس على الصرف وحده.
- رمز المشروع Project Code: يجمع تكلفة مشروع محدد عبر أكثر من مركز، مثل مشروع نقل الأنظمة إلى السحابة.
وعندما يطلب منك المدير المالي مركز التكلفة على طلب الشراء فهو يسأل: على ميزانية من يحسب هذا الصرف؟ والخطأ الشائع أن يكتب الفريق التقني مركز تكلفة تقنية المعلومات على كل شيء، فتبدو التقنية أغلى قسم في الشركة، وتبدو منتجات الشركة أرخص مما هي، والصحيح أن ما يخدم منتجاً واحداً يحمل على ذلك المنتج. وهذا هو نفس مبدأ التوزيع Allocation وعرض التكلفة Showback الذي شرحناه للسحابة في مقال FinOps، ولكن هنا على مستوى الدفاتر كلها.
السنة المالية والإقفال الشهري
السنة المالية Fiscal Year هي السنة التي تعد عنها الشركة قوائمها، وهي في كثير من الشركات السنة الميلادية، ولكنها قد تبدأ في شهر آخر حسب نظام الشركة الأساسي. وتقسم إلى فترات Periods، عادة اثنا عشر شهراً، وفي آخر كل فترة يأتي الإقفال Period Close، وهو أسبوع مزدحم في المالية تمر فيه خطوات ثابتة:
- التأكد من وصول كل فواتير الموردين وتسجيل المصروفات المستحقة لما لم يصل.
- تسجيل قيود التسوية Adjusting Entries، مثل نصيب الشهر من الإيراد المؤجل والمصروف المدفوع مقدماً والإهلاك.
- المطابقة Reconciliation بين الدفاتر وكشوف البنك ودفاتر الموردين والعملاء.
- ميزان المراجعة ثم القوائم، ثم مراجعتها مع الإدارة.
- قفل الفترة في النظام حتى لا يسجل فيها أحد قيداً جديداً بعد اعتماد أرقامها.
وهذا يفسر لماذا تصلك رسائل المالية في اليوم الأخير من الشهر وأول يومين بعده، فإذا تأخرت فاتورة السحابة أو استلام خدمة عندك فإن الشهر كله ينتظرك، وسوف نرى في مقال دورة الشراء كيف يمنع تسجيل الاستلام في وقته هذا التأخير.
وفي المملكة توجد مواعيد سنوية مرتبطة بنهاية السنة المالية، منها ما يلي:
- إقرار الزكاة وضريبة الدخل: تقول هيئة الزكاة والضريبة والجمارك في إعلانها بتاريخ 23 أبريل 2026 إن الإقرار يقدم وتدفع الزكاة خلال مدة لا تتجاوز 120 يوماً من نهاية السنة، فكان آخر موعد للسنة المنتهية في 31 ديسمبر 2025 هو 30 أبريل 2026.
- المعايير المحاسبية: قررت الهيئة السعودية للمراجعين والمحاسبين SOCPA تطبيق المعايير الدولية للتقرير المالي IFRS المعتمدة في المملكة على المنشآت المدرجة في السوق المالية للفترات التي تبدأ من 1 يناير 2017، وعلى المنشآت الأخرى للفترات التي تبدأ من 1 يناير 2018، كما في صفحة نظرة عامة على المعايير في موقع الهيئة، وتطبق المنشآت الصغيرة ومتوسطة الحجم معياراً خاصاً بها هو IFRS for SMEs كما يذكر ملف المملكة في موقع مؤسسة IFRS.
ولا تحتاج كمهندس أن تعرف تفاصيل هذه المعايير، ولكن يكفي أن تعرف أن قرارات مثل «هل نسجل هذه التكلفة أصلاً أم مصروفاً؟» تحكمها معايير مكتوبة وليست رأياً شخصياً للمحاسب، وسوف نرى أمثلة منها في المقال التالي.
من يفعل ماذا في قسم المالية؟
أسماء الأدوار تختلف من شركة إلى أخرى، وفي الشركة الصغيرة قد يحمل محاسب واحد أغلبها، ولكن الأدوار نفسها موجودة دائماً، والصورة التالية تبينها مع أكثر ما يطلبه كل دور من فريق التقنية:

| الدور | ماذا يفعل | متى تتعامل معه |
|---|---|---|
| المدير المالي CFO | مسؤول عن المالية كلها والتمويل والعلاقة مع المستثمرين والبنوك | عند عرض استثمار كبير أو دراسة جدوى |
| المراقب المالي Controller | الدفاتر والإقفال والقوائم والسياسات المحاسبية | عند السؤال هل هذه التكلفة أصل أم مصروف |
| الحسابات الدائنة Accounts Payable | تسجيل فواتير الموردين ودفعها في موعدها | عندما يشتكي مورد أن فاتورته لم تدفع |
| الحسابات المدينة Accounts Receivable | فواتير العملاء والتحصيل | عندما يبني فريقك نظام الفوترة أو يغيره |
| الخزينة Treasury | إدارة النقد والحسابات البنكية والدفعات الكبيرة | قبل دفعة كبيرة لمورد أو تجديد سنوي ضخم |
| التخطيط والتحليل المالي FP&A | الميزانية السنوية والتوقعات Forecasts وتحليل الانحراف | في دورة الميزانية وكل ربع سنة |
| المشتريات Procurement | اختيار الموردين والتفاوض والعقود، وتتبع أحياناً إدارة العمليات وليس المالية | قبل أي شراء أو تجديد عقد |
| المراجعة Audit | الداخلية تختبر الضوابط لصالح الإدارة ولجنة المراجعة، والخارجية تبدي رأياً مستقلاً في القوائم | عندما يطلبون سجل الصلاحيات أو دليل الموافقات من أنظمتك |
مختبر: شهر كامل لشركة صغيرة في دفتر قيود
الآن سوف نطبق كل ما سبق على شركة برمجيات صغيرة تبدأ عملها في أكتوبر 2026، فنسجل اثني عشر قيداً لشهرها الأول، ثم نخرج منها ميزان المراجعة والقوائم الثلاث. ونستخدم DuckDB لأنه قاعدة بيانات تحليلية تعمل داخل برنامج Python نفسه دون سيرفر، فتكتب التقارير بلغة SQL كما يفعل نظام محاسبي حقيقي، ونشغل كل شيء داخل Container حتى لا نثبت شيئاً على جهازك.
التجهيز
نحتاج ملفين في مجلد واحد، الأول ملف Dockerfile يبني Image فيها Python وDuckDB:
# Lab image for the finance series: Python 3.13.16 with DuckDB 1.5.6, nothing installed on the host.
FROM python:3.13.16-slim
RUN pip install --no-cache-dir duckdb==1.5.6
WORKDIR /wونبنيها مرة واحدة باسم يبدأ ببادئة المختبر:
docker build -t lab-fin2-duckdb:1 .دليل الحسابات والقيود
والملف الثاني هو السكربت ledger.py، وفيه دليل الحسابات، ثم القيود الاثنا عشر بالترتيب، ثم الاستعلامات التي تخرج التقارير:
"""A tiny company's first month (October 2026) in a double-entry ledger, and the three statements built from it.
All numbers are invented for the exercise (SAR, VAT left out on purpose). Runs inside python:3.13.16-slim + DuckDB:
python ledger.py the normal month
python ledger.py unbalanced break it: a typo in one entry
python ledger.py prepaid break it: the annual SaaS licence expensed all at once
"""
import sys
import duckdb
BREAK = sys.argv[1] if len(sys.argv) > 1 else ""
# Chart of accounts: number, name, type. The first digit tells the type, as most charts do.
ACCOUNTS = [
(1000, "Cash at bank", "asset"),
(1100, "Accounts receivable", "asset"),
(1200, "Prepaid expenses", "asset"),
(1500, "Equipment (laptops)", "asset"),
(1590, "Accumulated depreciation", "asset"), # contra-asset: its balance is a credit
(2000, "Accounts payable", "liability"),
(2100, "Deferred revenue", "liability"),
(3000, "Owners' capital", "equity"),
(4000, "Subscription revenue", "revenue"),
(5000, "Cloud hosting (cost of revenue)", "expense"),
(6000, "Salaries", "expense"),
(6100, "Office rent", "expense"),
(6200, "Software subscriptions", "expense"),
(6300, "Depreciation", "expense"),
]
# Journal: (entry, date, description, cash-flow class, [(account, debit, credit), ...])
J = [
(1, "2026-10-01", "Owners put in capital", "financing", [(1000, 200000, 0), (3000, 0, 200000)]),
(2, "2026-10-01", "Buy 4 laptops for the team", "investing", [(1500, 14400, 0), (1000, 0, 14400)]),
(3, "2026-10-02", "Annual licence for dev tools, Oct-Sep", "operating", [(1200, 12000, 0), (1000, 0, 12000)]),
(4, "2026-10-05", "Customer A pays a 12-month plan up front", "operating", [(1000, 24000, 0), (2100, 0, 24000)]),
(5, "2026-10-15", "Invoice monthly customers for October", "", [(1100, 60000, 0), (4000, 0, 60000)]),
(6, "2026-10-28", "Some monthly customers pay", "operating", [(1000, 18000, 0), (1100, 0, 18000)]),
(7, "2026-10-31", "October cloud bill, due in November", "", [(5000, 4500, 0), (2000, 0, 4500)]),
(8, "2026-10-31", "Pay October salaries", "operating", [(6000, 40000, 0), (1000, 0, 40000)]),
(9, "2026-10-31", "Pay October rent", "operating", [(6100, 5000, 0), (1000, 0, 5000)]),
# month-end adjustments (no cash moves)
(10, "2026-10-31", "Adjust: 1/12 of Customer A's plan is earned", "", [(2100, 2000, 0), (4000, 0, 2000)]),
(11, "2026-10-31", "Adjust: 1/12 of the dev-tools licence is used", "", [(6200, 1000, 0), (1200, 0, 1000)]),
(12, "2026-10-31", "Adjust: laptop depreciation, 14,400 / 36 months", "", [(6300, 400, 0), (1590, 0, 400)]),
]
if BREAK == "unbalanced": # someone types 4,000 instead of 40,000 on the credit side
J[7] = (8, "2026-10-31", "Pay October salaries", "operating", [(6000, 40000, 0), (1000, 0, 4000)])
if BREAK == "prepaid": # the whole year's licence goes straight to October's expenses
J[2] = (3, "2026-10-02", "Annual licence for dev tools, Oct-Sep", "operating", [(6200, 12000, 0), (1000, 0, 12000)])
J[10] = (11, "2026-10-31", "(no adjustment: nothing was prepaid)", "", [])
con = duckdb.connect()
con.execute("CREATE TABLE accounts(no INT PRIMARY KEY, name TEXT, type TEXT)")
con.executemany("INSERT INTO accounts VALUES (?, ?, ?)", ACCOUNTS)
con.execute("CREATE TABLE lines(entry INT, day DATE, memo TEXT, cf TEXT, account INT REFERENCES accounts(no), "
"debit DECIMAL(12,2), credit DECIMAL(12,2))")
for e, d, memo, cf, legs in J:
for acc, dr, cr in legs:
con.execute("INSERT INTO lines VALUES (?, ?, ?, ?, ?, ?, ?)", [e, d, memo, cf, acc, dr, cr])
def show(title, sql):
"""Prints a query result as a plain aligned table: numbers right-aligned with thousands separators."""
rows = con.execute(sql).fetchall()
cols = [c[0] for c in con.description]
num = lambda v: type(v).__name__ in ("Decimal", "float")
cells = [[f"{v:,.2f}" if num(v) else str(v) for v in r] for r in rows]
w = [max([len(c)] + [len(r[i]) for r in cells]) for i, c in enumerate(cols)]
print(f"\n== {title}")
print(" ".join(c.ljust(x) for c, x in zip(cols, w)))
for r, raw in zip(cells, rows):
print(" ".join(s.rjust(x) if num(v) else s.ljust(x) for s, v, x in zip(r, raw, w)))
if not rows:
print("(no rows)")
# 1. Every entry must balance: total debits = total credits
bad = con.execute("""SELECT entry, any_value(memo) memo, sum(debit) debits, sum(credit) credits
FROM lines GROUP BY entry HAVING sum(debit) <> sum(credit) ORDER BY entry""").fetchall()
show("Check: entries where debits <> credits", """SELECT entry, any_value(memo) memo, sum(debit) debits, sum(credit) credits
FROM lines GROUP BY entry HAVING sum(debit) <> sum(credit) ORDER BY entry""")
show("Trial balance", """
SELECT a.no, a.name, sum(l.debit) debits, sum(l.credit) credits FROM lines l JOIN accounts a ON a.no = l.account
GROUP BY ALL ORDER BY a.no""")
show("Trial balance totals", "SELECT sum(debit) total_debits, sum(credit) total_credits, sum(debit) - sum(credit) difference FROM lines")
if bad:
print("\nSTOP: the ledger does not balance, statements not produced.")
sys.exit(1)
# Balances: assets and expenses are debit-normal; liabilities, equity and revenue are credit-normal
con.execute("""CREATE VIEW bal AS SELECT a.no, a.name, a.type,
CASE WHEN a.type IN ('asset','expense') THEN coalesce(sum(l.debit - l.credit), 0)
ELSE coalesce(sum(l.credit - l.debit), 0) END AS balance
FROM accounts a LEFT JOIN lines l ON l.account = a.no GROUP BY a.no, a.name, a.type""")
show("Income statement (P&L), October 2026", """
SELECT line, amount FROM (
SELECT 1 o, 'Revenue' line, sum(balance) amount FROM bal WHERE type = 'revenue'
UNION ALL SELECT 2, 'Cost of revenue (cloud)', -sum(balance) FROM bal WHERE no = 5000
UNION ALL SELECT 3, 'Gross profit', (SELECT sum(balance) FROM bal WHERE type='revenue') - (SELECT balance FROM bal WHERE no=5000)
UNION ALL SELECT 4 + no / 100000, ' ' || name, -balance FROM bal WHERE type = 'expense' AND no >= 6000
UNION ALL SELECT 5, 'Operating (= net) profit', (SELECT sum(CASE WHEN type='revenue' THEN balance ELSE -balance END) FROM bal WHERE type IN ('revenue','expense'))
) ORDER BY o""")
show("Margins", """SELECT round(100 * (r - c) / r, 1) AS gross_margin_pct, round(100 * p / r, 1) AS net_margin_pct FROM (
SELECT (SELECT sum(balance) FROM bal WHERE type='revenue') r, (SELECT balance FROM bal WHERE no=5000) c,
(SELECT sum(CASE WHEN type='revenue' THEN balance ELSE -balance END) FROM bal WHERE type IN ('revenue','expense')) p)""")
profit = "(SELECT sum(CASE WHEN type='revenue' THEN balance ELSE -balance END) FROM bal WHERE type IN ('revenue','expense'))"
show("Balance sheet, 31 Oct 2026", f"""
SELECT section, line, amount FROM (
SELECT 1 + no / 100000 o, 'Assets' section, name line, balance amount FROM bal WHERE type='asset' AND balance <> 0
UNION ALL SELECT 2, 'Assets', 'TOTAL ASSETS', sum(balance) FROM bal WHERE type='asset'
UNION ALL SELECT 3 + no / 100000, 'Liabilities', name, balance FROM bal WHERE type='liability' AND balance <> 0
UNION ALL SELECT 4, 'Equity', name, balance FROM bal WHERE type='equity'
UNION ALL SELECT 5, 'Equity', 'Retained earnings (this month''s profit)', {profit}
UNION ALL SELECT 6, 'L + E', 'TOTAL LIABILITIES + EQUITY', (SELECT sum(balance) FROM bal WHERE type IN ('liability','equity')) + {profit}
) ORDER BY o""")
show("Cash flow statement (direct method): cash lines by class", """
SELECT class, net_cash FROM (
SELECT CASE cf WHEN 'operating' THEN 1 WHEN 'investing' THEN 2 ELSE 3 END o, cf AS class, sum(debit - credit) AS net_cash
FROM lines WHERE account = 1000 GROUP BY cf
UNION ALL SELECT 4, 'NET CHANGE IN CASH', sum(debit - credit) FROM lines WHERE account = 1000) ORDER BY o""")
show("Cash flow from operations (indirect method): from profit to cash", f"""
SELECT step, amount FROM (
SELECT 1 o, 'Net profit' step, {profit} amount
UNION ALL SELECT 2, '+ Depreciation (an expense with no cash)', (SELECT balance FROM bal WHERE no = 6300)
UNION ALL SELECT 3, '- Increase in receivables (billed, not collected)', -(SELECT balance FROM bal WHERE no = 1100)
UNION ALL SELECT 4, '- Increase in prepaid expenses', -(SELECT balance FROM bal WHERE no = 1200)
UNION ALL SELECT 5, '+ Increase in payables (bill not paid yet)', (SELECT balance FROM bal WHERE no = 2000)
UNION ALL SELECT 6, '+ Increase in deferred revenue (cash before revenue)', (SELECT balance FROM bal WHERE no = 2100)
UNION ALL SELECT 7, '= Cash from operations', {profit} + (SELECT sum(CASE WHEN no IN (6300, 2000, 2100) THEN balance
WHEN no IN (1100, 1200) THEN -balance ELSE 0 END) FROM bal)
) ORDER BY o""")
if BREAK == "prepaid":
show("Software subscriptions expense by month: prepaid and spread vs expensed at once", """
SELECT strftime(m, '%Y-%m') AS month, 1000.00 AS spread_over_12, CASE WHEN m = DATE '2026-10-01' THEN 12000.00 ELSE 0.00 END AS all_in_october
FROM generate_series(DATE '2026-10-01', DATE '2027-09-01', INTERVAL 1 MONTH) t(m) ORDER BY m""")في السكربت أعلاه لاحظ التالي:
- القيود من 1 إلى 9 عمليات حدثت فعلاً، والقيود من 10 إلى 12 قيود تسوية في آخر الشهر لا يتحرك فيها ريال واحد من البنك، وإنما تنقل جزءاً من حساب إلى آخر حسب الاستحقاق.
- القيد 4 يضع اشتراك العميل السنوي في الإيراد المؤجل وليس في الإيراد، ثم ينقل القيد 10 نصيب أكتوبر منه فقط، أي 2,000 ريال.
- القيد 3 يضع ترخيص الأدوات السنوي في المصروفات المدفوعة مقدماً، ثم ينقل القيد 11 نصيب أكتوبر منه إلى المصروف، أي 1,000 ريال.
- القيد 7 يسجل فاتورة السحابة كمصروف في أكتوبر رغم أن دفعها في نوفمبر، فهي التزام على الشركة للمورد حتى تدفع.
- الحاسوب في القيد 2 أصل وليس مصروفاً، ومصروف أكتوبر منه هو الإهلاك في القيد 12 فقط: 14,400 مقسومة على 36 شهراً.
- كل قيد عليه تصنيف للتدفق النقدي، وهذا يحتاجه السكربت فقط لقيود البنك، لأن قائمة التدفقات تقرأ حساب النقد وحده.
ونشغل السكربت داخل Container من ال Image التي بنيناها:
docker run --name lab-fin2-ledger -v "$PWD:/w" lab-fin2-duckdb:1 python ledger.pyوالمخرج طويل، لذلك نعرضه على أجزاء بترتيب ظهوره دون أي تعديل. وأوله الفحص الأول وميزان المراجعة:
== Check: entries where debits <> credits
entry memo debits credits
(no rows)
== Trial balance
no name debits credits
1000 Cash at bank 242,000.00 71,400.00
1100 Accounts receivable 60,000.00 18,000.00
1200 Prepaid expenses 12,000.00 1,000.00
1500 Equipment (laptops) 14,400.00 0.00
1590 Accumulated depreciation 0.00 400.00
2000 Accounts payable 0.00 4,500.00
2100 Deferred revenue 2,000.00 24,000.00
3000 Owners' capital 0.00 200,000.00
4000 Subscription revenue 0.00 62,000.00
5000 Cloud hosting (cost of revenue) 4,500.00 0.00
6000 Salaries 40,000.00 0.00
6100 Office rent 5,000.00 0.00
6200 Software subscriptions 1,000.00 0.00
6300 Depreciation 400.00 0.00
== Trial balance totals
total_debits total_credits difference
381,300.00 381,300.00 0.00لا يوجد أي قيد غير متوازن، ومجموع المدين في الميزان كله 381,300 ريال يساوي مجموع الدائن، وهذا لا يعني أن الأرقام صحيحة بالضرورة، فقيد في الحساب الخطأ يبقى متوازناً، ولكنه يعني أن الدفتر سليم من ناحية الشكل. ولاحظ أن حساب مجمع الإهلاك Accumulated Depreciation يحمل رصيداً دائناً رغم أنه في الأصول، لأنه حساب يخصم من قيمة الأجهزة، ويسمى حساباً مقابلاً Contra Account.
قائمة الدخل
== Income statement (P&L), October 2026
line amount
Revenue 62,000.00
Cost of revenue (cloud) -4,500.00
Gross profit 57,500.00
Salaries -40,000.00
Office rent -5,000.00
Software subscriptions -1,000.00
Depreciation -400.00
Operating (= net) profit 11,100.00
== Margins
gross_margin_pct net_margin_pct
92.70 17.90الشركة ربحت 11,100 ريال في شهرها الأول، بهامش إجمالي 92.7% وصافي هامش 17.9%. ولاحظ أن قائمة الدخل فيها 62,000 ريال إيراداً رغم أن العملاء دفعوا 42,000 فقط، لأن الإيراد هو ما قدمته الشركة من خدمة، وليس ما دخل البنك.
قائمة المركز المالي
== Balance sheet, 31 Oct 2026
section line amount
Assets Cash at bank 170,600.00
Assets Accounts receivable 42,000.00
Assets Prepaid expenses 11,000.00
Assets Equipment (laptops) 14,400.00
Assets Accumulated depreciation -400.00
Assets TOTAL ASSETS 237,600.00
Liabilities Accounts payable 4,500.00
Liabilities Deferred revenue 22,000.00
Equity Owners' capital 200,000.00
Equity Retained earnings (this month's profit) 11,100.00
L + E TOTAL LIABILITIES + EQUITY 237,600.00الأصول 237,600 ريال، والالتزامات مع حقوق الملكية 237,600 ريال، فالمعادلة متوازنة. ولاحظ سطرين يحملان قصة الاستحقاق كلها: العملاء 42,000 ريال، وهي فواتير لم تحصل بعد، والإيراد المؤجل 22,000 ريال، وهي خدمة ما زالت الشركة مدينة بها لعميل دفع مقدماً.
قائمة التدفقات النقدية
== Cash flow statement (direct method): cash lines by class
class net_cash
operating -15,000.00
investing -14,400.00
financing 200,000.00
NET CHANGE IN CASH 170,600.00
== Cash flow from operations (indirect method): from profit to cash
step amount
Net profit 11,100.00
+ Depreciation (an expense with no cash) 400.00
- Increase in receivables (billed, not collected) -42,000.00
- Increase in prepaid expenses -11,000.00
+ Increase in payables (bill not paid yet) 4,500.00
+ Increase in deferred revenue (cash before revenue) 22,000.00
= Cash from operations -15,000.00والجدول الأول هو الطريقة المباشرة Direct Method، وهو ببساطة كل حركة في حساب البنك مصنفة إلى تشغيل واستثمار وتمويل. والجدول الثاني هو الطريقة غير المباشرة Indirect Method، وهي التي تجدها في أغلب القوائم المنشورة، وتبدأ من صافي الربح ثم تصححه حتى تصل إلى النقد. وكلتا الطريقتين تصل إلى الرقم نفسه: النقد من التشغيل نقص 15,000 ريال في شهر ربحت فيه الشركة 11,100 ريال، والسبب الأكبر هو 42,000 ريال لم يحصلها فريق المبيعات بعد.
وقد يتساءل البعض: لماذا نضيف الإهلاك في الطريقة غير المباشرة؟ والإجابة أن الإهلاك مصروف في قائمة الدخل لا يخرج معه ريال من البنك، فالنقد خرج كاملاً يوم شراء الأجهزة، وظهر في قسم الاستثمار، ولهذا نعيده إلى الربح حتى لا نحسبه مرتين.
نكسر الدفتر: قيد غير متوازن
الآن سوف نخطئ كما يخطئ أي شخص يكتب القيود يدوياً، فنكتب 4,000 بدلاً من 40,000 في الجانب الدائن من قيد الرواتب:
docker run --name lab-fin2-ledger-unbalanced -v "$PWD:/w" lab-fin2-duckdb:1 python ledger.py unbalancedوالمخرج سوف يكون كما يلي:
== Check: entries where debits <> credits
entry memo debits credits
8 Pay October salaries 40,000.00 4,000.00
== Trial balance
no name debits credits
1000 Cash at bank 242,000.00 35,400.00
1100 Accounts receivable 60,000.00 18,000.00
1200 Prepaid expenses 12,000.00 1,000.00
1500 Equipment (laptops) 14,400.00 0.00
1590 Accumulated depreciation 0.00 400.00
2000 Accounts payable 0.00 4,500.00
2100 Deferred revenue 2,000.00 24,000.00
3000 Owners' capital 0.00 200,000.00
4000 Subscription revenue 0.00 62,000.00
5000 Cloud hosting (cost of revenue) 4,500.00 0.00
6000 Salaries 40,000.00 0.00
6100 Office rent 5,000.00 0.00
6200 Software subscriptions 1,000.00 0.00
6300 Depreciation 400.00 0.00
== Trial balance totals
total_debits total_credits difference
381,300.00 345,300.00 36,000.00
STOP: the ledger does not balance, statements not produced.الفحص الأول وجد القيد رقم 8 فوراً، وميزان المراجعة يختلف بمقدار 36,000 ريال، والسكربت رفض إخراج القوائم. وهذا هو سبب وجود القيد المزدوج أصلاً، فالخطأ في رقم واحد يكسر التوازن ويظهر في اليوم نفسه، بدلاً من أن يكتشف بعد أشهر. والأنظمة المحاسبية الحقيقية لا تسمح بحفظ قيد غير متوازن من الأساس، ولهذا إذا كتبت نظاماً يسجل قيوداً بنفسه، مثل نظام فوترة أو محفظة، فضع هذا الفحص في قاعدة البيانات نفسها كما فعلنا في مختبر المحفظة الرقمية.
نكسر الشهر: الاشتراك السنوي كله مصروف
هذا الخطأ أخطر من الأول لأنه لا يكسر أي توازن، وهو من أكثر ما يحدث مع اشتراكات البرامج السنوية: يدفع الفريق التقني ترخيص سنة كاملة، فيسجل كله مصروفاً في شهر الدفع:
docker run --name lab-fin2-ledger-prepaid -v "$PWD:/w" lab-fin2-duckdb:1 python ledger.py prepaidوالمخرج طويل لأن القوائم كلها تخرج، لذلك ننقل منه قائمة الدخل والجدول الأخير فقط كما ظهرا:
== Income statement (P&L), October 2026
line amount
Revenue 62,000.00
Cost of revenue (cloud) -4,500.00
Gross profit 57,500.00
Salaries -40,000.00
Office rent -5,000.00
Software subscriptions -12,000.00
Depreciation -400.00
Operating (= net) profit 100.00
== Margins
gross_margin_pct net_margin_pct
92.70 0.20== Software subscriptions expense by month: prepaid and spread vs expensed at once
month spread_over_12 all_in_october
2026-10 1,000.00 12,000.00
2026-11 1,000.00 0.00
2026-12 1,000.00 0.00
2027-01 1,000.00 0.00
2027-02 1,000.00 0.00
2027-03 1,000.00 0.00
2027-04 1,000.00 0.00
2027-05 1,000.00 0.00
2027-06 1,000.00 0.00
2027-07 1,000.00 0.00
2027-08 1,000.00 0.00
2027-09 1,000.00 0.00الدفتر متوازن والفحص لم يجد شيئاً، ولكن صافي الربح نزل من 11,100 إلى 100 ريال، وصافي الهامش من 17.9% إلى 0.2%، والسبب 11,000 ريال سجلت في أكتوبر وهي في الحقيقة تخص الأشهر الأحد عشر التالية. والنتيجة ليست أكتوبر سيئاً فقط، وإنما أحد عشر شهراً بعده تبدو أرخص مما هي، فإذا قارنت تكلفة الأدوات بين الأشهر لتقرر هل تجدد الترخيص أم لا فسوف تصل إلى قرار خاطئ. ولاحظ أيضاً أن قائمة التدفقات لم تتغير أبداً، فالنقد خرج في الحالتين، والخطأ كله في التوقيت، وهذا هو بالضبط ما يحله أساس الاستحقاق.
حذف المختبر
نحذف ال Containers الثلاثة وال Image بأسمائها فقط:
docker rm lab-fin2-ledger lab-fin2-ledger-unbalanced lab-fin2-ledger-prepaid
docker rmi lab-fin2-duckdb:1وإذا كنت ستكمل السلسلة فاترك ال Image، لأن مختبرات المقالات الثلاثة التالية تستخدمها نفسها.
أخطاء شائعة عند المهندسين
وفيما يلي أكثر ما نراه من سوء فهم بين الفرق التقنية والمالية، ومع كل خطأ سببه:
- اعتبار الربح نقداً: الشركة الرابحة قد لا تملك نقداً تدفع به اشتراكاً كبيراً هذا الشهر، فاسأل عن النقد قبل أن تطلب دفعة مقدمة كبيرة.
- دفع اشتراك سنوي دون ذكر فترته: يسجل كله مصروفاً كما رأينا في المختبر، ويشوه مقارنة الأشهر.
- إرسال الفواتير بعد الإقفال: تدخل في الشهر التالي فيبدو شهرك أرخص والشهر بعده أغلى، وتحتاج المالية قيود تصحيح.
- مركز تكلفة واحد لكل شيء تقني: يخفي تكلفة كل منتج ويجعل التقنية تبدو مصروفاً عاماً بلا صاحب.
- تعديل القيود القديمة في نظام تكتبه بنفسك: الدفاتر لا تعدل، وإنما يصحح الخطأ بقيد عكسي جديد، والسبب أن المراجع يحتاج أن يرى ما حدث وما صحح.
- تخمين الكلمات من معناها اليومي: «دائن» ليست ديناً لك، و«الميزانية العمومية» ليست الميزانية السنوية التي تخطط فيها الصرف، فاسأل عن المصطلح بدل أن تخمنه.
تدريب عملي
- أضف قيداً لاستلام 20,000 ريال إضافية من العملاء في 31 أكتوبر، ثم أعد التشغيل ولاحظ ما الذي تغير في قائمة الدخل، وما الذي تغير في قائمة التدفقات وقائمة المركز المالي.
- أضف شهر نوفمبر: قيود الرواتب والإيجار والسحابة، ودفع فاتورة سحابة أكتوبر، وقيود التسوية الثلاثة، ثم اكتب استعلاماً يخرج قائمة الدخل لكل شهر على حدة.
- غير قيد شراء الحواسيب لتكون 18,000 ريال وعمرها 24 شهراً، واحسب بنفسك الإهلاك الشهري قبل أن تشغل السكربت.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- الإيراد ما قدمته للعملاء، والمصروف ما يخص الفترة من تكاليفك، والربح الفرق بينهما، والهامش هو الربح كنسبة من الإيراد.
- الشركات تعمل بأساس الاستحقاق، فالإيراد والمصروف يسجلان وقت تقديم الخدمة واستهلاكها، وليس وقت دفعها.
- قائمة الدخل عن فترة وتخبرك بالربح، وقائمة التدفقات عن فترة وتخبرك بالنقد، وقائمة المركز المالي عن لحظة وتخبرك بما تملكه الشركة وما عليها.
- كل قيد متوازن بين مدين ودائن، ودليل الحسابات هو المخطط، والأستاذ العام هو القيود مجمعة حسب الحساب.
- مركز التكلفة يخبر المالية على ميزانية من يحسب الصرف، فاختره بعناية لكل طلب.
- أخبر المالية بفترة كل اشتراك وأرسل الفواتير قبل الإقفال، فهذان الأمران وحدهما يحلان أغلب الخلافات حول الأرقام.
وفي المقال القادم بإذن الله سوف نتحدث عن ميزانية التقنية بين CapEx وOpEx: متى يكون الشراء أصلاً يهلك على سنوات، وكيف تبني ميزانية سنوية وتشرح انحرافها عن الواقع.
سجل التحديثات
- أكتوبر 2026: كتابة المقال، والاطلاع على صفحة SOCPA عن التحول إلى المعايير الدولية وملف المملكة في موقع مؤسسة IFRS وإعلان هيئة الزكاة والضريبة والجمارك عن موعد إقرارات 2025 بتاريخ 14 أكتوبر 2026، وتشغيل المختبر على Python 3.13.16 وDuckDB 1.5.6 ببيانات مصطنعة.