Хабрахабр

[Перевод] Двойная бухгалтерская запись в реляционной БД

Я тогда ничего толком не понимал в вопросах бухгалтерии, в том как именно лучше хранить платежи и балансы. От переводчика: в ходе моей работы в нигерийском финтехе пришлось мне создавать с нуля одну платежную систему. Но было подозрение, что примитивный вариант с одной циферкой баланса в аккаунте пользователя слишком прост чтобы быть правильным.

При этом информации по теме "как сделать свою платежную систему" довольно мало, а в учебниках по бухучету программисту сходу разобраться не так просто (и очень нудно). Разобраться и избежать кучи граблей в этом деле мне помогла данная статья. Надеюсь, этот материал окажется полезным тем, кто только собирается что-то такое делать.

Сразу извиняюсь за возможные неточности в русскоязычных финансовых терминах — я все-таки программист, а не бухгалтер, и с русской терминологией в этой сфере недостаточно знаком.

Введение

При этом при проектировании и разработке такой БД часто встает вопрос, а как именно хранить эту информацию. Многие компьютерные системы, использующие реляционные БД, хранят в них какую-то финансовую информацию о балансах и транзакциях. Обычно выбор стоит между дешевой "простой записью" и более сложной "двойной записью".


Лука Пачоли, автор самой старой (15 век) дошедшей до нас книги с описанием принципов двойной записи

В системе с "двойной записью" каждое значение записывается дважды, как кредит (положительное значение) и как дебет (отрицательное значение). В системе с "простой записью" числовые значения записываются только один раз. Эти правила вам легко опишет любой опытный бухгалтер, хотя он может и не представлять, как именно они могут быть представлены в реляционной БД. При этом есть набор правил, определяющих связь между этими значениями.

Основные правила таковы:

  1. Каждая запись в системе должна быть сбалансированной, т.е. сумма всех значений в рамках одной операции должна давать ноль.
  2. Сумма всех значений во всей системе в любой момент времени должна давать ноль (правило т.н. "пробного баланса").
  3. Уже занесенные в БД значения нельзя редактировать или удалять. При необходимости исправлений операция сперва должна быть отменена другой операцией с противоположным знаком, а затем повторена с правильным значением. Это позволяет реализовать надежный аудиторский след (полный лог всех транзакций, часто требуемый при проверках).

Применимость двойной бухгалтерии

Однако в действительности часто использование простой записи оказывается ложной экономией. На старте проекта низкая цена простой записи всегда искушает, а стоимость реализации и сложность полноценной двойной записи кажется излишней.

Однако если хотя бы один из перечисленных ниже фактов о системе правдив, то с самого начала стоит использовать двойную запись: Если бухгалтерская информация в ИТ системе лишь копирует существующую бумажную отчетность, хранящуюся вне разрабатываемой БД, тогда простая запись еще имеет право на жизнь.

  1. Если когда-либо потребуется бухгалтерский аудит информации
  2. Если информация в системе — единственный источник сведений о собственности
  3. Если информация касается объектов, имеющих высокую ценность
  4. Если систему планируется серьезно развивать в дальнейшем

Пример двойной записи

