In diesem Artikel werden wir versuchen, in das Herz des "blutigen Unternehmens" einzudringen - der Buchhaltung. Zunächst werden wir eine Untersuchung des Hauptbuchs, der Konten und der Bilanz durchführen und deren inhärente Eigenschaften und Algorithmen identifizieren. Wir verwenden die Python- und Test Driven Development-Technologie. Hier werden wir uns mit dem Prototyping beschäftigen, also werden wir anstelle der Datenbank die grundlegenden Container verwenden: Listen, Wörterbücher und Tupel. Das Projekt wird gemäß den Anforderungen des Empire ERP-Projekts entwickelt .
Aufgabenbedingung
Weltraum ... Planet von Empireia ... Ein Staat auf dem ganzen Planeten. Die Bevölkerung arbeitet 2 Stunden in 2 Wochen, nach 2 Jahren in den Ruhestand. Der Kontenplan besteht aus 12 Positionen. Die Konten 1-4 sind aktiv, 5-8 sind aktiv-passiv, 9-12 sind passiv. Firmenhörner & Hufe. Alle Transaktionen werden in einer Berichtsperiode ausgeführt, zu Beginn der Periode gibt es keine Salden.
Projekteinrichtung
Wir klonen das Projekt vom Github:
git clone https://github.com/nomhoi/empire-erp.git
Wir entwickeln in Python 3.7.4. Richten Sie die virtuelle Umgebung ein, aktivieren Sie sie und installieren Sie pytest .
pip install pytest
1. Hauptbuch
Wechseln Sie in den Ordner reaserch / day1 / step1 .
Buchhaltung.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)
Wie wir sehen, wird das Hauptbuch in Form einer Liste von Aufzeichnungen präsentiert. Jeder Eintrag ist als Tupel konzipiert. Für die Aufzeichnung der Transaktion verwenden wir bisher nur die Kontonummern für Lastschrift und Gutschrift sowie den Betrag der Transaktion. Daten, Beschreibungen und andere Informationen werden noch nicht benötigt, wir werden sie später hinzufügen.
Der Ledger Latch und der parametrisierte Test test_ledger wurden in der Testdatei erstellt. Im Eintragsparameter übertragen wir sofort die gesamte Liste der Transaktionen. Zur Überprüfung führen wir den Befehl pytest -s -v im Terminal aus. Der Test sollte bestanden werden, und im Terminal wird die gesamte Liste der im Hauptbuch gespeicherten Transaktionen angezeigt:
General ledger 1 12 100.00 1 11 100.00
2. Konten
Fügen wir nun dem Projekt Rechnungsunterstützung hinzu. Wechseln Sie in den Ordner day1 / step2 .
Buchhaltung.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)
Die GeneralLedger- Klasse hat die Append- Methode überladen. Wenn Sie einem Buch eine Transaktion hinzufügen, fügen wir sie sofort den Konten hinzu.
Buchhaltung.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]))
Die Accounts- Klasse ist als Wörterbuch konzipiert. In den Schlüsseln die Kontonummer, in den Werten der Inhalt des Kontos, d.h. Eine Instanz der Account- Klasse, die wiederum die Felder für den Eröffnungs- und Schlusssaldo sowie eine Liste der mit diesem Konto verbundenen Transaktionen enthält. Beachten Sie, dass in dieser Liste die Beträge der Debit- und Kredittransaktionen in einem Feld gespeichert sind, der Debitbetrag positiv und der Kreditbetrag negativ ist.
test_accounting.py :
@pytest.fixture def accounts(): return Accounts() @pytest.fixture def ledger(accounts): return GeneralLedger(accounts)
In der Testdatei haben wir die Kontosperre hinzugefügt und die Ledgersperre angepasst.
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)
Ein neuer Test test_accounts wurde hinzugefügt .
Führen Sie den Test aus und beobachten Sie die Ausgabe:
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 ----------------------
In den Klassen Konto und Konten sind auch die Methoden __str__ überladen, wie Sie in den Projektquellen sehen können. Die Beträge der Buchungen und Salden werden zur besseren Übersichtlichkeit in zwei Spalten dargestellt: Lastschrift und Gutschrift.
3. Konten: Buchungsbestätigung
Wir erinnern uns an folgende Regel:
. . - .
Das heißt, in einer Instanz der Kontoklasse kann der Endwert (Endsaldo) auf aktiven Konten nicht negativ und auf passiven Konten nicht positiv sein.
Wechseln Sie in den Ordner day1 / step3 .
Buchhaltung.py :
class BalanceException(Exception): pass
BalanceException hinzugefügt .
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 ...
Der Kontoklasse wurde eine Prüfung hinzugefügt, um festzustellen, ob das Konto aktiv oder passiv ist.
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') ...
Der Klasse " Accounts.py" wurde ein Scheck hinzugefügt. Wenn durch Hinzufügen einer neuen Transaktion ein negativer Abbuchungswert für das aktive Konto generiert wird, wird eine Ausnahme ausgelöst, und dasselbe gilt, wenn ein passives Konto einen negativen Kreditwert erhält.
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) ...
In der GeneralLedger- Klasse prüfen wir , bevor wir die Buchung zu den Konten hinzufügen. Wenn eine Ausnahme ausgelöst wird, fällt die Buchung weder in die Konten noch in das Hauptbuch.
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
Der Test test_accounts_balance wurde dem Testmodul hinzugefügt. In der Liste der Transaktionen wurden zunächst alle möglichen Kombinationen von Transaktionen aufgelistet und alle Transaktionen auskommentiert, die keine Ausnahme auslösen. Führen Sie den Test aus und stellen Sie sicher, dass die verbleibenden 5 Buchungsoptionen eine BalanceException auslösen .
4. Balance
Wechseln Sie in den Ordner day1 / step4 .
Buchhaltung.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
Beim Erstellen eines Saldos sammeln wir einfach die Salden aller Konten in einer Tabelle.
test_accounting.py :
@pytest.fixture def balance(accounts): return Balance(accounts)
Erstellen Sie eine Balance- Verriegelung.
@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)
Wir haben den Test test_balance erstellt . In den Parameterlisten wurden alle möglichen Arten von Transaktionen aufgeführt: Erhöhung des Vermögenswerts und der Verbindlichkeit, Verringerung des Vermögenswerts und der Verbindlichkeit, Erhöhung des Vermögenswerts und Verringerung des Vermögenswerts, Erhöhung der Verbindlichkeit und Verringerung der Verbindlichkeit. Es wurden 4 Optionen für Buchungen ausgegeben, damit Sie Schritt für Schritt die Ausgabe sehen können. Für die letzte Option lautet die Ausgabe wie folgt:
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. Umkehren
Lassen Sie uns nun überprüfen, wie die Umkehrung durchgeführt wird.
@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)
Die Schlussfolgerung war wie folgt:
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 ======================
Alles scheint richtig zu sein.
Und wenn wir eine solche Reihe von Postings verwenden, besteht der Test:
( 1, 12, 100.00), (12, 1, 100.00), ( 1, 12,-100.00),
Und wenn ein solcher Satz die letzten 2 Zeilen an bestimmten Stellen vertauscht, erhalten wir eine Ausnahme:
( 1, 12, 100.00), ( 1, 12,-100.00), (12, 1, 100.00),
Um einen solchen Fehler zu erkennen, muss die Stornierung daher unmittelbar nach der Korrektur der Transaktion erfolgen.
Fazit
In den folgenden Artikeln werden wir das Studium der Rechnungslegung fortsetzen und alle Aspekte der Entwicklung des Systems gemäß der Liste der Anforderungen für Empire ERP berücksichtigen.