NL: Tabeldefinitie VvE-administratie

Deze pagina bevat een technische uitwerking van gegevens die nodig zijn voor een toereikende administratie van een vereniging van eigenaars.

De tabeldefinities zijn geen voorgeschreven softwaremodel. Zij dienen om zichtbaar te maken welke gegevens administratief afzonderlijk moeten kunnen worden vastgelegd om besluitvorming, controle, overdraagbaarheid en reconstructie van de administratie te ondersteunen.

Uitgangspunt is dat gegevens met een verschillende vaktechnische betekenis ook afzonderlijk worden vastgelegd. Zo worden toevoegingen aan en onttrekkingen uit bestemmingsreserves niet als één positieve of negatieve mutatie opgeslagen.

Waar een waarde rechtstreeks uit andere vastgelegde gegevens kan worden afgeleid, verdient berekening de voorkeur boven dubbele opslag. Dit vermindert het risico dat onderling strijdige waarden in de administratie ontstaan.

Resultaatbestemming en bestemmingsreserves

De beginstand, voorgestelde toevoeging en onttrekking en de uiteindelijk besloten toevoeging en onttrekking worden afzonderlijk vastgelegd. De voorgestelde en vastgestelde eindstanden kunnen daaruit worden berekend.

Onderstaand PostgreSQL-voorbeeld illustreert deze structuur.

CREATE TABLE IF NOT EXISTS result_appropriation (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,

    financial_year INTEGER NOT NULL,

    reserve_type VARCHAR(255) NOT NULL,
    reserve_name TEXT NOT NULL,

    start_amount DECIMAL(12,2) NOT NULL,

    proposed_addition DECIMAL(12,2) NOT NULL DEFAULT 0,
    proposed_withdrawal DECIMAL(12,2) NOT NULL DEFAULT 0,

    approved_addition DECIMAL(12,2),
    approved_withdrawal DECIMAL(12,2),

    CHECK (proposed_addition >= 0),
    CHECK (proposed_withdrawal >= 0),
    CHECK (approved_addition IS NULL OR approved_addition >= 0),
    CHECK (approved_withdrawal IS NULL OR approved_withdrawal >= 0)
);

De eindstanden worden niet afzonderlijk opgeslagen, maar uit de onderliggende gegevens afgeleid:

CREATE OR REPLACE VIEW result_appropriation_overview AS
SELECT
    id,
    financial_year,
    reserve_type,
    reserve_name,
    start_amount,

    proposed_addition,
    proposed_withdrawal,

    start_amount
        + proposed_addition
        - proposed_withdrawal
        AS proposed_final_amount,

    approved_addition,
    approved_withdrawal,

    CASE
        WHEN approved_addition IS NULL
         AND approved_withdrawal IS NULL
        THEN NULL
        ELSE
            start_amount
            + COALESCE(approved_addition, 0)
            - COALESCE(approved_withdrawal, 0)
    END AS approved_final_amount

FROM result_appropriation;

Daarmee geldt steeds:

beginstand + toevoeging − onttrekking = eindstand

De administratie bevat hierdoor geen afzonderlijk opgeslagen eindbedrag dat van de onderliggende mutaties kan gaan afwijken.

Controleerbaarheid van wijzigingen

Voor een controleerbare administratie moet achteraf kunnen worden vastgesteld welke gegevens zijn toegevoegd, gewijzigd of verwijderd.

Een eenvoudige audit-tabel kan daarvoor als volgt worden ingericht:

CREATE TABLE IF NOT EXISTS result_appropriation_log (
    log_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,

    result_appropriation_id BIGINT,

    action_type VARCHAR(10) NOT NULL
        CHECK (action_type IN ('INSERT', 'UPDATE', 'DELETE')),

    action_user VARCHAR(255) NOT NULL DEFAULT CURRENT_USER,
    action_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

    financial_year INTEGER,
    reserve_type VARCHAR(255),
    reserve_name TEXT,

    start_amount DECIMAL(12,2),

    proposed_addition DECIMAL(12,2),
    proposed_withdrawal DECIMAL(12,2),

    approved_addition DECIMAL(12,2),
    approved_withdrawal DECIMAL(12,2)
);

De bijbehorende triggerfunctie:

CREATE OR REPLACE FUNCTION result_appropriation_audit()
RETURNS TRIGGER AS $$
BEGIN

    IF TG_OP = 'DELETE' THEN

        INSERT INTO result_appropriation_log (
            result_appropriation_id,
            action_type,
            action_user,
            financial_year,
            reserve_type,
            reserve_name,
            start_amount,
            proposed_addition,
            proposed_withdrawal,
            approved_addition,
            approved_withdrawal
        )
        VALUES (
            OLD.id,
            'DELETE',
            CURRENT_USER,
            OLD.financial_year,
            OLD.reserve_type,
            OLD.reserve_name,
            OLD.start_amount,
            OLD.proposed_addition,
            OLD.proposed_withdrawal,
            OLD.approved_addition,
            OLD.approved_withdrawal
        );

        RETURN OLD;

    ELSIF TG_OP = 'UPDATE' THEN

        INSERT INTO result_appropriation_log (
            result_appropriation_id,
            action_type,
            action_user,
            financial_year,
            reserve_type,
            reserve_name,
            start_amount,
            proposed_addition,
            proposed_withdrawal,
            approved_addition,
            approved_withdrawal
        )
        VALUES (
            NEW.id,
            'UPDATE',
            CURRENT_USER,
            NEW.financial_year,
            NEW.reserve_type,
            NEW.reserve_name,
            NEW.start_amount,
            NEW.proposed_addition,
            NEW.proposed_withdrawal,
            NEW.approved_addition,
            NEW.approved_withdrawal
        );

        RETURN NEW;

    ELSIF TG_OP = 'INSERT' THEN

        INSERT INTO result_appropriation_log (
            result_appropriation_id,
            action_type,
            action_user,
            financial_year,
            reserve_type,
            reserve_name,
            start_amount,
            proposed_addition,
            proposed_withdrawal,
            approved_addition,
            approved_withdrawal
        )
        VALUES (
            NEW.id,
            'INSERT',
            CURRENT_USER,
            NEW.financial_year,
            NEW.reserve_type,
            NEW.reserve_name,
            NEW.start_amount,
            NEW.proposed_addition,
            NEW.proposed_withdrawal,
            NEW.approved_addition,
            NEW.approved_withdrawal
        );

        RETURN NEW;

    END IF;

    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

De trigger:

DROP TRIGGER IF EXISTS trg_result_appropriation_audit
ON result_appropriation;

CREATE TRIGGER trg_result_appropriation_audit
AFTER INSERT OR UPDATE OR DELETE
ON result_appropriation
FOR EACH ROW
EXECUTE FUNCTION result_appropriation_audit();