@fayz-ai/db 0.16.0 → 0.17.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/migrations/116_o_sdr_agrupa_a_empresa_que_ele_prospecta.sql +84 -0
- package/migrations/117_o_instagram_e_um_canal_de_contato.sql +41 -0
- package/migrations/118_a_view_mascarada_acompanha_a_tabela.sql +46 -0
- package/migrations/119_a_reuniao_tem_mais_de_uma_pessoa.sql +155 -0
- package/package.json +1 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 116_o_sdr_agrupa_a_empresa_que_ele_prospecta.sql — a chave que faltava no
|
|
3
|
+
-- recorte da 071.
|
|
4
|
+
--
|
|
5
|
+
-- ── o buraco ────────────────────────────────────────────────────────────
|
|
6
|
+
--
|
|
7
|
+
-- A 071 desenhou o SDR: leads, funil, orçamento, conversa. Não deu
|
|
8
|
+
-- `crm.companies`, e na época isso parecia certo — prospectar é falar com
|
|
9
|
+
-- gente, não manter cadastro de empresa.
|
|
10
|
+
--
|
|
11
|
+
-- Depois vieram os grupos: raiz de CNPJ (015), bandeira e federação (crm/039).
|
|
12
|
+
-- A proposta é automática e ERRA — é um palpite sobre nome e raiz, e cinco
|
|
13
|
+
-- Abrasel com cinco raízes distintas são a mesma instituição enquanto
|
|
14
|
+
-- `RESTAURANTE` com onze empresas não é parentesco nenhum. Quem vê o erro é
|
|
15
|
+
-- quem trabalha a lista, e quem trabalha a lista é o SDR.
|
|
16
|
+
--
|
|
17
|
+
-- E a tela de Grupos não é uma rota própria: mora DENTRO de Empresas, cuja
|
|
18
|
+
-- rota pede `crm.companies.read`. Sem esta chave o SDR não só não agrupa —
|
|
19
|
+
-- ele não chega à tela. Medido no cluster: 20 papéis `sdr`, nenhum com
|
|
20
|
+
-- qualquer permissão `crm.companies`.
|
|
21
|
+
--
|
|
22
|
+
-- ── por que um arquivo novo e não uma correção na 071 ───────────────────
|
|
23
|
+
--
|
|
24
|
+
-- A 071 já rodou, e está no ledger com o checksum do que rodou. Reescrevê-la
|
|
25
|
+
-- faria a linha do ledger descrever um arquivo que ninguém aplicou — é o
|
|
26
|
+
-- mesmo defeito que o `check-ledger-drift` chama de DRIFT e que faz um
|
|
27
|
+
-- `fayz db apply` parar no meio.
|
|
28
|
+
--
|
|
29
|
+
-- ── `edit` e não `manage` ───────────────────────────────────────────────
|
|
30
|
+
--
|
|
31
|
+
-- Agrupar, desagrupar e corrigir o nome do grupo: sim. Apagar empresa: não.
|
|
32
|
+
-- Uma empresa apagada leva junto os contatos que penduram nela, e isso é
|
|
33
|
+
-- decisão de quem responde pela carteira, não de quem prospecta.
|
|
34
|
+
-- ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
-- ── primeiro: `crm.companies` NÃO ESTÁ NO CATÁLOGO ─────────────────────────
|
|
37
|
+
--
|
|
38
|
+
-- Medido antes de escrever isto: `app.permissions` tem 15 chaves `crm.*` —
|
|
39
|
+
-- `leads`, `pipeline` e `quotes`, cinco ações cada — e NENHUMA de `companies`.
|
|
40
|
+
-- Mas a rota de Empresas do plugin-crm pede `crm.companies.read`, e as telas
|
|
41
|
+
-- do app concedem `crm.companies` a owner, gerente e vendas.
|
|
42
|
+
--
|
|
43
|
+
-- Ou seja: a chave é exigida pela rota e concedida pelo app, e o catálogo do
|
|
44
|
+
-- banco nunca soube dela. Conceder a alguém uma chave que o catálogo não tem
|
|
45
|
+
-- é escrever numa linha que ninguém lê — a 071 já se protege disso com o
|
|
46
|
+
-- `WHERE EXISTS` logo abaixo, e é por isso que este arquivo registra antes de
|
|
47
|
+
-- conceder.
|
|
48
|
+
--
|
|
49
|
+
-- As cinco ações, como os irmãos `crm.leads` e `crm.quotes` têm. Registrar
|
|
50
|
+
-- todas mantém o vocabulário parelho; quem recebe cada uma é decisão de papel,
|
|
51
|
+
-- não de catálogo.
|
|
52
|
+
INSERT INTO app.permissions (key, plugin)
|
|
53
|
+
SELECT 'crm.companies.' || a, 'crm'
|
|
54
|
+
FROM unnest(ARRAY['read', 'create', 'edit', 'delete', 'manage']) AS a
|
|
55
|
+
ON CONFLICT (key) DO NOTHING;
|
|
56
|
+
|
|
57
|
+
-- ── depois: o recorte do SDR ───────────────────────────────────────────────
|
|
58
|
+
INSERT INTO app.role_templates (template, role_key, role_name, sort_order, permission)
|
|
59
|
+
SELECT 'generic', 'sdr', 'SDR', 3, p
|
|
60
|
+
FROM unnest(ARRAY['crm.companies.read', 'crm.companies.edit']) AS p
|
|
61
|
+
-- Só o que o catálogo realmente tem: uma chave inventada aqui vira uma
|
|
62
|
+
-- permissão que nenhuma tela consulta — silenciosa, e por isso pior que um
|
|
63
|
+
-- erro. Mesma guarda da 071.
|
|
64
|
+
WHERE EXISTS (SELECT 1 FROM app.permissions perm WHERE perm.key = p)
|
|
65
|
+
ON CONFLICT DO NOTHING;
|
|
66
|
+
|
|
67
|
+
-- ── quem já tem o papel recebe a chave agora ───────────────────────────────
|
|
68
|
+
--
|
|
69
|
+
-- Sem isto a migration só serve a quem nascer depois dela, e os 20 SDR que já
|
|
70
|
+
-- existem seguem sem alcançar a tela — que é exatamente o defeito em aberto.
|
|
71
|
+
DO $$
|
|
72
|
+
DECLARE v_perms integer;
|
|
73
|
+
BEGIN
|
|
74
|
+
INSERT INTO app.role_permissions (tenant_id, role_id, permission)
|
|
75
|
+
SELECT r.tenant_id, r.id, t.permission
|
|
76
|
+
FROM app.roles r
|
|
77
|
+
JOIN app.role_templates t ON t.template = 'generic' AND t.role_key = 'sdr'
|
|
78
|
+
WHERE r.key = 'sdr'
|
|
79
|
+
AND t.permission LIKE 'crm.companies.%'
|
|
80
|
+
ON CONFLICT DO NOTHING;
|
|
81
|
+
GET DIAGNOSTICS v_perms = ROW_COUNT;
|
|
82
|
+
|
|
83
|
+
RAISE NOTICE '116: % permissão(ões) crm.companies concedida(s) a papéis SDR', v_perms;
|
|
84
|
+
END $$;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 117_o_instagram_e_um_canal_de_contato.sql
|
|
3
|
+
--
|
|
4
|
+
-- ── por que coluna, e não `custom_fields` ───────────────────────────────
|
|
5
|
+
--
|
|
6
|
+
-- `people` já tem `custom_fields` e `metadata`, e a tentação é pôr o Instagram
|
|
7
|
+
-- num deles para não mexer na espinha. Este repo já pagou por essa escolha: a
|
|
8
|
+
-- importação do Salão Abrasel pôs as 46 respostas do credenciamento em
|
|
9
|
+
-- `custom_fields` e o resultado foram 297 fichas com os dados no banco e três
|
|
10
|
+
-- campos na tela — porque NENHUMA tela de pessoa lê `custom_fields`. Está
|
|
11
|
+
-- escrito no cabeçalho do `import_crm.py`: um dado que o produto não abre não
|
|
12
|
+
-- está importado, está arquivado.
|
|
13
|
+
--
|
|
14
|
+
-- Coluna também é o que torna o campo buscável, exportável e ordenável sem
|
|
15
|
+
-- nenhum trabalho a mais — e um vendedor que preenche um handle quer achar a
|
|
16
|
+
-- ficha por ele depois.
|
|
17
|
+
--
|
|
18
|
+
-- ── e por que na ESPINHA, e não só no CRM ───────────────────────────────
|
|
19
|
+
--
|
|
20
|
+
-- Instagram é canal de contato, do mesmo tipo de `email` e `phone`, que já
|
|
21
|
+
-- moram aqui. Não é um conceito de vendas: a clínica tem o Instagram da
|
|
22
|
+
-- paciente, o salão tem o da cliente, o restaurante tem o do fornecedor.
|
|
23
|
+
-- Pôr no plugin-crm obrigaria toda vertical que quisesse o campo a instalar um
|
|
24
|
+
-- CRM, e a segunda que precisasse criaria a sua própria coluna com outro nome.
|
|
25
|
+
--
|
|
26
|
+
-- ── texto simples, sem CHECK ────────────────────────────────────────────
|
|
27
|
+
--
|
|
28
|
+
-- Quem digita vai escrever `@fulano`, `fulano`, `instagram.com/fulano` e
|
|
29
|
+
-- `https://www.instagram.com/fulano/`. Uma restrição que recusasse três dessas
|
|
30
|
+
-- formas transformaria um campo opcional numa briga — e o vendedor pararia de
|
|
31
|
+
-- preencher, que é o único modo de este campo falhar de verdade.
|
|
32
|
+
--
|
|
33
|
+
-- A normalização acontece na ESCRITA, na tela, onde há quem corrigir. O banco
|
|
34
|
+
-- guarda o que chegou.
|
|
35
|
+
-- ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
ALTER TABLE public.people
|
|
38
|
+
ADD COLUMN IF NOT EXISTS instagram text;
|
|
39
|
+
|
|
40
|
+
COMMENT ON COLUMN public.people.instagram IS
|
|
41
|
+
'O @ da pessoa, preenchido à mão por quem fala com ela. Canal de contato, ao lado de email e phone — não é conceito de vendas (117).';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 118_a_view_mascarada_acompanha_a_tabela.sql
|
|
3
|
+
--
|
|
4
|
+
-- ── o que o teste pegou ─────────────────────────────────────────────────
|
|
5
|
+
--
|
|
6
|
+
-- A 117 acrescentou `people.instagram` e `v_people` — a view mascarada — ficou
|
|
7
|
+
-- para trás. O `N16d` reclamou na hora, e o cabeçalho dele diz exatamente por
|
|
8
|
+
-- que a regra existe: uma coluna que existe na tabela e falta na view lê, do
|
|
9
|
+
-- lado do cliente, como "não existe esse campo" — não como um erro (#222).
|
|
10
|
+
--
|
|
11
|
+
-- Foi o teste que achou, não eu. Eu tinha conferido `v_leads` e `v_crm_leads`
|
|
12
|
+
-- e parado ali, satisfeito por ter lembrado das views. `v_people` é uma
|
|
13
|
+
-- terceira, gerada por outro mecanismo, e nenhuma leitura minha do código
|
|
14
|
+
-- teria mostrado isso — `app.masked_views` é um registro, não um `CREATE VIEW`
|
|
15
|
+
-- que se encontra buscando por nome.
|
|
16
|
+
--
|
|
17
|
+
-- ── e `phone_digits` já estava atrás ────────────────────────────────────
|
|
18
|
+
--
|
|
19
|
+
-- `app.masked_views_stale()` aponta duas colunas, não uma: `instagram` (minha)
|
|
20
|
+
-- e `phone_digits`, que entrou antes e passou pelo mesmo buraco. Regenerar
|
|
21
|
+
-- resolve as duas — e esta é a diferença entre corrigir a view à mão e chamar
|
|
22
|
+
-- quem a gera: à mão eu consertaria só a que me incomoda.
|
|
23
|
+
--
|
|
24
|
+
-- ── por isso `masked_view_refresh`, e não um CREATE VIEW ────────────────
|
|
25
|
+
--
|
|
26
|
+
-- A view é DERIVADA: as colunas mascaradas saem de `app.masked_columns` e do
|
|
27
|
+
-- contexto registrado. Escrevê-la à mão aqui congelaria a saída de hoje e a
|
|
28
|
+
-- próxima coluna voltaria a ficar para trás — com a diferença de que, aí, o
|
|
29
|
+
-- gerador e o arquivo discordariam sobre quem manda.
|
|
30
|
+
-- ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
DO $$
|
|
33
|
+
DECLARE r record;
|
|
34
|
+
BEGIN
|
|
35
|
+
-- Toda view mascarada atrasada, e não só a de `people`: a regra é "nenhuma
|
|
36
|
+
-- fica para trás", e consertar uma de cada vez é como se acumulam as outras.
|
|
37
|
+
FOR r IN
|
|
38
|
+
SELECT m.table_schema, m.table_name, m.context, m.reference
|
|
39
|
+
FROM app.masked_views m
|
|
40
|
+
JOIN app.masked_views_stale() s ON s.table_name = m.table_name
|
|
41
|
+
LOOP
|
|
42
|
+
PERFORM app.masked_view_refresh(
|
|
43
|
+
format('%I.%I', r.table_schema, r.table_name)::regclass, r.context, r.reference);
|
|
44
|
+
RAISE NOTICE 'db/118: % regenerada', r.table_name;
|
|
45
|
+
END LOOP;
|
|
46
|
+
END $$;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 119_a_reuniao_tem_mais_de_uma_pessoa.sql
|
|
3
|
+
--
|
|
4
|
+
-- ── o buraco ────────────────────────────────────────────────────────────
|
|
5
|
+
--
|
|
6
|
+
-- Um compromisso tem UMA pessoa responsável (`appointments.assignee_id`) e UM
|
|
7
|
+
-- cliente (`party_id`). Isso descreve bem um corte de cabelo e uma consulta:
|
|
8
|
+
-- um profissional atende uma pessoa.
|
|
9
|
+
--
|
|
10
|
+
-- Não descreve uma reunião comercial. Ali sentam o vendedor, o dono do
|
|
11
|
+
-- restaurante, às vezes o sócio dele e alguém do time que entra só para a
|
|
12
|
+
-- parte técnica. Hoje não há onde escrever isso, e quem tenta acaba pondo
|
|
13
|
+
-- nome no campo de observações — que ninguém consegue filtrar depois.
|
|
14
|
+
--
|
|
15
|
+
-- ── por que não usar o que já existe ────────────────────────────────────
|
|
16
|
+
--
|
|
17
|
+
-- `appointment_items.assignee_id` aceita N profissionais. Parece servir, e não
|
|
18
|
+
-- serve: cada linha ali é um ITEM DE SERVIÇO, com preço e duração. Pôr uma
|
|
19
|
+
-- pessoa na reunião exigiria inventar um serviço para ela, e um cadastro que
|
|
20
|
+
-- existe só para satisfazer uma validação é pior que o campo que faltava.
|
|
21
|
+
--
|
|
22
|
+
-- Alargar `assignee_id` para uma lista seria o outro caminho, e é o caminho
|
|
23
|
+
-- que quebra todo mundo: toda vertical lê essa coluna esperando uma pessoa.
|
|
24
|
+
--
|
|
25
|
+
-- ── por que isto não muda nada para quem já usa a agenda ────────────────
|
|
26
|
+
--
|
|
27
|
+
-- Tabela NOVA, nenhum ALTER. `appointments` sai daqui com exatamente as
|
|
28
|
+
-- colunas com que entrou, e `agenda_booking_create` não é tocada — acrescentar
|
|
29
|
+
-- parâmetro a uma função no Postgres cria uma SEGUNDA função, e as duas
|
|
30
|
+
-- convivendo deixam a chamada ambígua para todo mundo que já chamava.
|
|
31
|
+
--
|
|
32
|
+
-- Salão, clínica e restaurante nunca inserem uma linha aqui. Uma tabela sem
|
|
33
|
+
-- linhas é invisível: o comportamento deles continua idêntico.
|
|
34
|
+
--
|
|
35
|
+
-- ── espinha, e não plugin ───────────────────────────────────────────────
|
|
36
|
+
--
|
|
37
|
+
-- "Quem está neste compromisso" é pergunta de agenda, não de CRM. A relação é
|
|
38
|
+
-- `appointments` → `people`, as duas da espinha. Um `plg_crm_*` aqui obrigaria
|
|
39
|
+
-- qualquer outra vertical a instalar o CRM para poder convidar alguém.
|
|
40
|
+
--
|
|
41
|
+
-- Idempotente e replay-safe.
|
|
42
|
+
-- ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS public.appointment_attendees (
|
|
45
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
46
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
47
|
+
-- `booking_id`, e não `appointment_id`: é o nome que `appointment_items` já
|
|
48
|
+
-- usa para apontar para a mesma tabela. Duas colunas irmãs com nomes
|
|
49
|
+
-- diferentes para a mesma coisa é o tipo de detalhe que faz alguém escrever
|
|
50
|
+
-- o JOIN errado às duas da manhã.
|
|
51
|
+
booking_id uuid NOT NULL REFERENCES public.appointments(id) ON DELETE CASCADE,
|
|
52
|
+
-- CASCADE e não SET NULL: convidado sem pessoa não é convidado, é lixo. Se a
|
|
53
|
+
-- pessoa sai da base, a linha sai junto.
|
|
54
|
+
person_id uuid NOT NULL REFERENCES public.people(id) ON DELETE CASCADE,
|
|
55
|
+
-- `required` | `optional`. Texto livre com CHECK em vez de enum: a agenda do
|
|
56
|
+
-- Google fala a mesma língua, e o dia em que precisarmos de `organizer` a
|
|
57
|
+
-- mudança é um CHECK, não um tipo com dependentes.
|
|
58
|
+
--
|
|
59
|
+
-- NÃO pode se chamar `kind`: esse nome é reservado às colunas de espécie, e
|
|
60
|
+
-- toda coluna `kind` tem que ter um registro `*_kinds` com chave estrangeira
|
|
61
|
+
-- atrás dela (check-kind-registry). Isto aqui não é uma espécie.
|
|
62
|
+
role text NOT NULL DEFAULT 'required',
|
|
63
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
64
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
65
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
66
|
+
CONSTRAINT appointment_attendees_role_check
|
|
67
|
+
CHECK (role IN ('required', 'optional'))
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
-- A mesma pessoa duas vezes na mesma reunião não quer dizer nada. O formulário
|
|
71
|
+
-- já evita, mas o formulário não é a última palavra: duas abas abertas, um
|
|
72
|
+
-- clique duplo e um retry de rede chegam aqui sem passar por ele.
|
|
73
|
+
CREATE UNIQUE INDEX IF NOT EXISTS appointment_attendees_booking_person_idx
|
|
74
|
+
ON public.appointment_attendees (booking_id, person_id);
|
|
75
|
+
|
|
76
|
+
-- Ler os convidados DE UMA reserva é a única pergunta que a tela faz.
|
|
77
|
+
CREATE INDEX IF NOT EXISTS appointment_attendees_booking_idx
|
|
78
|
+
ON public.appointment_attendees (tenant_id, booking_id);
|
|
79
|
+
|
|
80
|
+
-- "Em que reuniões esta pessoa está" — a pergunta do outro lado, que a tela do
|
|
81
|
+
-- lead vai querer quando alguém abrir o perfil.
|
|
82
|
+
CREATE INDEX IF NOT EXISTS appointment_attendees_person_idx
|
|
83
|
+
ON public.appointment_attendees (tenant_id, person_id);
|
|
84
|
+
|
|
85
|
+
COMMENT ON TABLE public.appointment_attendees IS
|
|
86
|
+
'Quem mais está no compromisso, além do responsável. `assignee_id` continua sendo UM profissional — convidado é adição, não substituição (119).';
|
|
87
|
+
COMMENT ON COLUMN public.appointment_attendees.role IS
|
|
88
|
+
'required | optional — quem precisa estar e quem é bem-vindo.';
|
|
89
|
+
|
|
90
|
+
-- ---------------------------------------------------------------------------
|
|
91
|
+
-- O escopo herdado do compromisso
|
|
92
|
+
--
|
|
93
|
+
-- Quem grava um convidado sabe a reunião e a pessoa; não tem por que saber o
|
|
94
|
+
-- tenant nem a unidade. Deixar isso a cargo do cliente é como o `tenant_id`
|
|
95
|
+
-- errado entra numa tabela — e com RLS ligada o erro não aparece como erro,
|
|
96
|
+
-- aparece como linha que sumiu.
|
|
97
|
+
--
|
|
98
|
+
-- Cópia fiel de `trg_appointment_items_inherit_scope`, a irmã ao lado.
|
|
99
|
+
-- ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
CREATE OR REPLACE FUNCTION public.trg_appointment_attendees_inherit_scope()
|
|
102
|
+
RETURNS trigger
|
|
103
|
+
LANGUAGE plpgsql
|
|
104
|
+
SECURITY DEFINER
|
|
105
|
+
SET search_path = ''
|
|
106
|
+
AS $$
|
|
107
|
+
BEGIN
|
|
108
|
+
IF NEW.tenant_id IS NULL OR NEW.unit_id IS NULL THEN
|
|
109
|
+
SELECT coalesce(NEW.tenant_id, a.tenant_id), coalesce(NEW.unit_id, a.unit_id)
|
|
110
|
+
INTO NEW.tenant_id, NEW.unit_id
|
|
111
|
+
FROM public.appointments a
|
|
112
|
+
WHERE a.id = NEW.booking_id;
|
|
113
|
+
END IF;
|
|
114
|
+
RETURN NEW;
|
|
115
|
+
END;
|
|
116
|
+
$$;
|
|
117
|
+
|
|
118
|
+
REVOKE ALL ON FUNCTION public.trg_appointment_attendees_inherit_scope() FROM public, anon, authenticated;
|
|
119
|
+
|
|
120
|
+
DROP TRIGGER IF EXISTS appointment_attendees_inherit_scope ON public.appointment_attendees;
|
|
121
|
+
CREATE TRIGGER appointment_attendees_inherit_scope
|
|
122
|
+
BEFORE INSERT ON public.appointment_attendees
|
|
123
|
+
FOR EACH ROW EXECUTE FUNCTION public.trg_appointment_attendees_inherit_scope();
|
|
124
|
+
|
|
125
|
+
-- ---------------------------------------------------------------------------
|
|
126
|
+
-- RLS, políticas, `unit_id`/`owner_id`, índices de escopo e registro
|
|
127
|
+
--
|
|
128
|
+
-- Escrever as quatro políticas à mão aqui seria escrever de novo, pior, o que
|
|
129
|
+
-- `scaffold_table` gera igual para as outras. E as asserções de postura do
|
|
130
|
+
-- pgtap são escritas contra exatamente o que ele produz.
|
|
131
|
+
--
|
|
132
|
+
-- `p_owner_scoping = false`: convidado não tem dono próprio. Quem enxerga a
|
|
133
|
+
-- reunião enxerga quem está nela — esconder metade da sala de quem já está na
|
|
134
|
+
-- sala não protege nada e quebra a tela.
|
|
135
|
+
--
|
|
136
|
+
-- As permissões são as da agenda (`agenda.read/create/edit/delete`), que já
|
|
137
|
+
-- existem no catálogo. Nenhuma chave nova para ninguém conceder.
|
|
138
|
+
-- ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
SELECT app.scaffold_table(
|
|
141
|
+
'public.appointment_attendees'::regclass,
|
|
142
|
+
'agenda.appointment_attendee',
|
|
143
|
+
'agenda',
|
|
144
|
+
false,
|
|
145
|
+
'enforce'
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.appointment_attendees TO authenticated;
|
|
149
|
+
GRANT ALL ON public.appointment_attendees TO service_role;
|
|
150
|
+
|
|
151
|
+
-- A espinha declara o que é dela, para que nenhum plugin crie uma tabela com
|
|
152
|
+
-- este nome mais adiante achando que o terreno estava vazio.
|
|
153
|
+
INSERT INTO app.base_owned_tables (table_name, note)
|
|
154
|
+
VALUES ('appointment_attendees', 'Quem comparece a um Compromisso.')
|
|
155
|
+
ON CONFLICT (table_name) DO NOTHING;
|
package/package.json
CHANGED