Dalam artikel ini, kami akan mencoba untuk menembus ke jantung "perusahaan berdarah" - akuntansi. Pertama, kami akan melakukan studi buku besar, akun dan neraca, mengidentifikasi sifat dan algoritma yang melekat. Kami menggunakan teknologi Python dan Test Driven Development. Di sini kita akan terlibat dalam pembuatan prototipe, jadi alih-alih database kita akan menggunakan wadah dasar: daftar, kamus dan tupel. Proyek ini sedang dikembangkan sesuai dengan persyaratan proyek Empire ERP .
Kondisi tugas
Luar angkasa ... Planet Empireia ... Satu negara di seluruh planet ini. Populasi bekerja 2 jam dalam 2 minggu, setelah 2 tahun pensiun. Bagan akun terdiri dari 12 posisi. Akun 1-4 aktif, 5-8 aktif-pasif, 9-12 pasif. Tanduk & Kuku Perusahaan. Semua transaksi dilakukan dalam satu periode pelaporan, pada awal periode tidak ada saldo.
Penyiapan proyek
Kami mengkloning proyek dari github:
git clone https://github.com/nomhoi/empire-erp.git
Kami sedang mengembangkan di Python 3.7.4. Atur lingkungan virtual, aktifkan dan instal pytest .
pip install pytest
1. Buku besar
Buka folder 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)
Buku utama, seperti yang kita lihat, disajikan dalam bentuk daftar catatan. Setiap entri dirancang sebagai tupel. Untuk catatan transaksi sejauh ini kami hanya menggunakan nomor rekening untuk debit dan kredit dan jumlah transaksi. Tanggal, deskripsi, dan informasi lainnya belum diperlukan, kami akan menambahkannya nanti.
Kait buku besar dan test_ledger uji parameter dibuat dalam file uji. Dalam parameter tes entri , kami segera mentransfer seluruh daftar transaksi. Untuk memeriksa, kita menjalankan perintah pytest -s -v di terminal. Tes harus lulus, dan kita akan melihat di terminal seluruh daftar transaksi yang disimpan di buku besar:
General ledger 1 12 100.00 1 11 100.00
2. Akun
Sekarang mari kita tambahkan dukungan faktur ke proyek. Buka folder 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)
Kelas GeneralLedger membebani metode append . Saat Anda menambahkan transaksi ke buku, kami segera menambahkannya ke akun.
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]))
Kelas Akun dirancang sebagai kamus. Di tombol, nomor akun, dalam nilai isi akun, mis. turunan dari kelas Akun , yang pada gilirannya berisi bidang saldo pembukaan dan penutupan dan daftar transaksi yang terkait dengan akun ini. Perhatikan bahwa dalam daftar ini jumlah transaksi debit dan kredit disimpan dalam satu bidang, jumlah debet positif, jumlah pinjaman negatif.
test_accounting.py :
@pytest.fixture def accounts(): return Accounts() @pytest.fixture def ledger(accounts): return GeneralLedger(accounts)
Dalam file pengujian, kami menambahkan kunci akun dan menyesuaikan kunci buku besar .
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)
Menambahkan test_accounts pengujian baru.
Jalankan tes dan amati hasilnya:
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 ----------------------
Di kelas Akun dan Acconts , metode __str__ juga kelebihan beban, Anda dapat melihat di sumber proyek. Jumlah postingan dan saldo untuk kejelasan yang lebih baik disajikan dalam dua kolom: debit dan kredit.
3. Akun: memposting verifikasi
Kami mengingat aturan berikut:
. . - .
Yaitu, dalam instance kelas Akun , nilai akhir (saldo akhir) pada akun aktif tidak boleh negatif, dan pada akun pasif tidak boleh positif.
Buka folder day1 / step3 .
accounting.py :
class BalanceException(Exception): pass
Menambahkan 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 ...
Cek ditambahkan ke kelas Akun untuk menentukan apakah akun itu aktif atau pasif.
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') ...
Cek telah ditambahkan ke kelas Accounts.py , jika sebagai hasil dari menambahkan transaksi baru nilai debet negatif dihasilkan pada akun aktif, pengecualian akan dinaikkan, dan hal yang sama jika nilai kredit negatif diperoleh pada akun pasif.
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) ...
Di kelas GeneralLedger, sebelum menambahkan posting ke akun, kami memeriksa. Jika pengecualian muncul, posting tidak termasuk dalam akun atau buku besar umum.
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
Tes test_accounts_balance ditambahkan ke modul tes. Daftar transaksi pertama kali mencantumkan semua kemungkinan kombinasi transaksi dan mengomentari semua transaksi yang tidak menimbulkan pengecualian. Jalankan tes dan pastikan bahwa 5 opsi posting lainnya meningkatkan BalanceException .
4. Saldo
Buka folder 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
Saat membuat saldo, kami hanya mengumpulkan saldo dari semua akun dalam satu tabel.
test_accounting.py :
@pytest.fixture def balance(accounts): return Balance(accounts)
Buat kait keseimbangan .
@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)
Kami menciptakan tes test_balance . Dalam daftar parameter, semua jenis transaksi yang mungkin tercantum: meningkatkan aset dan liabilitas, mengurangi aset dan liabilitas, meningkatkan aset dan mengurangi aset, meningkatkan liabilitas dan mengurangi liabilitas. Menerbitkan 4 opsi untuk posting, sehingga Anda dapat langkah demi langkah melihat hasilnya. Untuk opsi terakhir, outputnya adalah sebagai berikut:
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. Membalik
Sekarang mari kita periksa bagaimana pembalikan dilakukan.
@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)
Kesimpulannya adalah sebagai berikut:
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 ======================
Segalanya tampak benar.
Dan jika kita menggunakan seperangkat posting seperti itu, maka tes akan lulus:
( 1, 12, 100.00), (12, 1, 100.00), ( 1, 12,-100.00),
Dan jika set tersebut, menukar 2 baris terakhir di tempat, maka kita mendapatkan pengecualian:
( 1, 12, 100.00), ( 1, 12,-100.00), (12, 1, 100.00),
Jadi, untuk menangkap kesalahan semacam itu, pembalikan harus dilakukan segera setelah transaksi dikoreksi.
Kesimpulan
Dalam artikel berikut kami akan melanjutkan studi akuntansi dan akan mempertimbangkan semua aspek pengembangan sistem sesuai dengan daftar persyaratan untuk Empire ERP.