feat: agregar control de visibilidad del total tarifado
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
-- =========================================================
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user