@fayz-ai/db 0.14.0 → 0.16.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/011_leadcontrol_joins_the_family.sql +5 -1
- package/migrations/029_o_administrador_enxerga_o_que_existe.sql +167 -0
- package/migrations/031_ticketcontrol_joins_the_family.sql +109 -0
- package/migrations/032_a_marca_e_um_conjunto_de_tokens.sql +291 -0
- package/migrations/041_a_entrega_tem_onde_guardar_a_politica.sql +53 -0
- package/migrations/042_o_token_de_marca_fecha_a_porta_anon.sql +17 -0
- package/migrations/043_fiscalcontrol_joins_the_family.sql +75 -0
- package/migrations/044_o_teste_gratis_dura_trinta_dias.sql +15 -0
- package/migrations/045_o_espelho_manda_enquanto_o_v2_nao_escreveu.sql +519 -0
- package/migrations/046_the_tick_advances_the_flows.sql +77 -0
- package/migrations/047_the_tick_sweeps_the_offers.sql +74 -0
- package/migrations/052_fullcontrol_joins_the_family.sql +213 -0
- package/migrations/053_o_erp_para_de_exigir_o_que_nao_desenha.sql +37 -0
- package/migrations/054_o_arquivo_migrado_ganha_bytes.sql +439 -0
- package/migrations/061_a_linha_sem_data_pode_ser_estrutura.sql +253 -0
- package/migrations/063_o_anexo_ja_registrado_recebe_os_bytes.sql +303 -0
- package/migrations/064_um_arquivo_anexado_a_dezenove_contas.sql +178 -0
- package/migrations/065_apagar_o_sobrevivente_nao_apaga_o_tenant.sql +59 -0
- package/migrations/066_the_port_machinery_closes_its_doors.sql +51 -0
- package/migrations/067_the_erp_keeps_the_front_door.sql +56 -0
- package/migrations/068_o_ledger_encontra_a_linha_pelo_caminho_que_o_writer_usa.sql +55 -0
- package/migrations/069_a_sondagem_de_irma_usa_indice.sql +293 -0
- package/migrations/071_o_sdr_trabalha_o_funil_e_mais_nada.sql +110 -0
- package/migrations/072_o_porte_nao_avisa_a_plataforma_de_uma_venda_antiga.sql +91 -0
- package/migrations/073_a_exclusao_e_contrato_do_cliente_nao_do_cluster.sql +300 -0
- package/migrations/074_a_classificacao_quebrada_nao_leva_o_dinheiro_junto.sql +332 -0
- package/migrations/075_o_apelido_da_referencia_nao_disputa_com_a_variavel.sql +276 -0
- package/migrations/076_o_desligado_tambem_precisa_atravessar.sql +110 -0
- package/migrations/077_a_unidade_escolhida_chega_ao_banco.sql +128 -0
- package/migrations/078_onde_o_profissional_atende_atravessa.sql +71 -0
- package/migrations/079_quem_aparece_na_agenda_e_quem_atende.sql +210 -0
- package/migrations/080_o_contexto_da_requisicao_se_calcula_uma_vez.sql +240 -0
- package/migrations/081_a_permissao_por_unidade_se_resolve_uma_vez_por_unidade.sql +88 -0
- package/migrations/082_a_cerca_tambem_se_lembra.sql +110 -0
- package/migrations/083_quem_e_profissional_continua_agendavel.sql +32 -0
- package/migrations/084_quem_atende_em_varias_nao_cabe_numa_coluna.sql +60 -0
- package/migrations/085_a_unidade_do_profissional_vem_de_onde_ele_atende.sql +47 -0
- package/migrations/086_a_ponte_de_clientes_volta_a_existir.sql +89 -0
- package/migrations/087_a_liberacao_pergunta_ao_indice_antes_da_funcao.sql +79 -0
- package/migrations/088_quem_pode_executar_o_que_o_porte_criou.sql +50 -0
- package/migrations/089_a_memoria_de_transacao_nao_serve_para_isto.sql +86 -0
- package/migrations/090_a_marca_tambem_conta_como_mudanca_de_configuracao.sql +75 -0
- package/migrations/091_o_shell_tambem_e_configuracao_do_tenant.sql +71 -0
- package/migrations/092_a_marca_tem_uma_grafia_so_e_uma_porta_so.sql +159 -0
- package/migrations/093_a_marca_carrega_o_tema_inteiro.sql +179 -0
- package/migrations/094_a_porta_do_dominio_aprende_a_grafia_nova.sql +55 -0
- package/migrations/095_o_aplicativo_tem_um_preset_que_a_conta_estende.sql +98 -0
- package/migrations/096_o_sublinhado_tambem_e_nome_de_modulo.sql +100 -0
- package/migrations/097_o_oficio_diz_o_que_oferece_e_a_conta_o_que_usa.sql +137 -0
- package/migrations/098_a_arvore_do_oficio_nomeia_quem_ainda_desenha.sql +22 -0
- package/migrations/099_o_estoque_mora_dentro_de_produtos.sql +48 -0
- package/migrations/100_o_chefcontrol_desenha_abas_de_modulo.sql +24 -0
- package/migrations/101_o_oficio_agrupa_o_plugin_nomeia.sql +93 -0
- package/migrations/102_tres_marcas_ganham_fundo_e_contraste.sql +73 -0
- package/migrations/103_great_djs_e_iam_club_entram_na_frota.sql +75 -0
- package/migrations/104_o_curso_e_a_bilheteria_ganham_preset.sql +29 -0
- package/migrations/105_o_que_o_oficio_exige_ele_tambem_oferece.sql +78 -0
- package/migrations/106_a_comunidade_se_gere_como_escola.sql +84 -0
- package/migrations/107_o_logo_da_coluna_e_projecao_do_documento.sql +53 -0
- package/migrations/108_o_modulo_do_oficio_vizinho_tem_onde_ser_visto.sql +78 -0
- package/migrations/109_a_conta_diz_o_que_nao_usa_e_a_marca_pinta_a_casa.sql +67 -0
- package/migrations/110_a_tabela_de_ramos_aprende_o_vocabulario_fechado.sql +60 -0
- package/migrations/111_o_erp_generico_nao_vende_por_pedido.sql +50 -0
- package/migrations/112_o_cache_do_contexto_sabe_de_quem_ele_e.sql +327 -0
- package/migrations/113_o_ramo_volta_a_saber_que_papeis_a_conta_nasce_tendo.sql +124 -0
- package/migrations/114_a_resposta_inicial_volta_a_dizer_o_que_a_pessoa_pode.sql +93 -0
- package/migrations/115_o_rail_e_de_quem_tem_um.sql +108 -0
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 079_o_oficio_diz_o_que_oferece_e_a_conta_o_que_usa.sql
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- Uma clínica abria mostrando módulo de restaurante, de loja e de escola ao
|
|
5
|
+
-- mesmo tempo. Não era dado sujo: era a regra.
|
|
6
|
+
--
|
|
7
|
+
-- ── Por que o aplicativo central mostra tudo ────────────────────────────────
|
|
8
|
+
-- `resolvePluginRuntime` é ADITIVO: uma linha `active` liga, uma linha
|
|
9
|
+
-- `disabled` desliga, e o silêncio cai no padrão do manifesto. Hoje 24 dos 29
|
|
10
|
+
-- plugins publicados nascem `defaultEnabled: true`.
|
|
11
|
+
--
|
|
12
|
+
-- Num aplicativo de OFÍCIO isso está certo: o resto-saas importa doze pacotes,
|
|
13
|
+
-- e a curadoria já aconteceu no `import`. O aplicativo central importa os vinte
|
|
14
|
+
-- e cinco — e ali o mesmo padrão quer dizer "toda conta recebe todo ofício,
|
|
15
|
+
-- a menos que escreva uma linha `disabled` para cada um que não quer".
|
|
16
|
+
--
|
|
17
|
+
-- A Clinica de Teste tem catorze linhas, todas `active`, nenhuma `disabled`.
|
|
18
|
+
-- Ela nunca disse que não era um restaurante. Ninguém nunca disse.
|
|
19
|
+
--
|
|
20
|
+
-- ── O que faltava: a OFERTA ────────────────────────────────────────────────
|
|
21
|
+
-- A 077 deu ao aplicativo um `preset` com `shell` e `nav` — como DESENHAR. Não
|
|
22
|
+
-- havia onde dizer o que ele OFERECE. Sem essa lista, o catálogo do aplicativo
|
|
23
|
+
-- central é a união de tudo que foi compilado, que não é catálogo nenhum.
|
|
24
|
+
--
|
|
25
|
+
-- app.apps.preset.modules o ofício — o que este ofício oferece
|
|
26
|
+
-- app.tenant_plugins a conta — o que esta casa ligou ou desligou
|
|
27
|
+
-- app.roles o papel — quem, dentro dela, enxerga
|
|
28
|
+
--
|
|
29
|
+
-- Com `modules`, o silêncio deixa de significar "ligado": um plugin que o
|
|
30
|
+
-- ofício não oferece está FORA, mesmo nascendo `defaultEnabled: true`. E um que
|
|
31
|
+
-- ele oferece continua obedecendo a conta — que é como o Artorius, sendo food,
|
|
32
|
+
-- não tem Balcão: o ChefControl OFERECE o PDV, e a conta dele diz que não usa.
|
|
33
|
+
--
|
|
34
|
+
-- O ofício propõe; a conta dispõe.
|
|
35
|
+
--
|
|
36
|
+
-- ── E o ramo (`vertical_id`)? Ele era quatro vocabulários ──────────────────
|
|
37
|
+
-- Quatro lugares diziam o ramo de uma conta, e nenhum concordava com o outro:
|
|
38
|
+
--
|
|
39
|
+
-- tenants.vertical_id food, restaurant, beauty, salon, clinic,
|
|
40
|
+
-- commerce, ecommerce, retail, null — 9 grafias
|
|
41
|
+
-- app.vertical_defaults agency, ecommerce, restaurant, salon, school
|
|
42
|
+
-- — 5 grafias
|
|
43
|
+
-- app.tenant_apps.app_id resto, beauty, shop, full, fiscal, school
|
|
44
|
+
-- PluginManifest.verticalId nunca preenchido (é `options?.verticalId`, e
|
|
45
|
+
-- o aplicativo central não passa nada)
|
|
46
|
+
--
|
|
47
|
+
-- A interseção das duas primeiras é de TRÊS nomes. Pertinho do Céu é `food`; a
|
|
48
|
+
-- tabela de defaults só conhece `restaurant`. Catorze das vinte e duas contas
|
|
49
|
+
-- têm um ramo que a tabela de ramos nunca ouviu falar — e é por isso que nada
|
|
50
|
+
-- específico de ofício jamais disparou.
|
|
51
|
+
--
|
|
52
|
+
-- A saída não é uma quinta grafia. É reconhecer qual das quatro tem dono:
|
|
53
|
+
-- `tenant_apps` é escrita pelo provisionamento, tem chave estrangeira para
|
|
54
|
+
-- `app.apps` e já viaja no `app_config()`. O RAMO DE UMA CONTA É O OFÍCIO DO
|
|
55
|
+
-- APLICATIVO QUE ELA RODA. `tenants.vertical_id` passa a ser consequência —
|
|
56
|
+
-- rótulo para relatório e para o onboarding, nunca portão.
|
|
57
|
+
-- ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
-- ── 1. O aplicativo declara seu ofício, uma vez ────────────────────────────
|
|
60
|
+
ALTER TABLE app.apps ADD COLUMN IF NOT EXISTS vertical_id text;
|
|
61
|
+
|
|
62
|
+
COMMENT ON COLUMN app.apps.vertical_id IS
|
|
63
|
+
'O ofício deste aplicativo, em UM vocabulário: food, beauty, commerce, education, events, accounting, generic. NULL = não é ofício (control-admin). `tenants.vertical_id` é derivada daqui — ver 079.';
|
|
64
|
+
|
|
65
|
+
UPDATE app.apps SET vertical_id = v.vertical FROM (VALUES
|
|
66
|
+
('resto', 'food'),
|
|
67
|
+
('beauty', 'beauty'), -- salão E clínica: os dois rodam o StudioControl
|
|
68
|
+
('shop', 'commerce'),
|
|
69
|
+
('school', 'education'),
|
|
70
|
+
('ticket', 'events'),
|
|
71
|
+
('fiscal', 'accounting'),
|
|
72
|
+
('full', 'generic'),
|
|
73
|
+
('crm', 'generic')
|
|
74
|
+
) AS v(id, vertical) WHERE app.apps.id = v.id;
|
|
75
|
+
|
|
76
|
+
-- ── 2. O ofício declara o que oferece ──────────────────────────────────────
|
|
77
|
+
-- Extraído do que cada aplicativo REALMENTE compõe hoje (o array `plugins` de
|
|
78
|
+
-- `src/config/app.tsx` de cada repositório), não de uma lista desejada.
|
|
79
|
+
-- `::jsonb` e nao so `m.modules`: num VALUES o literal `$$[...]$$` e TEXTO, e
|
|
80
|
+
-- `jsonb_build_object` com texto guarda uma STRING, nao um array. A diferenca
|
|
81
|
+
-- so aparece tres migrations adiante, quando a invariante da 101 chama
|
|
82
|
+
-- `jsonb_array_length` e recebe um escalar.
|
|
83
|
+
UPDATE app.apps SET preset = preset || jsonb_build_object('modules', m.modules::jsonb) FROM (VALUES
|
|
84
|
+
('resto', $$["dashboard","admin","notifications","menu","orders","tables","reservations","kitchen",
|
|
85
|
+
"resto-counter","resto-module-toggles","resto-printing","agenda","inventory","crm",
|
|
86
|
+
"financial","fiscal-br","custom_forms","marketing","automations","conversations",
|
|
87
|
+
"tasks","workforce","reports","openbanking"]$$),
|
|
88
|
+
('beauty', $$["dashboard","admin","notifications","agenda","crm","financial","inventory","marketing",
|
|
89
|
+
"automations","custom_forms","tasks","scribe","workforce","reports","openbanking"]$$),
|
|
90
|
+
('shop', $$["dashboard","admin","notifications","shop","orders","inventory","crm","marketing",
|
|
91
|
+
"financial","automations","reports","blog","sites"]$$),
|
|
92
|
+
('full', $$["dashboard","admin","notifications","crm","conversations","financial","inventory",
|
|
93
|
+
"marketing","automations","reports","tasks","custom_forms"]$$),
|
|
94
|
+
('school', $$["dashboard","admin","notifications","courses","agenda","crm","financial","marketing",
|
|
95
|
+
"automations","reports","custom_forms"]$$),
|
|
96
|
+
('fiscal', $$["dashboard","admin","notifications","financial","fiscal-br","reports","openbanking"]$$),
|
|
97
|
+
('ticket', $$["dashboard","admin","notifications","orders","crm","financial","marketing",
|
|
98
|
+
"automations","reports"]$$)
|
|
99
|
+
) AS m(id, modules) WHERE app.apps.id = m.id;
|
|
100
|
+
|
|
101
|
+
-- ── 3. A conta que rodava cinco ofícios volta a rodar o seu ────────────────
|
|
102
|
+
-- Resíduo do backfill que deu todos os aplicativos a todas as contas. Já
|
|
103
|
+
-- corrigido para os restaurantes; a clínica ficou para trás.
|
|
104
|
+
DELETE FROM app.tenant_apps ta
|
|
105
|
+
USING public.tenants t
|
|
106
|
+
WHERE ta.tenant_id = t.id
|
|
107
|
+
AND t.name = 'Clinica de Teste'
|
|
108
|
+
AND ta.app_id <> 'beauty';
|
|
109
|
+
|
|
110
|
+
-- ── 4. O ramo passa a ser consequência do ofício ───────────────────────────
|
|
111
|
+
-- Onde a conta roda um ofício, a coluna repete o que `tenant_apps` já diz. Onde
|
|
112
|
+
-- ela roda mais de um (Papa Léguas: resto + shop), vence o mais específico —
|
|
113
|
+
-- `fiscal` é retaguarda e nunca é o produto principal; `full` é o ERP sem ofício.
|
|
114
|
+
UPDATE public.tenants t SET vertical_id = d.vertical
|
|
115
|
+
FROM (
|
|
116
|
+
SELECT ta.tenant_id,
|
|
117
|
+
(array_agg(a.vertical_id ORDER BY (a.vertical_id = 'accounting'), (a.vertical_id = 'generic'), a.id))[1] AS vertical
|
|
118
|
+
FROM app.tenant_apps ta
|
|
119
|
+
JOIN app.apps a ON a.id = ta.app_id
|
|
120
|
+
WHERE ta.status = 'active' AND a.vertical_id IS NOT NULL
|
|
121
|
+
GROUP BY ta.tenant_id
|
|
122
|
+
) d
|
|
123
|
+
WHERE d.tenant_id = t.id AND t.vertical_id IS DISTINCT FROM d.vertical;
|
|
124
|
+
|
|
125
|
+
-- Sem aplicativo nenhum não há ofício a derivar. `generic` é a resposta honesta:
|
|
126
|
+
-- a conta existe e ainda não disse o que faz.
|
|
127
|
+
UPDATE public.tenants t SET vertical_id = 'generic'
|
|
128
|
+
WHERE NOT EXISTS (SELECT 1 FROM app.tenant_apps ta WHERE ta.tenant_id = t.id AND ta.status = 'active');
|
|
129
|
+
|
|
130
|
+
-- Fecha o vocabulário. Sem isto a próxima conta criada à mão reabre o problema.
|
|
131
|
+
ALTER TABLE public.tenants DROP CONSTRAINT IF EXISTS tenants_vertical_id_check;
|
|
132
|
+
ALTER TABLE public.tenants ADD CONSTRAINT tenants_vertical_id_check
|
|
133
|
+
CHECK (vertical_id IS NULL OR vertical_id IN
|
|
134
|
+
('food','beauty','commerce','education','events','accounting','generic'));
|
|
135
|
+
|
|
136
|
+
COMMENT ON COLUMN public.tenants.vertical_id IS
|
|
137
|
+
'O ramo desta conta, DERIVADO do ofício do aplicativo que ela roda (079). Rótulo para relatório e onboarding — quem decide o que aparece é app.apps.preset.modules cruzado com app.tenant_plugins, nunca esta coluna.';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 080_a_arvore_do_oficio_nomeia_quem_ainda_desenha.sql
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- Quando o ofício traz árvore curada (`preset.nav`), quem mais pode pôr linha
|
|
5
|
+
-- no rail?
|
|
6
|
+
--
|
|
7
|
+
-- "Ninguém" foi a primeira resposta, e custou o Painel e as Conversas: a árvore
|
|
8
|
+
-- do ChefControl nunca teve essas duas linhas porque, no resto-saas, elas vêm
|
|
9
|
+
-- da nav dos PRÓPRIOS plugins — lá só os de ofício levam `hideNav()`.
|
|
10
|
+
--
|
|
11
|
+
-- "Todos" é o erro oposto, e foi o que apareceu na tela seguinte: a Agenda
|
|
12
|
+
-- desenhou "Agenda" ao lado de "Operação" sendo que, dentro dela, ela já é a
|
|
13
|
+
-- linha "Reservas". Um módulo em dois lugares do mesmo menu.
|
|
14
|
+
--
|
|
15
|
+
-- `navExtras` é a exceção nomeada — o equivalente declarativo do `hideNav()`
|
|
16
|
+
-- que o aplicativo de ofício escreve em código. Só vale onde há `nav`: um
|
|
17
|
+
-- ofício que apenas ORDENA plugins (beauty, shop, full) não silencia ninguém, e
|
|
18
|
+
-- para eles a lista não existe.
|
|
19
|
+
-- ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
UPDATE app.apps SET preset = preset || '{"navExtras":["dashboard","conversations"]}'::jsonb
|
|
22
|
+
WHERE id = 'resto';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 081_o_estoque_mora_dentro_de_produtos.sql — o StoreControl ganha árvore.
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- PULSE e Artorius mostravam "Produtos" e "Estoque" como duas linhas irmãs do
|
|
5
|
+
-- rail. Duas entradas para a mesma coisa na cabeça de quem opera uma loja — e
|
|
6
|
+
-- não é que uma esteja errada: o catálogo (o que vendo, preço, foto) e a
|
|
7
|
+
-- posição (quanto tem, onde, a que custo) são fatos diferentes sobre o MESMO
|
|
8
|
+
-- produto. O que estava errado era pedir que a pessoa escolhesse entre eles no
|
|
9
|
+
-- menu, como se fossem assuntos separados.
|
|
10
|
+
--
|
|
11
|
+
-- O ChefControl já tinha resolvido isso: "Produtos ▸ Cardápio · Estoque". Aqui
|
|
12
|
+
-- é a mesma forma, com o vocabulário da loja — e é a segunda vez que a árvore
|
|
13
|
+
-- curada de um ofício responde uma pergunta que o rail montado de plugins
|
|
14
|
+
-- soltos não sabia responder.
|
|
15
|
+
--
|
|
16
|
+
-- ── Por que a árvore precisa nomear TUDO ───────────────────────────────────
|
|
17
|
+
-- Onde há `nav`, só quem está em `navExtras` ainda desenha linha própria (080).
|
|
18
|
+
-- Então um módulo ausente da árvore desaparece do menu — Marketing, Financeiro
|
|
19
|
+
-- e Relatórios entram aqui não por decoração, mas porque sem eles sumiriam.
|
|
20
|
+
-- ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
UPDATE app.apps SET preset = preset || $$
|
|
23
|
+
{
|
|
24
|
+
"navExtras": ["dashboard", "conversations"],
|
|
25
|
+
"nav": [
|
|
26
|
+
{ "route": "/orders", "label": "Pedidos", "icon": "ShoppingBag", "position": 10, "children": [
|
|
27
|
+
{ "route": "/orders", "label": "Pedidos", "icon": "ShoppingBag" },
|
|
28
|
+
{ "route": "/orders/customers", "label": "Clientes", "icon": "Users" }
|
|
29
|
+
]},
|
|
30
|
+
{ "route": "/products", "label": "Produtos", "icon": "Package", "position": 20, "children": [
|
|
31
|
+
{ "route": "/products", "label": "Catálogo", "icon": "Package" },
|
|
32
|
+
{ "route": "/products/collections", "label": "Coleções", "icon": "Layers" },
|
|
33
|
+
{ "route": "/products/categories", "label": "Categorias", "icon": "FolderTree" },
|
|
34
|
+
{ "route": "/inventory", "label": "Estoque", "icon": "Boxes" }
|
|
35
|
+
]},
|
|
36
|
+
{ "route": "/discounts", "label": "Descontos", "icon": "Tag", "position": 25, "children": [
|
|
37
|
+
{ "route": "/discounts", "label": "Descontos", "icon": "Tag" },
|
|
38
|
+
{ "route": "/discounts/gift-cards", "label": "Gift Cards", "icon": "CreditCard" }
|
|
39
|
+
]},
|
|
40
|
+
{ "route": "/content", "label": "Conteúdo", "icon": "Image", "position": 28 },
|
|
41
|
+
{ "route": "/sales", "label": "CRM", "icon": "Filter", "position": 30 },
|
|
42
|
+
{ "route": "/marketing", "label": "Marketing", "icon": "Megaphone", "position": 35 },
|
|
43
|
+
{ "route": "/financial", "label": "Financeiro", "icon": "DollarSign", "position": 40 },
|
|
44
|
+
{ "route": "/automations", "label": "Automações", "icon": "Zap", "position": 45 },
|
|
45
|
+
{ "route": "/analytics", "label": "Relatórios", "icon": "BarChart3", "position": 50 }
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
$$::jsonb WHERE id = 'shop';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 082_o_chefcontrol_desenha_abas_de_modulo.sql
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- O Estoque abria sem Produtos, Movimentações, Contagens e Receitas.
|
|
5
|
+
--
|
|
6
|
+
-- `ModulePage` com variante `sidebar` não desenha sub-navegação nenhuma — e a
|
|
7
|
+
-- razão é boa: nessa variante o RAIL é que lista as sub-telas do módulo como
|
|
8
|
+
-- filhas da linha dele. Só que aqui o rail não lista: as filhas de "Produtos"
|
|
9
|
+
-- são Cardápio e Estoque, que são MÓDULOS irmãos, não as abas de dentro do
|
|
10
|
+
-- Estoque. A promessa não era cumprida por ninguém, e as abas deixavam de
|
|
11
|
+
-- existir.
|
|
12
|
+
--
|
|
13
|
+
-- A 077 semeou `moduleNav: "sidebar"` no preset do ChefControl. Foi engano:
|
|
14
|
+
-- essa é a escolha do FullControl, que a declara em `src/config/app.tsx`. Os
|
|
15
|
+
-- três aplicativos de referência — resto-saas, beauty-saas e marketplace-saas —
|
|
16
|
+
-- não declaram `moduleNav` nenhum e caem no padrão do shell para
|
|
17
|
+
-- `layout: 'sidebar'`, que é `tabs`.
|
|
18
|
+
--
|
|
19
|
+
-- O preset volta a dizer o que o aplicativo de referência faz.
|
|
20
|
+
-- ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
UPDATE app.apps
|
|
23
|
+
SET preset = jsonb_set(preset, '{shell,moduleNav}', '"tabs"'::jsonb)
|
|
24
|
+
WHERE id = 'resto';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 083_o_oficio_agrupa_o_plugin_nomeia.sql — o preset para de redesenhar o rail.
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- A 077 deu ao ofício uma ÁRVORE: cada linha com rota, rótulo, ícone e posição,
|
|
5
|
+
-- escrita à mão em SQL. Mas o plugin já diz tudo isso — e duas fontes para o
|
|
6
|
+
-- mesmo fato brigam. Brigaram quatro vezes, e todas as quatro chegaram como
|
|
7
|
+
-- defeito de tela:
|
|
8
|
+
--
|
|
9
|
+
-- o Painel sumiu a árvore não o listava, e ela calava os plugins
|
|
10
|
+
-- a Agenda duplicou a árvore a chamava "Reservas"; o plugin, "Agenda"
|
|
11
|
+
-- "Pedidos" saiu 2x a rubrica e o primeiro filho tinham o mesmo rótulo
|
|
12
|
+
-- o Cardápio ficou solto o `menu` não estava na árvore da loja
|
|
13
|
+
--
|
|
14
|
+
-- Nenhum era erro de renderização. Todos eram a árvore discordando do catálogo.
|
|
15
|
+
--
|
|
16
|
+
-- ── A forma nova ───────────────────────────────────────────────────────────
|
|
17
|
+
-- O preset passa a declarar só o AGRUPAMENTO, numa lista única que se lê de
|
|
18
|
+
-- cima para baixo como o próprio rail:
|
|
19
|
+
--
|
|
20
|
+
-- "rail": [
|
|
21
|
+
-- "dashboard", ← as linhas deste plugin
|
|
22
|
+
-- { "label": "Produtos", "icon": "Package",
|
|
23
|
+
-- "under": ["/menu", "/inventory"] }, ← uma rubrica
|
|
24
|
+
-- "financial"
|
|
25
|
+
-- ]
|
|
26
|
+
--
|
|
27
|
+
-- Uma string é o plugin, com os rótulos DELE. Um objeto é a rubrica, e `under`
|
|
28
|
+
-- são prefixos de rota — o mesmo casamento por prefixo que o rail já usa para
|
|
29
|
+
-- aninhar. O preset nunca diz como uma linha se chama, então não tem como
|
|
30
|
+
-- discordar.
|
|
31
|
+
--
|
|
32
|
+
-- Quem não foi nomeado vai para o FIM, não some: é a regra do ByDesign — uma
|
|
33
|
+
-- pergunta que ninguém respondeu cai no padrão, não em "não". O contrário faria
|
|
34
|
+
-- do dia em que um aplicativo publica um plugin novo o dia em que ele
|
|
35
|
+
-- desaparece de toda conta, em silêncio.
|
|
36
|
+
--
|
|
37
|
+
-- `nav` e `navExtras` saem junto. `order` também: a ordem agora é a da lista.
|
|
38
|
+
-- ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
-- ── ChefControl ────────────────────────────────────────────────────────────
|
|
41
|
+
-- As sete rubricas do resto-saas, agora sem repetir o nome de nenhuma tela.
|
|
42
|
+
UPDATE app.apps SET preset = (preset - 'nav' - 'navExtras' - 'order') || $$
|
|
43
|
+
{
|
|
44
|
+
"rail": [
|
|
45
|
+
"dashboard",
|
|
46
|
+
"conversations",
|
|
47
|
+
{ "label": "Operação", "icon": "Utensils",
|
|
48
|
+
"under": ["/orders", "/kitchen", "/clients", "/reservations", "/tables"] },
|
|
49
|
+
{ "label": "Delivery", "icon": "Bike", "under": ["/delivery"] },
|
|
50
|
+
{ "label": "Produtos", "icon": "Package", "under": ["/menu", "/inventory"] },
|
|
51
|
+
"financial",
|
|
52
|
+
"workforce",
|
|
53
|
+
{ "label": "Cadastros", "icon": "Database", "under": ["/company", "/staff"] },
|
|
54
|
+
"reports"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
$$::jsonb WHERE id = 'resto';
|
|
58
|
+
|
|
59
|
+
-- ── StoreControl ───────────────────────────────────────────────────────────
|
|
60
|
+
-- "Produtos" junta o catálogo da loja, o estoque e — quando a casa tiver — o
|
|
61
|
+
-- cardápio. É a resposta à redundância Produtos × Estoque, agora sem inventar
|
|
62
|
+
-- rótulo nenhum.
|
|
63
|
+
UPDATE app.apps SET preset = (preset - 'nav' - 'navExtras' - 'order') || $$
|
|
64
|
+
{
|
|
65
|
+
"rail": [
|
|
66
|
+
"dashboard",
|
|
67
|
+
"conversations",
|
|
68
|
+
{ "label": "Vendas", "icon": "ShoppingBag", "under": ["/orders"] },
|
|
69
|
+
{ "label": "Produtos", "icon": "Package",
|
|
70
|
+
"under": ["/products", "/inventory", "/menu"] },
|
|
71
|
+
{ "label": "Descontos", "icon": "Tag", "under": ["/discounts"] },
|
|
72
|
+
"shop",
|
|
73
|
+
"crm",
|
|
74
|
+
"marketing",
|
|
75
|
+
"financial",
|
|
76
|
+
"automations",
|
|
77
|
+
"reports"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
$$::jsonb WHERE id = 'shop';
|
|
81
|
+
|
|
82
|
+
-- ── StudioControl e FullControl ────────────────────────────────────────────
|
|
83
|
+
-- Eles nunca desenharam rail à mão: ordenam os plugins, e é só isso que o
|
|
84
|
+
-- preset guarda. A lista vira `rail` para que exista UM vocabulário.
|
|
85
|
+
UPDATE app.apps SET preset = (preset - 'order') || $$
|
|
86
|
+
{ "rail": ["dashboard", "conversations", "agenda", "crm", "financial",
|
|
87
|
+
"inventory", "marketing", "workforce", "reports"] }
|
|
88
|
+
$$::jsonb WHERE id = 'beauty';
|
|
89
|
+
|
|
90
|
+
UPDATE app.apps SET preset = (preset - 'order') || $$
|
|
91
|
+
{ "rail": ["dashboard", "conversations", "crm", "financial", "inventory",
|
|
92
|
+
"marketing", "automations", "reports"] }
|
|
93
|
+
$$::jsonb WHERE id = 'full';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 084_tres_marcas_ganham_fundo_e_contraste.sql
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- Três contas passam a pintar a casa inteira, não só o rail. Os valores são os
|
|
5
|
+
-- que o founder passou; o que este arquivo guarda é a decisão de onde cada um
|
|
6
|
+
-- entra.
|
|
7
|
+
--
|
|
8
|
+
-- ── Espaço Renova Rio ──────────────────────────────────────────────────────
|
|
9
|
+
-- O fundo vira o marrom escuro da própria marca — antes a página era clara e só
|
|
10
|
+
-- a trilha era escura, o que lia como duas casas. O botão vai para uma pele
|
|
11
|
+
-- clara: sobre marrom escuro, um botão marrom não é um botão.
|
|
12
|
+
-- O rail fica um degrau ABAIXO da página (#100C0A contra #17120E) para ler como
|
|
13
|
+
-- recuo, não como uma segunda página encostada.
|
|
14
|
+
--
|
|
15
|
+
-- ── Clínica Sol ────────────────────────────────────────────────────────────
|
|
16
|
+
-- O laranja #FF7B05 da marca contra os azuis que ela já tinha. É o único par de
|
|
17
|
+
-- complementares aqui, e por isso o rail guarda o laranja só para o item ativo.
|
|
18
|
+
--
|
|
19
|
+
-- ── PULSE ──────────────────────────────────────────────────────────────────
|
|
20
|
+
-- Verde #83CA17 sobre preto — no RAIL. A página segue clara.
|
|
21
|
+
--
|
|
22
|
+
-- A primeira versão pintou a casa inteira de preto e o resultado foi ilegível:
|
|
23
|
+
-- carta #151515 sobre fundo #0B0B0B é contraste de 1.2:1, e todo número do
|
|
24
|
+
-- painel sumiu. "Preto" era a cor da trilha, não do sistema — e a diferença
|
|
25
|
+
-- entre uma marca escura e um tema escuro é que a segunda decisão precisa de
|
|
26
|
+
-- uma paleta inteira, não de duas cores.
|
|
27
|
+
--
|
|
28
|
+
-- ── O que isto expôs no código ─────────────────────────────────────────────
|
|
29
|
+
-- O vocabulário da marca sabe dizer a cor do BOTÃO e não a do TEXTO dentro
|
|
30
|
+
-- dele: `primaryForeground` cai em branco quando ninguém diz, e ninguém pode
|
|
31
|
+
-- dizer. Serviu enquanto toda marca era escura; verde-limão e bege com texto
|
|
32
|
+
-- branco são ilegíveis. A correção não é um campo novo — é derivar por
|
|
33
|
+
-- luminância (ver `readableOn` em packages/admin/src/app/admin-app.tsx), que
|
|
34
|
+
-- não tem como ser preenchido errado e já vale para a marca que ainda não
|
|
35
|
+
-- existe.
|
|
36
|
+
-- ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
UPDATE public.tenants SET settings = jsonb_set(settings, '{branding,tokens}', $$
|
|
39
|
+
{
|
|
40
|
+
"color": { "primary": "#E3B591", "accent": "#E8A46A" },
|
|
41
|
+
"surface": { "background": "#17120E", "card": "#211A15", "foreground": "#F5F1ED" },
|
|
42
|
+
"rail": { "background": "#100C0A", "foreground": "#F5F1ED", "border": "#2A2018",
|
|
43
|
+
"accent": "#2A2018", "accentForeground": "#E3B591", "muted": "#9B8B7C" },
|
|
44
|
+
"radius": "0.75rem"
|
|
45
|
+
}
|
|
46
|
+
$$::jsonb), updated_at = now()
|
|
47
|
+
WHERE name = 'Espaco Renova Rio';
|
|
48
|
+
|
|
49
|
+
UPDATE public.tenants SET settings = jsonb_set(settings, '{branding,tokens}', $$
|
|
50
|
+
{
|
|
51
|
+
"color": { "primary": "#FF7B05", "accent": "#FF9633" },
|
|
52
|
+
"surface": { "background": "#F6F8FC", "card": "#FFFFFF", "foreground": "#111620" },
|
|
53
|
+
"rail": { "background": "#121826", "foreground": "#EEF1F7", "border": "#1F2A40",
|
|
54
|
+
"accent": "#26324C", "accentForeground": "#FF9633", "muted": "#8A96AC" },
|
|
55
|
+
"radius": "0.75rem"
|
|
56
|
+
}
|
|
57
|
+
$$::jsonb), updated_at = now()
|
|
58
|
+
WHERE name = 'Clínica Sol - Espaço Terapêutico';
|
|
59
|
+
|
|
60
|
+
-- `jsonb_set` exige que o caminho pai exista, e a PULSE não tinha `tokens`:
|
|
61
|
+
-- por isso a mesclagem sobre `branding` em vez do caminho fundo.
|
|
62
|
+
UPDATE public.tenants
|
|
63
|
+
SET settings = jsonb_set(coalesce(settings,'{}'::jsonb), '{branding}',
|
|
64
|
+
coalesce(settings->'branding','{}'::jsonb) || $$
|
|
65
|
+
{ "tokens": {
|
|
66
|
+
"color": { "primary": "#83CA17", "accent": "#6FAE12" },
|
|
67
|
+
"surface": { "background": "#F7F8F5", "card": "#FFFFFF", "foreground": "#14160F" },
|
|
68
|
+
"rail": { "background": "#0B0B0B", "foreground": "#F2F4F0", "border": "#1E1E1E",
|
|
69
|
+
"accent": "#1C2612", "accentForeground": "#9BDB3A", "muted": "#8A8A8A" },
|
|
70
|
+
"radius": "0.5rem" } }
|
|
71
|
+
$$::jsonb, true),
|
|
72
|
+
updated_at = now()
|
|
73
|
+
WHERE name = 'PULSE';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 085_great_djs_e_iam_club_entram_na_frota.sql
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- As duas existiam como APLICATIVOS com backend próprio — Great DJs no projeto
|
|
5
|
+
-- `fayz-calendar`, IAM no `iam-community-app` — e não como CONTA no cluster do
|
|
6
|
+
-- Control, que é o único que o aplicativo central lê. Sem linha aqui elas não
|
|
7
|
+
-- apareciam no seletor, por mais que o produto existisse.
|
|
8
|
+
--
|
|
9
|
+
-- Great DJs → CourseControl (education). O produto dela é aula de DJ, e a
|
|
10
|
+
-- agenda de booking é consequência disso, não o contrário.
|
|
11
|
+
-- IAM Club → TicketControl (events). Clube é porta, lista e ingresso.
|
|
12
|
+
--
|
|
13
|
+
-- Os ids são os que `platform_tenant_create` sorteou; este arquivo reencena o
|
|
14
|
+
-- estado, não o recria — daí o `WHERE slug` em vez de `INSERT`.
|
|
15
|
+
--
|
|
16
|
+
-- ── Sobre os ladrilhos ─────────────────────────────────────────────────────
|
|
17
|
+
-- Os dois logos vieram com fundo branco chapado. Recortar por limiar comeria o
|
|
18
|
+
-- brilho de neon do IAM junto — ele é lavanda clarinho perto das letras. A
|
|
19
|
+
-- chave é tratar branco como TRANSPARÊNCIA e não como cor: alfa = 1 − brancura,
|
|
20
|
+
-- e a cor restante renormalizada. O que era halo sobre branco vira halo sobre
|
|
21
|
+
-- preto, que é como um neon se comporta de verdade.
|
|
22
|
+
--
|
|
23
|
+
-- Caminho `<tenant>/branding/tile.png` e não `branding/<tenant>/...` como as
|
|
24
|
+
-- marcas antigas: a política do bucket exige o id do tenant na PRIMEIRA pasta.
|
|
25
|
+
-- As antigas entraram por service_role, contornando a política — este caminho
|
|
26
|
+
-- é o que o próprio dono da conta consegue escrever.
|
|
27
|
+
-- ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
UPDATE public.tenants t SET settings = jsonb_set(coalesce(t.settings,'{}'::jsonb), '{branding}',
|
|
30
|
+
coalesce(t.settings->'branding','{}'::jsonb) || $$
|
|
31
|
+
{
|
|
32
|
+
"identity": { "name": "IAM Club",
|
|
33
|
+
"logoUrl": "https://klvfxzreepavcpyjiwla.supabase.co/storage/v1/object/public/avatars/d6ba2252-7807-4d25-895c-b6945a2a8171/branding/tile.png",
|
|
34
|
+
"markUrl": "https://klvfxzreepavcpyjiwla.supabase.co/storage/v1/object/public/avatars/d6ba2252-7807-4d25-895c-b6945a2a8171/branding/tile.png" },
|
|
35
|
+
"tokens": {
|
|
36
|
+
"color": { "primary": "#8F6CF5", "accent": "#A387F7" },
|
|
37
|
+
"surface": { "background": "#F8F7FD", "card": "#FFFFFF", "foreground": "#15121F" },
|
|
38
|
+
"rail": { "background": "#0B0912", "foreground": "#F1EEFB", "border": "#241E3A",
|
|
39
|
+
"accent": "#241E3A", "accentForeground": "#B6A0FA", "muted": "#8B84A6" },
|
|
40
|
+
"radius": "0.75rem" }
|
|
41
|
+
}
|
|
42
|
+
$$::jsonb, true), updated_at = now()
|
|
43
|
+
WHERE t.slug = 'iam-club';
|
|
44
|
+
|
|
45
|
+
UPDATE public.tenants t SET settings = jsonb_set(coalesce(t.settings,'{}'::jsonb), '{branding}',
|
|
46
|
+
coalesce(t.settings->'branding','{}'::jsonb) || $$
|
|
47
|
+
{
|
|
48
|
+
"identity": { "name": "Great DJs",
|
|
49
|
+
"logoUrl": "https://klvfxzreepavcpyjiwla.supabase.co/storage/v1/object/public/avatars/973e8465-09f8-41a9-93fb-a906d9667a1f/branding/tile.png",
|
|
50
|
+
"markUrl": "https://klvfxzreepavcpyjiwla.supabase.co/storage/v1/object/public/avatars/973e8465-09f8-41a9-93fb-a906d9667a1f/branding/tile.png" },
|
|
51
|
+
"tokens": {
|
|
52
|
+
"color": { "primary": "#F39A21", "accent": "#FFB44D" },
|
|
53
|
+
"surface": { "background": "#FAF8F4", "card": "#FFFFFF", "foreground": "#1A1611" },
|
|
54
|
+
"rail": { "background": "#0E0C09", "foreground": "#F7F3EC", "border": "#2A2114",
|
|
55
|
+
"accent": "#2A2114", "accentForeground": "#FFB44D", "muted": "#968B79" },
|
|
56
|
+
"radius": "0.75rem" }
|
|
57
|
+
}
|
|
58
|
+
$$::jsonb, true), updated_at = now()
|
|
59
|
+
WHERE t.slug = 'great-djs';
|
|
60
|
+
|
|
61
|
+
-- ── Correção de duas marcas da rodada anterior ─────────────────────────────
|
|
62
|
+
-- PULSE e Espaço Renova receberam a casa INTEIRA escura, e as duas ficaram
|
|
63
|
+
-- ilegíveis: carta sobre fundo quase da mesma cor. "Preto" e "marrom escuro"
|
|
64
|
+
-- eram a cor da TRILHA. Uma marca escura e um tema escuro são decisões
|
|
65
|
+
-- diferentes — a segunda precisa de uma paleta inteira, não de duas cores.
|
|
66
|
+
UPDATE public.tenants SET settings = jsonb_set(settings, '{branding,tokens}', $$
|
|
67
|
+
{
|
|
68
|
+
"color": { "primary": "#B4632A", "accent": "#C97B3C" },
|
|
69
|
+
"surface": { "background": "#FBF8F5", "card": "#FFFFFF", "foreground": "#1A1512" },
|
|
70
|
+
"rail": { "background": "#171310", "foreground": "#F5F1ED", "border": "#2A2018",
|
|
71
|
+
"accent": "#2A2018", "accentForeground": "#E3B591", "muted": "#9B8B7C" },
|
|
72
|
+
"radius": "0.75rem"
|
|
73
|
+
}
|
|
74
|
+
$$::jsonb), updated_at = now()
|
|
75
|
+
WHERE name = 'Espaco Renova Rio';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 086_o_curso_e_a_bilheteria_ganham_preset.sql
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- CourseControl e TicketControl tinham `modules` (079) e nada mais. Sem `shell`
|
|
5
|
+
-- eles herdavam o `moduleNav: 'sidebar'` do pacote central — e nessa variante
|
|
6
|
+
-- `ModulePage` não desenha sub-navegação, na promessa de que o rail lista as
|
|
7
|
+
-- sub-telas do módulo. O rail não lista. Mesmo defeito que tirou as abas do
|
|
8
|
+
-- Estoque (082), só que estas duas contas nasceram hoje e já nasceriam assim.
|
|
9
|
+
--
|
|
10
|
+
-- O `rail` é curto de propósito: nenhum dos dois tem aplicativo de referência
|
|
11
|
+
-- com rail curado, então o preset ordena e não agrupa. Agrupar é uma decisão
|
|
12
|
+
-- que se toma olhando a tela cheia de dados, e elas ainda estão vazias.
|
|
13
|
+
-- ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
UPDATE app.apps SET preset = preset || $$
|
|
16
|
+
{
|
|
17
|
+
"shell": { "layout": "sidebar", "moduleNav": "tabs", "contentFrame": true },
|
|
18
|
+
"rail": ["dashboard", "conversations", "courses", "agenda", "crm",
|
|
19
|
+
"marketing", "financial", "automations", "reports"]
|
|
20
|
+
}
|
|
21
|
+
$$::jsonb WHERE id = 'school';
|
|
22
|
+
|
|
23
|
+
UPDATE app.apps SET preset = preset || $$
|
|
24
|
+
{
|
|
25
|
+
"shell": { "layout": "sidebar", "moduleNav": "tabs", "contentFrame": true },
|
|
26
|
+
"rail": ["dashboard", "conversations", "orders", "crm",
|
|
27
|
+
"marketing", "financial", "automations", "reports"]
|
|
28
|
+
}
|
|
29
|
+
$$::jsonb WHERE id = 'ticket';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 087_o_que_o_ofício_exige, ele também oferece.
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- O IAM Club abriu com "Estoque" e "Cardápio" no rail de uma bilheteria. Não
|
|
5
|
+
-- foi a oferta falhando: foi `app.apps.requires` e `app.apps.preset.modules`
|
|
6
|
+
-- dizendo coisas diferentes sobre o mesmo aplicativo.
|
|
7
|
+
--
|
|
8
|
+
-- requires → menu, inventory, orders, financial, conversations
|
|
9
|
+
-- modules → ...orders, crm, financial, marketing, automations, reports
|
|
10
|
+
--
|
|
11
|
+
-- `platform_tenant_create` semeia linhas `active` a partir de `requires`, e uma
|
|
12
|
+
-- linha explícita vence a oferta — corretamente, é assim que o módulo vendido à
|
|
13
|
+
-- parte funciona. Só que aqui não havia venda nenhuma: havia duas listas
|
|
14
|
+
-- discordando, e a provisão obedeceu a que estava errada.
|
|
15
|
+
--
|
|
16
|
+
-- ── Qual das duas estava errada ────────────────────────────────────────────
|
|
17
|
+
-- Nenhuma, e é essa a parte interessante: um clube TEM bar. `requires` estava
|
|
18
|
+
-- certo sobre o negócio e `modules` é que estava incompleto — eu escrevi a
|
|
19
|
+
-- lista da 079 olhando os plugins que o app-shell do TicketControl importa, e
|
|
20
|
+
-- não o que a operação de um clube precisa.
|
|
21
|
+
--
|
|
22
|
+
-- Então a oferta cresce, e o rail ganha a rubrica que faltava: "Bar".
|
|
23
|
+
--
|
|
24
|
+
-- ── A invariante ───────────────────────────────────────────────────────────
|
|
25
|
+
-- `requires ⊆ modules`, sempre. Um aplicativo não pode EXIGIR o que não
|
|
26
|
+
-- OFERECE — seria pedir que a conta ligue algo que o catálogo dela não tem. A
|
|
27
|
+
-- checagem no fim deste arquivo é o que impede a próxima divergência de chegar
|
|
28
|
+
-- na tela em vez de na aplicação da migration.
|
|
29
|
+
-- ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
UPDATE app.apps SET preset = preset || $$
|
|
32
|
+
{
|
|
33
|
+
"modules": ["dashboard","admin","notifications","conversations","orders","menu","inventory",
|
|
34
|
+
"crm","financial","marketing","automations","reports"],
|
|
35
|
+
"rail": ["dashboard", "conversations",
|
|
36
|
+
{ "label": "Bilheteria", "icon": "Ticket", "under": ["/orders"] },
|
|
37
|
+
{ "label": "Bar", "icon": "Beer", "under": ["/menu", "/inventory"] },
|
|
38
|
+
"crm", "marketing", "financial", "automations", "reports"]
|
|
39
|
+
}
|
|
40
|
+
$$::jsonb WHERE id = 'ticket';
|
|
41
|
+
|
|
42
|
+
-- O `requires` do CourseControl cabe na oferta dele; só falta `conversations`,
|
|
43
|
+
-- que a oferta esqueceu e o rail já nomeia.
|
|
44
|
+
UPDATE app.apps SET preset = jsonb_set(preset, '{modules}',
|
|
45
|
+
(preset->'modules') || '["conversations"]'::jsonb)
|
|
46
|
+
WHERE id = 'school' AND NOT (preset->'modules' ? 'conversations');
|
|
47
|
+
|
|
48
|
+
-- Mesma falta no ChefControl e no StoreControl: `requires` não os inclui, mas
|
|
49
|
+
-- o rail deles nomeia `conversations` — e um nome no rail que não está na
|
|
50
|
+
-- oferta nunca desenha.
|
|
51
|
+
UPDATE app.apps SET preset = jsonb_set(preset, '{modules}',
|
|
52
|
+
(preset->'modules') || '["conversations"]'::jsonb)
|
|
53
|
+
WHERE id IN ('shop','full','fiscal') AND NOT (preset->'modules' ? 'conversations');
|
|
54
|
+
|
|
55
|
+
-- A checagem abaixo pegou esta linha enquanto eu a escrevia: a lista nova do
|
|
56
|
+
-- TicketControl tinha derrubado `conversations`, que o `requires` dele exige. É
|
|
57
|
+
-- o tipo de omissão que só aparece na tela três telas depois.
|
|
58
|
+
|
|
59
|
+
-- ── A invariante, verificada ───────────────────────────────────────────────
|
|
60
|
+
DO $$
|
|
61
|
+
DECLARE v_bad text;
|
|
62
|
+
BEGIN
|
|
63
|
+
SELECT string_agg(a.id || ' exige ' || r.m || ' e não oferece', '; ')
|
|
64
|
+
INTO v_bad
|
|
65
|
+
FROM app.apps a
|
|
66
|
+
CROSS JOIN LATERAL unnest(a.requires) AS r(m)
|
|
67
|
+
WHERE a.active
|
|
68
|
+
-- `jsonb_typeof` antes de `jsonb_array_length`: uma verificação que
|
|
69
|
+
-- ESTOURA numa forma inesperada é pior que a divergência que ela procura —
|
|
70
|
+
-- ela derruba a cadeia inteira em vez de nomear a linha errada. Um ofício
|
|
71
|
+
-- sem oferta declarada simplesmente não é verificado.
|
|
72
|
+
AND jsonb_typeof(a.preset->'modules') = 'array'
|
|
73
|
+
AND jsonb_array_length(a.preset->'modules') > 0
|
|
74
|
+
AND NOT (a.preset->'modules' ? r.m);
|
|
75
|
+
IF v_bad IS NOT NULL THEN
|
|
76
|
+
RAISE EXCEPTION 'requires fora da oferta: %', v_bad;
|
|
77
|
+
END IF;
|
|
78
|
+
END $$;
|