Transações, referências e como aplicar a contabilidade de dupla entrada? (PG)

8

A contabilidade de entrada dupla é

um conjunto de regras para o registro de informações financeiras em um sistema de contabilidade financeira no qual cada transação ou evento altera pelo menos duas contas contábeis nominais diferentes.

Uma conta pode ser "debitada" ou "creditada", e a soma de todos os créditos deve ser igual à soma de todos os débitos.

Como você implementaria isso em um banco de dados Postgres? Especificando o seguinte DDL:

CREATE TABLE accounts(
    account_id serial NOT NULL PRIMARY KEY,
    account_name varchar(64) NOT NULL
);


CREATE TABLE transactions(
    transaction_id serial NOT NULL PRIMARY KEY,
    transaction_date date NOT NULL
);


CREATE TABLE transactions_details(
    id serial8 NOT NULL PRIMARY KEY,
    transaction_id integer NOT NULL 
        REFERENCES transactions (transaction_id)
        ON UPDATE CASCADE
        ON DELETE CASCADE
        DEFERRABLE INITIALLY DEFERRED,
    account_id integer NOT NULL
        REFERENCES accounts (account_id)
        ON UPDATE CASCADE
        ON DELETE RESTRICT
        NOT DEFERRABLE INITIALLY IMMEDIATE,
    amount decimal(19,6) NOT NULL,
    flag varchar(1) NOT NULL CHECK (flag IN ('C','D'))
);

Nota: a tabela transaction_details não especifica uma conta explícita de débito / crédito, porque o sistema deve poder debitar / creditar mais de uma conta em uma única transação.

Essa DDL cria o seguinte requisito: Depois que uma transação do banco de dados é confirmada na tabela transaction_details, ela deve debitar e creditar o mesmo valor para cada uma transaction_id, por exemplo :

INSERT INTO accounts VALUES (100, 'Accounts receivable');
INSERT INTO accounts VALUES (200, 'Revenue');

INSERT INTO transactions VALUES (1, CURRENT_DATE);

-- The following must succeed
BEGIN;
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 100, '1000'::decimal, 'D');
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 200, '1000'::decimal, 'C');
COMMIT;


-- But this must raise some error
BEGIN;
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 100, '1000'::decimal, 'D');
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 200, '500'::decimal, 'C');
COMMIT;

É possível implementar isso em um banco de dados PostgreSQL? Sem especificar tabelas adicionais para armazenar estados de disparo.

Cochise Ruhulessin
fonte

Respostas:

5

Primeiro, essa é exatamente a pergunta que eu tinha em mente quando perguntei sobre restrições de modelagem em agregados de subconjuntos? que é certamente o lugar para começar. Embora essa pergunta seja mais geral do que essa, minha resposta aqui terá um pouco mais de informações sobre abordagens práticas.

Você provavelmente não deseja fazer isso declarativamente no PostgreSQL. As únicas soluções declarativas possíveis quebram 1NF ou são extremamente complicadas, o que significa que é imperativo.

No LedgerSMB , esperamos fazer essa imposição em duas etapas (ambas estritas).

  1. Todos os lançamentos contábeis manuais serão recebidos através de procedimentos armazenados. Esses procedimentos armazenados aceitam uma lista de itens de linha como uma matriz e verificam se a soma é igual a 0. Nosso modelo no banco de dados é que temos uma coluna de valor único com números negativos sendo débitos e números positivos sendo créditos (se eu fosse começando de novo, eu teria números positivos como débitos e números negativos como créditos, porque isso é um pouco mais natural, mas as razões aqui são obscuras). Débitos e créditos são mesclados no armazenamento e separados na recuperação pela camada de apresentação. Isso facilita muito a execução de totais.

  2. Usaremos um gatilho de restrição adiada que verificará a confirmação com base nos campos do sistema na tabela. Isso significa que as linhas inseridas em uma determinada transação devem ser equilibradas, mas podemos fazer isso além das próprias linhas.

Chris Travers
fonte
BTW, se você estiver fazendo muita contabilidade de dupla entrada, espera-se reprojetar nosso esquema financeiro (no postgreSQL) dentro do próximo ano. Não sei se você estaria interessado em colaborar com um projeto de código aberto, mas imaginei que faria um convite.
precisa
Estou atrasado para a festa, mas você poderia explicar - "verificará o commit com base nos campos do sistema na tabela"? Você está usando o campo do sistema xmin para descobrir quais linhas foram inseridas? Estou diante dessa situação exata e esse é o único segmento que está chegando perto de uma solução. No entanto, sou ignorante.
Código Poeta
Sim. Podemos observar as linhas criadas pela transação e insistir que a soma dos valores nelas seja 0. Isso significa, basicamente, verificar as colunas do sistema como xmin.
Chris Travers
4

Outra abordagem é adotar a posição de que é a transferência do valor financeiro que compreende um único registro.

Assim, você pode ter a estrutura:

create table ... (
  id                integer,
  debit_account_id  not null REFERENCES accounts (account_id),
  credit_account_id not null REFERENCES accounts (account_id),
  amount            numeric not null);

Uma restrição de cheque pode garantir que as contas de débito e crédito sejam diferentes e haja apenas um valor a ser armazenado. Portanto, há integridade garantida, que é o que o modelo de dados deve fornecer naturalmente.

Eu trabalhei com sistemas que adotaram essa abordagem com sucesso. Há um pouco menos de eficiência na consulta de registros em uma conta específica, mas a tabela era mais compacta e as consultas de uma conta apenas como débito ou crédito eram apenas um pouco mais eficientes.

David Aldridge
fonte