перев.: не нашел как это адекватно назвать по-русски, может кто подскажет?). Ключевой идеей двойной записи является существование особого "cash book" аккаунта (прим. Таким образом, текущий баланс этого аккаунта отражает общее количество ценностей в системе. Этот аккаунт содержит записи, сделанные когда ценности (например, деньги) вносятся или выводятся из нашей бухгалтерской системы.

Дальше показан простой пример с двумя аккаунтами, "cash book" и "Смит".

Создается кредит на £300 в аккаунте Смита (кредит справа, дебет слева). (а) £300 вводится в систему и кладется на счет Смита. Чтобы уровнять эту сумму, создается дебет на £300 в аккаунте cash book.

Создаем дебет на эту сумму в аккаунте Смита и кредит в cash book. (b) Затем Смит выводит £50 из системы.

Для этого нам понадобится создать дебет на эту сумму у Смита и кредит у Паттела. (c ) Добавим еще один аккаунт Паттел и переведем 100£ ему от Смита.

Мы создаем дебет в его аккаунте и кредит в Cash book. (d) В качестве завершающего штриха пусть теперь Паттел выведет из системы 60£.

Основываясь на этих простых правилах и операциях в дальнейшем можно построить очень комплексные системы контроля ценностями. В результате всех этих операций мы можем подсчитать, что итоговый баланс Смита 150£, Паттела 40£, а в Cash Book -190£, отрицательная сумма балансов всех остальных аккаунтов.

Модель данных

Структура простой модели данных, которая может использоваться для представления всей этой информации:

Хранение всех цифр в одной таблице сильно упрощает все вычисления. Таблица POSTING содержит сами двойные записи. Значения при этом должны идти подряд, в таком случае по номеру всегда можно будет убедиться, что ни одна запись не была удалена. В качестве первичного ключа стоит использовать монотонно возрастающий счетчик. Таблицы BATCH и JOURNAL используются для контроля и ввода данных в эту таблицу POSTING.

Такая транзакция — это завершенная единица работы или какого-либо бизнес-процесса. Каждая запись в таблице JOURNAL представляет транзакцию (с точки зрения бизнеса), которая генерирует двойные записи. Сумма всех POSTING записей в рамках одной транзакции должна давать ноль. Либо все POSTING записи, ассоциированные с JOURNAL записью должны быть успешно завершены, либо ни одна из них. Каждая операция по переводу средств из примера выше представляется своей записью в таблице JOURNAL

Она используется для группировки записей JOURNAL в удобные пакеты, например набор чеков для ввода в систему, какой-то глобальный бизнес-процесс вроде начисления процентов сразу всем пользователям и т.п. Запись в таблице BATCH сделана для удобства ввода данных.

Таблица ACCOUNT хранит данные о владельцах ценностей в системе.

Добавив тип ценности в первичный ключ таблицы POSTING можно сделать систему, оперирующую сразу несколькими видами ценностей (например, обработку нескольких валют). Таблица ASSET TYPE содержит информацию о типах ценностей, использующихся в системе.

Вот как может выглядеть такая БД для примера выше в максимально упрощенном виде:

Баланс столбца Amount в таблице POSTING всегда равен нулю после завершения любой транзакции из JOURNAL (ПО должно гарантировать отсутствие записи незавершенных транзакций в БД).

Сумма операций для аккаунта Cash Book дает -190, что равно сумме балансов Смита и Паттела с обратным знаком.

Если Смит хочет поменять 20 фунтов на доллары по курсу 1 за 1. Для демонстрации работы с многовалютностью был добавлен новый вид ценности. 5, транзакция будет проведена через Cash Book таким образом:

Расчетные периоды

Модель, которую мы получили, отлично выглядит, но в реальности очень быстро сломается под высокой нагрузкой из-за того, что мы не можем ничего удалять и вынуждены постоянно пересчитывать все возрастающее количество записей в POSTING.

Такой период подсказывает удобные моменты для разделения потока данных. Большинство систем бухучета имеют понятие расчетного периода — обычно это месяц, три месяца или год. Обычно удобной точкой является конец года, календарного или финансового.

Если бы в примере выше часть записей попала на новый расчетный период, балансы аккаунтов были бы перенесены следующим образом. Мы можем добавить столбец с индикатором периода к таблице POSTING и к ее первичному ключу, разбив данные на группы, которые могут обрабатываться независимо.

Сперва были бы очищены балансы предыдущего периода.

image

А затем они были бы перенесены в новый период

После определенного времени все записи периода YEAR 1 могут быть отправлены в архив и удалены из системы без потери ее целостности.

Агрегирование транзакций

Например, выплата процентов всем пользователям в виде доли от их текущего баланса. Некоторые операции в бухгалтерской системе могут затрагивать сразу многих, или вообще всех пользователей.

Это позволит соблюсти все перечисленные выше правила бухгалтерского учета и при этом сократить в два раза количество записей в БД. Такие операции могут быть обработаны в рамках одной транзакции в таблице JOURNAL и можно агрегировать все операции с Cash Book в одну общую запись в таблице POSTING (вместо создания отдельной операции для каждого аккаунта). При использовании такого подхода конец года в БД будет выглядеть так:

Пакетная обработка

Пакетная обработка часто используется для упрощения ввода данных в бухгалтерскую систему.

Бухгалтеру выдавалась пачка из десяти чеков, номер пачки и общая сумма всех чеков. Исторически так работала обработка чеков. При этом через таблицу BATCH проверяется их количество и общая сумма, и только если они совпадают с правильным значением пользователю позволяется закоммитить пачку. На первом этапе чеки вносятся в систему в виде "неавторизованных" записей. После того как это сделано, пачка отправляется другому сотруднику, который проверяет ее на валидность и затем "авторизует" если все введено верно.

Такой процесс называется "maker/checker" и может использоваться для ввода любых значимых данных в систему.

Также можно иметь целый ряд таких таблиц для разных бизнес-процессов. Правильным при этом будет хранить "неавторизованные" записи в отдельной таблице от основного набора двойных записей в таблице POSTING. Так как второй, Cash Book, в таких операциях всегда подразумевается неявно. Например, в случае с чеками, через которые осуществляется ввод или вывод денег из системы, бухгалтеру надо будет проверить только один аккаунт. В таком случае в таблице CHEQUE можно будет обойтись только одним столбцом с аккаунтом, в то время как в гипотетической таблице FUND TRANSFER нужно будет два столбца: "отправитель" и "получатель".

Большинство людей в обычной жизни встречают только простые бумажные книги учета. Именно тут возникает основное непонимание принципов двойной записи. Однако в ней по-прежнему есть неявная двойная запись, так как всегда неявно присутствует Cash Book аккаунт (в данном случае — этот самый клуб), ведь все движения средств это всегда или ввод (оплата взносов участниками), или вывод денег из системы (траты клуба). В такой бумажной книге, например для учета финансов какого-то клуба по интересам, нужна только одна запись для каждой операции.

Хотя если бы этот человек вел свою бухгалтерскую книгу, эта запись была бы в ней учтена как "дебет" — так как банк должен эти деньги своему клиенту. Вторая причина заблуждений — то что в личных выписках по счету деньги, положенные на счет будут считаться "кредитом", потому что человек по сути кредитует банк, который получает его деньги. Эти деньги выводятся из "платежной системы" пользователя и вводятся в систему банка.

Архитектура программной части

Уровни получаются такими: ПО, реализующее такую бухгалтерскую систему с двойной записью, лучше всего разрабатывать с помощью ООП и многоуровневого подхода.

  1. Внешний интерфейс
  2. Бизнес-логика
  3. Работа с БД

Конечно, архитектура системы будет зависеть от того, что именно эта система должна делать, однако можно предположить наличие в ней следующих модулей:

Он отвечает за вставку записей, присвоение ID и таймстампов. PostEntry: модуль, который управляет созданием двойных записей в таблице POSTING. Таблица POSTING должна быть доступна только на чтение для всех других модулей. Модуль не может удалять или изменять записи и никакой другой модуль не должен удалять или изменять эти записи, за исключением возможного удаления старых архивированных записей для уже неактуальных расчетных периодов.

Они будут использовать модуль PostEntry для занесения своих результатов в БД MakeDeposit, MakeWithdrawal, MakeTransfer: эти модули реализуют базовую бизнес-логику для операций переводов средств.

Они будут использовать модули бизнес-слоя для выполнения своих функций. ChequeEntry и ChequeAuthorisation, ReceiveBACS (прим перев: BACS — система межбанковских платежей): эти модули будут связывать систему с внешним миром и предоставят высокоуровневый интерфейс. В таком случае можно гарантировать правильность процессинга вне зависимости от метода ввода данных, так как и ChequeEntry и ReceiveBACS будут работать через тот же самый MakeDeposit

При этом может иметь смысл, например, разрешить модулю генерации отчетов (например TestTrialBalance) прямой доступ к БД с уровня интерфейса — вместо того чтобы создавать промежуточные модули на бизнес и БД слоях. Эту методологию разделения слоев можно применять в большей или меньшей степени, в зависимости от сложности системы и желаемой чистоты использования принципов объектного дизайна.

Пробный баланс

Если все записи вносились в систему по правилам двойной записи и не было никаких ошибок, в таком случае сумма всех записей должна давать ноль. "Пробный баланс" — главный способ проверки целостности бухгалтерской системы. Вероятность того, что несколько отдельных ошибок сложатся в и сумме дадут ноль на невалидной базе обычно настолько мала, что ей пренебрегают.

Проверки имеет смысл выполнять в таком порядке: Лучшим способом проверки является последовательное движение от верхнего уровня к нижнему.

  1. Сумма всех значений в столбце POSTING.Amount
    Если найдена ошибка (значение не ноль), то:
  2. Сумма всех значений POSTING.Amount, но отдельно посчитанная для разных типов ценностей и расчетных периодов
    На этом этапе должно стать яснее, в какой части системы произошла ошибка.
  3. Проверка отдельных операций в таблице JOURNAL. Поскольку сумма всех POSTING.Account в каждой транзакции из таблицы JOURNAL должна тоже давать ноль, дальше можно отследить конкретную проблемную транзакцию.

Типы записей в JOURNAL

Таблица JOURNAL содержит простое представление сущностей, которые однако часто оказываются на деле более сложными и вовлеченными в различные отношения.

Например на MATERIALISED и DEMATERIALISED, которые могут иметь различный набор столбцов, например для материальных сущностей могут потребоваться данные об их текущем местоположении. Иногда имеет смысл разбить одну таблицу на несколько.

Или же в одной таблице могут храниться различные подтипы ценностей, такие как валюта или ценные бумаги, у каждого подтипа при этом может быть свой набор свойств и атрибутов.

Сущности, имеющие как под- так и надтипы могут организовываться в БД одним из четырех способов (это довольно стандартная ситуация для любой БД):

  1. Одна общая большая таблица с множеством необязательных столбцов для атрибутов подтипов
  2. Отдельная таблица для каждого подтипа, с дублированием всех общих столбцов
  3. Разделение сущностей таким образом, чтобы надтип хранился в отдельной таблице и джоинился с другими таблицами, содержащими только специфичные для подтипов столбцы
  4. Так же как в 3, но с дублированием столбцов супертипа в таблицах подтипов

С точки зрения двойной записи полезно иметь общую таблицу для POSTING записей. У каждого из четырех вариантов есть свои плюсы и минусы. Type). Вариант 1 лучше подходит для простой бухгалтерской системы (как в примерах в данной статье, где единственное отличие в типах ценностей определяется столбцом JOURNAL. Вариант 3, вероятно, лучше подходит для сложных систем, работающих с широким спектром сильно отличающихся ценностей.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»