| Version 1 (modified by , 3 days ago) ( diff ) |
|---|
Напреден развој на базата
Финансиски месечни пресметки и распределба на буџет (Finance Monthly Calculations & Allocation)
Опис на барањата за податочни ограничувања
Системот мора да обезбеди дека:
- Финансискиот корисник мора да внесе точно 5 процентуални вредности за распределба на буџетот.
- Секоја од процентуалните вредности мора да биде во опсег од 0 до 100.
- Збирот на сите 5 проценти мора да биде 100.
- Месечните приходи по корисник мора да можат да се пресметуваат без дополнителна логика во апликацијата.
- За тековниот месец мора да постои преглед на вкупно заработените средства и распределбата по буџетски категории.
Имплементација
Тригери
BEFORE INSERT и BEFORE UPDATE тригер на finance_users за валидација на 5-те процентуални полиња. Тригeрот гарантира дека сите вредности се внесени, се во дозволен опсег и дека збирот е 100.
CREATE OR REPLACE FUNCTION trekr.fn_validate_finance_percentages()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
s NUMERIC;
v NUMERIC;
vals NUMERIC[] := ARRAY[
NEW.spending_budget,
NEW.saving_budget,
NEW.investing_budget,
NEW.donation_budget,
NEW.credit
];
eps CONSTANT NUMERIC := 0.01;
BEGIN
FOREACH v IN ARRAY vals LOOP
IF v IS NULL THEN
RAISE EXCEPTION 'All 5 finance percentage values are required';
END IF;
IF v < 0 OR v > 100 THEN
RAISE EXCEPTION 'Finance percentage values must be between 0 and 100';
END IF;
END LOOP;
s := (
NEW.spending_budget +
NEW.saving_budget +
NEW.investing_budget +
NEW.donation_budget +
NEW.credit
)::numeric;
IF abs(s - 100) > eps THEN
RAISE EXCEPTION 'Finance percentages must sum to 100 (got: %)', s;
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_validate_finance_percentages
BEFORE INSERT OR UPDATE ON trekr.finance_users
FOR EACH ROW
EXECUTE FUNCTION trekr.fn_validate_finance_percentages();
Функции / Stored Procedures
Функција за пресметка на вкупно заработени средства во тековниот месец за даден финансиски корисник.
CREATE OR REPLACE FUNCTION trekr.fn_finance_total_earned_this_month(p_user_id bigint)
RETURNS numeric
LANGUAGE sql
AS $$
SELECT COALESCE(SUM(i.amount), 0)
FROM trekr.incomes i
WHERE i.finance_user_id = p_user_id
AND date_trunc('month', i.date) = date_trunc('month', current_date);
$$;
Функција за пресметка на распределбата на тековниот месечен приход по 5-те финансиски категории.
CREATE OR REPLACE FUNCTION trekr.fn_finance_monthly_allocation(p_user_id bigint)
RETURNS TABLE (
spending_amount numeric,
saving_amount numeric,
investing_amount numeric,
donation_amount numeric,
credit_amount numeric
)
LANGUAGE sql
AS $$
SELECT
COALESCE(f.spending_budget, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS spending_amount,
COALESCE(f.saving_budget, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS saving_amount,
COALESCE(f.investing_budget, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS investing_amount,
COALESCE(f.donation_budget, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS donation_amount,
COALESCE(f.credit, 0) * COALESCE(v.total_earned_this_month, 0) / 100.0 AS credit_amount
FROM trekr.finance_users f
LEFT JOIN trekr.vw_finance_current_month v
ON v.user_id = f.user_id
WHERE f.user_id = p_user_id;
$$;
Погледи (Views)
Поглед за месечен приход по корисник, по година и месец. Овој поглед се користи за историјски извештаи и агрегирани графици.
CREATE OR REPLACE VIEW trekr.vw_finance_monthly_summary AS
SELECT
i.finance_user_id AS user_id,
EXTRACT(YEAR FROM i.date)::int AS year,
EXTRACT(MONTH FROM i.date)::int AS month,
SUM(i.amount) AS total_income
FROM trekr.incomes i
GROUP BY i.finance_user_id, EXTRACT(YEAR FROM i.date), EXTRACT(MONTH FROM i.date);
Поглед за вкупно заработени средства во тековниот месец по корисник. Овој поглед е основа за пресметка на распределбата на буџетот.
CREATE OR REPLACE VIEW trekr.vw_finance_current_month AS
SELECT
f.user_id,
COALESCE(SUM(i.amount), 0) AS total_earned_this_month
FROM trekr.finance_users f
LEFT JOIN trekr.incomes i
ON i.finance_user_id = f.user_id
AND date_trunc('month', i.date) = date_trunc('month', current_date)
GROUP BY f.user_id;
Поглед за распределба на тековниот месечен приход по буџетски категории. Погледот прикажува колку пари од вкупниот месечен приход припаѓаат на секоја категорија.
CREATE OR REPLACE VIEW trekr.vw_finance_allocations AS
SELECT
f.user_id,
v.total_earned_this_month,
(f.spending_budget * v.total_earned_this_month / 100.0) AS spending_amount,
(f.saving_budget * v.total_earned_this_month / 100.0) AS saving_amount,
(f.investing_budget * v.total_earned_this_month / 100.0) AS investing_amount,
(f.donation_budget * v.total_earned_this_month / 100.0) AS donation_amount,
(f.credit * v.total_earned_this_month / 100.0) AS credit_amount
FROM trekr.finance_users f
LEFT JOIN trekr.vw_finance_current_month v
ON v.user_id = f.user_id;
Следење на дневна завршеност на дисциплински задачи (Daily Discipline Completion)
Опис на барањата за податочни ограничувања
Системот мора да обезбеди дека:
- Дневната завршеност се пресметува за конкретен корисник и конкретен датум.
- Ако дневната завршеност за тој корисник и датум веќе постои, не смее да се креира дупликат.
- По пресметка на дневната завршеност, сите дисциплински задачи на тој корисник мора да се ресетираат на
is_finished = false. - Решението мора да може да се извршува и без апликациски сервер, преку складирана функција што може да се повикува од cron.
Имплементација
Тригери / Functions
Функција за пресметка на дневна завршеност за еден корисник и еден ден. Функцијата пресметува процент на завршени задачи и ја зачувува дневната завршеност само ако не постои веќе запис за тој датум.
CREATE OR REPLACE FUNCTION trekr.fn_compute_daily_completion(p_user_id bigint, p_day date)
RETURNS TABLE(created boolean, daily_completion_id bigint, procent numeric)
LANGUAGE plpgsql
AS $$
DECLARE
total_count bigint;
finished_count bigint;
pct numeric;
dc_id bigint;
BEGIN
IF NOT EXISTS (
SELECT 1
FROM trekr.discipline_users du
WHERE du.user_id = p_user_id
) THEN
RAISE EXCEPTION 'Discipline tracking is not enabled for this user';
END IF;
SELECT dc.daily_completion_id, dc.procent
INTO dc_id, pct
FROM trekr.daily_completions dc
WHERE dc.user_id = p_user_id
AND dc.date = p_day
LIMIT 1;
IF dc_id IS NOT NULL THEN
RETURN QUERY SELECT false, dc_id, pct;
RETURN;
END IF;
SELECT COUNT(*)
INTO total_count
FROM trekr.tasks t
WHERE t.discipline_user_id = p_user_id;
SELECT COUNT(*)
INTO finished_count
FROM trekr.tasks t
WHERE t.discipline_user_id = p_user_id
AND t.is_finished = TRUE;
pct := CASE
WHEN total_count = 0 THEN 0
ELSE ROUND((finished_count::numeric / total_count::numeric) * 100, 2)
END;
INSERT INTO trekr.daily_completions (user_id, date, procent)
VALUES (p_user_id, p_day, pct)
RETURNING daily_completion_id INTO dc_id;
UPDATE trekr.tasks
SET is_finished = FALSE
WHERE discipline_user_id = p_user_id
AND is_finished = TRUE;
RETURN QUERY SELECT true, dc_id, pct;
END;
$$;
Функција за пресметка на дневна завршеност за сите дисциплински корисници за даден датум. Оваа функција е погодна за автоматско стартување преку cron job.
CREATE OR REPLACE FUNCTION trekr.fn_compute_daily_completion_for_all(p_day date)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
u RECORD;
BEGIN
FOR u IN
SELECT user_id
FROM trekr.discipline_users
LOOP
BEGIN
PERFORM trekr.fn_compute_daily_completion(u.user_id, p_day);
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'compute_daily_completion failed for user %: %', u.user_id, SQLERRM;
END;
END LOOP;
END;
$$;
Погледи (Views)
Поглед за преглед на сите дневни завршености по корисник. Ги прикажува историјските проценти, датумите и корисникот на кој припаѓаат.
CREATE OR REPLACE VIEW trekr.vw_daily_completion_history AS
SELECT
dc.daily_completion_id,
dc.user_id,
dc.date,
dc.procent
FROM trekr.daily_completions dc
ORDER BY dc.date DESC, dc.daily_completion_id DESC;
Интеграција и употреба во апликацијата
Опис
Овие тригери, функции и погледи се користат за:
- валидација на внесот во финансискиот модул,
- автоматска пресметка на месечни финансиски суми,
- пресметка на распределба на буџет според тековниот приход,
- пресметка и архивирање на дневна дисциплинска завршеност,
- прикажување на агрегирани извештаи во frontend без дополнителни сложени пресметки.
Забелешка
SQL објектите се дизајнирани да бидат:
- идемпотентни каде што е можно,
- лесни за користење од backend и frontend,
- погодни за annual / monthly reporting,
- компатибилни со директно повикување од апликацијата или преку scheduled job.
