الإمبراطورية ERP. مسلية المحاسبة: دفتر الأستاذ العام ، والحسابات ، والتوازن

في هذه المقالة ، سنحاول اختراق جوهر "المؤسسة الدامية" - المحاسبة. أولاً ، سنجري دراسة دفتر الأستاذ العام والحسابات والميزانية العمومية ، ونحدد خصائصها وخوارزمياتها المتأصلة. نحن نستخدم تقنية بيثون واختبار التطوير. سنشارك هنا في النماذج الأولية ، لذا بدلاً من قاعدة البيانات ، سنستخدم الحاويات الأساسية: القوائم ، والقواميس ، و tuples. يتم تطوير المشروع وفقًا لمتطلبات مشروع Empire ERP .


شرط المهمة


الفضاء ... كوكب الإمبراطورية ... دولة واحدة على الكوكب بأسره. يعمل السكان 2 ساعة في 2 أسابيع ، بعد 2 سنوات للتقاعد. يتكون مخطط الحسابات من 12 وظيفة. الحسابات 1-4 نشطة ، 5-8 نشطة-سلبية ، 9-12 سلبية. شركة الأبواق والحوافر. يتم تنفيذ جميع المعاملات في فترة واحدة من التقارير ، في بداية الفترة لا توجد أرصدة.


إعداد المشروع


نحن استنساخ المشروع من جيثب:


git clone https://github.com/nomhoi/empire-erp.git 

نحن نعمل على تطوير بيثون 3.7.4. قم بإعداد البيئة الافتراضية ، وقم بتنشيطها وتثبيت pytest .


 pip install pytest 

1. دفتر الأستاذ العام


انتقل إلى مجلد reaserch / day1 / step1 .


accounting.py :


 DEBIT = 0 CREDIT = 1 AMOUNT = 2 class GeneralLedger(list): def __str__(self): res = '\nGeneral ledger' for e in self: res += '\n {:2} {:2} {:8.2f}'.format(e[DEBIT], e[CREDIT], e[AMOUNT]) res += "\n----------------------" return res 

test_accounting.py :


 import pytest from accounting import * from decimal import * @pytest.fixture def ledger(): return GeneralLedger() @pytest.mark.parametrize('entries', [ [(1, 12, 100.00), (1, 11, 100.00)] ]) def test_ledger(ledger, entries): for entry in entries: ledger.append((entry[DEBIT], entry[CREDIT], Decimal(entry[AMOUNT]))) assert len(ledger) == 2 assert ledger[0][DEBIT] == 1 assert ledger[0][CREDIT] == 12 assert ledger[0][AMOUNT] == Decimal(100.00) assert ledger[1][DEBIT] == 1 assert ledger[1][CREDIT] == 11 assert ledger[1][AMOUNT] == Decimal(100.00) print(ledger) 

يقدم الكتاب الرئيسي ، كما نرى ، في شكل قائمة من السجلات. تم تصميم كل إدخال بمثابة tuple. لسجل المعاملة حتى الآن ، نستخدم فقط أرقام الحسابات للخصم والائتمان ومقدار المعاملة. ليست هناك حاجة إلى التواريخ والأوصاف وغيرها من المعلومات حتى الآن ، وسوف نضيفها لاحقًا.


تم إنشاء مزلاج دفتر الأستاذ و test_ledger اختبار معلمات في ملف الاختبار. في معلمة اختبار الإدخالات ، ننقل على الفور قائمة المعاملات بالكامل. للتحقق ، نقوم بتنفيذ أمر pytest -s -v في الجهاز. يجب أن يجتاز الاختبار ، وسنرى في المحطة القائمة الكاملة للمعاملات المخزنة في دفتر الأستاذ العام:


 General ledger 1 12 100.00 1 11 100.00 

2. الحسابات


الآن لنقم بإضافة دعم الفاتورة للمشروع. انتقل إلى المجلد day1 / step2 .


