Index: db-scripts/triggers/compute_daily_completion.sql
===================================================================
--- db-scripts/triggers/compute_daily_completion.sql	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
+++ db-scripts/triggers/compute_daily_completion.sql	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
@@ -0,0 +1,84 @@
+-- Compute daily completion for a single user and date entirely in the DB
+-- Inserts a daily_completions row (if not exists), links finished tasks into task_daily_completions,
+-- and resets tasks.finished = false for that user.
+-- Usage: SELECT trekr.fn_compute_daily_completion(user_id := 123, day := DATE '2026-05-06');
+
+CREATE OR REPLACE FUNCTION trekr.fn_compute_daily_completion(user_id bigint, 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;
+    user_rec record;
+    finished_tasks RECORD;
+BEGIN
+    IF user_id IS NULL THEN
+        RAISE EXCEPTION 'user_id is required';
+    END IF;
+    IF day IS NULL THEN
+        RAISE EXCEPTION 'day is required';
+    END IF;
+    IF day > current_date THEN
+        RAISE EXCEPTION 'date cannot be in the future';
+    END IF;
+
+    -- ensure user tracking exists: rely on discipline_users table existence
+    IF NOT EXISTS (SELECT 1 FROM trekr.discipline_users du WHERE du.user_id = user_id) THEN
+        RAISE EXCEPTION 'Discipline tracking is not enabled for this user';
+    END IF;
+
+    -- if already computed for that user+date, return existing
+    SELECT dc.daily_completion_id, dc.procent INTO dc_id, pct
+    FROM trekr.daily_completions dc
+    WHERE dc.user_id = user_id AND dc.date = 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 = (
+        SELECT du.discipline_user_id FROM trekr.discipline_users du WHERE du.user_id = user_id
+    );
+
+    SELECT COUNT(*) INTO finished_count FROM trekr.tasks t WHERE t.discipline_user_id = (
+        SELECT du.discipline_user_id FROM trekr.discipline_users du WHERE du.user_id = user_id
+    ) AND t.finished = true;
+
+    IF total_count <= 0 THEN
+        pct := 0;
+    ELSE
+        pct := round((finished_count::numeric * 100) / total_count::numeric, 2);
+    END IF;
+
+    -- insert daily completion
+    INSERT INTO trekr.daily_completions (user_id, date, procent)
+    VALUES (user_id, day, pct)
+    RETURNING daily_completion_id INTO dc_id;
+
+    -- link finished tasks
+    FOR finished_tasks IN
+        SELECT t.task_id FROM trekr.tasks t
+        WHERE t.discipline_user_id = (
+            SELECT du.discipline_user_id FROM trekr.discipline_users du WHERE du.user_id = user_id
+        ) AND t.finished = true
+    LOOP
+        INSERT INTO trekr.task_daily_completions (task_id, daily_completion_id)
+        VALUES (finished_tasks.task_id, dc_id)
+        ON CONFLICT DO NOTHING;
+    END LOOP;
+
+    -- reset all tasks for user
+    UPDATE trekr.tasks t SET finished = false
+    WHERE t.discipline_user_id = (
+        SELECT du.discipline_user_id FROM trekr.discipline_users du WHERE du.user_id = user_id
+    );
+
+    RETURN QUERY SELECT true, dc_id, pct;
+END;
+$$;
+
Index: db-scripts/triggers/compute_daily_completion_all.sql
===================================================================
--- db-scripts/triggers/compute_daily_completion_all.sql	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
+++ db-scripts/triggers/compute_daily_completion_all.sql	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
@@ -0,0 +1,31 @@
+-- Compute daily completion for all discipline users for a given date
+-- Usage: SELECT trekr.fn_compute_daily_completion_for_all(day := DATE '2026-05-06');
+
+CREATE OR REPLACE FUNCTION trekr.fn_compute_daily_completion_for_all(day date)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    u RECORD;
+BEGIN
+    IF day IS NULL THEN
+        RAISE EXCEPTION 'day is required';
+    END IF;
+
+    FOR u IN SELECT user_id FROM trekr.discipline_users LOOP
+        BEGIN
+            PERFORM trekr.fn_compute_daily_completion(u.user_id, day);
+        EXCEPTION WHEN OTHERS THEN
+            -- log and continue (requires a logging table or use RAISE NOTICE)
+            RAISE NOTICE 'compute_daily_completion failed for user %: %', u.user_id, SQLERRM;
+        END;
+    END LOOP;
+END;
+$$;
+
+-- Optionally create a pg_cron job to run nightly (requires pg_cron extension)
+-- Example (commented):
+-- CREATE EXTENSION IF NOT EXISTS pg_cron;
+-- SELECT cron.schedule('compute_daily_completions_every_day', '59 23 * * *',
+--     $$SELECT trekr.fn_compute_daily_completion_for_all(current_date - INTERVAL '1 day')$$);
+
Index: db-scripts/triggers/finance_validate_trigger.sql
===================================================================
--- db-scripts/triggers/finance_validate_trigger.sql	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
+++ db-scripts/triggers/finance_validate_trigger.sql	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
@@ -0,0 +1,48 @@
+-- Validate finance percentages on insert/update
+-- Ensures the five budget fields sum to ~100 (tolerance 0.01) and each is between 0 and 100
+-- Schema: trekr
+
+CREATE OR REPLACE FUNCTION trekr.fn_validate_finance_percentages()
+RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    s NUMERIC;
+    eps CONSTANT NUMERIC := 0.01; -- allow small rounding differences
+    vals NUMERIC[] := ARRAY[NEW.spending_budget, NEW.saving_budget, NEW.investing_budget, NEW.donation_budget, NEW.credit];
+    v NUMERIC;
+BEGIN
+    -- Null checks: all five values must be provided (business rule)
+    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;
+$$;
+
+-- Trigger on finance_users table
+DO $$
+BEGIN
+    IF NOT EXISTS (
+        SELECT 1 FROM pg_trigger t
+        JOIN pg_class c ON t.tgrelid = c.oid
+        WHERE t.tgname = 'trg_validate_finance_percentages' AND c.relname = 'finance_users'
+    ) THEN
+        CREATE TRIGGER trg_validate_finance_percentages
+        BEFORE INSERT OR UPDATE ON trekr.finance_users
+        FOR EACH ROW
+        EXECUTE FUNCTION trekr.fn_validate_finance_percentages();
+    END IF;
+END$$;
+
Index: db-scripts/triggers/uniqueness_and_training_checks.sql
===================================================================
--- db-scripts/triggers/uniqueness_and_training_checks.sql	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
+++ db-scripts/triggers/uniqueness_and_training_checks.sql	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
@@ -0,0 +1,36 @@
+-- Add useful DB constraints and checks
+
+-- Unique index: ensure a user has at most one daily intake per date
+DO $$
+BEGIN
+    IF NOT EXISTS (
+        SELECT 1 FROM pg_indexes WHERE schemaname = 'trekr' AND tablename = 'daily_intakes' AND indexname = 'uq_daily_intake_user_date'
+    ) THEN
+        CREATE UNIQUE INDEX uq_daily_intake_user_date ON trekr.daily_intakes (weight_user_id, date);
+    END IF;
+END$$;
+
+-- Prevent inserting training sessions with future date
+CREATE OR REPLACE FUNCTION trekr.fn_check_training_date()
+RETURNS trigger LANGUAGE plpgsql AS $$
+BEGIN
+    IF NEW.date > current_date THEN
+        RAISE EXCEPTION 'Training session date cannot be in the future: %', NEW.date;
+    END IF;
+    RETURN NEW;
+END$$;
+
+DO $$
+BEGIN
+    IF NOT EXISTS (
+        SELECT 1 FROM pg_trigger t
+        JOIN pg_class c ON t.tgrelid = c.oid
+        WHERE t.tgname = 'trg_check_training_date' AND c.relname = 'training_sessions'
+    ) THEN
+        CREATE TRIGGER trg_check_training_date
+        BEFORE INSERT OR UPDATE ON trekr.training_sessions
+        FOR EACH ROW
+        EXECUTE FUNCTION trekr.fn_check_training_date();
+    END IF;
+END$$;
+
