431 lines
15 KiB
PL/PgSQL
431 lines
15 KiB
PL/PgSQL
-- =========================================================
|
|
-- TABLERO CDC - Catálogo dinámico de tarifario + guardado seguro de links + RPC de totales
|
|
-- Seguro para producción: no borra proyectos, no altera Google Sheets, no cambia n8n.
|
|
-- La fuente de mantenimiento del tarifario será Google Sheets; n8n sincroniza hacia esta tabla.
|
|
-- =========================================================
|
|
|
|
-- 1) Catálogo dinámico de tarifas estándar.
|
|
-- La app mantiene el tarifario base como respaldo. Esta tabla es el destino normalizado
|
|
-- que n8n llenará leyendo el Google Sheet del tarifario. La app solo consume esta tabla.
|
|
create table if not exists public.tablero_cdc_tariff_catalog (
|
|
id text primary key,
|
|
catalog_item_id text not null,
|
|
section text not null check (section in ('grafico', 'estrategia')),
|
|
category text not null default '',
|
|
service text not null default '',
|
|
notes text not null default '',
|
|
work_type_id text not null default 'reference',
|
|
work_type_label text not null default 'Referencia',
|
|
work_type_short_label text not null default 'Ref.',
|
|
hour_reference text not null default '',
|
|
level_id text not null default 'project',
|
|
level_label text not null default 'Precio único',
|
|
reference text not null default '',
|
|
reference_min numeric,
|
|
reference_max numeric,
|
|
level_hint text not null default '',
|
|
sort_order integer not null default 9999,
|
|
is_active boolean not null default true,
|
|
created_by uuid references auth.users(id),
|
|
updated_by uuid references auth.users(id),
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists tablero_cdc_tariff_catalog_section_idx
|
|
on public.tablero_cdc_tariff_catalog(section, is_active, sort_order);
|
|
|
|
create index if not exists tablero_cdc_tariff_catalog_item_idx
|
|
on public.tablero_cdc_tariff_catalog(catalog_item_id);
|
|
|
|
alter table public.tablero_cdc_tariff_catalog enable row level security;
|
|
|
|
drop policy if exists "tablero_cdc_tariff_catalog_select_authenticated" on public.tablero_cdc_tariff_catalog;
|
|
drop policy if exists "tablero_cdc_tariff_catalog_insert_pricing_admins" on public.tablero_cdc_tariff_catalog;
|
|
drop policy if exists "tablero_cdc_tariff_catalog_update_pricing_admins" on public.tablero_cdc_tariff_catalog;
|
|
drop policy if exists "tablero_cdc_tariff_catalog_delete_pricing_admins" on public.tablero_cdc_tariff_catalog;
|
|
|
|
create policy "tablero_cdc_tariff_catalog_select_authenticated"
|
|
on public.tablero_cdc_tariff_catalog
|
|
for select
|
|
to authenticated
|
|
using (true);
|
|
|
|
create policy "tablero_cdc_tariff_catalog_insert_pricing_admins"
|
|
on public.tablero_cdc_tariff_catalog
|
|
for insert
|
|
to authenticated
|
|
with check (
|
|
lower(coalesce(auth.jwt() ->> 'email', '')) in (
|
|
'gmarrero@gomezleemarketing.com',
|
|
'iaracena@gomezleemarketing.com'
|
|
)
|
|
);
|
|
|
|
create policy "tablero_cdc_tariff_catalog_update_pricing_admins"
|
|
on public.tablero_cdc_tariff_catalog
|
|
for update
|
|
to authenticated
|
|
using (
|
|
lower(coalesce(auth.jwt() ->> 'email', '')) in (
|
|
'gmarrero@gomezleemarketing.com',
|
|
'iaracena@gomezleemarketing.com'
|
|
)
|
|
)
|
|
with check (
|
|
lower(coalesce(auth.jwt() ->> 'email', '')) in (
|
|
'gmarrero@gomezleemarketing.com',
|
|
'iaracena@gomezleemarketing.com'
|
|
)
|
|
);
|
|
|
|
-- No recomendamos borrar tarifas usadas históricamente. La app usa activar/desactivar.
|
|
-- Aun así dejamos delete restringido por si en pruebas se necesita limpiar un registro creado por error.
|
|
create policy "tablero_cdc_tariff_catalog_delete_pricing_admins"
|
|
on public.tablero_cdc_tariff_catalog
|
|
for delete
|
|
to authenticated
|
|
using (
|
|
lower(coalesce(auth.jwt() ->> 'email', '')) in (
|
|
'gmarrero@gomezleemarketing.com',
|
|
'iaracena@gomezleemarketing.com'
|
|
)
|
|
);
|
|
|
|
create or replace function public.set_tablero_cdc_tariff_catalog_updated_at()
|
|
returns trigger
|
|
language plpgsql
|
|
as $$
|
|
begin
|
|
new.updated_at = now();
|
|
new.updated_by = auth.uid();
|
|
if tg_op = 'INSERT' then
|
|
new.created_by = coalesce(new.created_by, auth.uid());
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
drop trigger if exists trg_tablero_cdc_tariff_catalog_updated_at on public.tablero_cdc_tariff_catalog;
|
|
|
|
create trigger trg_tablero_cdc_tariff_catalog_updated_at
|
|
before insert or update on public.tablero_cdc_tariff_catalog
|
|
for each row
|
|
execute function public.set_tablero_cdc_tariff_catalog_updated_at();
|
|
|
|
-- 2) Guardado seguro de links de Propuesta / Artes Finales.
|
|
-- La app llama esta función para mover links de un campo a otro en una sola operación transaccional.
|
|
-- Si cualquier insert falla, el delete también se revierte automáticamente.
|
|
create or replace function public.tablero_cdc_replace_project_links(
|
|
p_project_id uuid,
|
|
p_links jsonb default '[]'::jsonb
|
|
)
|
|
returns void
|
|
language plpgsql
|
|
security invoker
|
|
set search_path = public
|
|
as $$
|
|
begin
|
|
delete from public.tablero_cdc_project_links
|
|
where project_id = p_project_id;
|
|
|
|
insert into public.tablero_cdc_project_links (project_id, link_type, url, label)
|
|
select
|
|
p_project_id,
|
|
nullif(trim(link_type), ''),
|
|
trim(url),
|
|
nullif(trim(label), '')
|
|
from jsonb_to_recordset(coalesce(p_links, '[]'::jsonb)) as x(
|
|
link_type text,
|
|
url text,
|
|
label text
|
|
)
|
|
where nullif(trim(url), '') is not null;
|
|
end;
|
|
$$;
|
|
|
|
grant execute on function public.tablero_cdc_replace_project_links(uuid, jsonb) to authenticated;
|
|
|
|
|
|
-- 3) RPC paginado actualizado para que el monto interno solo viaje a Marrero e Isaac.
|
|
-- No cambia datos. Solo reemplaza la función de lectura si ya existe.
|
|
create or replace function public.tablero_cdc_get_projects_paginated(
|
|
p_tab text default 'todos',
|
|
p_search text default '',
|
|
p_page integer default 1,
|
|
p_page_size integer default 24,
|
|
p_country text default '',
|
|
p_brand text default '',
|
|
p_client text default '',
|
|
p_cm text default ''
|
|
)
|
|
returns jsonb
|
|
language sql
|
|
stable
|
|
security invoker
|
|
set search_path = public
|
|
as $$
|
|
with params as (
|
|
select
|
|
lower(coalesce(nullif(trim(p_tab), ''), 'todos')) as tab,
|
|
trim(coalesce(p_search, '')) as search_text,
|
|
greatest(coalesce(p_page, 1), 1) as page_number,
|
|
least(greatest(coalesce(p_page_size, 24), 1), 100) as page_size,
|
|
trim(coalesce(p_country, '')) as country_filter,
|
|
trim(coalesce(p_brand, '')) as brand_filter,
|
|
trim(coalesce(p_client, '')) as client_filter,
|
|
trim(coalesce(p_cm, '')) as cm_filter
|
|
),
|
|
base_filtered as (
|
|
select p.*
|
|
from public.tablero_cdc_projects p
|
|
cross join params prm
|
|
where
|
|
case
|
|
when prm.tab in ('abiertos', 'activos', 'active', 'open') then
|
|
coalesce(nullif(trim(p.status), ''), 'Activo') = 'Activo'
|
|
when prm.tab in ('cerrados', 'closed') then
|
|
coalesce(nullif(trim(p.status), ''), 'Activo') <> 'Activo'
|
|
else
|
|
true
|
|
end
|
|
and (
|
|
prm.search_text = ''
|
|
or coalesce(p.title, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.client, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.brand, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.country, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.requested_by, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.country_manager, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.description, '') ilike '%' || prm.search_text || '%'
|
|
)
|
|
),
|
|
filtered as (
|
|
select bf.*
|
|
from base_filtered bf
|
|
cross join params prm
|
|
where
|
|
(prm.country_filter = '' or coalesce(bf.country, '') ilike '%' || prm.country_filter || '%')
|
|
and (prm.brand_filter = '' or coalesce(bf.brand, '') ilike '%' || prm.brand_filter || '%')
|
|
and (prm.client_filter = '' or coalesce(bf.client, '') ilike '%' || prm.client_filter || '%')
|
|
and (prm.cm_filter = '' or coalesce(bf.country_manager, '') ilike '%' || prm.cm_filter || '%')
|
|
),
|
|
total as (
|
|
select count(*)::integer as total_count
|
|
from filtered
|
|
),
|
|
paged as (
|
|
select f.*
|
|
from filtered f
|
|
cross join params prm
|
|
order by f.created_at desc nulls last, f.id desc
|
|
limit (select page_size from params)
|
|
offset ((select page_number - 1 from params) * (select page_size from params))
|
|
),
|
|
option_values as (
|
|
select
|
|
coalesce(
|
|
(
|
|
select jsonb_agg(value order by value)
|
|
from (
|
|
select distinct trim(country) as value
|
|
from base_filtered
|
|
where nullif(trim(coalesce(country, '')), '') is not null
|
|
) countries
|
|
),
|
|
'[]'::jsonb
|
|
) as countries,
|
|
coalesce(
|
|
(
|
|
select jsonb_agg(value order by value)
|
|
from (
|
|
select distinct trim(brand) as value
|
|
from base_filtered
|
|
where nullif(trim(coalesce(brand, '')), '') is not null
|
|
) brands
|
|
),
|
|
'[]'::jsonb
|
|
) as brands,
|
|
coalesce(
|
|
(
|
|
select jsonb_agg(value order by value)
|
|
from (
|
|
select distinct trim(client) as value
|
|
from base_filtered
|
|
where nullif(trim(coalesce(client, '')), '') is not null
|
|
) clients
|
|
),
|
|
'[]'::jsonb
|
|
) as clients,
|
|
coalesce(
|
|
(
|
|
select jsonb_agg(value order by value)
|
|
from (
|
|
select distinct trim(country_manager) as value
|
|
from base_filtered
|
|
where nullif(trim(coalesce(country_manager, '')), '') is not null
|
|
) cms
|
|
),
|
|
'[]'::jsonb
|
|
) as cms
|
|
)
|
|
select jsonb_build_object(
|
|
'total_count', (select total_count from total),
|
|
'filter_options', jsonb_build_object(
|
|
'countries', (select countries from option_values),
|
|
'brands', (select brands from option_values),
|
|
'clients', (select clients from option_values),
|
|
'cms', (select cms from option_values)
|
|
),
|
|
'projects', coalesce(
|
|
(
|
|
select jsonb_agg(
|
|
jsonb_build_object(
|
|
'id', p.id,
|
|
'title', p.title,
|
|
'client', p.client,
|
|
'brand', p.brand,
|
|
'country', p.country,
|
|
'requested_by', p.requested_by,
|
|
'country_manager', p.country_manager,
|
|
'description', p.description,
|
|
'status', p.status,
|
|
'internal_amount', case
|
|
when lower(coalesce(auth.jwt() ->> 'email', '')) in (
|
|
'gmarrero@gomezleemarketing.com',
|
|
'iaracena@gomezleemarketing.com'
|
|
)
|
|
then to_jsonb(p.internal_amount)
|
|
else 'null'::jsonb
|
|
end,
|
|
'brief_link', p.brief_link,
|
|
'created_at', p.created_at,
|
|
'extra_data', p.extra_data,
|
|
'project_links', coalesce(
|
|
(
|
|
select jsonb_agg(
|
|
jsonb_build_object(
|
|
'id', l.id,
|
|
'link_type', l.link_type,
|
|
'url', l.url,
|
|
'label', l.label
|
|
)
|
|
order by l.label asc nulls last, l.id asc
|
|
)
|
|
from public.tablero_cdc_project_links l
|
|
where l.project_id = p.id
|
|
),
|
|
'[]'::jsonb
|
|
)
|
|
)
|
|
order by p.created_at desc nulls last, p.id desc
|
|
)
|
|
from paged p
|
|
),
|
|
'[]'::jsonb
|
|
)
|
|
);
|
|
$$;
|
|
|
|
grant execute on function public.tablero_cdc_get_projects_paginated(text, text, integer, integer, text, text, text, text) to authenticated;
|
|
|
|
-- 4) RPC de total tarifado global/filtrado.
|
|
-- Calcula en Supabase el total de Interno Cargado y la cantidad de proyectos según los mismos filtros de la pantalla.
|
|
-- Solo Marrero e Isaac reciben valores reales; otros usuarios reciben 0.
|
|
create or replace function public.tablero_cdc_get_pricing_summary(
|
|
p_tab text default 'todos',
|
|
p_search text default '',
|
|
p_country text default '',
|
|
p_brand text default '',
|
|
p_client text default '',
|
|
p_cm text default ''
|
|
)
|
|
returns jsonb
|
|
language sql
|
|
stable
|
|
security invoker
|
|
set search_path = public
|
|
as $$
|
|
with allowed as (
|
|
select lower(coalesce(auth.jwt() ->> 'email', '')) in (
|
|
'gmarrero@gomezleemarketing.com',
|
|
'iaracena@gomezleemarketing.com'
|
|
) as can_view_pricing
|
|
),
|
|
params as (
|
|
select
|
|
lower(coalesce(nullif(trim(p_tab), ''), 'todos')) as tab,
|
|
trim(coalesce(p_search, '')) as search_text,
|
|
trim(coalesce(p_country, '')) as country_filter,
|
|
trim(coalesce(p_brand, '')) as brand_filter,
|
|
trim(coalesce(p_client, '')) as client_filter,
|
|
trim(coalesce(p_cm, '')) as cm_filter
|
|
),
|
|
base_filtered as (
|
|
select p.*
|
|
from public.tablero_cdc_projects p
|
|
cross join params prm
|
|
where
|
|
case
|
|
when prm.tab in ('abiertos', 'activos', 'active', 'open') then
|
|
coalesce(nullif(trim(p.status), ''), 'Activo') = 'Activo'
|
|
when prm.tab in ('cerrados', 'closed') then
|
|
coalesce(nullif(trim(p.status), ''), 'Activo') <> 'Activo'
|
|
else
|
|
true
|
|
end
|
|
and (
|
|
prm.search_text = ''
|
|
or coalesce(p.title, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.client, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.brand, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.country, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.requested_by, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.country_manager, '') ilike '%' || prm.search_text || '%'
|
|
or coalesce(p.description, '') ilike '%' || prm.search_text || '%'
|
|
)
|
|
),
|
|
filtered as (
|
|
select bf.*
|
|
from base_filtered bf
|
|
cross join params prm
|
|
where
|
|
(prm.country_filter = '' or coalesce(bf.country, '') ilike '%' || prm.country_filter || '%')
|
|
and (prm.brand_filter = '' or coalesce(bf.brand, '') ilike '%' || prm.brand_filter || '%')
|
|
and (prm.client_filter = '' or coalesce(bf.client, '') ilike '%' || prm.client_filter || '%')
|
|
and (prm.cm_filter = '' or coalesce(bf.country_manager, '') ilike '%' || prm.cm_filter || '%')
|
|
),
|
|
totals as (
|
|
select
|
|
count(*)::integer as project_count,
|
|
coalesce(
|
|
sum(
|
|
greatest(
|
|
coalesce(nullif(trim(internal_amount::text), '')::numeric, 0),
|
|
0
|
|
)
|
|
),
|
|
0
|
|
)::numeric as total_amount
|
|
from filtered
|
|
)
|
|
select case
|
|
when (select can_view_pricing from allowed) then
|
|
jsonb_build_object(
|
|
'total_amount', (select total_amount from totals),
|
|
'project_count', (select project_count from totals)
|
|
)
|
|
else
|
|
jsonb_build_object(
|
|
'total_amount', 0,
|
|
'project_count', 0
|
|
)
|
|
end;
|
|
$$;
|
|
|
|
grant execute on function public.tablero_cdc_get_pricing_summary(text, text, text, text, text, text) to authenticated;
|
|
|
|
select
|
|
'tablero_cdc_tariff_catalog, safe links y RPC de totales listos' as status,
|
|
(select count(*) from public.tablero_cdc_tariff_catalog) as tarifas_dinamicas_actuales;
|