@fayz-ai/plugin-agenda 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BookingPaymentPanel.d.ts.map +1 -1
- package/dist/components/SeriesDeleteChoice.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +324 -156
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/UnitTurnQueueWidget.d.ts +14 -0
- package/dist/views/UnitTurnQueueWidget.d.ts.map +1 -0
- package/package.json +6 -6
package/dist/locales/en.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkTrC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkTvC,CAAA"}
|
|
@@ -18,6 +18,7 @@ export declare const MIGRATION_016_A_VEZ_E_UM_FATO = "-- A lista da vez is an op
|
|
|
18
18
|
export declare const MIGRATION_017_A_EQUIPE_DO_DIA_APARECE = "-- The board must show the whole team expected at this unit today, including\n-- professionals who have not explicitly joined the walk-in queue yet.\n\nCREATE OR REPLACE FUNCTION public.agenda_turn_queue_snapshot(p_unit_id uuid)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path = '' STABLE AS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_queue public.plg_agenda_turn_queues%ROWTYPE;\n v_person uuid;\n v_timezone text;\n v_day date;\n v_entries jsonb;\n v_events jsonb;\n v_professionals jsonb;\nBEGIN\n IF p_unit_id IS NULL THEN\n RAISE EXCEPTION 'unit_required' USING ERRCODE = '22023';\n END IF;\n IF NOT app.has_unit(p_unit_id) OR NOT app.has_permission('agenda.turn_queue.read', p_unit_id) THEN\n RAISE EXCEPTION 'agenda.turn_queue.read is required' USING ERRCODE = '42501';\n END IF;\n\n SELECT coalesce(t.settings->>'timezone', 'America/Sao_Paulo')\n INTO v_timezone\n FROM public.tenants t\n WHERE t.id = v_tenant;\n v_day := (now() AT TIME ZONE v_timezone)::date;\n v_person := app.person_for_login(v_tenant, auth.uid());\n\n SELECT * INTO v_queue\n FROM public.plg_agenda_turn_queues q\n WHERE q.tenant_id = v_tenant\n AND q.unit_id = p_unit_id\n AND q.queue_key = 'walk_in'\n AND q.is_active;\n\n SELECT coalesce(jsonb_agg(jsonb_build_object(\n 'id', e.id, 'queue_id', e.queue_id, 'unit_id', e.unit_id,\n 'professional_id', e.professional_id, 'professional_name', p.name,\n 'professional_avatar_url', p.avatar_url, 'status', e.status, 'position', e.position,\n 'state_changed_at', e.state_changed_at, 'available_at', e.available_at,\n 'appointment_id', e.appointment_id\n ) ORDER BY CASE e.status WHEN 'available' THEN 0 WHEN 'serving' THEN 1 ELSE 2 END, e.position), '[]'::jsonb)\n INTO v_entries\n FROM public.plg_agenda_turn_entries e\n JOIN public.people p ON p.id = e.professional_id AND p.tenant_id = e.tenant_id\n WHERE e.queue_id = v_queue.id;\n\n SELECT coalesce(jsonb_agg(jsonb_build_object(\n 'id', ev.id, 'professional_id', ev.professional_id, 'professional_name', p.name,\n 'event_type', ev.event_type, 'origin', ev.origin, 'reason', ev.reason,\n 'occurred_at', ev.occurred_at\n ) ORDER BY ev.occurred_at DESC), '[]'::jsonb)\n INTO v_events\n FROM (\n SELECT * FROM public.plg_agenda_turn_events\n WHERE queue_id = v_queue.id\n AND (occurred_at AT TIME ZONE v_timezone)::date = v_day\n ORDER BY occurred_at DESC LIMIT 50\n ) ev\n JOIN public.people p ON p.id = ev.professional_id AND p.tenant_id = ev.tenant_id;\n\n WITH scheduled AS (\n SELECT DISTINCT s.assignee_id AS professional_id\n FROM public.schedules s\n WHERE s.tenant_id = v_tenant\n AND s.kind = 'working_hours'\n AND s.assignee_id IS NOT NULL\n AND coalesce(s.unit_id, s.location_id) = p_unit_id\n AND (s.valid_from IS NULL OR s.valid_from <= v_day)\n AND (s.valid_to IS NULL OR v_day <= s.valid_to)\n AND (\n (s.specific_date = v_day AND s.is_active)\n OR (\n s.specific_date IS NULL\n AND s.is_active\n AND s.day_of_week = extract(dow FROM v_day)::smallint\n AND public.agenda_week_parity_active(s.valid_from, v_day, s.week_interval)\n AND NOT EXISTS (\n SELECT 1 FROM public.schedules day_override\n WHERE day_override.tenant_id = s.tenant_id\n AND day_override.assignee_id = s.assignee_id\n AND coalesce(day_override.unit_id, day_override.location_id) = p_unit_id\n AND day_override.specific_date = v_day\n )\n )\n )\n ), booked AS (\n SELECT a.assignee_id AS professional_id,\n count(*)::integer AS appointment_count,\n min(a.starts_at) FILTER (WHERE a.starts_at >= now()) AS next_appointment_at\n FROM public.appointments a\n WHERE a.tenant_id = v_tenant\n AND a.assignee_id IS NOT NULL\n AND coalesce(a.unit_id, a.location_id) = p_unit_id\n AND (a.starts_at AT TIME ZONE v_timezone)::date = v_day\n AND coalesce(a.status, '') NOT IN ('cancelled', 'canceled')\n GROUP BY a.assignee_id\n ), candidates AS (\n SELECT professional_id FROM scheduled\n UNION\n SELECT professional_id FROM booked\n UNION\n SELECT e.professional_id FROM public.plg_agenda_turn_entries e WHERE e.queue_id = v_queue.id\n )\n SELECT coalesce(jsonb_agg(jsonb_build_object(\n 'professional_id', p.id,\n 'professional_name', p.name,\n 'professional_avatar_url', p.avatar_url,\n 'status', coalesce(e.status, 'unavailable'),\n 'position', coalesce(e.position, 0),\n 'last_checkin_at', last_checkin.occurred_at,\n 'is_scheduled', scheduled.professional_id IS NOT NULL,\n 'appointment_count', coalesce(booked.appointment_count, 0),\n 'next_appointment_at', booked.next_appointment_at\n ) ORDER BY\n CASE coalesce(e.status, 'unavailable') WHEN 'available' THEN 0 WHEN 'serving' THEN 1 ELSE 2 END,\n CASE WHEN booked.next_appointment_at IS NOT NULL THEN 0 ELSE 1 END,\n e.position NULLS LAST,\n p.name), '[]'::jsonb)\n INTO v_professionals\n FROM candidates c\n JOIN public.people p ON p.id = c.professional_id AND p.tenant_id = v_tenant AND p.is_active\n LEFT JOIN scheduled ON scheduled.professional_id = p.id\n LEFT JOIN booked ON booked.professional_id = p.id\n LEFT JOIN public.plg_agenda_turn_entries e ON e.queue_id = v_queue.id AND e.professional_id = p.id\n LEFT JOIN LATERAL (\n SELECT ev.occurred_at\n FROM public.plg_agenda_turn_events ev\n WHERE ev.queue_id = v_queue.id\n AND ev.professional_id = p.id\n AND ev.event_type IN ('available', 'override_available')\n AND (ev.occurred_at AT TIME ZONE v_timezone)::date = v_day\n ORDER BY ev.occurred_at DESC\n LIMIT 1\n ) last_checkin ON true;\n\n RETURN jsonb_build_object(\n 'queue_id', v_queue.id,\n 'unit_id', p_unit_id,\n 'queue_name', coalesce(v_queue.name, 'Pr\u00F3ximo da vez'),\n 'current_professional_id', v_person,\n 'date', v_day,\n 'professionals', v_professionals,\n 'entries', v_entries,\n 'events', v_events\n );\nEND $$;\n\nREVOKE ALL ON FUNCTION public.agenda_turn_queue_snapshot(uuid) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.agenda_turn_queue_snapshot(uuid) TO authenticated;\n";
|
|
19
19
|
export declare const MIGRATION_018_O_AGENDAMENTO_QUE_SE_REPETE = "-- O agendamento que se repete.\n--\n-- \"Repetir\" no agendamento \u2014 o que o Google Calendar chama de s\u00E9rie. At\u00E9 aqui\n-- s\u00F3 o BLOQUEIO se repetia (`schedules` kind='block'); o compromisso de cliente\n-- era sempre avulso, e marcar o retorno de quinze em quinze dias por seis meses\n-- era doze aberturas do mesmo formul\u00E1rio.\n--\n-- \u2500\u2500 Por que aqui a ocorr\u00EAncia \u00E9 LINHA, e no bloqueio n\u00E3o \u00E9 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- A 010 escolheu o contr\u00E1rio, e escolheu certo para o que ela trata: o bloqueio\n-- recorrente \u00E9 UMA linha por dia da semana com vig\u00EAncia e paridade, expandida\n-- na LEITURA (`agenda_busy_blocks` no banco, `lib/schedule-engine.ts` na tela).\n-- O motivo est\u00E1 escrito l\u00E1: um almo\u00E7o de segunda a sexta at\u00E9 o fim do ano s\u00E3o\n-- 1.300 ocorr\u00EAncias de uma coisa que n\u00E3o tem identidade pr\u00F3pria \u2014 e a grade\n-- ETERNA (248 das 482 do V1 t\u00EAm `valid_to` nulo) n\u00E3o se expande de jeito nenhum.\n--\n-- Um compromisso de cliente \u00E9 o caso oposto, e por tr\u00EAs raz\u00F5es concretas que\n-- vivem neste schema:\n--\n-- 1. CADA OCORR\u00CANCIA \u00C9 UM FATO SEPARADO. Ela tem status pr\u00F3prio (a de 12/11\n-- foi falta, a de 26/11 foi atendida), check-in pr\u00F3prio (`checked_in_at`),\n-- e pode ter PEDIDO pr\u00F3prio \u2014 `order_id`, com fatura e recebimento. Nada\n-- disso cabe numa regra: a regra n\u00E3o pode estar \"paga\".\n-- 2. TODO LEITOR J\u00C1 L\u00CA LINHA. A grade da semana, `checkConflict`, a fila da\n-- vez (`plg_agenda_turn_entries.appointment_id`), as confirma\u00E7\u00F5es, o\n-- hist\u00F3rico da ficha. S\u00E9rie virtual obrigaria cada um deles a aprender a\n-- expandir uma regra \u2014 seis lugares, a mesma regra, divergindo em seis.\n-- 3. A JANELA \u00C9 FINITA POR NATUREZA. Ningu\u00E9m marca \"retorno toda semana para\n-- sempre\": marca at\u00E9 uma data ou um n\u00FAmero de vezes. O que n\u00E3o se expande\n-- \u00E9 a grade eterna, e esta n\u00E3o \u00E9 uma.\n--\n-- Por isso: ocorr\u00EAncia = linha em `public.appointments`, com `series_id` REAL.\n-- N\u00E3o por casamento de campos \u2014 a identidade do bloqueio \u00E9 reconstru\u00EDda\n-- comparando t\u00EDtulo + hor\u00E1rio + profissional (`views/CalendarView.tsx`), e \u00E9\n-- justamente a parte que n\u00E3o se deve copiar: dois almo\u00E7os de t\u00EDtulos iguais em\n-- hor\u00E1rios iguais s\u00E3o o mesmo bloqueio para aquela heur\u00EDstica.\n--\n-- \u2500\u2500 A regra tamb\u00E9m fica guardada \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Materializar n\u00E3o dispensa a regra. Sem ela a tela n\u00E3o sabe dizer \"a cada 2\n-- semanas, 10 vezes\" ao reabrir uma ocorr\u00EAncia, e estender a s\u00E9rie depois seria\n-- adivinhar o intervalo pela dist\u00E2ncia entre duas linhas. O precedente \u00E9 o\n-- Financeiro: `RecurringBill` guarda a regra E materializa as parcelas.\n--\n-- \u2500\u2500 O que N\u00C3O est\u00E1 aqui \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- RRULE livre (v\u00E1rios dias da semana, \"primeira ter\u00E7a do m\u00EAs\" escolhida \u00E0 m\u00E3o).\n-- `freq` \u00E9 uma lista fechada de tr\u00EAs; crescer \u00E9 acrescentar valor ao CHECK e um\n-- caso no expansor de `lib/recurrence.ts`, n\u00E3o mudar o modelo.\n--\n-- \"Este e os seguintes\" tamb\u00E9m n\u00E3o: ele exige PARTIR a regra em duas (a antiga\n-- ganha `until_date`, a nova nasce do dia editado). Esta migration n\u00E3o impede \u2014\n-- \u00E9 um INSERT a mais no mesmo formato.\n--\n-- `v_appointments` N\u00C3O \u00E9 alargada. O provider j\u00E1 l\u00EA `public.appointments`\n-- direto e funde com a view (`data/supabase.ts`), justamente porque a view n\u00E3o\n-- exp\u00F5e `resource_id` nem `unit_id`; `series_id` entra pelo mesmo caminho e n\u00E3o\n-- custa uma redefini\u00E7\u00E3o de view que a 007 teve de reescrever inteira.\n--\n-- Idempotente.\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 1. A REGRA\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nCREATE TABLE IF NOT EXISTS public.plg_agenda_recurrences (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n -- A cerca da franquia, herdada da primeira ocorr\u00EAncia. Nula \u00E9 leg\u00EDtima: a\n -- conta de uma unidade s\u00F3 n\u00E3o escolhe unidade, e a 004 estampa `unit_id` no\n -- agendamento por trigger \u2014 a regra nasce antes de a trigger ter rodado.\n unit_id uuid,\n -- `daily` | `weekly` | `monthly_weekday`. Mensal \u00E9 \"mesmo dia da semana\"\n -- (a 2\u00AA ter\u00E7a), n\u00E3o \"mesmo dia do m\u00EAs\": \u00E9 o que o retorno de sal\u00E3o e de\n -- cl\u00EDnica significa, e \u00E9 o que o Google oferece como preset.\n freq text NOT NULL,\n -- A cada N per\u00EDodos. 2 + weekly = \"a cada 2 semanas\".\n interval_count integer DEFAULT 1 NOT NULL,\n -- O dia da PRIMEIRA ocorr\u00EAncia, que \u00E9 a \u00E2ncora de tudo: o weekday da\n -- semanal e a ordinal do m\u00EAs da mensal saem dele.\n starts_on date NOT NULL,\n -- `HH:MM` da s\u00E9rie. Guardado para a edi\u00E7\u00E3o \"todos\" saber que hor\u00E1rio\n -- aplicar sem reler uma ocorr\u00EAncia qualquer e torcer para ser a certa.\n time_of_day time without time zone,\n -- O fim, por UMA ponta s\u00F3. Ver o CHECK abaixo.\n until_date date,\n occurrence_count integer,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_agenda_recurrences_pkey PRIMARY KEY (id),\n CONSTRAINT plg_agenda_recurrences_freq_chk\n CHECK (freq IN ('daily', 'weekly', 'monthly_weekday')),\n CONSTRAINT plg_agenda_recurrences_interval_chk\n CHECK (interval_count BETWEEN 1 AND 12),\n -- UMA ponta, n\u00E3o duas. Data E contagem juntas dariam duas respostas para\n -- \"quando acaba\", e quem lesse a regra depois n\u00E3o saberia qual valeu.\n CONSTRAINT plg_agenda_recurrences_end_chk CHECK (\n (until_date IS NOT NULL AND occurrence_count IS NULL)\n OR (until_date IS NULL AND occurrence_count IS NOT NULL)\n ),\n -- O MESMO TETO DA TELA, no banco. `RECURRENCE_MAX_OCCURRENCES` em\n -- `lib/recurrence.ts` \u00E9 60; um teto que s\u00F3 existe no navegador n\u00E3o \u00E9 teto.\n CONSTRAINT plg_agenda_recurrences_count_chk\n CHECK (occurrence_count IS NULL OR occurrence_count BETWEEN 1 AND 60)\n);\n\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_agenda_recurrences_tenant_fk') THEN\n ALTER TABLE public.plg_agenda_recurrences\n ADD CONSTRAINT plg_agenda_recurrences_tenant_fk\n FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_agenda_recurrences_unit_fk') THEN\n ALTER TABLE public.plg_agenda_recurrences\n ADD CONSTRAINT plg_agenda_recurrences_unit_fk\n FOREIGN KEY (unit_id) REFERENCES app.units(id) ON DELETE SET NULL;\n END IF;\nEND $$;\n\nCREATE INDEX IF NOT EXISTS plg_agenda_recurrences_tenant_idx\n ON public.plg_agenda_recurrences (tenant_id, starts_on DESC);\n\nCOMMENT ON TABLE public.plg_agenda_recurrences IS\n 'A regra de uma s\u00E9rie de agendamentos (\"Repetir\"). As ocorr\u00EAncias s\u00E3o linhas reais em public.appointments, ligadas por appointments.series_id \u2014 ver o cabe\u00E7alho da migration 018 para por que aqui se materializa e no bloqueio n\u00E3o.';\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 2. A OCORR\u00CANCIA APONTA PARA A REGRA\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nALTER TABLE public.appointments ADD COLUMN IF NOT EXISTS series_id uuid;\n\nCOMMENT ON COLUMN public.appointments.series_id IS\n 'A s\u00E9rie de \"Repetir\" a que esta ocorr\u00EAncia pertence. NULL = agendamento avulso, que \u00E9 a esmagadora maioria. ON DELETE SET NULL: apagar a regra solta as ocorr\u00EAncias, nunca as apaga.';\n\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'appointments_series_fk') THEN\n -- SET NULL, e n\u00E3o CASCADE. A 007 j\u00E1 custou uma agenda inteira a um CASCADE\n -- (\"o cascade levou a agenda junto\"): a regra \u00E9 METADADO da s\u00E9rie, e perder\n -- o metadado n\u00E3o pode apagar atendimentos que j\u00E1 aconteceram e j\u00E1 foram\n -- cobrados. Soltar a ocorr\u00EAncia \u00E9 a perda honesta.\n ALTER TABLE public.appointments\n ADD CONSTRAINT appointments_series_fk\n FOREIGN KEY (series_id) REFERENCES public.plg_agenda_recurrences(id) ON DELETE SET NULL;\n END IF;\nEND $$;\n\n-- A pergunta da tela: quais s\u00E3o as outras ocorr\u00EAncias desta s\u00E9rie, em ordem.\nCREATE INDEX IF NOT EXISTS idx_appointments_series\n ON public.appointments USING btree (tenant_id, series_id, starts_at)\n WHERE series_id IS NOT NULL;\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 3. updated_at\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nDROP TRIGGER IF EXISTS plg_agenda_recurrences_updated_at ON public.plg_agenda_recurrences;\nCREATE TRIGGER plg_agenda_recurrences_updated_at\n BEFORE UPDATE ON public.plg_agenda_recurrences\n FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 4. Quem enxerga\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- A mesma cerca das ocorr\u00EAncias: quem pode ler a agenda da unidade pode ler a\n-- regra das s\u00E9ries dela. Regra sem unidade (conta de uma unidade s\u00F3) fica no\n-- alcance de tenant \u2014 ela n\u00E3o guarda cliente nem valor, s\u00F3 freq e datas.\n\nALTER TABLE public.plg_agenda_recurrences ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_agenda_recurrences FORCE ROW LEVEL SECURITY;\nGRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.plg_agenda_recurrences TO authenticated;\nGRANT ALL ON TABLE public.plg_agenda_recurrences TO service_role;\n\nDROP POLICY IF EXISTS plg_agenda_recurrences_authz_select ON public.plg_agenda_recurrences;\nCREATE POLICY plg_agenda_recurrences_authz_select ON public.plg_agenda_recurrences\n FOR SELECT TO authenticated USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (plg_agenda_recurrences.unit_id IS NULL OR (SELECT app.has_unit(plg_agenda_recurrences.unit_id)))\n AND (SELECT app.has_permission('agenda.read', plg_agenda_recurrences.unit_id)));\n\nDROP POLICY IF EXISTS plg_agenda_recurrences_authz_insert ON public.plg_agenda_recurrences;\nCREATE POLICY plg_agenda_recurrences_authz_insert ON public.plg_agenda_recurrences\n FOR INSERT TO authenticated WITH CHECK (\n tenant_id = (SELECT app.current_tenant_id())\n AND (plg_agenda_recurrences.unit_id IS NULL OR (SELECT app.has_unit(plg_agenda_recurrences.unit_id)))\n AND (SELECT app.has_permission('agenda.create', plg_agenda_recurrences.unit_id)));\n\nDROP POLICY IF EXISTS plg_agenda_recurrences_authz_update ON public.plg_agenda_recurrences;\nCREATE POLICY plg_agenda_recurrences_authz_update ON public.plg_agenda_recurrences\n FOR UPDATE TO authenticated USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (plg_agenda_recurrences.unit_id IS NULL OR (SELECT app.has_unit(plg_agenda_recurrences.unit_id)))\n AND (SELECT app.has_permission('agenda.edit', plg_agenda_recurrences.unit_id)))\n WITH CHECK (\n tenant_id = (SELECT app.current_tenant_id())\n AND (plg_agenda_recurrences.unit_id IS NULL OR (SELECT app.has_unit(plg_agenda_recurrences.unit_id)))\n AND (SELECT app.has_permission('agenda.edit', plg_agenda_recurrences.unit_id)));\n\nDROP POLICY IF EXISTS plg_agenda_recurrences_authz_delete ON public.plg_agenda_recurrences;\nCREATE POLICY plg_agenda_recurrences_authz_delete ON public.plg_agenda_recurrences\n FOR DELETE TO authenticated USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (plg_agenda_recurrences.unit_id IS NULL OR (SELECT app.has_unit(plg_agenda_recurrences.unit_id)))\n AND (SELECT app.has_permission('agenda.delete', plg_agenda_recurrences.unit_id)));\n\n-- N\u00C3O entra em `scoped_resources`, pela mesma raz\u00E3o da 013: aquele registro\n-- pede `owner_id` porque responde \"de quem \u00E9 esta linha\". Uma regra de\n-- recorr\u00EAncia n\u00E3o tem dono \u2014 ela descreve as ocorr\u00EAncias, e a cerca das\n-- ocorr\u00EAncias j\u00E1 est\u00E1 em `appointments`.\n";
|
|
20
20
|
export declare const MIGRATION_019_A_SERIE_NASCE_NUMA_VIAGEM_SO = "-- 019_a_serie_nasce_numa_viagem_so.sql \u2014 a s\u00E9rie inteira em uma ida ao banco.\n--\n-- A 018 materializou a s\u00E9rie, e o cliente a escrevia no la\u00E7o: uma chamada por\n-- ocorr\u00EAncia, em s\u00E9rie, e cada ocorr\u00EAncia custava QUATRO requisi\u00E7\u00F5es \u2014 o RPC de\n-- cria\u00E7\u00E3o, o update da sala, a sincroniza\u00E7\u00E3o de convidados e o carimbo do\n-- `series_id`. Uma s\u00E9rie de doze ter\u00E7as era ~48 viagens, uma depois da outra,\n-- com a aba aberta o tempo todo. Sessenta (o teto) seriam 240.\n--\n-- O custo n\u00E3o \u00E9 s\u00F3 espera. Entre a primeira e a \u00FAltima n\u00E3o h\u00E1 transa\u00E7\u00E3o: fechar\n-- a aba no meio deixa metade da s\u00E9rie criada, e metade de uma s\u00E9rie \u00E9 pior que\n-- nenhuma \u2014 o cliente recebeu doze datas por telefone.\n--\n-- Ent\u00E3o o la\u00E7o desce para o banco. Uma chamada, uma transa\u00E7\u00E3o, tudo ou nada.\n--\n-- N\u00C3O \u00C9 UM SEGUNDO CAMINHO DE ESCRITA. Esta fun\u00E7\u00E3o n\u00E3o insere em\n-- `appointments`: ela chama `agenda_booking_create`, que \u00E9 a mesma porta que o\n-- cliente usava, dentro do la\u00E7o. Pedido, linhas, dura\u00E7\u00E3o e idempot\u00EAncia\n-- continuam decididos num lugar s\u00F3 \u2014 o que muda \u00E9 de onde o la\u00E7o roda. A sala\n-- e os convidados, que o cliente estampava depois porque a assinatura do RPC\n-- est\u00E1 congelada, entram aqui pelo mesmo motivo e na mesma transa\u00E7\u00E3o.\n\nSET check_function_bodies = false;\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- A s\u00E9rie, de uma vez\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- `p_days` s\u00E3o DIAS locais (`YYYY-MM-DD`) j\u00E1 expandidos por\n-- `lib/recurrence.expandRecurrence`. A expans\u00E3o fica no TypeScript de\n-- prop\u00F3sito: \u00E9 a parte que erra se algo errar, e teste de aritm\u00E9tica de datas\n-- n\u00E3o deve precisar de banco. O que o banco faz \u00E9 escrever.\n--\n-- `p_time_of_day` chega como texto `HH:MM` e o instante \u00E9 montado AQUI, dia a\n-- dia, no fuso da sess\u00E3o. Mandar 12 timestamps prontos daria no mesmo at\u00E9 a\n-- s\u00E9rie cruzar um hor\u00E1rio de ver\u00E3o \u2014 e a\u00ED a oitava ocorr\u00EAncia cairia uma hora\n-- fora sem ningu\u00E9m ter mexido nela.\nCREATE OR REPLACE FUNCTION public.agenda_series_create(\n p_days date[],\n p_time_of_day text,\n p_rule jsonb,\n p_lines jsonb DEFAULT '[]'::jsonb,\n p_party_id uuid DEFAULT NULL,\n p_assignee_id uuid DEFAULT NULL,\n p_unit_id uuid DEFAULT NULL,\n p_location_id uuid DEFAULT NULL,\n p_resource_id uuid DEFAULT NULL,\n p_kind text DEFAULT 'appointment',\n p_notes text DEFAULT NULL,\n p_metadata jsonb DEFAULT '{}'::jsonb,\n p_attendee_ids uuid[] DEFAULT NULL\n) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_series uuid;\n v_day date;\n v_starts timestamptz;\n v_res jsonb;\n v_booking uuid;\n v_ids uuid[] := '{}';\n v_failed jsonb := '[]'::jsonb;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'agenda_series_create: no active tenant' USING ERRCODE = '42501';\n END IF;\n -- A MESMA permiss\u00E3o de uma ocorr\u00EAncia. Criar doze n\u00E3o \u00E9 um poder novo; \u00E9 o\n -- mesmo gesto doze vezes, e `agenda_booking_create` vai conferir de novo em\n -- cada uma. Conferir aqui \u00E9 para a s\u00E9rie falhar ANTES de escrever a regra.\n IF NOT (select app.has_permission('agenda.create', p_unit_id)) THEN\n RAISE EXCEPTION 'agenda_series_create: agenda.create is required' USING ERRCODE = '42501';\n END IF;\n IF p_days IS NULL OR array_length(p_days, 1) IS NULL THEN\n RAISE EXCEPTION 'agenda_series_create: a s\u00E9rie n\u00E3o tem nenhuma data' USING ERRCODE = '22023';\n END IF;\n -- O MESMO teto do expansor (`RECURRENCE_MAX_OCCURRENCES`). Aqui porque o\n -- cliente n\u00E3o \u00E9 a \u00FAnica porta: um agente ou um script chamam este RPC direto,\n -- e um `p_days` de mil datas \u00E9 uma transa\u00E7\u00E3o que segura a tabela.\n IF array_length(p_days, 1) > 60 THEN\n RAISE EXCEPTION 'agenda_series_create: uma s\u00E9rie tem no m\u00E1ximo 60 ocorr\u00EAncias' USING ERRCODE = '22023';\n END IF;\n\n INSERT INTO public.plg_agenda_recurrences (\n tenant_id, unit_id, freq, interval_count, starts_on, time_of_day, until_date, occurrence_count\n ) VALUES (\n v_tenant, p_unit_id,\n p_rule ->> 'freq',\n coalesce((p_rule ->> 'interval')::integer, 1),\n p_days[1],\n -- `time_of_day` \u00E9 `time`; o par\u00E2metro chega como `HH:MM` de texto porque \u00E9\n -- assim que a tela o tem. O cast \u00E9 expl\u00EDcito para o erro ser aqui, e n\u00E3o\n -- uma s\u00E9rie recusada inteira por causa de dois pontos.\n p_time_of_day::time,\n nullif(p_rule ->> 'until', '')::date,\n CASE WHEN nullif(p_rule ->> 'until', '') IS NULL\n THEN coalesce((p_rule ->> 'count')::integer, array_length(p_days, 1))\n ELSE NULL END\n )\n RETURNING id INTO v_series;\n\n FOREACH v_day IN ARRAY p_days LOOP\n v_starts := (v_day::text || ' ' || p_time_of_day)::timestamp AT TIME ZONE current_setting('TimeZone');\n\n BEGIN\n v_res := public.agenda_booking_create(\n p_starts_at => v_starts,\n p_ends_at => NULL,\n p_lines => p_lines,\n p_party_id => p_party_id,\n p_assignee_id => p_assignee_id,\n p_unit_id => p_unit_id,\n p_location_id => p_location_id,\n p_kind => p_kind,\n p_notes => p_notes,\n p_metadata => p_metadata);\n\n v_booking := (v_res ->> 'appointment_id')::uuid;\n\n -- S\u00E9rie e sala no mesmo UPDATE: as duas s\u00E3o colunas que a porta\n -- congelada de `agenda_booking_create` n\u00E3o escreve, e separ\u00E1-las seria\n -- escrever a mesma linha duas vezes.\n UPDATE public.appointments\n SET series_id = v_series,\n resource_id = coalesce(p_resource_id, resource_id)\n WHERE id = v_booking;\n\n IF p_attendee_ids IS NOT NULL AND array_length(p_attendee_ids, 1) > 0 THEN\n INSERT INTO public.appointment_attendees (tenant_id, booking_id, person_id, role)\n SELECT v_tenant, v_booking, a, 'required'\n FROM unnest(p_attendee_ids) a\n ON CONFLICT DO NOTHING;\n END IF;\n\n v_ids := v_ids || v_booking;\n EXCEPTION WHEN OTHERS THEN\n -- UMA DATA QUE N\u00C3O ENTROU N\u00C3O DERRUBA AS OUTRAS, e tamb\u00E9m n\u00E3o some: o\n -- bloco devolve o motivo e a tela conta quantas nasceram. \u00C9 a mesma\n -- regra do conflito \u2014 avisa, n\u00E3o impede \u2014, e o `EXCEPTION` aninhado\n -- desfaz s\u00F3 esta itera\u00E7\u00E3o, n\u00E3o a s\u00E9rie.\n v_failed := v_failed || jsonb_build_object('date', v_day::text, 'message', SQLERRM);\n END;\n END LOOP;\n\n RETURN jsonb_build_object(\n 'series_id', v_series,\n 'booking_ids', to_jsonb(v_ids),\n 'failed', v_failed);\nEND $$;\n\nREVOKE ALL ON FUNCTION public.agenda_series_create(date[], text, jsonb, jsonb, uuid, uuid, uuid, uuid, uuid, text, text, jsonb, uuid[]) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.agenda_series_create(date[], text, jsonb, jsonb, uuid, uuid, uuid, uuid, uuid, text, text, jsonb, uuid[]) TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.agenda_series_create(date[], text, jsonb, jsonb, uuid, uuid, uuid, uuid, uuid, text, text, jsonb, uuid[]) IS\n 'Cria a regra de recorr\u00EAncia e TODAS as ocorr\u00EAncias numa transa\u00E7\u00E3o, chamando agenda_booking_create por dia. Substitui o la\u00E7o de ~4 requisi\u00E7\u00F5es por ocorr\u00EAncia que o cliente fazia.';\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- Mudar a s\u00E9rie inteira\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- S\u00F3 o que \u00E9 DA S\u00C9RIE: o hor\u00E1rio, o profissional, a sala, o local, a nota e o\n-- status. O DIA de cada ocorr\u00EAncia \u00E9 dela \u2014 mandar um `starts_at` \u00FAnico para\n-- todas empilharia a s\u00E9rie num dia s\u00F3, que \u00E9 o defeito que `planSeriesEdit`\n-- existe para n\u00E3o deixar acontecer no cliente. Aqui \u00E9 a mesma regra, do lado\n-- do banco, para quem chamar direto.\n--\n-- AS LINHAS (servi\u00E7os) N\u00C3O ENTRAM. Reescrev\u00EA-las pede saber se o agendamento\n-- tem pedido ou n\u00E3o \u2014 a regra que `updateBooking` carrega no TypeScript \u2014 e\n-- duplic\u00E1-la aqui seria o segundo caminho de escrita que esta migration\n-- recusa. Quem muda servi\u00E7o numa s\u00E9rie inteira continua indo pelo cliente.\nCREATE OR REPLACE FUNCTION public.agenda_series_update(\n p_series_id uuid,\n p_time_of_day text,\n p_assignee_id uuid DEFAULT NULL,\n p_location_id uuid DEFAULT NULL,\n p_resource_id uuid DEFAULT NULL,\n p_notes text DEFAULT NULL,\n p_status text DEFAULT NULL,\n p_touch text[] DEFAULT '{}'\n) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_unit uuid;\n v_count integer;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'agenda_series_update: no active tenant' USING ERRCODE = '42501';\n END IF;\n\n SELECT r.unit_id INTO v_unit FROM public.plg_agenda_recurrences r\n WHERE r.id = p_series_id AND r.tenant_id = v_tenant;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'agenda_series_update: s\u00E9rie % n\u00E3o encontrada', p_series_id USING ERRCODE = 'P0002';\n END IF;\n IF NOT (select app.has_permission('agenda.edit', v_unit)) THEN\n RAISE EXCEPTION 'agenda_series_update: agenda.edit is required' USING ERRCODE = '42501';\n END IF;\n\n -- `p_touch` diz QUAIS campos a tela mexeu. Sem ele, um NULL seria amb\u00EDguo\n -- entre \"n\u00E3o mexi\" e \"esvaziei\" \u2014 e desatribuir o profissional de uma s\u00E9rie\n -- \u00E9 uma decis\u00E3o t\u00E3o leg\u00EDtima quanto troc\u00E1-lo.\n UPDATE public.appointments a\n SET starts_at = ((a.starts_at AT TIME ZONE current_setting('TimeZone'))::date::text\n || ' ' || p_time_of_day)::timestamp AT TIME ZONE current_setting('TimeZone'),\n ends_at = CASE WHEN a.ends_at IS NULL THEN NULL ELSE\n ((a.starts_at AT TIME ZONE current_setting('TimeZone'))::date::text\n || ' ' || p_time_of_day)::timestamp AT TIME ZONE current_setting('TimeZone')\n + (a.ends_at - a.starts_at) END,\n assignee_id = CASE WHEN 'assigneeId' = ANY(p_touch) THEN p_assignee_id ELSE a.assignee_id END,\n location_id = CASE WHEN 'locationId' = ANY(p_touch) THEN p_location_id ELSE a.location_id END,\n resource_id = CASE WHEN 'resourceId' = ANY(p_touch) THEN p_resource_id ELSE a.resource_id END,\n notes = CASE WHEN 'notes' = ANY(p_touch) THEN p_notes ELSE a.notes END,\n status = CASE WHEN 'status' = ANY(p_touch) THEN p_status ELSE a.status END,\n updated_at = now()\n WHERE a.series_id = p_series_id\n AND a.tenant_id = v_tenant;\n GET DIAGNOSTICS v_count = ROW_COUNT;\n\n UPDATE public.plg_agenda_recurrences\n SET time_of_day = p_time_of_day::time, updated_at = now()\n WHERE id = p_series_id;\n\n RETURN jsonb_build_object('updated', v_count);\nEND $$;\n\nREVOKE ALL ON FUNCTION public.agenda_series_update(uuid, text, uuid, uuid, uuid, text, text, text[]) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.agenda_series_update(uuid, text, uuid, uuid, uuid, text, text, text[]) TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.agenda_series_update(uuid, text, uuid, uuid, uuid, text, text, text[]) IS\n 'Aplica a mesma mudan\u00E7a a todas as ocorr\u00EAncias da s\u00E9rie num UPDATE s\u00F3, preservando o DIA de cada uma. N\u00E3o toca nas linhas de servi\u00E7o \u2014 essa regra vive no cliente.';\n";
|
|
21
|
+
export declare const MIGRATION_020_A_LISTA_DA_VEZ_SE_LIGA_NO_CADASTRO_DA_UNIDADE = "-- A lista da vez se liga no cadastro da unidade.\n--\n-- No V1 o interruptor \"Ativar lista da vez nesta unidade\" e o raio da cerca\n-- moram no cadastro da empresa (V1: companies.turn_queue_enabled,\n-- turn_queue_radius_m). Em 016 a fila nasceu sem tela de configura\u00E7\u00E3o: a linha\n-- surge sozinha, LIGADA, no primeiro \"estou dispon\u00EDvel\", e o app s\u00F3 pode l\u00EA-la.\n--\n-- Esta RPC \u00E9 a porta que faltava \u2014 a aba \"Lista da vez\" da unidade grava por\n-- ela. Quem configura \u00E9 quem administra a filial (settings.manage NA unidade).\n-- A coordenada continua sendo a do endere\u00E7o da unidade (142); n\u00E3o h\u00E1 segunda\n-- latitude aqui.\n--\n-- Chave que conecta: plg_agenda_turn_queues.unit_id = app.units.id\n-- (UNIQUE tenant_id, unit_id, queue_key). Idempotente.\n\nCREATE OR REPLACE FUNCTION public.agenda_turn_queue_configure(\n p_unit_id uuid,\n p_enabled boolean,\n p_geofence_required boolean DEFAULT NULL,\n p_radius_meters integer DEFAULT NULL\n)\nRETURNS public.plg_agenda_turn_queues\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $function$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_row public.plg_agenda_turn_queues;\nBEGIN\n IF v_tenant IS NULL OR auth.uid() IS NULL THEN\n RAISE EXCEPTION 'agenda_turn_queue_configure: no session or no active tenant' USING ERRCODE = '42501';\n END IF;\n IF NOT EXISTS (SELECT 1 FROM app.units u WHERE u.id = p_unit_id AND u.tenant_id = v_tenant) THEN\n RAISE EXCEPTION 'agenda_turn_queue_configure: unit does not belong to this tenant' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_unit(p_unit_id) OR NOT app.has_permission('settings.manage', p_unit_id) THEN\n RAISE EXCEPTION 'agenda_turn_queue_configure: settings.manage is required on this unit' USING ERRCODE = '42501';\n END IF;\n IF p_radius_meters IS NOT NULL AND (p_radius_meters < 10 OR p_radius_meters > 1000) THEN\n RAISE EXCEPTION 'agenda_turn_queue_configure: radius must be between 10 and 1000 meters' USING ERRCODE = '22023';\n END IF;\n\n INSERT INTO public.plg_agenda_turn_queues AS q (tenant_id, unit_id, is_active, geofence_required, geofence_radius_meters)\n VALUES (v_tenant, p_unit_id, coalesce(p_enabled, false), coalesce(p_geofence_required, true), coalesce(p_radius_meters, 80))\n ON CONFLICT (tenant_id, unit_id, queue_key) DO UPDATE\n SET is_active = coalesce(p_enabled, q.is_active),\n geofence_required = coalesce(p_geofence_required, q.geofence_required),\n geofence_radius_meters = coalesce(p_radius_meters, q.geofence_radius_meters),\n updated_at = now()\n RETURNING * INTO v_row;\n RETURN v_row;\nEND $function$;\n\nREVOKE ALL ON FUNCTION public.agenda_turn_queue_configure(uuid, boolean, boolean, integer) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.agenda_turn_queue_configure(uuid, boolean, boolean, integer) TO authenticated, service_role;\n";
|
|
21
22
|
export declare const MIGRATIONS: Array<{
|
|
22
23
|
id: string;
|
|
23
24
|
sql: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,05vEAg2ClC,CAAA;AAED,eAAO,MAAM,uDAAuD,mvEAgDnE,CAAA;AAED,eAAO,MAAM,4CAA4C,w6EAqDyB,CAAA;AAElF,eAAO,MAAM,oDAAoD,61CAyBhE,CAAA;AAED,eAAO,MAAM,8CAA8C,w7EA4C1D,CAAA;AAED,eAAO,MAAM,iDAAiD,woFAiD7D,CAAA;AAED,eAAO,MAAM,oDAAoD,kxOAmHhE,CAAA;AAED,eAAO,MAAM,4CAA4C,mrZA2OxD,CAAA;AAED,eAAO,MAAM,sCAAsC,8tuBAoXlD,CAAA;AAED,eAAO,MAAM,4CAA4C,wkIA8DxD,CAAA;AAED,eAAO,MAAM,qCAAqC,q5pDAo1BjD,CAAA;AAED,eAAO,MAAM,yCAAyC,0rIA+ErD,CAAA;AAED,eAAO,MAAM,+CAA+C,+sIA0F3D,CAAA;AAED,eAAO,MAAM,kDAAkD,ogYAoK9D,CAAA;AAED,eAAO,MAAM,6CAA6C,w/GA0DzD,CAAA;AAED,eAAO,MAAM,wDAAwD,mtBAYpE,CAAA;AAED,eAAO,MAAM,6BAA6B,y4hBAmVzC,CAAA;AAED,eAAO,MAAM,qCAAqC,utMAwJjD,CAAA;AAED,eAAO,MAAM,yCAAyC,09fAoNrD,CAAA;AAED,eAAO,MAAM,0CAA0C,o4aAyOtD,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,05vEAg2ClC,CAAA;AAED,eAAO,MAAM,uDAAuD,mvEAgDnE,CAAA;AAED,eAAO,MAAM,4CAA4C,w6EAqDyB,CAAA;AAElF,eAAO,MAAM,oDAAoD,61CAyBhE,CAAA;AAED,eAAO,MAAM,8CAA8C,w7EA4C1D,CAAA;AAED,eAAO,MAAM,iDAAiD,woFAiD7D,CAAA;AAED,eAAO,MAAM,oDAAoD,kxOAmHhE,CAAA;AAED,eAAO,MAAM,4CAA4C,mrZA2OxD,CAAA;AAED,eAAO,MAAM,sCAAsC,8tuBAoXlD,CAAA;AAED,eAAO,MAAM,4CAA4C,wkIA8DxD,CAAA;AAED,eAAO,MAAM,qCAAqC,q5pDAo1BjD,CAAA;AAED,eAAO,MAAM,yCAAyC,0rIA+ErD,CAAA;AAED,eAAO,MAAM,+CAA+C,+sIA0F3D,CAAA;AAED,eAAO,MAAM,kDAAkD,ogYAoK9D,CAAA;AAED,eAAO,MAAM,6CAA6C,w/GA0DzD,CAAA;AAED,eAAO,MAAM,wDAAwD,mtBAYpE,CAAA;AAED,eAAO,MAAM,6BAA6B,y4hBAmVzC,CAAA;AAED,eAAO,MAAM,qCAAqC,utMAwJjD,CAAA;AAED,eAAO,MAAM,yCAAyC,09fAoNrD,CAAA;AAED,eAAO,MAAM,0CAA0C,o4aAyOtD,CAAA;AAED,eAAO,MAAM,2DAA2D,s3FAwDvE,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAsBzD,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* "Lista da vez" of the V1 company form. V1 keeps its own latitude/longitude for
|
|
4
|
+
* the store entrance; V2 uses the coordinate of the unit's address (142), so
|
|
5
|
+
* here it is read-only and points to where it is edited.
|
|
6
|
+
*/
|
|
7
|
+
export declare function UnitTurnQueueWidget({ item }: {
|
|
8
|
+
item?: {
|
|
9
|
+
id?: string;
|
|
10
|
+
latitude?: number | null;
|
|
11
|
+
longitude?: number | null;
|
|
12
|
+
};
|
|
13
|
+
}): React.JSX.Element;
|
|
14
|
+
//# sourceMappingURL=UnitTurnQueueWidget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnitTurnQueueWidget.d.ts","sourceRoot":"","sources":["../../src/views/UnitTurnQueueWidget.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,qBA2H5H"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"status": "beta",
|
|
5
5
|
"dependencies": []
|
|
6
6
|
},
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.21.0",
|
|
8
8
|
"description": "Fayz SDK — plugin-agenda plugin",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"sideEffects": [
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"@fullcalendar/resource-timegrid": "^6.1.0",
|
|
43
43
|
"@fullcalendar/timegrid": "^6.1.20",
|
|
44
44
|
"@tanstack/react-table": "^8.20.0",
|
|
45
|
-
"@fayz-ai/admin": "^0.
|
|
46
|
-
"@fayz-ai/core": "^0.
|
|
47
|
-
"@fayz-ai/plugin-financial": "^0.
|
|
48
|
-
"@fayz-ai/plugin-forms": "^0.13.
|
|
49
|
-
"@fayz-ai/ui": "^0.
|
|
45
|
+
"@fayz-ai/admin": "^0.36.0",
|
|
46
|
+
"@fayz-ai/core": "^0.36.0",
|
|
47
|
+
"@fayz-ai/plugin-financial": "^0.19.0",
|
|
48
|
+
"@fayz-ai/plugin-forms": "^0.13.3",
|
|
49
|
+
"@fayz-ai/ui": "^0.36.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/react": "^18.3.0",
|