Files
route-commerce/supabase/migrations/140_time_tracking_field_rpcs.sql
T

264 lines
7.1 KiB
PL/PgSQL

-- verify_time_tracking_pin RPC
-- Looks up worker by brand_id + pin, creates a session token, returns worker info
CREATE OR REPLACE FUNCTION verify_time_tracking_pin(
p_brand_id uuid,
p_pin text
) RETURNS json
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_worker time_tracking_workers%ROWTYPE;
v_session_id text;
v_expires_at timestamptz;
BEGIN
SELECT * INTO v_worker
FROM time_tracking_workers
WHERE brand_id = p_brand_id AND pin = p_pin AND active = true
LIMIT 1;
IF v_worker IS NULL THEN
RETURN json_build_object('success', false, 'error', 'Invalid PIN');
END IF;
-- Update last_used_at
UPDATE time_tracking_workers SET last_used_at = now() WHERE id = v_worker.id;
-- Generate session
v_session_id := gen_random_uuid()::text;
v_expires_at := now() + interval '12 hours';
RETURN json_build_object(
'success', true,
'worker_id', v_worker.id,
'name', v_worker.name,
'role', v_worker.role,
'lang', v_worker.lang,
'brand_id', v_worker.brand_id,
'session_id', v_session_id,
'expires_at', v_expires_at
);
END;
$$;
-- clock_in_worker RPC
-- Creates a new time tracking log entry with clock_in set to now
CREATE OR REPLACE FUNCTION clock_in_worker(
p_brand_id uuid,
p_worker_id uuid,
p_task_id uuid DEFAULT NULL,
p_task_name text DEFAULT 'General Labor'
) RETURNS json
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_task_id uuid;
v_log_id uuid;
BEGIN
-- Resolve task_name to task_id if provided
IF p_task_id IS NULL AND p_task_name IS NOT NULL THEN
SELECT id INTO v_task_id FROM time_tracking_tasks WHERE brand_id = p_brand_id AND name = p_task_name LIMIT 1;
ELSE
v_task_id := p_task_id;
END IF;
-- Check for open clock-in
IF EXISTS (
SELECT 1 FROM time_tracking_logs
WHERE worker_id = p_worker_id AND clock_out IS NULL
) THEN
RETURN json_build_object('success', false, 'error', 'Already clocked in');
END IF;
-- Create log entry
INSERT INTO time_tracking_logs (brand_id, worker_id, task_id, clock_in, submitted_via)
VALUES (p_brand_id, p_worker_id, v_task_id, now(), 'kiosk')
RETURNING id INTO v_log_id;
RETURN json_build_object(
'success', true,
'log_id', v_log_id,
'clock_in', now()
);
END;
$$;
-- clock_out_worker RPC
-- Closes the open clock-in for a worker, calculates total minutes
CREATE OR REPLACE FUNCTION clock_out_worker(
p_worker_id uuid,
p_lunch_minutes integer DEFAULT 0,
p_notes text DEFAULT NULL
) RETURNS json
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_log time_tracking_logs%ROWTYPE;
v_total_minutes integer;
BEGIN
-- Find open clock-in
SELECT * INTO v_log
FROM time_tracking_logs
WHERE worker_id = p_worker_id AND clock_out IS NULL
ORDER BY clock_in DESC
LIMIT 1;
IF v_log IS NULL THEN
RETURN json_build_object('success', false, 'error', 'No open clock-in found');
END IF;
-- Calculate total minutes (excluding lunch)
v_total_minutes := (EXTRACT(EPOCH FROM (now() - v_log.clock_in))::integer / 60) - p_lunch_minutes;
IF v_total_minutes < 0 THEN v_total_minutes := 0; END IF;
-- Update log
UPDATE time_tracking_logs
SET clock_out = now(),
lunch_break_minutes = p_lunch_minutes,
notes = p_notes
WHERE id = v_log.id;
RETURN json_build_object(
'success', true,
'log_id', v_log.id,
'clock_out', now(),
'total_minutes', v_total_minutes
);
END;
$$;
-- get_open_clock_in RPC
-- Returns the open clock-in for a worker if exists
CREATE OR REPLACE FUNCTION get_open_clock_in(
p_worker_id uuid
) RETURNS json
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_log time_tracking_logs%ROWTYPE;
v_task_name text;
v_elapsed integer;
BEGIN
SELECT l.*, t.name AS task_name INTO v_log
FROM time_tracking_logs l
LEFT JOIN time_tracking_tasks t ON t.id = l.task_id
WHERE l.worker_id = p_worker_id AND l.clock_out IS NULL
ORDER BY l.clock_in DESC
LIMIT 1;
IF v_log IS NULL THEN
RETURN json_build_object('success', true, 'open', false);
END IF;
v_elapsed := (EXTRACT(EPOCH FROM (now() - v_log.clock_in))::integer / 60);
RETURN json_build_object(
'success', true,
'open', true,
'log_id', v_log.id,
'task_name', v_log.task_name,
'clock_in', v_log.clock_in,
'elapsed_minutes', v_elapsed
);
END;
$$;
-- get_worker_pay_period_hours RPC
-- Calculates hours for current pay period (bi-weekly, starting from brand's pay_period_start)
CREATE OR REPLACE FUNCTION get_worker_pay_period_hours(
p_brand_id uuid,
p_worker_id uuid
) RETURNS json
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_period_start timestamptz;
v_period_end timestamptz;
v_total_minutes integer;
v_daily_minutes integer;
v_weekly_minutes integer;
BEGIN
-- Default to bi-weekly from beginning of current month
v_period_start := date_trunc('month', now()) + ((EXTRACT(DOW FROM date_trunc('month', now()))::integer + 6) % 7 || ' days')::interval;
v_period_end := v_period_start + interval '13 days';
-- If we're past end of period, roll forward
WHILE v_period_end < now() LOOP
v_period_start := v_period_start + interval '14 days';
v_period_end := v_period_end + interval '14 days';
END LOOP;
-- Total minutes in period
SELECT COALESCE(SUM(
CASE
WHEN clock_out IS NOT NULL THEN
(EXTRACT(EPOCH FROM (clock_out - clock_in))::integer / 60) - lunch_break_minutes
ELSE 0
END
), 0) INTO v_total_minutes
FROM time_tracking_logs
WHERE worker_id = p_worker_id
AND clock_in >= v_period_start
AND clock_in <= v_period_end;
-- Daily minutes (today)
SELECT COALESCE(SUM(
CASE
WHEN clock_out IS NOT NULL THEN
(EXTRACT(EPOCH FROM (clock_out - clock_in))::integer / 60) - lunch_break_minutes
ELSE 0
END
), 0) INTO v_daily_minutes
FROM time_tracking_logs
WHERE worker_id = p_worker_id
AND clock_in >= date_trunc('day', now())
AND clock_in < date_trunc('day', now()) + interval '1 day';
-- Weekly minutes (current week starting Monday)
SELECT COALESCE(SUM(
CASE
WHEN clock_out IS NOT NULL THEN
(EXTRACT(EPOCH FROM (clock_out - clock_in))::integer / 60) - lunch_break_minutes
ELSE 0
END
), 0) INTO v_weekly_minutes
FROM time_tracking_logs
WHERE worker_id = p_worker_id
AND clock_in >= date_trunc('week', now())
AND clock_in < date_trunc('week', now()) + interval '7 days';
RETURN json_build_object(
'total_minutes', v_total_minutes,
'total_hours', ROUND(v_total_minutes::numeric / 60, 2),
'daily_minutes', v_daily_minutes,
'daily_hours', ROUND(v_daily_minutes::numeric / 60, 2),
'weekly_minutes', v_weekly_minutes,
'weekly_hours', ROUND(v_weekly_minutes::numeric / 60, 2),
'daily_overtime', v_daily_minutes > 720,
'weekly_overtime', v_weekly_minutes > 3360,
'period_start', v_period_start,
'period_end', v_period_end
);
END;
$$;
-- get_time_tracking_settings RPC
CREATE OR REPLACE FUNCTION get_time_tracking_settings(
p_brand_id uuid
) RETURNS json
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Returns default settings; brand_settings integration can extend this later
RETURN json_build_object(
'pay_period_type', 'biweekly',
'daily_overtime_threshold', 12,
'weekly_overtime_threshold', 56
);
END;
$$;