İzin listesi,yetkilendirme vb
This commit is contained in:
@@ -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();
|
||||
Reference in New Issue
Block a user