Comments 54
В статье речь скорее про дебет.
Кэш, это не только наличные, это все не кредитные средства клюоторыми вы можете распоряжаться. Налиные, это дословный перевод который не отражает реалное значение этого термина.
Правильно ли я понял работу с расчетными периодами?
Предположим, что после периода A наступил период B. Мы выводим остатки по каждому аккаунту в сash book в периоде A, а потом возвращаем остатки аккаунтов из сash book в периоде B.
В итоге каждый период можно рассматривать независимо от всех остальных периодов. Можно выкинуть из БД все периоды кроме текущего, и это никак не повлияет на балансы пользователей и выполнение правил общего нулевого баланса. Зато сильно упростит подсчеты — ибо транзакции копятся, и с миллионами архивных записей в БД подсчеты начинают уже подтормаживать.
Да, картинки были действительно перепутаны, поправил
Блокировать новые транзакции пока не будут перенесены все остатки на новый период? (Но тут много вопросов. Что делать если переход занимает неприемлемое время (SLA)? Как организовать блокировку при микросервисной архитектуре? И т.д)
Или обновлять каждый аккаунт при первой транзакции отдельно, а неактивные аккаунты в фоне?
У нас в системе около ста тысяч аккаунтов, операция подсчетов балансов по всем занимает пару минут. Это не так долго. В это время апи просто на все запросы о переводах отдает код ошибки «на сервере ведутся работы».
Нагрузка у нас невелика (где-то один запрос в две-три секунды), а ночью почти совсем сходит на нет, так что это все не проблема.
В супер-пупер хайлоаде, возможно, применяются какие-то другие подходы. Хотя я и у вполне серьезных банков встречал такие клиринговые периоды, когда выполнение операций приостанавливается на час по ночам.
— транзакиям по определенным правилам назначаются posting period id чтобы заранее определить к какому периоду они относятся. В зависимости от принятых правил, транзакция может быть отнесена к следующему периоду если даже она началась в текущем.
— перенос остатков осуществляется процедурой закрытия периода
Сущность Account имеет поле period
Баланс юзера счетается примерно так (пример абстрактный, не используйте в продакшн):
SELECT SUM(amount) as balance FROM Transaction WHERE account_id = account.id AND period <= account.period
Организовать блокировку можно средствами самой СУБД:
SELECT * FROM Account WHERE period < "current period" FOR UPDATE SKIP LOCKED LIMIT 10
Таким образом, условный мускул пикнет вам 10 аккаунтов и залочит их, так что параллельные транзакции не смогут получить доступ к этим записям. И перенос балансов можно повесить на крон, либо придумать что-то более эффективное, исходя из вашей конкретной ситауции. Почитайте про такие фичи как FOR UPDATE, SKIP LOCKED, NOWAIT
Можно сделать saldo(тут account, в АБС видел ledger) по дням(неделям/месяцам) и считать его накопительным итогом при этом для проведения платёжного документа (тут journal, но, вроде как обычно это payment_document) можно считать входящий остаток как сумму закрытого периода по saldo + записи из task (мне чаще попадалось названное как journal) за незакрытые периоды. Это может помочь решить вопросы с производительностью.
p.s. названия не привычны мне, привык к тому что journal — это полупроводки, ledger/saldo — остатки account — справочник счетов, payment_document -платёжный документ (может быть иерархическим, т.е. заменяет и batch из статьи)
В контексте данной статьи account переводится как счет
Кажется тут происходит попытка изобретения Event Source'инга. Не надо так (с) Не нужны вам никакие cash book если все движения денег описаны транзакциями (записями), а перекладывание денег со счета на счет это всегда две транзакции ссылающиеся на общий обьект "причина" или типа того.
Ну и главное тут на самом деле — не столько организовать транзакции (их можно было бы и без двойной записи легко сделать), сколько организовать контроль за их корректностью. А правила нулевых суммарных балансов — очень мощная штука, позволяющая легко искать ошибки и проверять валидность состояния системы. Очень, просто супер легко, одним запросом в духе «сложить все значения amount с timestamp в указанном периоде и сравнить с нулем».
У меня в последнее время большие сомнения, что физическая двойная запись оправдана в современных ИС. Таблиц транзакций и операций с ид транзакции, суммой, валютой, счётом дебета и счётом кредита вполне достаточно.
Любая транзакция — 1+N вставок только в две таблицы, где N — количество операций в транзакции, в простых случаях одна только. В целом можно обойтись даже одной вставкой в одну таблицу, если база поддерживает эффективную работу со сложными типами данных типа объектов и их массивов.
Двойная запись появилась во времена, когда агрегация операций по счетам была крайне неэффективной и подвержена большой вероятности ошибок, поэтому и требовалось обеспечить, с одной стороны, эффективное кэширование агрегации (карточка счёта), а, с другой, быструю (относительно) проверку целостности.
P. S. Архаичный перевод архаичного cash book — амбарная книга
При этом если речь идет о бухгалтерской системе — каждая такая ошибка имеет вполне конкретную цену в деньгах. Плюс если вы работаете там, где есть регулирование — любая ошибка обычно ведет к жалобе клиента в регулирующие органы и выписыванию вам офигительных штрафов.
Поэтому все и перестраховываются. Опять же повторю мнение из комментария выше — двойная запись нужна не для оптимальной организации хранения данных (для этого и простая запись подойдет), а для гарантий надежности и верифицируемости. Причем в такой записи эти задачи (та же проверка нулевого общего баланса) делаются тривиально, быстро и просто одним запросом к БД. Вот в чем основной профит такой архитектуры.
(c ) Добавим еще один аккаунт Паттел и переведем 100£ ему от Смита. Для этого нам понадобится создать дебет на эту сумму у Смита и кредит у Паттела.
Вроде бы Смит теряет деньги (передает со своего счета/аккаунта в счет/аккаунт Паттела) и сумму нужно писать в кредит, но нет, именно в этой операции все на оборот! Как нужно правильно мыслить, что бы понять в каком случае нужно писать в дебет, а в каком в кредит?
Но поскольку такая вот система предназначена для работы только с пассивными счетами, для них все просто. Кредит — то что дали (плюс), дебет — то что забрали (минус). Слово дебет вообще происходит от слова debt, долг.
Это надо просто запомнить.
Ну а дальше любая операция просто состоит из того, что мы берем деньги в одном месте (и записываем их туда с минусом, дебет), а в другое место их кладем — и туда записываем с плюсом, кредит. Если операция связана с движением денег между границами системы и окружающего мира — в качестве одного из счетов используем специальный cash book.
Если лезть в глубины бухучета — там вообще черт ногу сломит.Я думал, что я один считаю бух учет адом адским, ведь все говорят — «Это же так просто!». Для меня это всегда мУка, вообще не понимаю что происходит. Кроме того есть ведь не только активные и пассивные счета, в мире 1С к примеру есть еще активно-пассивные и парные счета =0 Я вообще не понимаю зачем было изобретать двойную запись, я просто не могу этого понять…
При одиночной записи невозможно понять, где была допущена ошибка — а писарь легко мог цифру перепутать. При двойной записи с использованием проверок на нулевые балансы легко найти ошибочный период и даже конкретную операцию, где дебет с кредитом не сходятся.
Дебет — это операция "+" (по аналогии с дебетовой картой — на ней должен быть положительный остаток, в минус как бы нельзя уйти), кредит — это операция "-" (на полноценной кредитке можно уходить в минус).
Активные счета — это то, что у нас есть и можно потрогать. Остатка по кредиту быть не может. Например, 50 счет — касса. Дебет — прибавляем, кредит — убавляем. Минус сколько-то денег в ней лежать не может — это верный признак ошибки бух. учета.
С пассивными счетами все наоборот. Они как бы со знаком минус. Это наши долги кому-то. Дебет увеличивает долг — т.е. минусует нас (плюс на минус дает минус), кредит — уменьшает долг, т.е. плюсует нас (минус на минус дает плюс). Соответственно, остаток может быть только по кредиту — мы либо чего-то должны, либо ничего. Дебетовый остаток — ошибка учета.
Все еще усложняется тем, что есть два вида счетов — активные и пассивные, для которых дебет с кредитом имеют противоположный смысл.
Более того, есть ещё и активно-пассивные счета, где сальдо может быть и дебетовое, и кредитовое. Например, расчеты с контрагентами :)
Там же два правила, квадрат 2х2 и "дебет слева, кредит справа", но я первый никак запомнить не могу :)
Из жизни, т.к. cr при сортировке идёт раньше db (debit) выгрузил как-то данные с обратным порядком записей, попросили больше так не делать, т.к. очень неудобно :)
"Алексей Виноградов
Азбука бухгалтерского учета. Что надо знать для работы с бухгалтерскими программами" Мне в свое время очень помогла эта книга, сразу говорю — она небольшая, но даже программист поймет основы.
Вопрос, а при изучении и создании такой сложной системы были готовые решения? Если были, то расскажите какие и почему отмели, например из-за стоимости или неподходящего функционала? спасибо
Ну мы за два месяца в ударном темпе его и нафигачили с нуля. Год уже работает, вроде полет нормальный.
Налоги и прочие сложности там не нужны были, чисто баланс кошельков пользователей.
Не могу понять пример с переводом 100 евро от Сёмы к Пете. Если Сёма вывел из системы 50 евро и они отразились в кассовой книге, то откуда у него взялось ещё 100 евро в дебете мимо кассовой книги? И как утверждение "-190£, отрицательная сумма балансов всех остальных аккаунтов" соотносится с требованием "Сумма всех значений во всей системе в любой момент времени должна давать ноль"?
Если Сёма вывел из системы 50 евро и они отразились в кассовой книге, то откуда у него взялось ещё 100 евро в дебете мимо кассовой книги?
Тут «вывел из системы» означает «обналичил», т.е. сначала положил на счёт 300, потом забрал 50, осталось 250. Из них перевёл 100, осталось 150, у Пети стало 100, Петя вывел 60, осталось 40.
И как утверждение "-190£, отрицательная сумма балансов всех остальных аккаунтов"
Мне кажется, это ошибка перевода, автор оригинала скорее всего там не минус, а тире написал. Баланс по кассе не -190, а 190. Активы по кассе 190, пассивы Сеня 150, Петя 40, итого тоже 190, баланс бьётся, главбух в экстазе.
Разобрались с коллегой :) В общем, меня запутало что:
Я представлял кассовую книгу как историю транзакций, кредит как карту с деньгами, а дебет как наличку. Чтобы перевести от Сёмы к Пете 100 евро надо было бы их вывести с карты Сёмы и внести на карту Пети (через наличку или прямым переводом). И соответственно я ожидал, что сумма всех операций в кассовой книге должна быть равна нулю.
По факту же правильно рассматривать кассовую книгу как денежное хранилище в банке, а Сёму и Петю как его клиентов, переводящих средства без процентов. С точки зрения денежного хранилища операция перевода сотки от Сёмы к Пете ни на что не повлияла. А вот трата Петей средств Сёмы уже отразилось на системе. Таким образом итоговый баланс Пети и Сёмы равен 190 евро, а в кассовой книге -190 евро, что даёт 0 при сложении.
А что по поводу процентов, которые себе забирает платёжный агрегатор при вводе и выводе средств из системы (cash book)? Как их правильно учитывать? Или если я хочу удерживать процент с транзакций, которые происходят в пределах системы?
Отличная статья и теория, и практика, и даже примерный проект реализации - много полезного. Согласен с автором, во всем том, что написано во введении, что в сети сложно найти информацию по вопросу. Либо ее мало, либо нужны особые поисковые запросы, чтобы найти. А если находишь, то она бывает весьма специфической и сложной для понимания. Здесь, например, в ответе есть фундаментальный подход вопросу https://stackoverflow.com/questions/59432964/relational-data-model-for-double-entry-accounting/59465148#59465148 но если не работаешь в бухгалтерской сфере или банковской, понять его сложно. По поводу вашей статьи, остался не раскрытым вопрос о том как должна на практике выглядеть batch таблица? Понятно, что каждый ее может сам организовать, но все-таки остальные компоненты полностью разложены, в том числе по таблицам базы данных, а batch осталось несколько неясным, что в нее писать на примере вашей статьи.
Да, не очень понятно зачем там эта batch таблица, мы в итоге без нее обходились. Так как записи и так группируются в бизнес-транзакции в таблице journal. Возможно это имеет смысл для упрощения ручного ввода большого объема данных в систему, когда в одной транзакции очень много записей, и лучше вводить их кусочками.
В posting таблице есть journal id, хотя на схеме posting идет первой. Posting -> Journal -> Batch. Journal как доп проверка для posting. Получается journal запись первой создается, из этой записи берется id и используется в posting?
Двойная бухгалтерская запись в реляционной БД