在本文中,我们将尝试渗透到“血腥企业”的核心-会计。 首先,我们将研究总账,账目和资产负债表,确定其固有属性和算法。 我们使用Python和测试驱动开发技术。 在这里,我们将进行原型制作,因此,我们将使用基本容器(列表,字典和元组)代替数据库。 该项目是根据Empire ERP项目的要求开发的 。
任务条件
太空...帝国星球...整个星球上的一个州。 退休2年后,人口在2周内工作2个小时。 会计科目表包含12个职位。 帐户1-4是主动的,帐户5-8是主动-被动的,帐户9-12是被动的。 公司的喇叭和蹄。 所有交易均在一个报告期内进行,期初没有余额。
项目设置
我们从github克隆项目:
git clone https://github.com/nomhoi/empire-erp.git
我们正在使用Python 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)
如我们所见,主要书籍以记录列表的形式呈现。 每个条目都设计为一个元组。 对于到目前为止的交易记录,我们仅使用借方和贷方帐号以及交易金额。 日期,描述和其他信息尚不需要,我们将在以后添加。
在测试文件中创建了分类帐锁存器和参数化测试test_ledger 。 在entrys测试参数中,我们立即传输整个交易清单。 为了进行检查,我们在终端中执行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类重载了append方法。 当您将交易添加到书籍时,我们会立即将其添加到帐户中。
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]))
Accounts类被设计为字典。 在键中,是帐号,在值中是帐号的内容,即 Account类的实例,该类又包含期初和期末余额字段以及与此帐户相关的交易列表。 请注意,在此列表中,借方和贷方交易的金额存储在一个字段中,借方金额为正,借方金额为负。
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 ----------------------
在Account和Acconts类中, __str__方法也很重载,您可以在项目源代码中看到。 为了更清楚地说明过帐和结余,列在两个列中:借方和贷方。
3.帐户:过帐验证
我们回顾以下规则:
. . - .
也就是说,在Account类的实例中,活动帐户的最终值(最终余额)不能为负,而在被动帐户上则不能为正。
转到文件夹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测试添加到测试模块 。 事务列表首先列出了所有可能的事务组合,并注释掉了没有引发异常的所有事务。 运行测试,并确保其余5个发布选项引发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的要求清单考虑系统开发的所有方面。