Files
personel/supabase/migrations/20240319000003_redesign_leave_system.sql
T

263 lines
12 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 1. Create Public Holidays Table
CREATE TABLE IF NOT EXISTS public.public_holidays (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
date DATE NOT NULL UNIQUE,
name TEXT NOT NULL,
is_recurring BOOLEAN DEFAULT false, -- If true, year is ignored (e.g., Oct 29)
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);
-- Seed some recurring Turkish public holidays
INSERT INTO public.public_holidays (date, name, is_recurring) VALUES
('2024-01-01', 'Yılbaşı', true),
('2024-04-23', 'Ulusal Egemenlik ve Çocuk Bayramı', true),
('2024-05-01', 'Emek ve Dayanışma Günü', true),
('2024-05-19', 'Atatürk''ü Anma, Gençlik ve Spor Bayramı', true),
('2024-07-15', 'Demokrasi ve Milli Birlik Günü', true),
('2024-08-30', 'Zafer Bayramı', true),
('2024-10-29', 'Cumhuriyet Bayramı', true)
ON CONFLICT (date) DO NOTHING;
-- 2. Create Leave Types Table
CREATE TABLE IF NOT EXISTS public.leave_types (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
is_paid BOOLEAN DEFAULT true,
requires_approval BOOLEAN DEFAULT true,
is_deductible BOOLEAN DEFAULT true, -- If true, reduces standard balance
only_admin_can_create BOOLEAN DEFAULT false, -- If true, only managers/admins can initiate
color_code TEXT,
display_order INTEGER DEFAULT 99, -- Sort order for UI
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);
-- Add column if it doesn't exist (for cases where table already existed)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leave_types' AND column_name='display_order') THEN
ALTER TABLE public.leave_types ADD COLUMN display_order INTEGER DEFAULT 99;
END IF;
END $$;
-- Seed Leave Types with display_order
INSERT INTO public.leave_types (name, description, is_paid, requires_approval, is_deductible, only_admin_can_create, color_code, display_order) VALUES
('Yıllık İzin', 'Kıdeme bağlı hakedilen ücretli izin', true, true, true, false, '#4CAF50', 1),
('Mazeret İzni', 'Özel durumlar için kısa süreli izinler', true, true, true, false, '#FF9800', 2),
('Hastalık İzni', 'Raporlu olunan günler', true, true, false, false, '#F44336', 3),
('Evlilik İzni', 'Evlilik durumunda verilen 3 günlük yasal izin', true, true, false, false, '#E91E63', 4),
('Vefat İzni', 'Birinci derece yakın vefatı durumunda verilen 3 günlük yasal izin', true, true, false, false, '#607D8B', 5),
('İdari İzin', 'Yönetim tarafından verilen idari izin', true, true, false, true, '#2196F3', 6),
('İcap', 'Nöbet/İcap görevleri için verilen izin', true, true, false, true, '#9C27B0', 7)
ON CONFLICT (name) DO UPDATE SET display_order = EXCLUDED.display_order;
-- 3. Modify Leave Requests Table
-- First, add columns (handle case where they might already exist if re-run)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leave_requests' AND column_name='leave_type_id') THEN
ALTER TABLE public.leave_requests ADD COLUMN leave_type_id UUID REFERENCES public.leave_types(id);
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leave_requests' AND column_name='total_days') THEN
ALTER TABLE public.leave_requests ADD COLUMN total_days DECIMAL(5,2);
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leave_requests' AND column_name='approved_by') THEN
ALTER TABLE public.leave_requests ADD COLUMN approved_by UUID REFERENCES auth.users(id);
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leave_requests' AND column_name='approval_date') THEN
ALTER TABLE public.leave_requests ADD COLUMN approval_date TIMESTAMP WITH TIME ZONE;
END IF;
END $$;
-- 4. Update Leave Balances Table
-- We need to drop the unique constraint on employee_id because now it's employee_id + leave_type_id + year
ALTER TABLE public.leave_balances DROP CONSTRAINT IF EXISTS leave_balances_employee_id_key;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leave_balances' AND column_name='leave_type_id') THEN
ALTER TABLE public.leave_balances ADD COLUMN leave_type_id UUID REFERENCES public.leave_types(id);
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leave_balances' AND column_name='year') THEN
ALTER TABLE public.leave_balances ADD COLUMN year INTEGER DEFAULT EXTRACT(YEAR FROM CURRENT_DATE);
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leave_balances' AND column_name='accrued_days') THEN
ALTER TABLE public.leave_balances ADD COLUMN accrued_days DECIMAL(5,2) DEFAULT 0;
END IF;
END $$;
-- Update existing data: map annual leave type to existing balances
DO $$
DECLARE
annual_leave_id UUID;
BEGIN
SELECT id INTO annual_leave_id FROM public.leave_types WHERE name = 'Yıllık İzin' LIMIT 1;
UPDATE public.leave_balances SET leave_type_id = annual_leave_id WHERE leave_type_id IS NULL;
UPDATE public.leave_requests SET leave_type_id = annual_leave_id WHERE leave_type_id IS NULL;
END $$;
-- Now add NOT NULL and UNIQUE constraint
ALTER TABLE public.leave_balances ALTER COLUMN leave_type_id SET NOT NULL;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'leave_balances_employee_type_year_unique') THEN
ALTER TABLE public.leave_balances ADD CONSTRAINT leave_balances_employee_type_year_unique UNIQUE(employee_id, leave_type_id, year);
END IF;
END $$;
-- 5. Helper Function: Calculate Leave Days
CREATE OR REPLACE FUNCTION public.calculate_leave_days(p_start_date DATE, p_end_date DATE)
RETURNS DECIMAL AS $$
DECLARE
curr_date DATE;
total_days DECIMAL := 0;
is_holiday BOOLEAN;
BEGIN
curr_date := p_start_date;
WHILE curr_date <= p_end_date LOOP
-- Sunday (0) is never counts.
-- Saturday logic: Depends on company, but generally counts in law.
-- We'll exclude Sundays and Public Holidays.
IF EXTRACT(DOW FROM curr_date) != 0 THEN
SELECT EXISTS(
SELECT 1 FROM public.public_holidays
WHERE (is_recurring AND EXTRACT(MONTH FROM date) = EXTRACT(MONTH FROM curr_date) AND EXTRACT(DAY FROM date) = EXTRACT(DAY FROM curr_date))
OR (NOT is_recurring AND date = curr_date)
) INTO is_holiday;
IF NOT is_holiday THEN
total_days := total_days + 1;
END IF;
END IF;
curr_date := curr_date + 1;
END LOOP;
RETURN total_days;
END;
$$ LANGUAGE plpgsql;
-- 6. Trigger to auto-calculate total_days on leave_requests
CREATE OR REPLACE FUNCTION public.handle_leave_request_days()
RETURNS TRIGGER AS $$
BEGIN
NEW.total_days := public.calculate_leave_days(NEW.start_date, NEW.end_date);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS tr_calculate_leave_days ON public.leave_requests;
CREATE TRIGGER tr_calculate_leave_days
BEFORE INSERT OR UPDATE OF start_date, end_date ON public.leave_requests
FOR EACH ROW EXECUTE PROCEDURE public.handle_leave_request_days();
-- 7. Seniority based Accrual Function
CREATE OR REPLACE FUNCTION public.calculate_annual_leave_entitlement(p_employee_id UUID)
RETURNS INTEGER AS $$
DECLARE
v_hire_date DATE;
v_years_service INTEGER;
BEGIN
SELECT COALESCE(start_date, hire_date, created_at::date) INTO v_hire_date
FROM public.employees WHERE id = p_employee_id;
v_years_service := EXTRACT(YEAR FROM age(CURRENT_DATE, v_hire_date));
IF v_years_service < 1 THEN RETURN 0; -- No leave in first year
ELSIF v_years_service < 5 THEN RETURN 14;
ELSIF v_years_service < 15 THEN RETURN 20;
ELSE RETURN 26;
END IF;
END;
$$ LANGUAGE plpgsql;
-- 8. Updated Balance Trigger
-- We need to replace handle_new_employee_balance to handle new logic
CREATE OR REPLACE FUNCTION public.handle_new_employee_balance()
RETURNS TRIGGER AS $$
DECLARE
annual_leave_id UUID;
BEGIN
SELECT id INTO annual_leave_id FROM public.leave_types WHERE name = 'Yıllık İzin' LIMIT 1;
INSERT INTO public.leave_balances (employee_id, leave_type_id, year, accrued_days, total_days)
VALUES (NEW.id, annual_leave_id, EXTRACT(YEAR FROM CURRENT_DATE), 0, 0)
ON CONFLICT DO NOTHING;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 9. Function to update balance based on leave request status
CREATE OR REPLACE FUNCTION public.update_leave_balance_on_status_change()
RETURNS TRIGGER AS $$
DECLARE
v_is_deductible BOOLEAN;
v_annual_leave_id UUID;
v_year INTEGER;
BEGIN
SELECT is_deductible INTO v_is_deductible FROM public.leave_types WHERE id = NEW.leave_type_id;
IF v_is_deductible THEN
v_year := EXTRACT(YEAR FROM NEW.start_date);
-- Ensure balance record exists for this year/type
INSERT INTO public.leave_balances (employee_id, leave_type_id, year)
VALUES (NEW.employee_id, NEW.leave_type_id, v_year)
ON CONFLICT (employee_id, leave_type_id, year) DO NOTHING;
-- Update based on status
IF NEW.status = 'approved' AND (OLD.status IS NULL OR OLD.status != 'approved') THEN
UPDATE public.leave_balances
SET used_days = used_days + NEW.total_days,
pending_days = CASE WHEN OLD.status = 'pending' THEN pending_days - NEW.total_days ELSE pending_days END
WHERE employee_id = NEW.employee_id AND leave_type_id = NEW.leave_type_id AND year = v_year;
ELSIF NEW.status = 'pending' AND (OLD.status IS NULL OR OLD.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;
ELSIF (NEW.status = 'rejected' OR NEW.status = 'cancelled') AND OLD.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;
ELSIF (NEW.status = 'cancelled') AND OLD.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;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 10. Update RLS for leave_balances (ensure proper access)
ALTER TABLE public.leave_balances ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Admins can do everything on leave_balances" ON public.leave_balances;
CREATE POLICY "Admins can do everything on leave_balances"
ON public.leave_balances
FOR ALL TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.employees e
JOIN public.roles r ON e.role_id = r.id
WHERE e.user_id = auth.uid() AND r.name IN ('admin', 'manager')
)
);
DROP POLICY IF EXISTS "Users can view their own leave balance" ON public.leave_balances;
CREATE POLICY "Users can view their own leave balance"
ON public.leave_balances
FOR SELECT TO authenticated
USING (
employee_id IN (
SELECT id FROM public.employees WHERE user_id = auth.uid()
)
);
DROP TRIGGER IF EXISTS tr_update_leave_balance ON public.leave_requests;
CREATE TRIGGER tr_update_leave_balance
AFTER INSERT OR UPDATE OF status ON public.leave_requests
FOR EACH ROW EXECUTE PROCEDURE public.update_leave_balance_on_status_change();