Changes between Initial Version and Version 1 of AdvancedDatabaseDevelopment


Ignore:
Timestamp:
05/07/26 13:51:36 (3 days ago)
Author:
233062
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedDatabaseDevelopment

    v1 v1  
     1= Напреден развој на базата =
     2
     3== Финансиски месечни пресметки и распределба на буџет (Finance Monthly Calculations & Allocation) ==
     4
     5=== Опис на барањата за податочни ограничувања ===
     6
     7Системот мора да обезбеди дека:
     8* Финансискиот корисник мора да внесе точно 5 процентуални вредности за распределба на буџетот.
     9* Секоја од процентуалните вредности мора да биде во опсег од 0 до 100.
     10* Збирот на сите 5 проценти мора да биде 100.
     11* Месечните приходи по корисник мора да можат да се пресметуваат без дополнителна логика во апликацијата.
     12* За тековниот месец мора да постои преглед на вкупно заработените средства и распределбата по буџетски категории.
     13
     14=== Имплементација ===
     15
     16==== Тригери ====
     17
     18BEFORE INSERT и BEFORE UPDATE тригер на finance_users за валидација на 5-те процентуални полиња.
     19Тригeрот гарантира дека сите вредности се внесени, се во дозволен опсег и дека збирот е 100.
     20{{{
     21CREATE OR REPLACE FUNCTION trekr.fn_validate_finance_percentages()
     22RETURNS trigger
     23LANGUAGE plpgsql
     24AS $$
     25DECLARE
     26    s NUMERIC;
     27    v NUMERIC;
     28    vals NUMERIC[] := ARRAY[
     29        NEW.spending_budget,
     30        NEW.saving_budget,
     31        NEW.investing_budget,
     32        NEW.donation_budget,
     33        NEW.credit
     34    ];
     35    eps CONSTANT NUMERIC := 0.01;
     36BEGIN
     37    FOREACH v IN ARRAY vals LOOP
     38        IF v IS NULL THEN
     39            RAISE EXCEPTION 'All 5 finance percentage values are required';
     40        END IF;
     41
     42        IF v < 0 OR v > 100 THEN
     43            RAISE EXCEPTION 'Finance percentage values must be between 0 and 100';
     44        END IF;
     45    END LOOP;
     46
     47    s := (
     48        NEW.spending_budget +
     49        NEW.saving_budget +
     50        NEW.investing_budget +
     51        NEW.donation_budget +
     52        NEW.credit
     53    )::numeric;
     54
     55    IF abs(s - 100) > eps THEN
     56        RAISE EXCEPTION 'Finance percentages must sum to 100 (got: %)', s;
     57    END IF;
     58
     59    RETURN NEW;
     60END;
     61$$;
     62
     63CREATE TRIGGER trg_validate_finance_percentages
     64BEFORE INSERT OR UPDATE ON trekr.finance_users
     65FOR EACH ROW
     66EXECUTE FUNCTION trekr.fn_validate_finance_percentages();
     67}}}
     68
     69==== Функции / Stored Procedures ====
     70
     71Функција за пресметка на вкупно заработени средства во тековниот месец за даден финансиски корисник.
     72{{{
     73CREATE OR REPLACE FUNCTION trekr.fn_finance_total_earned_this_month(p_user_id bigint)
     74RETURNS numeric
     75LANGUAGE sql
     76AS $$
     77    SELECT COALESCE(SUM(i.amount), 0)
     78    FROM trekr.incomes i
     79    WHERE i.finance_user_id = p_user_id
     80      AND date_trunc('month', i.date) = date_trunc('month', current_date);
     81$$;
     82}}}
     83
     84Функција за пресметка на распределбата на тековниот месечен приход по 5-те финансиски категории.
     85{{{
     86CREATE OR REPLACE FUNCTION trekr.fn_finance_monthly_allocation(p_user_id bigint)
     87RETURNS TABLE (
     88    spending_amount numeric,
     89    saving_amount numeric,
     90    investing_amount numeric,
     91    donation_amount numeric,
     92    credit_amount numeric
     93)
     94LANGUAGE sql
     95AS $$
     96    SELECT
     97        COALESCE(f.spending_budget, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS spending_amount,
     98        COALESCE(f.saving_budget, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS saving_amount,
     99        COALESCE(f.investing_budget, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS investing_amount,
     100        COALESCE(f.donation_budget, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS donation_amount,
     101        COALESCE(f.credit, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS credit_amount
     102    FROM trekr.finance_users f
     103    LEFT JOIN trekr.vw_finance_current_month v
     104        ON v.user_id = f.user_id
     105    WHERE f.user_id = p_user_id;
     106$$;
     107}}}
     108
     109==== Погледи (Views) ====
     110
     111Поглед за месечен приход по корисник, по година и месец.
     112Овој поглед се користи за историјски извештаи и агрегирани графици.
     113{{{
     114CREATE OR REPLACE VIEW trekr.vw_finance_monthly_summary AS
     115SELECT
     116    i.finance_user_id AS user_id,
     117    EXTRACT(YEAR FROM i.date)::int AS year,
     118    EXTRACT(MONTH FROM i.date)::int AS month,
     119    SUM(i.amount) AS total_income
     120FROM trekr.incomes i
     121GROUP BY i.finance_user_id, EXTRACT(YEAR FROM i.date), EXTRACT(MONTH FROM i.date);
     122}}}
     123
     124Поглед за вкупно заработени средства во тековниот месец по корисник.
     125Овој поглед е основа за пресметка на распределбата на буџетот.
     126{{{
     127CREATE OR REPLACE VIEW trekr.vw_finance_current_month AS
     128SELECT
     129    f.user_id,
     130    COALESCE(SUM(i.amount), 0) AS total_earned_this_month
     131FROM trekr.finance_users f
     132LEFT JOIN trekr.incomes i
     133    ON i.finance_user_id = f.user_id
     134   AND date_trunc('month', i.date) = date_trunc('month', current_date)
     135GROUP BY f.user_id;
     136}}}
     137
     138Поглед за распределба на тековниот месечен приход по буџетски категории.
     139Погледот прикажува колку пари од вкупниот месечен приход припаѓаат на секоја категорија.
     140{{{
     141CREATE OR REPLACE VIEW trekr.vw_finance_allocations AS
     142SELECT
     143    f.user_id,
     144    v.total_earned_this_month,
     145    (f.spending_budget * v.total_earned_this_month / 100.0) AS spending_amount,
     146    (f.saving_budget * v.total_earned_this_month / 100.0) AS saving_amount,
     147    (f.investing_budget * v.total_earned_this_month / 100.0) AS investing_amount,
     148    (f.donation_budget * v.total_earned_this_month / 100.0) AS donation_amount,
     149    (f.credit * v.total_earned_this_month / 100.0) AS credit_amount
     150FROM trekr.finance_users f
     151LEFT JOIN trekr.vw_finance_current_month v
     152    ON v.user_id = f.user_id;
     153}}}
     154
     155----
     156
     157== Следење на дневна завршеност на дисциплински задачи (Daily Discipline Completion) ==
     158
     159=== Опис на барањата за податочни ограничувања ===
     160
     161Системот мора да обезбеди дека:
     162* Дневната завршеност се пресметува за конкретен корисник и конкретен датум.
     163* Ако дневната завршеност за тој корисник и датум веќе постои, не смее да се креира дупликат.
     164* По пресметка на дневната завршеност, сите дисциплински задачи на тој корисник мора да се ресетираат на `is_finished = false`.
     165* Решението мора да може да се извршува и без апликациски сервер, преку складирана функција што може да се повикува од cron.
     166
     167=== Имплементација ===
     168
     169==== Тригери / Functions ====
     170
     171Функција за пресметка на дневна завршеност за еден корисник и еден ден.
     172Функцијата пресметува процент на завршени задачи и ја зачувува дневната завршеност само ако не постои веќе запис за тој датум.
     173{{{
     174CREATE OR REPLACE FUNCTION trekr.fn_compute_daily_completion(p_user_id bigint, p_day date)
     175RETURNS TABLE(created boolean, daily_completion_id bigint, procent numeric)
     176LANGUAGE plpgsql
     177AS $$
     178DECLARE
     179    total_count bigint;
     180    finished_count bigint;
     181    pct numeric;
     182    dc_id bigint;
     183BEGIN
     184    IF NOT EXISTS (
     185        SELECT 1
     186        FROM trekr.discipline_users du
     187        WHERE du.user_id = p_user_id
     188    ) THEN
     189        RAISE EXCEPTION 'Discipline tracking is not enabled for this user';
     190    END IF;
     191
     192    SELECT dc.daily_completion_id, dc.procent
     193    INTO dc_id, pct
     194    FROM trekr.daily_completions dc
     195    WHERE dc.user_id = p_user_id
     196      AND dc.date = p_day
     197    LIMIT 1;
     198
     199    IF dc_id IS NOT NULL THEN
     200        RETURN QUERY SELECT false, dc_id, pct;
     201        RETURN;
     202    END IF;
     203
     204    SELECT COUNT(*)
     205    INTO total_count
     206    FROM trekr.tasks t
     207    WHERE t.discipline_user_id = p_user_id;
     208
     209    SELECT COUNT(*)
     210    INTO finished_count
     211    FROM trekr.tasks t
     212    WHERE t.discipline_user_id = p_user_id
     213      AND t.is_finished = TRUE;
     214
     215    pct := CASE
     216        WHEN total_count = 0 THEN 0
     217        ELSE ROUND((finished_count::numeric / total_count::numeric) * 100, 2)
     218    END;
     219
     220    INSERT INTO trekr.daily_completions (user_id, date, procent)
     221    VALUES (p_user_id, p_day, pct)
     222    RETURNING daily_completion_id INTO dc_id;
     223
     224    UPDATE trekr.tasks
     225    SET is_finished = FALSE
     226    WHERE discipline_user_id = p_user_id
     227      AND is_finished = TRUE;
     228
     229    RETURN QUERY SELECT true, dc_id, pct;
     230END;
     231$$;
     232}}}
     233
     234Функција за пресметка на дневна завршеност за сите дисциплински корисници за даден датум.
     235Оваа функција е погодна за автоматско стартување преку cron job.
     236{{{
     237CREATE OR REPLACE FUNCTION trekr.fn_compute_daily_completion_for_all(p_day date)
     238RETURNS void
     239LANGUAGE plpgsql
     240AS $$
     241DECLARE
     242    u RECORD;
     243BEGIN
     244    FOR u IN
     245        SELECT user_id
     246        FROM trekr.discipline_users
     247    LOOP
     248        BEGIN
     249            PERFORM trekr.fn_compute_daily_completion(u.user_id, p_day);
     250        EXCEPTION
     251            WHEN OTHERS THEN
     252                RAISE NOTICE 'compute_daily_completion failed for user %: %', u.user_id, SQLERRM;
     253        END;
     254    END LOOP;
     255END;
     256$$;
     257}}}
     258
     259==== Погледи (Views) ====
     260
     261Поглед за преглед на сите дневни завршености по корисник.
     262Ги прикажува историјските проценти, датумите и корисникот на кој припаѓаат.
     263{{{
     264CREATE OR REPLACE VIEW trekr.vw_daily_completion_history AS
     265SELECT
     266    dc.daily_completion_id,
     267    dc.user_id,
     268    dc.date,
     269    dc.procent
     270FROM trekr.daily_completions dc
     271ORDER BY dc.date DESC, dc.daily_completion_id DESC;
     272}}}
     273
     274----
     275
     276== Интеграција и употреба во апликацијата ==
     277
     278=== Опис ===
     279
     280Овие тригери, функции и погледи се користат за:
     281* валидација на внесот во финансискиот модул,
     282* автоматска пресметка на месечни финансиски суми,
     283* пресметка на распределба на буџет според тековниот приход,
     284* пресметка и архивирање на дневна дисциплинска завршеност,
     285* прикажување на агрегирани извештаи во frontend без дополнителни сложени пресметки.
     286
     287=== Забелешка ===
     288
     289SQL објектите се дизајнирани да бидат:
     290* идемпотентни каде што е можно,
     291* лесни за користење од backend и frontend,
     292* погодни за annual / monthly reporting,
     293* компатибилни со директно повикување од апликацијата или преку scheduled job.