Actualiza frontend, integraciones y dist de producción
This commit is contained in:
@@ -930,3 +930,451 @@ commit;
|
||||
|
||||
-- Prueba de recordatorios de una fecha:
|
||||
-- select * from public.tax_due_reminders('2026-07-24'::date);
|
||||
|
||||
|
||||
-- ===========================================================================
|
||||
-- ACTUALIZACIÓN JULIO 2026: BambooHR, Google Calendar y fechas futuras
|
||||
-- ===========================================================================
|
||||
-- Seguimiento de Impuestos GLM
|
||||
-- Actualización: contactos BambooHR, edición ampliada, Google Calendar y bloqueo de fechas pasadas.
|
||||
-- Ejecutar una sola vez en Supabase SQL Editor antes de desplegar el frontend y activar los workflows.
|
||||
|
||||
begin;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Contactos: metadatos de BambooHR y tercer canal de notificación
|
||||
-- ---------------------------------------------------------------------------
|
||||
alter table public.tax_contacts
|
||||
add column if not exists department text,
|
||||
add column if not exists division text,
|
||||
add column if not exists location text,
|
||||
add column if not exists source text not null default 'manual',
|
||||
add column if not exists bamboo_employee_id text,
|
||||
add column if not exists bamboo_employee_number text,
|
||||
add column if not exists bamboo_status text,
|
||||
add column if not exists bamboo_synced_at timestamptz,
|
||||
add column if not exists calendar_enabled boolean not null default true;
|
||||
|
||||
alter table public.tax_contacts
|
||||
drop constraint if exists tax_contacts_area_check;
|
||||
|
||||
alter table public.tax_contacts
|
||||
add constraint tax_contacts_area_check
|
||||
check (area in ('admin', 'nomina', 'regional', 'other'));
|
||||
|
||||
update public.tax_contacts
|
||||
set calendar_enabled = false
|
||||
where email is null or length(trim(email)) <= 3;
|
||||
|
||||
alter table public.tax_contacts
|
||||
drop constraint if exists tax_contacts_calendar_email_when_enabled;
|
||||
|
||||
alter table public.tax_contacts
|
||||
add constraint tax_contacts_calendar_email_when_enabled
|
||||
check (not calendar_enabled or length(trim(coalesce(email, ''))) > 3);
|
||||
|
||||
create unique index if not exists tax_contacts_unique_active_bamboo_employee
|
||||
on public.tax_contacts (bamboo_employee_id)
|
||||
where active = true and bamboo_employee_id is not null;
|
||||
|
||||
create index if not exists tax_contacts_bamboo_employee_number_idx
|
||||
on public.tax_contacts (bamboo_employee_number)
|
||||
where bamboo_employee_number is not null;
|
||||
|
||||
-- Amplía la normalización existente para los nuevos campos.
|
||||
create or replace function public.tax_normalize_contact()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
new.full_name := trim(new.full_name);
|
||||
new.email := nullif(lower(trim(coalesce(new.email, ''))), '');
|
||||
new.whatsapp_number := nullif(regexp_replace(coalesce(new.whatsapp_number, ''), '[^0-9]', '', 'g'), '');
|
||||
new.job_title := nullif(trim(coalesce(new.job_title, '')), '');
|
||||
new.department := nullif(trim(coalesce(new.department, '')), '');
|
||||
new.division := nullif(trim(coalesce(new.division, '')), '');
|
||||
new.location := nullif(trim(coalesce(new.location, '')), '');
|
||||
new.source := coalesce(nullif(lower(trim(coalesce(new.source, ''))), ''), 'manual');
|
||||
new.bamboo_employee_id := nullif(trim(coalesce(new.bamboo_employee_id, '')), '');
|
||||
new.bamboo_employee_number := nullif(trim(coalesce(new.bamboo_employee_number, '')), '');
|
||||
new.bamboo_status := nullif(trim(coalesce(new.bamboo_status, '')), '');
|
||||
|
||||
if new.source = 'bamboohr' then
|
||||
if tg_op = 'INSERT' then
|
||||
new.bamboo_synced_at := now();
|
||||
elsif new.bamboo_employee_id is distinct from old.bamboo_employee_id
|
||||
or new.full_name is distinct from old.full_name
|
||||
or new.email is distinct from old.email
|
||||
or new.whatsapp_number is distinct from old.whatsapp_number then
|
||||
new.bamboo_synced_at := now();
|
||||
end if;
|
||||
end if;
|
||||
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- No permitir obligaciones nuevas o movidas a fechas pasadas
|
||||
-- ---------------------------------------------------------------------------
|
||||
create or replace function public.tax_validate_future_obligation()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
declare
|
||||
local_today date := (now() at time zone 'America/Santo_Domingo')::date;
|
||||
begin
|
||||
if new.active = true
|
||||
and new.is_historical = false
|
||||
and new.due_date < local_today then
|
||||
if tg_op = 'INSERT' then
|
||||
raise exception 'No se puede crear una obligación en una fecha que ya pasó.'
|
||||
using errcode = '22007';
|
||||
elsif new.due_date is distinct from old.due_date then
|
||||
raise exception 'No se puede mover una obligación a una fecha que ya pasó.'
|
||||
using errcode = '22007';
|
||||
end if;
|
||||
end if;
|
||||
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
drop trigger if exists tax_validate_future_obligation_trigger on public.tax_obligations;
|
||||
create trigger tax_validate_future_obligation_trigger
|
||||
before insert or update on public.tax_obligations
|
||||
for each row execute function public.tax_validate_future_obligation();
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Sincronización con Google Calendar
|
||||
-- Un evento de Google Calendar por obligación, con todos los destinatarios
|
||||
-- aplicables como invitados.
|
||||
-- ---------------------------------------------------------------------------
|
||||
create table if not exists public.tax_google_calendar_events (
|
||||
obligation_id uuid primary key references public.tax_obligations(id) on delete cascade,
|
||||
google_event_id text,
|
||||
calendar_id text not null default 'primary',
|
||||
attendees jsonb not null default '[]'::jsonb,
|
||||
status text not null default 'active'
|
||||
check (status in ('active', 'deleted', 'failed')),
|
||||
last_action text,
|
||||
provider_response jsonb,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
synced_at timestamptz
|
||||
);
|
||||
|
||||
alter table public.tax_google_calendar_events enable row level security;
|
||||
revoke all on table public.tax_google_calendar_events from anon, authenticated;
|
||||
grant select, insert, update, delete on table public.tax_google_calendar_events to service_role;
|
||||
|
||||
create or replace function public.tax_google_calendar_payload(p_obligation_id uuid)
|
||||
returns table (
|
||||
obligation_id uuid,
|
||||
active boolean,
|
||||
due_date date,
|
||||
description text,
|
||||
category text,
|
||||
country_id uuid,
|
||||
country_name text,
|
||||
country_code text,
|
||||
attendees jsonb,
|
||||
google_event_id text,
|
||||
calendar_id text,
|
||||
sync_status text
|
||||
)
|
||||
language sql
|
||||
stable
|
||||
security definer
|
||||
set search_path = ''
|
||||
as $$
|
||||
select
|
||||
obligation.id,
|
||||
obligation.active,
|
||||
obligation.due_date,
|
||||
obligation.description,
|
||||
obligation.category,
|
||||
obligation.country_id,
|
||||
country.name,
|
||||
country.code,
|
||||
coalesce(
|
||||
(
|
||||
select jsonb_agg(recipient.email order by recipient.email)
|
||||
from (
|
||||
select distinct lower(contact.email) as email
|
||||
from public.tax_contacts contact
|
||||
join public.tax_countries contact_country
|
||||
on contact_country.id = contact.country_id
|
||||
where contact.active = true
|
||||
and contact.calendar_enabled = true
|
||||
and contact.email is not null
|
||||
and length(trim(contact.email)) > 3
|
||||
and (
|
||||
contact.area = 'regional'
|
||||
or (
|
||||
contact_country.is_regional = true
|
||||
and contact.area = obligation.category
|
||||
)
|
||||
or (
|
||||
contact.country_id = obligation.country_id
|
||||
and contact.area = obligation.category
|
||||
)
|
||||
)
|
||||
) recipient
|
||||
),
|
||||
'[]'::jsonb
|
||||
) as attendees,
|
||||
sync.google_event_id,
|
||||
coalesce(sync.calendar_id, 'primary') as calendar_id,
|
||||
sync.status
|
||||
from public.tax_obligations obligation
|
||||
join public.tax_countries country on country.id = obligation.country_id
|
||||
left join public.tax_google_calendar_events sync
|
||||
on sync.obligation_id = obligation.id
|
||||
where obligation.id = p_obligation_id;
|
||||
$$;
|
||||
|
||||
revoke all on function public.tax_google_calendar_payload(uuid)
|
||||
from public, anon, authenticated;
|
||||
grant execute on function public.tax_google_calendar_payload(uuid)
|
||||
to service_role;
|
||||
|
||||
create or replace function public.tax_record_google_calendar_sync(
|
||||
p_obligation_id uuid,
|
||||
p_google_event_id text,
|
||||
p_calendar_id text default 'primary',
|
||||
p_attendees jsonb default '[]'::jsonb,
|
||||
p_status text default 'active',
|
||||
p_last_action text default null,
|
||||
p_provider_response jsonb default null
|
||||
)
|
||||
returns void
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
insert into public.tax_google_calendar_events (
|
||||
obligation_id,
|
||||
google_event_id,
|
||||
calendar_id,
|
||||
attendees,
|
||||
status,
|
||||
last_action,
|
||||
provider_response,
|
||||
synced_at,
|
||||
updated_at
|
||||
)
|
||||
values (
|
||||
p_obligation_id,
|
||||
nullif(trim(coalesce(p_google_event_id, '')), ''),
|
||||
coalesce(nullif(trim(coalesce(p_calendar_id, '')), ''), 'primary'),
|
||||
coalesce(p_attendees, '[]'::jsonb),
|
||||
p_status,
|
||||
p_last_action,
|
||||
p_provider_response,
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
on conflict (obligation_id)
|
||||
do update set
|
||||
google_event_id = coalesce(excluded.google_event_id, public.tax_google_calendar_events.google_event_id),
|
||||
calendar_id = excluded.calendar_id,
|
||||
attendees = excluded.attendees,
|
||||
status = excluded.status,
|
||||
last_action = excluded.last_action,
|
||||
provider_response = excluded.provider_response,
|
||||
synced_at = now(),
|
||||
updated_at = now();
|
||||
end;
|
||||
$$;
|
||||
|
||||
revoke all on function public.tax_record_google_calendar_sync(uuid, text, text, jsonb, text, text, jsonb)
|
||||
from public, anon, authenticated;
|
||||
grant execute on function public.tax_record_google_calendar_sync(uuid, text, text, jsonb, text, text, jsonb)
|
||||
to service_role;
|
||||
|
||||
commit;
|
||||
|
||||
-- Verificaciones opcionales:
|
||||
-- select * from public.tax_google_calendar_payload('UUID_DE_OBLIGACION');
|
||||
-- select * from public.tax_google_calendar_events order by updated_at desc;
|
||||
|
||||
|
||||
|
||||
-- Seguimiento de Impuestos GLM
|
||||
-- Actualización incremental: retirar WhatsApp y habilitar Google Chat.
|
||||
-- Ejecutar después de actualizacion_bamboohr_google_calendar.sql.
|
||||
|
||||
begin;
|
||||
|
||||
alter table public.tax_contacts
|
||||
add column if not exists google_chat_enabled boolean not null default true;
|
||||
|
||||
alter table public.tax_contacts
|
||||
alter column whatsapp_enabled set default false;
|
||||
|
||||
-- La nueva operación deja de usar WhatsApp. Se conserva la información histórica
|
||||
-- para no destruir datos ni bitácoras anteriores.
|
||||
update public.tax_contacts
|
||||
set
|
||||
whatsapp_enabled = false,
|
||||
google_chat_enabled = case
|
||||
when email_enabled = true and email is not null and length(trim(email)) > 3 then true
|
||||
else false
|
||||
end;
|
||||
|
||||
alter table public.tax_contacts
|
||||
drop constraint if exists tax_contacts_google_chat_email_when_enabled;
|
||||
|
||||
alter table public.tax_contacts
|
||||
add constraint tax_contacts_google_chat_email_when_enabled
|
||||
check (not google_chat_enabled or length(trim(coalesce(email, ''))) > 3);
|
||||
|
||||
alter table public.tax_notification_log
|
||||
drop constraint if exists tax_notification_log_channel_check;
|
||||
|
||||
alter table public.tax_notification_log
|
||||
add constraint tax_notification_log_channel_check
|
||||
check (channel in ('email', 'google_chat', 'whatsapp'));
|
||||
|
||||
-- CREATE OR REPLACE no permite cambiar las columnas retornadas, por eso se elimina
|
||||
-- y se recrea la RPC usada por n8n.
|
||||
drop function if exists public.tax_due_reminders(date);
|
||||
|
||||
create function public.tax_due_reminders(p_run_date date default current_date)
|
||||
returns table (
|
||||
obligation_id uuid,
|
||||
contact_id uuid,
|
||||
alert_date date,
|
||||
alert_type text,
|
||||
due_date date,
|
||||
description text,
|
||||
category text,
|
||||
country_name text,
|
||||
country_code text,
|
||||
contact_name text,
|
||||
contact_email text,
|
||||
email_enabled boolean,
|
||||
google_chat_enabled boolean
|
||||
)
|
||||
language sql
|
||||
stable
|
||||
security definer
|
||||
set search_path = ''
|
||||
as $$
|
||||
with due as (
|
||||
select
|
||||
obligation.id,
|
||||
obligation.due_date,
|
||||
obligation.description,
|
||||
obligation.category,
|
||||
obligation.country_id,
|
||||
country.name as country_name,
|
||||
country.code as country_code,
|
||||
case
|
||||
when p_run_date = obligation.due_date then 'same_day'
|
||||
else 'previous_wednesday'
|
||||
end as alert_type
|
||||
from public.tax_obligations obligation
|
||||
join public.tax_countries country on country.id = obligation.country_id
|
||||
where obligation.active = true
|
||||
and (
|
||||
p_run_date = obligation.due_date
|
||||
or p_run_date = public.tax_previous_wednesday(obligation.due_date)
|
||||
)
|
||||
)
|
||||
select
|
||||
due.id as obligation_id,
|
||||
contact.id as contact_id,
|
||||
p_run_date as alert_date,
|
||||
due.alert_type,
|
||||
due.due_date,
|
||||
due.description,
|
||||
due.category,
|
||||
due.country_name,
|
||||
due.country_code,
|
||||
contact.full_name as contact_name,
|
||||
contact.email as contact_email,
|
||||
(
|
||||
contact.email_enabled
|
||||
and contact.email is not null
|
||||
and not exists (
|
||||
select 1
|
||||
from public.tax_notification_log log
|
||||
where log.obligation_id = due.id
|
||||
and log.contact_id = contact.id
|
||||
and log.alert_date = p_run_date
|
||||
and log.channel = 'email'
|
||||
and log.status = 'sent'
|
||||
)
|
||||
) as email_enabled,
|
||||
(
|
||||
contact.google_chat_enabled
|
||||
and contact.email is not null
|
||||
and not exists (
|
||||
select 1
|
||||
from public.tax_notification_log log
|
||||
where log.obligation_id = due.id
|
||||
and log.contact_id = contact.id
|
||||
and log.alert_date = p_run_date
|
||||
and log.channel = 'google_chat'
|
||||
and log.status = 'sent'
|
||||
)
|
||||
) as google_chat_enabled
|
||||
from due
|
||||
join public.tax_contacts contact on contact.active = true
|
||||
join public.tax_countries contact_country
|
||||
on contact_country.id = contact.country_id
|
||||
and (
|
||||
contact.area = 'regional'
|
||||
or (
|
||||
contact_country.is_regional = true
|
||||
and contact.area = due.category
|
||||
)
|
||||
or (
|
||||
contact.country_id = due.country_id
|
||||
and contact.area = due.category
|
||||
)
|
||||
)
|
||||
where
|
||||
(
|
||||
contact.email_enabled
|
||||
and contact.email is not null
|
||||
and not exists (
|
||||
select 1
|
||||
from public.tax_notification_log log
|
||||
where log.obligation_id = due.id
|
||||
and log.contact_id = contact.id
|
||||
and log.alert_date = p_run_date
|
||||
and log.channel = 'email'
|
||||
and log.status = 'sent'
|
||||
)
|
||||
)
|
||||
or
|
||||
(
|
||||
contact.google_chat_enabled
|
||||
and contact.email is not null
|
||||
and not exists (
|
||||
select 1
|
||||
from public.tax_notification_log log
|
||||
where log.obligation_id = due.id
|
||||
and log.contact_id = contact.id
|
||||
and log.alert_date = p_run_date
|
||||
and log.channel = 'google_chat'
|
||||
and log.status = 'sent'
|
||||
)
|
||||
)
|
||||
order by due.due_date, due.country_name, contact.full_name;
|
||||
$$;
|
||||
|
||||
revoke all on function public.tax_due_reminders(date) from public, anon, authenticated;
|
||||
grant execute on function public.tax_due_reminders(date) to service_role;
|
||||
|
||||
commit;
|
||||
|
||||
-- Verificación opcional:
|
||||
-- select full_name, email_enabled, google_chat_enabled, calendar_enabled, whatsapp_enabled
|
||||
-- from public.tax_contacts where active = true order by full_name;
|
||||
|
||||
Reference in New Issue
Block a user