Empire ERP. Comptabilité divertissante: grand livre, comptes, solde

Dans cet article, nous tenterons de pénétrer au cœur même de "l'entreprise sanglante" - la comptabilité. Dans un premier temps, nous effectuerons une étude du grand livre général, des comptes et du bilan, identifierons leurs propriétés et algorithmes inhérents. Nous utilisons la technologie de développement Python et Test Driven. Ici, nous serons engagés dans le prototypage, donc au lieu de la base de données, nous utiliserons les conteneurs de base: listes, dictionnaires et tuples. Le projet est développé conformément aux exigences du projet Empire ERP .


Condition de tâche


Espace ... Planète d'Empireia ... Un état sur toute la planète. La population travaille 2 heures en 2 semaines, après 2 ans de retraite. Le plan comptable comprend 12 positions. Les comptes 1-4 sont actifs, 5-8 sont actifs-passifs, 9-12 sont passifs. Entreprise Horns & Hooves. Toutes les transactions sont effectuées au cours d'une même période de reporting, au début de la période il n'y a pas de soldes.


Configuration du projet


Nous clonons le projet depuis le github:


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

Nous développons en Python 3.7.4. Configurez l'environnement virtuel, activez-le et installez pytest .


 pip install pytest 

1. Grand livre


Accédez au dossier 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) 

Le livre principal, comme nous le voyons, se présente sous la forme d'une liste de documents. Chaque entrée est conçue comme un tuple. Pour l'enregistrement de la transaction jusqu'à présent, nous utilisons uniquement les numéros de compte pour le débit et le crédit et le montant de la transaction. Les dates, descriptions et autres informations ne sont pas encore nécessaires, nous les ajouterons plus tard.


Le verrou du grand livre et le test test_ledger paramétré ont été créés dans le fichier de test. Dans le paramètre de test des entrées , nous transférons immédiatement la liste complète des transactions. Pour vérification est effectuée dans -v du pytest terminal. Le test devrait passer, et nous verrons dans le terminal la liste complète des transactions stockées dans le grand livre:


 General ledger 1 12 100.00 1 11 100.00 

2. Comptes


Ajoutons maintenant la prise en charge des factures au projet. Accédez au dossier 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) 

La classe GeneralLedger a surchargé la méthode append . Lorsque vous ajoutez une transaction à un livre, nous l'ajoutons immédiatement aux comptes.


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])) 

La classe Accounts est conçue comme un dictionnaire. Dans les clés, le numéro de compte, dans les valeurs le contenu du compte, c'est-à-dire une instance de la classe Account , qui à son tour contient les champs de solde d'ouverture et de fermeture et une liste des transactions liées à ce compte. Notez que dans cette liste, les montants des transactions de débit et de crédit sont stockés dans un champ, le montant du débit est positif, le montant du prêt est négatif.


test_accounting.py :


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

Dans le fichier de test, nous avons ajouté le verrou des comptes et ajusté le verrou du grand livre .


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) 

Ajout d'un nouveau test test_accounts .


Exécutez le test et observez la sortie:


 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 ---------------------- 

Dans les classes Account et Acconts , les méthodes __str__ sont également surchargées, vous pouvez le voir dans les sources du projet. Les montants des écritures et des soldes pour plus de clarté sont présentés dans deux colonnes: débit et crédit.


3. Comptes: validation de validation


Nous rappelons la règle suivante:


         .         .   -         . 

En d'autres termes, dans une instance de la classe Account , la valeur finale (solde final) sur les comptes actifs ne peut pas être négative et sur les comptes passifs, elle ne peut pas être positive.


Accédez au dossier day1 / step3 .


accounting.py :


 class BalanceException(Exception): pass 

Ajout d'une 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 ... 

La classe compte ajoutée compte de chèques appartient à quel type: actif ou passif.


 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') ... 

Un chèque a été ajouté à la classe Accounts.py , si à la suite de l'ajout d'une nouvelle transaction une valeur de débit négative est générée sur le compte actif, une exception sera levée, et la même chose si une valeur de crédit négative est obtenue sur le compte passif.


 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) ... 

Dans la classe GeneralLedger, avant d'ajouter la comptabilisation aux comptes, nous effectuons une vérification. Si une exception est levée, l'écriture ne tombe ni dans les comptes ni dans le grand livre.


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 

Le test test_accounts_balance a été ajouté au module de test. La liste des transactions énumère d'abord toutes les combinaisons possibles de transactions et commente toutes les transactions qui ne déclenchent pas d'exception. Exécutez le test et assurez-vous que les 5 options de publication restantes déclenchent une BalanceException .


4. Équilibre


Accédez au dossier 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 

Lors de la création d'un solde, nous collectons simplement les soldes de tous les comptes dans une seule table.


test_accounting.py :


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

Créez un verrou d' équilibre .


 @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) 

Nous avons créé le test test_balance . Dans les listes de paramètres, tous les types de transactions possibles ont été répertoriés: augmentation de l'actif et du passif, diminution de l'actif et du passif, augmentation de l'actif et diminution de l'actif, augmentation du passif et diminution du passif. Émis 4 options pour les publications, afin que vous puissiez voir étape par étape la sortie. Pour la dernière option, la sortie est la suivante:


 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. Inverser


Vérifions maintenant comment l'inversion est effectuée.


 @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) 

La conclusion Ă©tait la suivante:


 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 ====================== 

Tout semble aller bien.


Et si nous utilisons un tel ensemble de publications, le test réussira:


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

Et si un tel ensemble, échangez les 2 dernières lignes par endroits, alors nous obtenons une exception:


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

Ainsi, afin de détecter une telle erreur, l'annulation doit être effectuée immédiatement après la correction de la transaction.


Conclusion


Dans les articles suivants, nous poursuivrons l'étude de la comptabilité et examinerons tous les aspects du développement du système conformément à la liste des exigences d'Empire ERP.

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


All Articles