De um tradutor : durante meu trabalho na fintech nigeriana , tive que criar um sistema de pagamento do zero. Naquela época, eu realmente não entendia nada de contabilidade, como é melhor armazenar pagamentos e saldos. Mas havia uma suspeita de que a opção primitiva com um dígito de saldo na conta do usuário é muito simples para ser correta.
Este artigo me ajudou a entender e evitar um monte de desvios nesse assunto. Ao mesmo tempo, as informações sobre o tópico "como criar seu próprio sistema de pagamento" são muito pequenas e não é tão fácil para um programador entender livros sobre contabilidade (e muito entediantes). Espero que este material seja útil para aqueles que apenas farão algo assim.
Peço desculpas imediatamente por possíveis imprecisões em termos financeiros de língua russa - ainda sou programador, não contador, e não estou familiarizado com a terminologia russa nesta área.
1. Introdução
Muitos sistemas de computador que usam bancos de dados relacionais armazenam algum tipo de informação financeira sobre saldos e transações. Além disso, o design e desenvolvimento de um banco de dados desse tipo geralmente levanta a questão de como armazenar essas informações. Geralmente, a escolha é entre uma "gravação simples" barata e uma "gravação dupla" mais complexa.

Luca Pacioli, autor do livro sobrevivente mais antigo (século XV) que descreve os princípios da dupla entrada
Em um sistema de “registro simples”, os valores numéricos são registrados apenas uma vez. Em um sistema de entrada dupla, cada valor é registrado duas vezes como crédito (valor positivo) e como débito (valor negativo). Há um conjunto de regras que determinam o relacionamento entre esses valores. Essas regras serão descritas facilmente a você por qualquer contador experiente, embora ele nem possa imaginar como elas podem ser representadas em um banco de dados relacional.
As regras básicas são as seguintes:
- Cada entrada no sistema deve ser equilibrada, ou seja, a soma de todos os valores em uma operação deve dar zero.
- A soma de todos os valores em todo o sistema em um dado momento deve dar zero (a regra do chamado "balancete").
- Os valores já inseridos no banco de dados não podem ser editados ou excluídos. Se forem necessárias correções, a operação deve primeiro ser cancelada por outra operação com o sinal oposto e depois repetida com o valor correto. Isso permite implementar uma trilha de auditoria confiável (um registro completo de todas as transações, geralmente necessário durante as inspeções).
Aplicabilidade da contabilidade dupla
No início de um projeto, o baixo preço de uma gravação simples é sempre tentador, e o custo da implementação e a complexidade de uma gravação dupla completa parecem desnecessários. No entanto, na realidade, geralmente usar uma gravação simples é uma falsa economia.
Se as informações contábeis no sistema de TI apenas copiarem os registros em papel existentes armazenados fora do banco de dados em desenvolvimento, um registro simples ainda terá direito à vida. No entanto, se pelo menos um dos fatos sobre o sistema listado abaixo for verdadeiro, a entrada dupla deve ser usada desde o início:
- Se alguma vez for necessária uma auditoria contábil das informações
- Se as informações no sistema forem a única fonte de informações sobre a propriedade
- Se a informação diz respeito a objetos de alto valor
- Se o sistema for planejado para ser seriamente desenvolvido no futuro
Exemplo de entrada dupla
A idéia principal da entrada dupla é a existência de uma conta especial "livro de caixa" ( aproximadamente tradução: não achei como chamá-la adequadamente em russo, alguém pode me dizer? ). Essa conta contém registros feitos quando objetos de valor (como dinheiro) são depositados ou retirados de nosso sistema contábil. Assim, o saldo atual desta conta reflete o número total de valores no sistema.
A seguir, é apresentado um exemplo simples com duas contas, "caixa" e "Smith".
(a) £ 300 são inseridos no sistema e depositados na conta de Smith. Um empréstimo de £ 300 é criado na conta de Smith (crédito à direita, débito à esquerda). Para nivelar esse valor, um débito de £ 300 é criado na conta do livro caixa.