accounting.py :


 class GeneralLedger(list): def __init__(self, accounts=None): self.accounts = accounts def append(self, entry): if self.accounts is not None: self.accounts.append_entry(entry) super().append(entry) 

الفئة GeneralLedger overloaded أسلوب إلحاقي . عندما تضيف معاملة إلى كتاب ، فإننا نضيفها على الفور إلى الحسابات.


accounting.py :


 class Account: def __init__(self, id, begin=Decimal(0.00)): self.id = id self.begin = begin self.end = begin self.entries = [] def append(self, id, amount): self.entries.append((id, amount)) self.end += amount class Accounts(dict): def __init__(self): self.range = range(1, 13) for i in self.range: self[i] = Account(i) def append_entry(self, entry): self[entry[DEBIT]].append(entry[CREDIT], Decimal(entry[AMOUNT])) self[entry[CREDIT]].append(entry[DEBIT], Decimal(-entry[AMOUNT])) 

تم تصميم فئة الحسابات كقاموس. في المفاتيح ، رقم الحساب ، في قيم محتويات الحساب ، أي مثيل لفئة الحساب ، والذي يحتوي بدوره على حقول الرصيد الافتتاحي والإغلاق وقائمة المعاملات المتعلقة بهذا الحساب. لاحظ أنه في هذه القائمة ، يتم تخزين مبالغ معاملات الخصم والائتمان في حقل واحد ، ومبلغ الخصم مدين ، ومبلغ القرض سالب.


test_accounting.py :


 @pytest.fixture def accounts(): return Accounts() @pytest.fixture def ledger(accounts): return GeneralLedger(accounts) 

في ملف الاختبار ، أضفنا قفل الحسابات وعدّلنا قفل دفتر الأستاذ .


test_accounting.py :


 @pytest.mark.parametrize('entries', [ [(1, 12, 100.00), (1, 11, 100.00)] ]) def test_accounts(accounts, ledger, entries): for entry in entries: ledger.append((entry[DEBIT], entry[CREDIT], Decimal(entry[AMOUNT]))) assert len(ledger) == 2 assert ledger[0][DEBIT] == 1 assert ledger[0][CREDIT] == 12 assert ledger[0][AMOUNT] == Decimal(100.00) assert len(accounts) == 12 assert accounts[1].end == Decimal(200.00) assert accounts[11].end == Decimal(-100.00) assert accounts[12].end == Decimal(-100.00) print(ledger) print(accounts) 

إضافة اختبار جديد test_accounts .


قم بإجراء الاختبار ولاحظ الإخراج:


 General ledger 1 12 100.00 1 11 100.00 ---------------------- Account 1 beg: 0.00 0.00 12: 100.00 0.00 11: 100.00 0.00 end: 200.00 0.00 ---------------------- Account 11 beg: 0.00 0.00 1: 0.00 100.00 end: 0.00 100.00 ---------------------- Account 12 beg: 0.00 0.00 1: 0.00 100.00 end: 0.00 100.00 ---------------------- 

في فئتي الحساب والحساب ، يتم تحميل أساليب __str__ أيضًا بشكل مفرط ، ويمكنك أن ترى في مصادر المشروع. يتم تقديم مبالغ الترحيلات والأرصدة لتحسين الوضوح في عمودين: الخصم والائتمان.


3. الحسابات: نشر التحقق


نذكر القاعدة التالية:


         .         .   -         . 

أي أنه في حالة فئة الحساب ، لا يمكن أن تكون القيمة النهائية (الرصيد النهائي) للحسابات النشطة سلبية ، وفي الحسابات السلبية لا يمكن أن تكون إيجابية.


انتقل إلى المجلد day1 / step3 .


accounting.py :


 class BalanceException(Exception): pass 

وأضاف BalanceException .


 class Account: ... def is_active(self): return True if self.id < 5 else False def is_passive(self): return True if self.id > 8 else False ... 

