916ad39176
Deploy to route.crispygoat.com / deploy (push) Failing after 3m1s
- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject, deleteObject, presigned URL helpers, and BUCKETS constant - Wire product images, brand logos, and water log photos to MinIO via the new storage client - Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call) - Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge Function proxy) - Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass brand_settings.logo_url from all call sites - Update email templates to use dynamic logoUrl instead of hardcoded Supabase bucket URLs - Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage, TimeTrackingFieldClient; use brand_settings props + local public/ fallback - Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to public/ for local development - Add MinIO env vars to .env.example (endpoint, access key, secret, buckets) - Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props - Fix admin/users.ts logoUrl type (null → undefined for optional string) - Remove stale sb- cookie from wholesale-auth - Migrate tuxedo/about page to remove supabase import and use pool query for wholesale_settings lookup
116 lines
3.4 KiB
PL/PgSQL
116 lines
3.4 KiB
PL/PgSQL
-- Update time log RPC
|
|
CREATE OR REPLACE FUNCTION update_worker_time_log(
|
|
p_log_id uuid,
|
|
p_task_name text,
|
|
p_clock_in timestamptz,
|
|
p_clock_out timestamptz DEFAULT NULL,
|
|
p_lunch_minutes integer DEFAULT 0,
|
|
p_notes text DEFAULT NULL
|
|
) RETURNS json
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
AS $$
|
|
DECLARE
|
|
v_task_id uuid;
|
|
BEGIN
|
|
SELECT id INTO v_task_id FROM time_tracking_tasks WHERE name = p_task_name LIMIT 1;
|
|
|
|
UPDATE time_tracking_logs
|
|
SET task_id = v_task_id,
|
|
clock_in = p_clock_in,
|
|
clock_out = p_clock_out,
|
|
lunch_break_minutes = p_lunch_minutes,
|
|
notes = p_notes
|
|
WHERE id = p_log_id;
|
|
|
|
RETURN json_build_object('success', true);
|
|
END;
|
|
$$;
|
|
|
|
-- Delete time log RPC
|
|
CREATE OR REPLACE FUNCTION delete_worker_time_log(p_log_id uuid)
|
|
RETURNS json
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
AS $$
|
|
BEGIN
|
|
DELETE FROM time_tracking_logs WHERE id = p_log_id;
|
|
RETURN json_build_object('success', true);
|
|
END;
|
|
$$;
|
|
|
|
-- Get time tracking summary RPC
|
|
CREATE OR REPLACE FUNCTION get_time_tracking_summary(
|
|
p_brand_id uuid,
|
|
p_start timestamptz,
|
|
p_end timestamptz
|
|
) RETURNS json
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
AS $$
|
|
BEGIN
|
|
RETURN (
|
|
WITH worker_stats AS (
|
|
SELECT
|
|
l.worker_id,
|
|
w.name AS worker_name,
|
|
COUNT(*) AS entry_count,
|
|
SUM(
|
|
CASE
|
|
WHEN l.clock_out IS NOT NULL THEN
|
|
(EXTRACT(EPOCH FROM (l.clock_out - l.clock_in))::integer / 60) - l.lunch_break_minutes
|
|
ELSE 0
|
|
END
|
|
) AS total_minutes
|
|
FROM time_tracking_logs l
|
|
JOIN time_tracking_workers w ON w.id = l.worker_id
|
|
WHERE l.brand_id = p_brand_id
|
|
AND l.clock_in >= p_start
|
|
AND l.clock_in <= p_end
|
|
GROUP BY l.worker_id, w.name
|
|
),
|
|
task_stats AS (
|
|
SELECT
|
|
l.task_id,
|
|
t.name AS task_name,
|
|
t.name_es AS task_name_es,
|
|
COUNT(*) AS entry_count,
|
|
SUM(
|
|
CASE
|
|
WHEN l.clock_out IS NOT NULL THEN
|
|
(EXTRACT(EPOCH FROM (l.clock_out - l.clock_in))::integer / 60) - l.lunch_break_minutes
|
|
ELSE 0
|
|
END
|
|
) AS total_minutes
|
|
FROM time_tracking_logs l
|
|
LEFT JOIN time_tracking_tasks t ON t.id = l.task_id
|
|
WHERE l.brand_id = p_brand_id
|
|
AND l.clock_in >= p_start
|
|
AND l.clock_in <= p_end
|
|
GROUP BY l.task_id, t.name, t.name_es
|
|
),
|
|
totals AS (
|
|
SELECT
|
|
COUNT(*) AS entry_count,
|
|
COALESCE(SUM(
|
|
CASE
|
|
WHEN l.clock_out IS NOT NULL THEN
|
|
(EXTRACT(EPOCH FROM (l.clock_out - l.clock_in))::integer / 60) - l.lunch_break_minutes
|
|
ELSE 0
|
|
END
|
|
), 0) AS total_minutes,
|
|
COUNT(*) FILTER (WHERE l.clock_out IS NULL) AS open_count
|
|
FROM time_tracking_logs l
|
|
WHERE l.brand_id = p_brand_id
|
|
AND l.clock_in >= p_start
|
|
AND l.clock_in <= p_end
|
|
)
|
|
SELECT json_build_object(
|
|
'by_worker', (SELECT json_agg(json_build_object('id', worker_id, 'name', worker_name, 'entry_count', entry_count, 'total_hours', ROUND(total_minutes::numeric / 60, 2))) FROM worker_stats),
|
|
'by_task', (SELECT json_agg(json_build_object('id', task_id, 'name', task_name, 'name_es', task_name_es, 'entry_count', entry_count, 'total_hours', ROUND(total_minutes::numeric / 60, 2))) FROM task_stats),
|
|
'totals', (SELECT json_build_object('entry_count', entry_count, 'total_hours', ROUND(total_minutes::numeric / 60, 2), 'open_count', open_count) FROM totals)
|
|
)
|
|
);
|
|
END;
|
|
$$;
|