(b) Smith deduz 50 libras do sistema. Criamos um débito para esse valor na conta de Smith e um crédito no livro caixa.

(c) Adicione outra conta Pattel e transfira 100 £ para ele de Smith. Para fazer isso, precisamos criar um débito nesse valor com Smith e um empréstimo com Pattel.
(d) Como toque final, deixe agora a Pattel retirar £ 60 do sistema. Criamos um débito em sua conta e um crédito no livro Caixa.

Como resultado de todas essas operações, podemos calcular que o saldo total da Smith é de 150 £, Pattela 40 £ e, no Cash Book -190 £, a soma negativa dos saldos de todas as outras contas. Com base nessas regras e operações simples no futuro, você pode criar um sistema de controle de valor muito abrangente.
Modelo de dados
A estrutura de um modelo de dados simples que pode ser usado para representar todas essas informações:

A tabela POSTING contém as próprias entradas duplas. Armazenar todos os números em uma tabela simplifica muito todos os cálculos. Um contador monotonicamente crescente deve ser usado como chave primária. Os valores devem ser seguidos, nesse caso, pelo número em que você sempre pode garantir que nenhum registro tenha sido excluído. As tabelas BATCH e JOURNAL são usadas para controlar e inserir dados nessa tabela POSTING.
Cada entrada na tabela JOURNAL representa uma transação (de uma perspectiva comercial) que gera entradas duplas. Essa transação é uma unidade de trabalho concluída ou um processo de negócios. Todos os registros POSTING associados ao registro JOURNAL devem ser concluídos com êxito ou nenhum deles. A soma de todos os registros POSTING em uma única transação deve ser zero. Cada operação de transferência do exemplo acima é representada por sua entrada na tabela JOURNAL
A entrada na tabela BATCH é feita para facilitar a entrada de dados. É usado para agrupar registros JOURNAL em pacotes convenientes, por exemplo, um conjunto de verificações para entrar no sistema, algum tipo de processo comercial global, como cobrar juros a todos os usuários de uma só vez, etc.
A tabela CONTA armazena dados sobre os proprietários dos valores no sistema.
A tabela ASSET TYPE contém informações sobre os tipos de valores usados no sistema. Ao adicionar um tipo de valor à chave primária da tabela POSTING, você pode criar um sistema que opere em vários tipos de valores de uma só vez (por exemplo, processando várias moedas).
Aqui está como esse banco de dados pode procurar o exemplo acima da forma mais simplificada:

O saldo da coluna Valor na tabela POSTING é sempre zero após a conclusão de qualquer transação do JOURNAL (o software deve garantir que não haja registros de transações incompletas no banco de dados).
A soma das operações da conta Cash Book dá -190, que é igual à soma dos saldos de Smith e Pattel com o sinal oposto.
Para demonstrar a operação com várias moedas, um novo tipo de valor foi adicionado. Se Smith quiser trocar 20 libras por dólares à taxa de 1 por 1,5, a transação será realizada através do Cash Book da seguinte maneira:

Períodos de cobrança
O modelo que obtivemos parece ótimo, mas, na realidade, ele será quebrado muito rapidamente sob carga alta, devido ao fato de que não podemos excluir nada e somos forçados a recontar constantemente o número cada vez maior de registros no POSTING.
A maioria dos sistemas de contabilidade tem o conceito de um período de cobrança - geralmente um mês, três meses ou um ano. Esse período sugere pontos convenientes para dividir o fluxo de dados. Normalmente, um ponto conveniente é o final do ano, calendário ou financeiro.
Podemos adicionar uma coluna com um indicador de período à tabela POSTING e à sua chave primária, dividindo os dados em grupos que podem ser processados independentemente. Se no exemplo acima alguns dos registros caírem em um novo período de faturamento, os saldos das contas serão transportados da seguinte maneira.
Primeiro, os saldos do período anterior seriam apurados.

E então eles seriam transferidos para um novo período