تمت إضافة التحقق إلى فئة الحساب لتحديد ما إذا كان الحساب نشطًا أم غير نشط.


 class Accounts(dict): ... def check_balance(self, entry): if self[entry[CREDIT]].end - Decimal(entry[AMOUNT]) < 0 and self[entry[CREDIT]].is_active(): raise BalanceException('BalanceException') if self[entry[DEBIT]].end + Decimal(entry[AMOUNT]) > 0 and self[entry[DEBIT]].is_passive(): raise BalanceException('BalanceException') ... 

تمت إضافة شيك إلى فئة Accounts.py ، إذا تم إنشاء قيمة مدين سالبة على الحساب النشط نتيجة لإضافة معاملة جديدة ، سيتم رفع استثناء ، ونفس الشيء إذا حصل حساب خامل على قيمة ائتمان سالبة.


 class GeneralLedger(list): ... def append(self, entry): if self.accounts is not None: self.accounts.check_balance(entry) self.accounts.append_entry(entry) super().append(entry) ... 

في فئة GeneralLedger ، قبل إضافة النشر إلى الحسابات ، نقوم بإجراء فحص. إذا تم رفع استثناء ، فلا يقع النشر في الحسابات أو دفتر الأستاذ العام.


test_accounting.py :


 @pytest.mark.parametrize('entries, exception', [ ([(12, 1, 100.00)], BalanceException('BalanceException')), ([(12, 6, 100.00)], BalanceException('BalanceException')), ([(12, 11, 100.00)], BalanceException('BalanceException')), ([(6, 2, 100.00)], BalanceException('BalanceException')), #([(6, 7, 100.00)], BalanceException('BalanceException')), #([(6, 12, 100.00)], BalanceException('BalanceException')), ([(1, 2, 100.00)], BalanceException('BalanceException')), #([(1, 6, 100.00)], BalanceException('BalanceException')), #([(1, 12, 100.00)], BalanceException('BalanceException')), ]) def test_accounts_balance(accounts, ledger, entries, exception): for entry in entries: try: ledger.append((entry[DEBIT], entry[CREDIT], Decimal(entry[AMOUNT]))) except BalanceException as inst: assert isinstance(inst, type(exception)) assert inst.args == exception.args else: pytest.fail("Expected error but found none") assert len(ledger) == 0 assert len(accounts) == 12 

تمت إضافة test_accounts_balance test إلى وحدة الاختبار. أدرجت قائمة المعاملات أولاً جميع المجموعات الممكنة للمعاملات وعلقت على جميع المعاملات التي لا تثير استثناء. قم بإجراء الاختبار وتأكد من أن خيارات النشر الخمسة الباقية تثير BalanceException .


4. الرصيد


انتقل إلى المجلد day1 / step4 .


accounting.py :


 class Balance(list): def __init__(self, accounts): self.accounts = accounts self.suma = Decimal(0.00) self.sump = Decimal(0.00) def create(self): self.suma = Decimal(0.00) self.sump = Decimal(0.00) for i in self.accounts.range: active = self.accounts[i].end if self.accounts[i].end >= 0 else Decimal(0.00) passive = -self.accounts[i].end if self.accounts[i].end < 0 else Decimal(0.00) self.append((active, passive)) self.suma += active self.sump += passive 

عند إنشاء رصيد ، نقوم ببساطة بتجميع الأرصدة من جميع الحسابات في جدول واحد.


test_accounting.py :


 @pytest.fixture def balance(accounts): return Balance(accounts) 

إنشاء مزلاج التوازن .


 @pytest.mark.parametrize('entries', [ [ ( 1, 12, 200.00), # increase active and passive ],[ ( 1, 12, 200.00), # increase active and passive (12, 1, 100.00), # decrease passive and decrease active ],[ ( 1, 12, 300.00), # increase active and passive (12, 1, 100.00), # decrease passive and decrease active ( 2, 1, 100.00), # increase active and decrease active ],[ ( 1, 12, 300.00), # increase active and passive (12, 1, 100.00), # decrease passive and decrease active ( 2, 1, 100.00), # increase active and decrease active (12, 11, 100.00), # decrease passive and increase passive ] ]) def test_balance(accounts, ledger, balance, entries): for entry in entries: ledger.append(entry) balance.create() print(accounts) print(balance) 

لقد أنشأنا اختبار test_balance . في قوائم المعلمات ، تم إدراج جميع أنواع المعاملات الممكنة: زيادة الأصول والخصوم ، وخفض الأصل والخصوم ، وزيادة الأصل وخفض الأصل ، وزيادة الالتزام وخفض الالتزام. أصدر 4 خيارات للترحيلات ، بحيث يمكنك خطوة بخطوة رؤية الإخراج. بالنسبة للخيار الأخير ، يكون الإخراج كما يلي:


 General ledger 1 12 300.00 12 1 100.00 2 1 100.00 12 11 100.00 ---------------------- Account 1 beg: 0.00 0.00 12: 300.00 0.00 12: 0.00 100.00 2: 0.00 100.00 end: 100.00 0.00 ---------------------- Account 2 beg: 0.00 0.00 1: 100.00 0.00 end: 100.00 0.00 ---------------------- Account 11 beg: 0.00 0.00 12: 0.00 100.00 end: 0.00 100.00 ---------------------- Account 12 beg: 0.00 0.00 1: 0.00 300.00 1: 100.00 0.00 11: 100.00 0.00 end: 0.00 100.00 ---------------------- Balance 1 : 100.00 0.00 2 : 100.00 0.00 3 : 0.00 0.00 4 : 0.00 0.00 5 : 0.00 0.00 6 : 0.00 0.00 7 : 0.00 0.00 8 : 0.00 0.00 9 : 0.00 0.00 10 : 0.00 0.00 11 : 0.00 100.00 12 : 0.00 100.00 ---------------------- sum: 200.00 200.00 ====================== 

5. عكس


الآن دعونا نتحقق من كيفية إجراء الانعكاس.


 @pytest.mark.parametrize('entries', [ [ ( 1, 12, 100.00), ( 1, 12,-100.00), ] ]) def test_storno(accounts, ledger, balance, entries): for entry in entries: ledger.append(entry) balance.create() print(ledger) print(accounts) print(balance) 

الاستنتاج كان على النحو التالي:


 General ledger 1 12 100.00 1 12 -100.00 ---------------------- Account 1 beg: 0.00 0.00 12: 100.00 0.00 12: 0.00 100.00 end: 0.00 0.00 ---------------------- Account 12 beg: 0.00 0.00 1: 0.00 100.00 1: 100.00 0.00 end: 0.00 0.00 ---------------------- Balance 1 : 0.00 0.00 2 : 0.00 0.00 3 : 0.00 0.00 4 : 0.00 0.00 5 : 0.00 0.00 6 : 0.00 0.00 7 : 0.00 0.00 8 : 0.00 0.00 9 : 0.00 0.00 10 : 0.00 0.00 11 : 0.00 0.00 12 : 0.00 0.00 ---------------------- sum: 0.00 0.00 ====================== 

يبدو أن كل شيء على حق.


وإذا استخدمنا هذه المجموعة من المنشورات ، فسوف يمر الاختبار:


 ( 1, 12, 100.00), (12, 1, 100.00), ( 1, 12,-100.00), 

وإذا كانت هذه المجموعة ، قم بتبديل آخر سطرين في الأماكن ، فسنحصل على استثناء:


 ( 1, 12, 100.00), ( 1, 12,-100.00), (12, 1, 100.00), 

وبالتالي ، من أجل اكتشاف مثل هذا الخطأ ، يجب وضع الانعكاس فورًا بعد تصحيح المعاملة.


استنتاج


في المقالات التالية ، سنواصل دراسة المحاسبة وسندرس جميع جوانب تطوير النظام وفقًا لقائمة متطلبات Empire ERP.

Source: https://habr.com/ru/post/ar471304/


All Articles