| 1 | -- Compute daily completion for a single user and date entirely in the DB
|
|---|
| 2 | -- Inserts a daily_completions row (if not exists), links finished tasks into task_daily_completions,
|
|---|
| 3 | -- and resets tasks.finished = false for that user.
|
|---|
| 4 | -- Usage: SELECT trekr.fn_compute_daily_completion(user_id := 123, day := DATE '2026-05-06');
|
|---|
| 5 |
|
|---|
| 6 | CREATE OR REPLACE FUNCTION trekr.fn_compute_daily_completion(user_id bigint, day date)
|
|---|
| 7 | RETURNS TABLE(created boolean, daily_completion_id bigint, procent numeric)
|
|---|
| 8 | LANGUAGE plpgsql
|
|---|
| 9 | AS $$
|
|---|
| 10 | DECLARE
|
|---|
| 11 | total_count bigint;
|
|---|
| 12 | finished_count bigint;
|
|---|
| 13 | pct numeric;
|
|---|
| 14 | dc_id bigint;
|
|---|
| 15 | user_rec record;
|
|---|
| 16 | finished_tasks RECORD;
|
|---|
| 17 | BEGIN
|
|---|
| 18 | IF user_id IS NULL THEN
|
|---|
| 19 | RAISE EXCEPTION 'user_id is required';
|
|---|
| 20 | END IF;
|
|---|
| 21 | IF day IS NULL THEN
|
|---|
| 22 | RAISE EXCEPTION 'day is required';
|
|---|
| 23 | END IF;
|
|---|
| 24 | IF day > current_date THEN
|
|---|
| 25 | RAISE EXCEPTION 'date cannot be in the future';
|
|---|
| 26 | END IF;
|
|---|
| 27 |
|
|---|
| 28 | -- ensure user tracking exists: rely on discipline_users table existence
|
|---|
| 29 | IF NOT EXISTS (SELECT 1 FROM trekr.discipline_users du WHERE du.user_id = user_id) THEN
|
|---|
| 30 | RAISE EXCEPTION 'Discipline tracking is not enabled for this user';
|
|---|
| 31 | END IF;
|
|---|
| 32 |
|
|---|
| 33 | -- if already computed for that user+date, return existing
|
|---|
| 34 | SELECT dc.daily_completion_id, dc.procent INTO dc_id, pct
|
|---|
| 35 | FROM trekr.daily_completions dc
|
|---|
| 36 | WHERE dc.user_id = user_id AND dc.date = day
|
|---|
| 37 | LIMIT 1;
|
|---|
| 38 |
|
|---|
| 39 | IF dc_id IS NOT NULL THEN
|
|---|
| 40 | RETURN QUERY SELECT false, dc_id, pct;
|
|---|
| 41 | RETURN;
|
|---|
| 42 | END IF;
|
|---|
| 43 |
|
|---|
| 44 | SELECT COUNT(*) INTO total_count FROM trekr.tasks t WHERE t.discipline_user_id = (
|
|---|
| 45 | SELECT du.discipline_user_id FROM trekr.discipline_users du WHERE du.user_id = user_id
|
|---|
| 46 | );
|
|---|
| 47 |
|
|---|
| 48 | SELECT COUNT(*) INTO finished_count FROM trekr.tasks t WHERE t.discipline_user_id = (
|
|---|
| 49 | SELECT du.discipline_user_id FROM trekr.discipline_users du WHERE du.user_id = user_id
|
|---|
| 50 | ) AND t.finished = true;
|
|---|
| 51 |
|
|---|
| 52 | IF total_count <= 0 THEN
|
|---|
| 53 | pct := 0;
|
|---|
| 54 | ELSE
|
|---|
| 55 | pct := round((finished_count::numeric * 100) / total_count::numeric, 2);
|
|---|
| 56 | END IF;
|
|---|
| 57 |
|
|---|
| 58 | -- insert daily completion
|
|---|
| 59 | INSERT INTO trekr.daily_completions (user_id, date, procent)
|
|---|
| 60 | VALUES (user_id, day, pct)
|
|---|
| 61 | RETURNING daily_completion_id INTO dc_id;
|
|---|
| 62 |
|
|---|
| 63 | -- link finished tasks
|
|---|
| 64 | FOR finished_tasks IN
|
|---|
| 65 | SELECT t.task_id FROM trekr.tasks t
|
|---|
| 66 | WHERE t.discipline_user_id = (
|
|---|
| 67 | SELECT du.discipline_user_id FROM trekr.discipline_users du WHERE du.user_id = user_id
|
|---|
| 68 | ) AND t.finished = true
|
|---|
| 69 | LOOP
|
|---|
| 70 | INSERT INTO trekr.task_daily_completions (task_id, daily_completion_id)
|
|---|
| 71 | VALUES (finished_tasks.task_id, dc_id)
|
|---|
| 72 | ON CONFLICT DO NOTHING;
|
|---|
| 73 | END LOOP;
|
|---|
| 74 |
|
|---|
| 75 | -- reset all tasks for user
|
|---|
| 76 | UPDATE trekr.tasks t SET finished = false
|
|---|
| 77 | WHERE t.discipline_user_id = (
|
|---|
| 78 | SELECT du.discipline_user_id FROM trekr.discipline_users du WHERE du.user_id = user_id
|
|---|
| 79 | );
|
|---|
| 80 |
|
|---|
| 81 | RETURN QUERY SELECT true, dc_id, pct;
|
|---|
| 82 | END;
|
|---|
| 83 | $$;
|
|---|
| 84 |
|
|---|