-- Robust Leave Balance Trigger to handle ALL status transitions and edits -- This handles Reset to Pending, Date Changes, and Role Changes CREATE OR REPLACE FUNCTION public.update_leave_balance_on_status_change() RETURNS TRIGGER AS $$ DECLARE v_is_deductible_old BOOLEAN; v_is_deductible_new BOOLEAN; v_year_old INTEGER; v_year_new INTEGER; BEGIN -- 1. REVERT OLD STATE IF OLD IS NOT NULL THEN SELECT is_deductible INTO v_is_deductible_old FROM public.leave_types WHERE id = OLD.leave_type_id; IF v_is_deductible_old THEN v_year_old := EXTRACT(YEAR FROM OLD.start_date); -- Revert based on OLD status IF OLD.status = 'approved' THEN UPDATE public.leave_balances SET used_days = used_days - OLD.total_days WHERE employee_id = OLD.employee_id AND leave_type_id = OLD.leave_type_id AND year = v_year_old; ELSIF OLD.status = 'pending' THEN UPDATE public.leave_balances SET pending_days = pending_days - OLD.total_days WHERE employee_id = OLD.employee_id AND leave_type_id = OLD.leave_type_id AND year = v_year_old; END IF; END IF; END IF; -- 2. APPLY NEW STATE IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN IF NEW.status != 'cancelled' AND NEW.status != 'rejected' THEN SELECT is_deductible INTO v_is_deductible_new FROM public.leave_types WHERE id = NEW.leave_type_id; IF v_is_deductible_new THEN v_year_new := EXTRACT(YEAR FROM NEW.start_date); -- Ensure balance record exists INSERT INTO public.leave_balances (employee_id, leave_type_id, year) VALUES (NEW.employee_id, NEW.leave_type_id, v_year_new) ON CONFLICT (employee_id, leave_type_id, year) DO NOTHING; -- Apply based on NEW status IF NEW.status = 'approved' THEN UPDATE public.leave_balances SET used_days = used_days + NEW.total_days WHERE employee_id = NEW.employee_id AND leave_type_id = NEW.leave_type_id AND year = v_year_new; ELSIF NEW.status = 'pending' THEN UPDATE public.leave_balances SET pending_days = pending_days + NEW.total_days WHERE employee_id = NEW.employee_id AND leave_type_id = NEW.leave_type_id AND year = v_year_new; END IF; END IF; END IF; END IF; IF (TG_OP = 'DELETE') THEN RETURN OLD; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; DROP TRIGGER IF EXISTS tr_update_leave_balance ON public.leave_requests; CREATE TRIGGER tr_update_leave_balance AFTER INSERT OR UPDATE OR DELETE ON public.leave_requests FOR EACH ROW EXECUTE PROCEDURE public.update_leave_balance_on_status_change();