İzin listesi,yetkilendirme vb

This commit is contained in:
2026-03-20 02:30:35 +03:00
parent b34623350e
commit 015caea52e
13 changed files with 1601 additions and 386 deletions
@@ -0,0 +1,262 @@
-- 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();
@@ -0,0 +1,59 @@
-- Fix Leave Requests RLS to allow managers to approve/reject
-- Enable UPDATE for managers belonging to the same company as the request
DROP POLICY IF EXISTS "Managers can update leave requests in their company" ON public.leave_requests;
CREATE POLICY "Managers can update leave requests in their company"
ON public.leave_requests
FOR UPDATE TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.employees manager_emp
JOIN public.roles r ON manager_emp.role_id = r.id
WHERE manager_emp.user_id = auth.uid()
AND r.name IN ('admin', 'manager')
AND manager_emp.company_id = (
SELECT company_id FROM public.employees target_emp
WHERE target_emp.id = public.leave_requests.employee_id
)
)
)
WITH CHECK (
EXISTS (
SELECT 1 FROM public.employees manager_emp
JOIN public.roles r ON manager_emp.role_id = r.id
WHERE manager_emp.user_id = auth.uid()
AND r.name IN ('admin', 'manager')
AND manager_emp.company_id = (
SELECT company_id FROM public.employees target_emp
WHERE target_emp.id = public.leave_requests.employee_id
)
)
);
-- Ensure managers can also view everything in their company
DROP POLICY IF EXISTS "Managers can view all leave requests in their company" ON public.leave_requests;
CREATE POLICY "Managers can view all leave requests in their company"
ON public.leave_requests
FOR SELECT TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.employees manager_emp
JOIN public.roles r ON manager_emp.role_id = r.id
WHERE manager_emp.user_id = auth.uid()
AND r.name IN ('admin', 'manager')
AND manager_emp.company_id = (
SELECT company_id FROM public.employees target_emp
WHERE target_emp.id = public.leave_requests.employee_id
)
)
OR
employee_id IN (
SELECT id FROM public.employees WHERE user_id = auth.uid()
)
);
-- Update existing records with NULL total_days
UPDATE public.leave_requests
SET total_days = public.calculate_leave_days(start_date, end_date)
WHERE total_days IS NULL;
@@ -0,0 +1,69 @@
-- 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();