Files
accounted/supabase/migrations/20260512120100_worked_days_absence_conflict_trigger.sql
T
Mattsson 8f15f98687 Bug/employees creation (#440)
* Fix/employees page salary display logic and labels

* feat: add salary_worked_days table and related functionality

- Implemented the salary_worked_days table to track per-day worked hours for hourly employees.
- Established row-level security (RLS) policies to ensure tenant isolation for salary_worked_days.
- Created unique index on (employee_id, work_date) to enforce uniqueness.
- Added trigger to enforce a 24-hour cap across worked and absence days for the same employee and date.
- Developed tests to validate RLS, uniqueness, and 24-hour cap logic.

* Fix: update hourly_salary calculation and refresh logic in salary run processing
2026-05-12 01:10:01 +02:00

84 lines
3.0 KiB
PL/PgSQL

-- Migration: 24-hour cap across worked + absence days for the same employee+date
--
-- Half-day mixing is allowed: an employee can have 4h worked + 4h sick on the
-- same date. The legal/biological constraint is that a single calendar day
-- has 24 hours — combined worked + absence hours must not exceed that.
--
-- This is enforced as a trigger (not a CHECK constraint) because CHECK cannot
-- reference another table. Two BEFORE INSERT/UPDATE triggers, one on each
-- table, both call the shared function below. The function sums the OTHER
-- table's hours for the same (employee_id, date), adds NEW.hours, and raises
-- if the total exceeds 24.
--
-- For UPDATE on the same row, we exclude the row's own previous contribution
-- from the sum (otherwise editing 8h → 6h on a worked day with 8h absence
-- would falsely report 16+8 > 24). This is handled by the WHERE id <> NEW.id
-- on the same-table side.
CREATE OR REPLACE FUNCTION public.check_salary_day_hours_cap()
RETURNS TRIGGER AS $$
DECLARE
v_employee_id UUID;
v_date DATE;
v_other_total NUMERIC(6, 2);
v_same_total NUMERIC(6, 2);
v_total NUMERIC(6, 2);
BEGIN
-- Resolve the (employee, date) pair from whichever table fired the trigger.
IF TG_TABLE_NAME = 'salary_worked_days' THEN
v_employee_id := NEW.employee_id;
v_date := NEW.work_date;
SELECT COALESCE(SUM(hours), 0) INTO v_other_total
FROM public.salary_absence_days
WHERE employee_id = v_employee_id
AND absence_date = v_date;
SELECT COALESCE(SUM(hours), 0) INTO v_same_total
FROM public.salary_worked_days
WHERE employee_id = v_employee_id
AND work_date = v_date
AND id <> NEW.id;
ELSIF TG_TABLE_NAME = 'salary_absence_days' THEN
v_employee_id := NEW.employee_id;
v_date := NEW.absence_date;
SELECT COALESCE(SUM(hours), 0) INTO v_other_total
FROM public.salary_worked_days
WHERE employee_id = v_employee_id
AND work_date = v_date;
SELECT COALESCE(SUM(hours), 0) INTO v_same_total
FROM public.salary_absence_days
WHERE employee_id = v_employee_id
AND absence_date = v_date
AND id <> NEW.id;
ELSE
-- Defensive: should not be reachable given the trigger bindings below.
RETURN NEW;
END IF;
v_total := v_other_total + v_same_total + NEW.hours;
IF v_total > 24 THEN
RAISE EXCEPTION 'Total tid (arbete + frånvaro) för % får inte överstiga 24 timmar (försökte boka %, befintligt %)',
v_date, NEW.hours, (v_other_total + v_same_total)
USING ERRCODE = 'check_violation';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER salary_worked_days_24h_cap
BEFORE INSERT OR UPDATE ON public.salary_worked_days
FOR EACH ROW EXECUTE FUNCTION public.check_salary_day_hours_cap();
CREATE TRIGGER salary_absence_days_24h_cap
BEFORE INSERT OR UPDATE ON public.salary_absence_days
FOR EACH ROW EXECUTE FUNCTION public.check_salary_day_hours_cap();
NOTIFY pgrst, 'reload schema';