Hlavní navigace

Jak na účetnictví na Linuxu (3)

29. 10. 2002
Doba čtení: 6 minut

Sdílet

V minulém dílu jsme vytvořili jednoduchou datovou strukturu pro podvojné účetnictví. Nyní využijeme dalších možností PostgreSQL pro lepší zabezpečení datové integrity.

Základním předpokladem pro konzistenci celého účetnictví je shodná částka na straně má dáti a na straně dal. Můžeme spoléhat na program běžící nad databází. Lépe ale bude, pokud kontrolní mechanismy zabudujeme do databáze. Potřebovali bychom napsat funkci, která by po ukončení transakce kontrolovala, co jsme do databáze zapsali. V transakci je posledním příkazem update tabulky ei_poradi. Zkusme tedy vytvořit funkci, která bude kontrolovat platnost celé operace. Použijeme k tomu procedurální jazyk PL/pgSQL. Před prvním použitím musíte jazyk v databázi definovat (jako administrátor databáze). Funkce bude triggerem automaticky vyvolána před každou změnou v tabulce ei_poradi:

CREATE FUNCTION plpgsql_call_handler () RETURNS OPAQUE AS
  '$libdir/plpgsql' LANGUAGE C;

CREATE TRUSTED PROCEDURAL LANGUAGE plpgsql
  HANDLER plpgsql_call_handler;

create or replace function ei_poradi_update() returns opaque as '
  declare zaznam_p RECORD;
  declare zaznam_m RECORD;
  declare zaznam_d RECORD;
  begin
  select into zaznam_p * from ei_popis where cislo=old.cislo;
  if not found then
    raise
      exception ''Účetní operace % nemá zadaný popis.'',
         old.cislo;
    end if;
  select into zaznam_m sum(castka) as castka
       from ei_denik
      where mdd=''M''
        and cislo=old.cislo;
  if zaznam_m.castka is null then
    raise
      exception ''Účetní operace % nemá stranu má dáti.'',
         old.cislo;
    end if;
  select into zaznam_d sum(castka) as castka
       from ei_denik
      where mdd=''D''
        and cislo=old.cislo;
  if zaznam_d.castka is null then
    raise
       exception ''Účetní operace % nemá stranu dal.'',
          old.cislo;
    end if;
  if zaznam_m.castka != zaznam_d.castka then
    raise
      exception ''Operace % nemá shodnou stranu má dáti a dal.'',
         old.cislo;
    end if;
  return new;
  end;
' language 'plpgsql';

create trigger ei_poradi_update before update
    on ei_poradi for each row
    execute procedure ei_poradi_update();

Update tabulky ei_poradi je v zápisu účetního případu na posledním místě, takže vyvolaná funkce může zkontrolovat konzistenci celého zápisu. Update tabulky je samozřejmě možné volat i dříve, ale kontrolní funkce zjistí, že k celému dokončení transakce chybí část údajů, transakci zastaví a databázi uvede do původního stavu.

Funkce pro kontrolu konzistence

Žádným způsobem zatím není hlídaná hlavní kniha. Snažíme-li se opravit zůstatky v hlavní knize, mlčky předpokládáme, že údaje v hlavní knize existují. Pokud k danému účtu záznam v hlavní knize chybí, databáze to nepozná – jednoduše se neopraví žádná věta v hlavní knize a údaje v deníku nebudou odpovídat údajům v hlavní knize. Stejně tak budou údaje v hlavní knize chybět, pokud na zápis jednoduše zapomeneme. Naštěstí můžeme využít jednoduchého faktu, že každý záznam do deníku musí automaticky znamenat i záznam do hlavní knihy. Takže můžeme vyrobit funkci, která bude triggerem vyvolávána při zápisu do deníku a bude automaticky opravovat údaje v hlavní knize. Celý zápis transakce se tak zjednoduší o dva příkazy update. I když správně by nemělo být možné údaje v účetnictví přepisovat (dalo by se to zajistit pravidlem zakazujícím update tabulky ei_denik), v naší databázi to jde. Potřebujeme proto dvě funkce – jedna bude aktualizovat hlavní knihu při nových zápisech, druhá bude aktualizovat hlavní knihu při opravách. Pokud by bylo možné v naší databázi účetní operace i mazat, bylo by potřeba napsat další funkci. Výpis těchto funkcí už v textu neuvádím, protože jejich princip se příliš neliší od funkce uvedené výše.

Funkce pro aktualizaci hlavní knihy

