408 lines
14 KiB
PL/PgSQL
408 lines
14 KiB
PL/PgSQL
-- Cruce de Cuentas GLM
|
|
-- Equivalencias validadas para falsos positivos de "Banco sin BambooHR"
|
|
-- Ejecutar en el SQL Editor del Supabase empresarial.
|
|
-- Script idempotente: puede ejecutarse nuevamente sin borrar datos existentes.
|
|
|
|
begin;
|
|
|
|
create extension if not exists pgcrypto;
|
|
|
|
create or replace function public.cruce_cuentas_normalizar_nombre(p_value text)
|
|
returns text
|
|
language sql
|
|
immutable
|
|
strict
|
|
as $$
|
|
select trim(
|
|
regexp_replace(
|
|
translate(
|
|
lower(coalesce(p_value, '')),
|
|
'áàäâãåéèëêíìïîóòöôõúùüûñçÁÀÄÂÃÅÉÈËÊÍÌÏÎÓÒÖÔÕÚÙÜÛÑÇ',
|
|
'aaaaaaeeeeiiiiooooouuuuncAAAAAAEEEEIIIIOOOOOUUUUNC'
|
|
),
|
|
'[^a-z0-9]+',
|
|
' ',
|
|
'g'
|
|
)
|
|
);
|
|
$$;
|
|
|
|
create table if not exists public.cruce_cuentas_bamboo_aliases (
|
|
id uuid primary key default gen_random_uuid(),
|
|
pais text not null check (pais in ('GT', 'TT')),
|
|
nombre_origen text not null,
|
|
nombre_origen_normalizado text not null,
|
|
cuenta_bancaria text not null default '',
|
|
bamboo_employee_number text,
|
|
bamboo_employee_id text,
|
|
bamboo_nombre text not null,
|
|
motivo text,
|
|
comentario text,
|
|
activo boolean not null default true,
|
|
validado_por_email text not null,
|
|
validado_por_nombre text,
|
|
last_reported_at timestamptz not null default now(),
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
-- Compatibilidad si una versión anterior del script creó la tabla sin cuenta.
|
|
alter table public.cruce_cuentas_bamboo_aliases
|
|
add column if not exists cuenta_bancaria text not null default '';
|
|
|
|
drop index if exists public.ux_cruce_cuentas_bamboo_aliases_pais_nombre;
|
|
|
|
create unique index if not exists ux_cruce_cuentas_bamboo_aliases_pais_nombre_cuenta
|
|
on public.cruce_cuentas_bamboo_aliases (pais, nombre_origen_normalizado, cuenta_bancaria);
|
|
|
|
create index if not exists ix_cruce_cuentas_bamboo_aliases_employee_number
|
|
on public.cruce_cuentas_bamboo_aliases (pais, bamboo_employee_number)
|
|
where activo = true and bamboo_employee_number is not null;
|
|
|
|
create table if not exists public.cruce_cuentas_bamboo_correcciones (
|
|
id uuid primary key default gen_random_uuid(),
|
|
pais text not null check (pais in ('GT', 'TT')),
|
|
nombre_origen_principal text not null,
|
|
nombres_origen text[] not null default array[]::text[],
|
|
cuenta_bancaria text,
|
|
monto_banco numeric(18,2),
|
|
moneda text,
|
|
bamboo_employee_number text,
|
|
bamboo_employee_id text,
|
|
bamboo_nombre text not null,
|
|
motivo text not null,
|
|
comentario text,
|
|
execution_id text,
|
|
report_url text,
|
|
period_label text,
|
|
period_start date,
|
|
period_end date,
|
|
solicitado_por_email text not null,
|
|
solicitado_por_nombre text,
|
|
destinatarios_rrhh text[] not null default array[]::text[],
|
|
alias_ids uuid[] not null default array[]::uuid[],
|
|
estado text not null default 'pendiente_notificacion'
|
|
check (estado in ('pendiente_notificacion', 'notificado_rrhh', 'corregido_bamboo', 'cancelado')),
|
|
correo_enviado boolean not null default false,
|
|
correo_enviado_at timestamptz,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists ix_cruce_cuentas_bamboo_correcciones_pais_created
|
|
on public.cruce_cuentas_bamboo_correcciones (pais, created_at desc);
|
|
|
|
create index if not exists ix_cruce_cuentas_bamboo_correcciones_estado
|
|
on public.cruce_cuentas_bamboo_correcciones (estado, created_at desc);
|
|
|
|
create or replace function public.cruce_cuentas_bamboo_set_normalized()
|
|
returns trigger
|
|
language plpgsql
|
|
set search_path = public
|
|
as $$
|
|
begin
|
|
new.pais := upper(trim(new.pais));
|
|
new.nombre_origen := trim(new.nombre_origen);
|
|
new.nombre_origen_normalizado := public.cruce_cuentas_normalizar_nombre(new.nombre_origen);
|
|
new.cuenta_bancaria := regexp_replace(coalesce(new.cuenta_bancaria, ''), '[^0-9A-Za-z]+', '', 'g');
|
|
new.validado_por_email := lower(trim(new.validado_por_email));
|
|
new.updated_at := now();
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
drop trigger if exists trg_cruce_cuentas_bamboo_alias_normalized
|
|
on public.cruce_cuentas_bamboo_aliases;
|
|
|
|
create trigger trg_cruce_cuentas_bamboo_alias_normalized
|
|
before insert or update on public.cruce_cuentas_bamboo_aliases
|
|
for each row execute function public.cruce_cuentas_bamboo_set_normalized();
|
|
|
|
create or replace function public.cruce_cuentas_bamboo_touch_updated_at()
|
|
returns trigger
|
|
language plpgsql
|
|
set search_path = public
|
|
as $$
|
|
begin
|
|
new.updated_at := now();
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
drop trigger if exists trg_cruce_cuentas_bamboo_correcciones_updated_at
|
|
on public.cruce_cuentas_bamboo_correcciones;
|
|
|
|
create trigger trg_cruce_cuentas_bamboo_correcciones_updated_at
|
|
before update on public.cruce_cuentas_bamboo_correcciones
|
|
for each row execute function public.cruce_cuentas_bamboo_touch_updated_at();
|
|
|
|
-- RPC consumida únicamente por los workflows generadores de reportes.
|
|
-- Siempre devuelve un objeto con aliases=[], incluso cuando no existen registros.
|
|
create or replace function public.cruce_cuentas_bamboo_aliases_activos(p_pais text)
|
|
returns jsonb
|
|
language sql
|
|
stable
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
select jsonb_build_object(
|
|
'ok', true,
|
|
'pais', upper(trim(coalesce(p_pais, ''))),
|
|
'aliases', coalesce(
|
|
jsonb_agg(
|
|
jsonb_build_object(
|
|
'id', a.id,
|
|
'pais', a.pais,
|
|
'nombre_origen', a.nombre_origen,
|
|
'nombre_origen_normalizado', a.nombre_origen_normalizado,
|
|
'cuenta_bancaria', a.cuenta_bancaria,
|
|
'bamboo_employee_number', a.bamboo_employee_number,
|
|
'bamboo_employee_id', a.bamboo_employee_id,
|
|
'bamboo_nombre', a.bamboo_nombre,
|
|
'motivo', a.motivo,
|
|
'activo', a.activo
|
|
)
|
|
order by a.updated_at desc
|
|
) filter (where a.id is not null),
|
|
'[]'::jsonb
|
|
)
|
|
)
|
|
from public.cruce_cuentas_bamboo_aliases a
|
|
where a.activo = true
|
|
and a.pais = upper(trim(coalesce(p_pais, '')))
|
|
and upper(trim(coalesce(p_pais, ''))) in ('GT', 'TT');
|
|
$$;
|
|
|
|
alter table public.cruce_cuentas_bamboo_aliases enable row level security;
|
|
alter table public.cruce_cuentas_bamboo_correcciones enable row level security;
|
|
|
|
-- El navegador nunca escribe ni lee estas tablas directamente.
|
|
-- Toda operación pasa por n8n con service_role.
|
|
revoke all on table public.cruce_cuentas_bamboo_aliases from anon, authenticated;
|
|
revoke all on table public.cruce_cuentas_bamboo_correcciones from anon, authenticated;
|
|
grant all on table public.cruce_cuentas_bamboo_aliases to service_role;
|
|
grant all on table public.cruce_cuentas_bamboo_correcciones to service_role;
|
|
|
|
revoke all on function public.cruce_cuentas_bamboo_aliases_activos(text) from public, anon, authenticated;
|
|
grant execute on function public.cruce_cuentas_bamboo_aliases_activos(text) to service_role;
|
|
|
|
-- ============================================================
|
|
-- HISTÓRICOS: conservar y recuperar Banco sin Bamboo por reporte
|
|
-- ============================================================
|
|
|
|
-- Los workflows ya envían el detalle de cada caso. Esta columna hace que el
|
|
-- detalle quede persistido y pueda abrirse días después desde Históricos.
|
|
alter table public.cruces_cuentas_gt_reportes
|
|
add column if not exists detalle_banco_sin_bamboo jsonb not null default '[]'::jsonb;
|
|
|
|
-- Identificador generado por el navegador para trazabilidad de una ejecución.
|
|
-- No se usa como llave única porque no se reintenta automáticamente un POST
|
|
-- de conciliación (evita duplicar reportes, correos o Sheets).
|
|
alter table public.cruces_cuentas_gt_reportes
|
|
add column if not exists client_request_id text;
|
|
|
|
update public.cruces_cuentas_gt_reportes
|
|
set detalle_banco_sin_bamboo = '[]'::jsonb
|
|
where detalle_banco_sin_bamboo is null;
|
|
|
|
create index if not exists ix_cruces_cuentas_reportes_client_request
|
|
on public.cruces_cuentas_gt_reportes (client_request_id)
|
|
where client_request_id is not null;
|
|
|
|
create index if not exists ix_cruce_cuentas_bamboo_correcciones_execution
|
|
on public.cruce_cuentas_bamboo_correcciones (execution_id, created_at desc)
|
|
where execution_id is not null;
|
|
|
|
-- Reemplaza la RPC histórica manteniendo exactamente la misma firma y
|
|
-- estructura pública, pero excluye detalle_banco_sin_bamboo de la lista para
|
|
-- no descargar todos los casos cada vez que se abre Históricos. El detalle se
|
|
-- solicita únicamente cuando el usuario pulsa Banco sin Bamboo.
|
|
create or replace function public.cruce_cuentas_get_historicos(
|
|
p_country text,
|
|
p_page integer default 1,
|
|
p_page_size integer default 10,
|
|
p_search text default null,
|
|
p_status text default null
|
|
)
|
|
returns jsonb
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
declare
|
|
v_country text := upper(trim(coalesce(p_country, ''));
|
|
v_page integer := greatest(coalesce(p_page, 1), 1);
|
|
v_page_size integer := least(greatest(coalesce(p_page_size, 10), 1), 100);
|
|
v_offset integer;
|
|
v_search text := nullif(trim(coalesce(p_search, '')), '');
|
|
v_status text := nullif(trim(coalesce(p_status, '')), '');
|
|
v_total bigint;
|
|
v_filtered_total bigint;
|
|
v_reports jsonb;
|
|
begin
|
|
if v_country not in ('GT', 'TT') then
|
|
raise exception 'País no permitido: %', v_country;
|
|
end if;
|
|
|
|
v_offset := (v_page - 1) * v_page_size;
|
|
|
|
select count(*)
|
|
into v_total
|
|
from public.cruces_cuentas_gt_reportes r
|
|
where upper(coalesce(r.country, 'GT')) = v_country;
|
|
|
|
select count(*)
|
|
into v_filtered_total
|
|
from public.cruces_cuentas_gt_reportes r
|
|
where upper(coalesce(r.country, 'GT')) = v_country
|
|
and (v_status is null or r.estado = v_status)
|
|
and (
|
|
v_search is null
|
|
or coalesce(r.period_label, '') ilike '%' || v_search || '%'
|
|
or coalesce(r.payroll_file_name, '') ilike '%' || v_search || '%'
|
|
or coalesce(r.ejecutado_por_nombre, '') ilike '%' || v_search || '%'
|
|
or coalesce(r.resuelto_por_nombre, '') ilike '%' || v_search || '%'
|
|
or r.id::text ilike '%' || v_search || '%'
|
|
);
|
|
|
|
select coalesce(
|
|
jsonb_agg(
|
|
(to_jsonb(q) - 'detalle_banco_sin_bamboo')
|
|
order by q.created_at desc
|
|
),
|
|
'[]'::jsonb
|
|
)
|
|
into v_reports
|
|
from (
|
|
select r.*
|
|
from public.cruces_cuentas_gt_reportes r
|
|
where upper(coalesce(r.country, 'GT')) = v_country
|
|
and (v_status is null or r.estado = v_status)
|
|
and (
|
|
v_search is null
|
|
or coalesce(r.period_label, '') ilike '%' || v_search || '%'
|
|
or coalesce(r.payroll_file_name, '') ilike '%' || v_search || '%'
|
|
or coalesce(r.ejecutado_por_nombre, '') ilike '%' || v_search || '%'
|
|
or coalesce(r.resuelto_por_nombre, '') ilike '%' || v_search || '%'
|
|
or r.id::text ilike '%' || v_search || '%'
|
|
)
|
|
order by r.created_at desc
|
|
limit v_page_size
|
|
offset v_offset
|
|
) q;
|
|
|
|
return jsonb_build_object(
|
|
'ok', true,
|
|
'country', v_country,
|
|
'page', v_page,
|
|
'page_size', v_page_size,
|
|
'total', v_total,
|
|
'filtered_total', v_filtered_total,
|
|
'total_pages', greatest(ceil(v_filtered_total::numeric / v_page_size)::integer, 1),
|
|
'reports', v_reports
|
|
);
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function public.cruce_cuentas_get_historicos(text, integer, integer, text, text) from public, anon;
|
|
grant execute on function public.cruce_cuentas_get_historicos(text, integer, integer, text, text) to authenticated;
|
|
|
|
-- RPC de detalle utilizada por Históricos al abrir un reporte específico.
|
|
-- No otorga SELECT directo sobre las tablas de aliases/correcciones.
|
|
create or replace function public.cruce_cuentas_get_banco_sin_bamboo_reporte(
|
|
p_report_id text,
|
|
p_country text
|
|
)
|
|
returns jsonb
|
|
language plpgsql
|
|
stable
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
declare
|
|
v_country text := upper(trim(coalesce(p_country, ''));
|
|
v_report public.cruces_cuentas_gt_reportes%rowtype;
|
|
v_corrections jsonb := '[]'::jsonb;
|
|
begin
|
|
if auth.uid() is null then
|
|
raise exception 'No autenticado.';
|
|
end if;
|
|
|
|
if v_country not in ('GT', 'TT') then
|
|
raise exception 'País no permitido: %', v_country;
|
|
end if;
|
|
|
|
select r.*
|
|
into v_report
|
|
from public.cruces_cuentas_gt_reportes r
|
|
where r.id::text = trim(coalesce(p_report_id, ''))
|
|
and upper(coalesce(r.country, 'GT')) = v_country
|
|
limit 1;
|
|
|
|
if not found then
|
|
return jsonb_build_object(
|
|
'ok', false,
|
|
'message', 'No se encontró el reporte solicitado.'
|
|
);
|
|
end if;
|
|
|
|
select coalesce(
|
|
jsonb_agg(
|
|
jsonb_build_object(
|
|
'id', c.id,
|
|
'pais', c.pais,
|
|
'nombre_origen_principal', c.nombre_origen_principal,
|
|
'nombres_origen', c.nombres_origen,
|
|
'cuenta_bancaria', c.cuenta_bancaria,
|
|
'bamboo_employee_number', c.bamboo_employee_number,
|
|
'bamboo_nombre', c.bamboo_nombre,
|
|
'estado', c.estado,
|
|
'correo_enviado', c.correo_enviado,
|
|
'correo_enviado_at', c.correo_enviado_at,
|
|
'created_at', c.created_at
|
|
)
|
|
order by c.created_at desc
|
|
),
|
|
'[]'::jsonb
|
|
)
|
|
into v_corrections
|
|
from public.cruce_cuentas_bamboo_correcciones c
|
|
where c.pais = v_country
|
|
and (
|
|
c.execution_id = v_report.id::text
|
|
or (
|
|
nullif(trim(coalesce(c.execution_id, '')), '') is null
|
|
and nullif(trim(coalesce(c.report_url, '')), '') is not null
|
|
and c.report_url = v_report.report_url
|
|
)
|
|
);
|
|
|
|
return jsonb_build_object(
|
|
'ok', true,
|
|
'report', jsonb_build_object(
|
|
'id', v_report.id,
|
|
'country', v_report.country,
|
|
'period_label', v_report.period_label,
|
|
'period_start', v_report.period_start,
|
|
'period_end', v_report.period_end,
|
|
'report_url', v_report.report_url,
|
|
'banco_sin_bamboo', v_report.banco_sin_bamboo
|
|
),
|
|
'cases', coalesce(v_report.detalle_banco_sin_bamboo, '[]'::jsonb),
|
|
'corrections', v_corrections
|
|
);
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function public.cruce_cuentas_get_banco_sin_bamboo_reporte(text, text) from public, anon;
|
|
grant execute on function public.cruce_cuentas_get_banco_sin_bamboo_reporte(text, text) to authenticated;
|
|
|
|
commit;
|
|
|
|
-- Validaciones opcionales después de ejecutar:
|
|
-- select public.cruce_cuentas_bamboo_aliases_activos('GT');
|
|
-- select public.cruce_cuentas_bamboo_aliases_activos('TT');
|
|
-- select column_name, data_type from information_schema.columns
|
|
-- where table_schema='public' and table_name='cruces_cuentas_gt_reportes'
|
|
-- and column_name in ('detalle_banco_sin_bamboo', 'client_request_id');
|