Após um certo tempo, todos os registros do período do ANO 1 podem ser enviados para o arquivo morto e excluídos do sistema sem perder sua integridade.
Agregação de transação
Algumas operações no sistema de contabilidade podem afetar muitos ou mesmo todos os usuários de uma só vez. Por exemplo, pagamentos de juros a todos os usuários na forma de uma parcela do saldo atual.
Essas operações podem ser processadas como parte de uma única transação na tabela JOURNAL e você pode agregar todas as operações com o Cash Book em um registro comum na tabela POSTING (em vez de criar uma operação separada para cada conta). Isso permitirá que você cumpra todas as regras contábeis acima e reduza pela metade o número de registros no banco de dados. Usando essa abordagem, o final do ano no banco de dados será parecido com o seguinte:

Processamento em lote
O processamento em lote é frequentemente usado para simplificar a entrada de dados no sistema contábil.
Historicamente, o processamento de cheques funcionou assim. O contador recebeu um pacote de dez cheques, o número do pacote e o valor total de todos os cheques. No primeiro estágio, as verificações são inseridas no sistema na forma de entradas "não autorizadas". Além disso, através da tabela BATCH, sua quantidade e quantidade total são verificadas, e somente se corresponderem ao valor correto é que o usuário poderá confirmar o pacote. Depois disso, o pacote é enviado a outro funcionário que verifica a validade e "autoriza" se tudo for inserido corretamente.
Esse processo é chamado de "fabricante / verificador" e pode ser usado para inserir quaisquer dados relevantes no sistema.
Nesse caso, os registros "não autorizados" em uma tabela separada do conjunto principal de registros duplos na tabela POSTING estarão corretos. Você também pode ter várias dessas tabelas para diferentes processos de negócios. Por exemplo, no caso de cheques através dos quais o dinheiro é inserido ou retirado do sistema, o contador precisa apenas verificar uma conta. Desde o segundo, Cash Book, em tais operações é sempre implícito. Nesse caso, na tabela CHECK, será possível gerenciar apenas uma coluna com a conta, enquanto na tabela hipotética FUND TRANSFER serão necessárias duas colunas: "remetente" e "destinatário".
É aqui que surge o mal-entendido básico dos princípios da dupla gravação. A maioria das pessoas na vida cotidiana se depara apenas com simples livros contábeis em papel. Nesse livro em papel, por exemplo, para contabilizar as finanças de um determinado clube de interesse, você precisa de apenas uma entrada para cada operação. No entanto, ele ainda possui uma dupla entrada implícita, pois sempre existe uma conta implícita no Cash Book (nesse caso, este é o clube), porque todos os fluxos de caixa sempre são inseridos (pagamento de taxas pelos participantes) ou retirada de dinheiro do sistema (gastos clube).
A segunda razão para conceitos errôneos é que, nos extratos de conta pessoal, o dinheiro depositado na conta será considerado um "empréstimo", porque uma pessoa empresta essencialmente a um banco que recebe seu dinheiro. Embora se essa pessoa mantivesse seu livro contábil, essa entrada teria sido registrada como "débito" - uma vez que o banco deve esse dinheiro ao seu cliente. Esse dinheiro é retirado do "sistema de pagamento" do usuário e inserido no sistema bancário.
Arquitetura de Software
O software que implementa esse sistema de contabilidade de entrada dupla é melhor desenvolvido usando OOP e uma abordagem em camadas. Os níveis são os seguintes:
- Interface externa
- Lógica de negócios
- Trabalhar com um banco de dados
Obviamente, a arquitetura do sistema dependerá do que exatamente esse sistema deve fazer; no entanto, podemos assumir a presença dos seguintes módulos:
PostEntry: um módulo que controla a criação de entradas duplas na tabela POSTING. Ele é responsável por inserir registros, atribuir IDs e carimbos de data e hora. O módulo não pode excluir ou modificar registros e nenhum outro módulo deve excluir ou modificar esses registros, exceto a possível exclusão de registros antigos arquivados por períodos de cobrança já irrelevantes. A tabela POSTING deve ser somente leitura para todos os outros módulos.
MakeDeposit, MakeWithdrawal, MakeTransfer: esses módulos implementam a lógica comercial básica para operações de transferência de fundos. Eles usarão o módulo PostEntry para inserir seus resultados no banco de dados.
ChequeEntry e ChequeAuthorisation, ReceiveBACS ( nota: BACS é um sistema de pagamento interbancário ): esses módulos conectarão o sistema ao mundo externo e fornecerão uma interface de alto nível. Eles usarão os módulos da camada de negócios para desempenhar suas funções. Nesse caso, você pode garantir o processamento correto, independentemente do método de entrada de dados, pois o ChequeEntry e o ReceiveBACS funcionarão através do mesmo MakeDeposit
Essa metodologia para separar camadas pode ser aplicada em maior ou menor grau, dependendo da complexidade do sistema e da pureza desejada do uso dos princípios do design de objetos. Ao mesmo tempo, pode fazer sentido, por exemplo, permitir que o módulo de geração de relatórios (por exemplo, TestTrialBalance) acesse diretamente o banco de dados a partir do nível da interface - em vez de criar módulos intermediários nas camadas de negócios e banco de dados.