Možná vás při pohledu do hlavní knihy napadne, že informace v ní jsou nadbytečné, že by ji bylo možné rekonstruovat z deníku. Není to tak docela pravda. Informace v deníku bývají dost často při účetní uzávěrce přestěhovány do archivu a z tabulek vymazány. Jediná informace o zůstatcích tak zůstává v hlavní knize. Druhý aspekt je výkonnostní. Při větším množství vět v deníku by rekonstrukce hlavní knihy mohla trvat poměrně dlouho.

Po doplnění funkcí a triggerů pro aktualizaci hlavní knihy se nám celá transakce smrsknula na tři příkazy insert – jednou do tabulky ei_popis a dvakrát do tabulky ei_denik – a na update tabulky ei_poradi. Kontrolou dat přímo v databázi a transakčním zpracováním jsme si práci ulehčili natolik, že nepotřebujeme data před zápisem do databáze nijak zvláště pečlivě kontrolovat. Databáze nám jednoduše nedovolí zapsat chybné údaje.

Šla by práce ještě více zjednodušit? Šla… Zkusíme využít toho, že většina operací bude mít pouze jeden záznam na straně má dáti a jeden záznam na straně dal (u složitějších operací už nic nezjednodušíme). Vytvoříme si pomocnou tabulku ei_zapis, která bude obsahovat všechny potřebné údaje. Při zápisu do této tabulky se vyvolá triggerem funkce, která udělá všechny potřebné operace za nás.

create table ei_zapis (
    datum date default now(), popis varchar(128),
    ucetm int4, analm, int4, dokladm varchar(64),
    ucetd int4, anald, int4, dokladd varchar(64),
    castka float8
);

Volaná funkce a trigger budou naprosto přímočaré. Za povšimnutí stojí snad jen návratová hodnota funkce null. Tím zabezpečíme, že ve skutečnosti se žádné věty do tabulky ei_zapis nebudou vkládát. Jde nám přece pouze o vyvolání funkce:

create or replace function ei_zapis() returns opaque as '
    begin
    lock ei_poradi;
    insert into ei_popis (cislo, popis)
           values ((select cislo from ei_poradi), new.popis);
    insert into ei_denik (cislo, mdd, ucet, anal, castka)
           values ((select cislo from ei_poradi),
                    ''M'', new.ucetm, new.analm, new.castka);
    insert into ei_denik (cislo, mdd, ucet, anal, castka)
           values ((select cislo from ei_poradi),
                    ''D'', new.ucetd, new.anald, new.castka);
    update ei_poradi set cislo=cislo+1;
    return null;
    end;
' language 'plpgsql';

create trigger ei_zapis before insert
    on ei_zapis for each row
    execute procedure ei_zapis();

Vytvoření tabulky ei_zapis a souvisejících funkcí

Celý zápis jednoduchého dvouřádkového účetního případu je nyní možné udělat jediným příkazem insert. Všechny potřebné kontroly jsou již obsaženy:

insert into ei_zapis (ucetm, analm, ucetd, anald, popis, castka)
         values (221, 100, 600, 100, 'Výplata', 23000);

Zajímá-li nás číslo záznamu, které bylo zápisu přiděleno, můžeme před příkazem insert uzamknout tabulku ei_poradi a přečíst číslo.

root_podpora

I když v datovém návrhu existuje množství dalších problémů – není například zabezpečený update tabulky ei_poradi, je možné zapsat údaje do deníku mimo pořadí, je možné opravovat údaje v hlavní knize a jistě by se našla spousta dalšího – je databáze slušně zabezpečená proti běžným chybám, které dokáže programátor v aplikaci napáchat. Zároveň má aplikační programátor podstatným způsobem ušetřenou práci a nemusí pamatovat na většinu kontrol, které je jinak nutné provádět pořád a pořád do úmoru a nikomu se dělat nechtějí.

Aplikační programátor totiž často ani netuší, co vše je nutné zkontrolovat. Dokažete si například uvědomit, co vše musíte ověřit, než v naší jednoduché ukázkové databázi vymažete jediný záznam z účetní osnovy? Přiznám se, že mně by se to při programování ani nechtělo zjišťovat, a kdyby mě pak zavolali do účtárny řešit problém s účetní osnovou, hodil bych to na někoho jiného: „…se nedivte, paní účetní, že máte v účetnictví čurbes, když si mažete v osnově, co vás zrovna napadne… si to teď ťukejte do počítače zpátky sama!“

Byl pro vás článek přínosný?