Transactions, références et comment faire respecter la comptabilité en partie double? (PG)

8

La comptabilité à double entrée est

un ensemble de règles d'enregistrement des informations financières dans un système de comptabilité financière dans lequel chaque transaction ou événement modifie au moins deux comptes différents du grand livre nominal.

Un compte peut être "débité" ou "crédité", et la somme de tous les crédits doit être égale à la somme de tous les débits.

Comment implémenteriez-vous cela dans une base de données Postgres? Spécification de la DDL suivante:

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'))
);

Remarque: la table transaction_details ne spécifie pas de compte de débit / crédit explicite, car le système doit pouvoir débiter / créditer plusieurs comptes en une seule transaction.

Cette DDL crée l'exigence suivante: Une fois qu'une transaction de base de données est validée sur la table transactions_details, elle doit débiter et créditer le même montant pour chacune transaction_id, par exemple :

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;

Est-il possible de l'implémenter dans une base de données PostgreSQL? Sans spécifier de tables supplémentaires pour stocker les états de déclenchement.

Cochise Ruhulessin
la source

Réponses:

5

Tout d'abord, c'est exactement la question que j'avais en tête lorsque j'ai posé la question des contraintes de modélisation sur les agrégats de sous-ensembles? qui est certainement le point de départ. Cette question est cependant plus générale que celle-ci et ma réponse contiendra donc un peu plus d'informations sur les approches pratiques.

Vous ne voulez probablement pas le faire de manière déclarative dans PostgreSQL. Les seules solutions déclaratives possibles cassent 1NF ou sont extrêmement compliquées et cela signifie donc de le faire impérativement.

Dans LedgerSMB, nous prévoyons de faire cette application en deux étapes (qui sont toutes deux strictes).

  1. Toutes les entrées de journal entreront via des procédures stockées. Ces procédures stockées accepteront une liste d'éléments de campagne sous forme de tableau et vérifieront que la somme est égale à 0. Notre modèle dans la base de données est que nous avons une colonne de montant unique avec des nombres négatifs étant des débits et des nombres positifs étant des crédits (si j'étais en recommençant, j'aurais des nombres positifs comme débits et des nombres négatifs comme crédits parce que c'est juste un peu plus naturel mais les raisons ici sont obscures). Les débits et les crédits sont fusionnés sur le stockage et séparés lors de la récupération par la couche de présentation. Cela rend l'exécution des totaux beaucoup plus facile.

  2. Nous utiliserons un déclencheur de contrainte différée qui vérifiera la validation en fonction des champs système de la table. Cela signifie que les lignes entrées dans une transaction donnée doivent être équilibrées, mais nous pouvons le faire au-delà des lignes elles-mêmes.

Chris Travers
la source
BTW, si vous faites beaucoup de comptabilité à double entrée, nous prévoyons de réorganiser notre schéma financier (sur postgreSQL) au cours de la prochaine année. Je ne sais pas si vous seriez intéressé à collaborer avec un projet open source mais j'ai pensé que j'allais lancer une invitation.
Chris Travers
Je suis en retard à la fête, mais pourriez-vous expliquer - "vérifiera la validation en fonction des champs système sur la table"? Utilisez-vous le champ système xmin pour savoir quelles lignes ont été insérées? Je suis confronté à cette situation exacte et c'est le seul fil qui approche d'une solution. Cependant, je suis ignorant.
Code Poet
Ouais. Nous pouvons regarder les lignes créées par la transaction et insister pour que la somme des montants qu'elles contiennent soit 0. Cela signifie, fondamentalement, vérifier les colonnes du système comme xmin.
Chris Travers
4

Une autre approche consiste à adopter la position selon laquelle c'est le transfert du montant financier qui comprend un seul enregistrement.

Ainsi, vous pourriez avoir la structure:

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);

Une contrainte de vérification peut garantir que les comptes de débit et de crédit sont différents et qu'il n'y a qu'un seul montant à stocker. Ainsi, l'intégrité est garantie, ce que le modèle de données devrait fournir naturellement.

J'ai travaillé avec des systèmes qui ont adopté cette approche avec succès. Il y a un peu moins d'efficacité à interroger des enregistrements sur un compte particulier, mais le tableau était plus compact et les requêtes pour un compte car le débit uniquement ou le crédit uniquement étaient un peu plus efficaces.

David Aldridge
la source