Balancete
"Balancete" - a principal maneira de verificar a integridade do sistema contábil. Se todas as entradas foram inseridas no sistema de acordo com as regras de entrada dupla e não houve erros, a soma de todas as entradas deve ser zero. A probabilidade de vários erros separados serem somados e fornecer um total de zero em uma base inválida é geralmente tão pequena que é negligenciada.
A melhor maneira de verificar é um movimento consistente do nível superior para o inferior. As verificações fazem sentido nesta ordem:
- A soma de todos os valores na coluna POSTING.Amount
Se um erro for encontrado (o valor não é zero), então: - A soma de todos os valores POSTING.Amount, mas calculados separadamente para diferentes tipos de valores e períodos de liquidação
Nesse estágio, deve ficar mais claro em qual parte do sistema ocorreu um erro. - Verificando operações individuais na tabela JOURNAL. Como a soma de todos os POSTING.Account em cada transação da tabela JOURNAL também deve dar zero, é possível rastrear a transação problemática específica.
Tipos de postagem JOURNAL
A tabela JOURNAL contém uma representação simples de entidades, que, no entanto, muitas vezes provam ser de fato mais complexas e envolvidas em vários relacionamentos.
Às vezes, faz sentido dividir uma tabela em várias. Por exemplo, em MATERIALIZED e DEMATERIALIZED, que podem ter um conjunto diferente de colunas, por exemplo, entidades de material podem exigir dados em sua localização atual.
Ou, em uma tabela, diferentes subtipos de valores, como moeda ou títulos, podem ser armazenados, cada subtipo pode ter seu próprio conjunto de propriedades e atributos.
As entidades com subtipos e supertipos podem ser organizadas no banco de dados de uma de quatro maneiras (esta é uma situação bastante padrão para qualquer banco de dados):
- Uma tabela grande comum com muitas colunas opcionais para atributos de subtipo
- Tabela separada para cada subtipo, com duplicação de todas as colunas comuns
- Entidades separadas para que o supertipo seja armazenado em uma tabela separada e se junte a outras tabelas que contêm apenas colunas específicas a subtipos
- O mesmo que em 3, mas com duplicação de colunas de supertipo em tabelas de subtipo
Cada uma das quatro opções tem seus prós e contras. Do ponto de vista da dupla entrada, é útil ter uma tabela comum para as entradas POSTING. A opção 1 é mais adequada para um sistema contábil simples (como nos exemplos deste artigo, onde a única diferença nos tipos de valores é determinada pela coluna JOURNAL.Type). A opção 3 provavelmente é mais adequada para sistemas complexos que trabalham com uma ampla gama de valores muito diferentes.