¿Transacciones, referencias y cómo hacer cumplir la contabilidad de doble entrada? (PG)

8

La contabilidad de doble entrada es

un conjunto de reglas para registrar información financiera en un sistema de contabilidad financiera en el que cada transacción o evento cambia al menos dos cuentas contables nominales diferentes.

Una cuenta puede ser "debitada" o "acreditada", y la suma de todos los créditos debe ser igual a la suma de todos los débitos.

¿Cómo implementaría esto en una base de datos Postgres? Especificando el siguiente 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: la tabla transacción_detalles no especifica una cuenta explícita de débito / crédito, porque el sistema debería poder debitar / acreditar más de una cuenta en una sola transacción.

Este DDL crea el siguiente requisito: después de que una transacción de base de datos se confirma en la tabla transacciones_detalles, debe debitar y acreditar la misma cantidad para cada uno transaction_id, por ejemplo :

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;

¿Es posible implementar esto en una base de datos PostgreSQL? Sin especificar tablas adicionales para almacenar estados de activación.

Cochise Ruhulessin
fuente

Respuestas:

5

Primero, esta es exactamente la pregunta que tenía en mente cuando pregunté ¿ Restricciones de modelado en agregados de subconjuntos? que sin duda es el lugar para comenzar. Sin embargo, esa pregunta es más general que esta, por lo que mi respuesta aquí tendrá un poco más de información sobre los enfoques prácticos.

Probablemente no desee hacer esto declarativamente en PostgreSQL. Las únicas soluciones declarativas posibles rompen 1NF o son extremadamente complicadas, por lo que esto significa hacerlo imperativamente.

En LedgerSMB esperamos hacer esta aplicación en dos etapas (ambas estrictas).

  1. Todas las entradas del diario se ingresarán mediante procedimientos almacenados. Estos procedimientos almacenados aceptarán una lista de elementos de línea como una matriz y comprobarán que la suma es igual a 0. Nuestro modelo en el DB es que tenemos una columna de cantidad única con números negativos como débitos y números positivos como créditos (si yo fuera comenzando de nuevo, tendría números positivos como débitos y números negativos como créditos porque esto es un poco más natural, pero las razones aquí son oscuras). Los débitos y créditos se fusionan en el almacenamiento y se separan en la recuperación por la capa de presentación. Esto hace que los totales acumulados sean mucho más fáciles.

  2. Utilizaremos un activador de restricción diferido que verificará la confirmación en función de los campos del sistema en la tabla. Esto significa que las líneas ingresadas en una transacción dada deben equilibrarse, pero podemos hacer esto más allá de las líneas mismas.

Chris Travers
fuente
Por cierto, si está haciendo una gran cantidad de contabilidad de doble entrada, esperamos rediseñar nuestro esquema financiero (en postgreSQL) dentro del próximo año más o menos. No sé si estaría interesado en colaborar con un proyecto de código abierto, pero pensé que podría extender una invitación.
Chris Travers
Llego tarde a la fiesta, pero ¿podría explicar por favor: "verificará la confirmación en función de los campos del sistema en la tabla"? ¿Está utilizando el campo del sistema xmin para averiguar qué filas se insertaron? Me enfrento a esta situación exacta y este es el único hilo que se acerca a una solución. Sin embargo, soy ignorante.
Código poeta
Si. Podemos observar las filas creadas por la transacción e insistir en que la suma de las cantidades en ellas sea 0. Eso significa, básicamente, verificar columnas del sistema como xmin.
Chris Travers
4

Otro enfoque es adoptar la posición de que es la transferencia del monto financiero que comprende un solo registro.

Por lo tanto, puede tener la estructura:

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

Una restricción de verificación puede garantizar que las cuentas de débito y crédito sean diferentes y que solo haya una cantidad para almacenar. Por lo tanto, hay integridad garantizada, que es lo que el modelo de datos debe proporcionar de forma natural.

He trabajado con sistemas que adoptaron este enfoque con éxito. Hay un poco menos de eficiencia en la consulta de cualquier registro en una cuenta en particular, pero la tabla era más compacta y las consultas para una cuenta solo como débito o como crédito solo eran un poco más eficientes.

David Aldridge
fuente