@fayz-ai/db 0.18.2 → 0.20.1
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/136_a_baixa_do_espelho_e_do_porte_nao_do_v2.sql +204 -0
- package/migrations/137_a_retirada_e_um_canal_de_venda.sql +43 -0
- package/migrations/138_o_produto_tem_cor_e_a_conta_so_a_veste_se_pagar.sql +606 -0
- package/migrations/139_a_equipe_e_um_agrupamento_declarado.sql +50 -0
- package/migrations/140_a_unidade_se_escreve_pela_mesma_porta_que_se_le.sql +130 -0
- package/migrations/141_o_endereco_da_unidade_mora_na_tabela_de_enderecos.sql +203 -0
- package/migrations/142_a_coordenada_e_do_endereco_nao_da_unidade.sql +157 -0
- package/migrations/143_o_preset_para_de_nomear_telas_que_nao_existem.sql +29 -0
- package/migrations/144_marketing_e_um_botao_nao_uma_gaveta.sql +46 -0
- package/migrations/145_campanhas_e_templates_somem_em_toda_conta.sql +36 -0
- package/migrations/146_a_clinica_ve_as_proprias_analises.sql +31 -0
- package/migrations/147_tres_saloes_desligam_o_rh.sql +30 -0
- package/migrations/148_servicos_ganham_a_permissao_que_nunca_tiveram.sql +37 -0
- package/migrations/149_a_conta_do_fornecedor_nao_e_conta_da_casa.sql +131 -0
- package/migrations/150_todo_cadastro_tem_observacao_e_etiqueta.sql +62 -0
- package/migrations/151_a_parceria_e_uma_especie_de_pessoa.sql +35 -0
- package/migrations/152_a_profissao_e_a_especie_de_pet_sao_catalogos.sql +31 -0
- package/migrations/153_a_sala_nao_e_a_filial.sql +94 -0
- package/migrations/154_as_duas_tabelas_novas_dizem_de_quem_sao.sql +33 -0
- package/migrations/155_o_pacote_tem_observacao_e_etiqueta_e_diz_de_quem_e.sql +28 -0
- package/migrations/156_restaurante_nao_vende_servico.sql +44 -0
- package/migrations/157_o_que_a_porta_da_unidade_criou_tambem_se_revoga.sql +45 -0
- package/migrations/158_a_producao_e_do_restaurante_e_mora_no_estoque.sql +129 -0
- package/migrations/159_a_loja_tambem_confundia_o_que_vende_com_o_que_guarda.sql +98 -0
- package/migrations/161_vender_e_falar_com_quem_ainda_nao_comprou_sao_o_mesmo_assunto.sql +73 -0
- package/package.json +1 -1
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 138_o_produto_tem_cor_e_a_conta_so_a_veste_se_pagar.sql
|
|
3
|
+
--
|
|
4
|
+
-- O tema de cada Control deixa de ser código e vira dado — e a marca da conta
|
|
5
|
+
-- passa a ser uma CAMADA sobre ele, não um substituto dele.
|
|
6
|
+
--
|
|
7
|
+
-- ── O que a tela mostrou ───────────────────────────────────────────────────
|
|
8
|
+
-- Medido no cluster: `Pertinho do Céu` é um RESTAURANTE (vertical food, roda o
|
|
9
|
+
-- `resto`) pintado de azul-marinho #304878. Ninguém escolheu esse azul: o
|
|
10
|
+
-- `onboarding/submit.ts` grava `primaryColor`/`accentColor` no cadastro da
|
|
11
|
+
-- conta, e daí em diante o `ThemeInitializer` deixava a conta sobrescrever o
|
|
12
|
+
-- aplicativo SEMPRE, sem perguntar nada a ninguém. Vinte das trinta contas do
|
|
13
|
+
-- cluster têm um tema inteiro (rail + surface + color) que nunca foi decisão de
|
|
14
|
+
-- ninguém.
|
|
15
|
+
--
|
|
16
|
+
-- O resultado é um produto que não tem cara. ChefControl é ouro #FBAC00 — é o
|
|
17
|
+
-- único que o brand book v4 lhe dá —, e o restaurante abria azul.
|
|
18
|
+
--
|
|
19
|
+
-- ── Onde o tema do produto morava ──────────────────────────────────────────
|
|
20
|
+
-- Em `src/config/theme.ts`, DENTRO do bundle de cada aplicativo. Isso funcionou
|
|
21
|
+
-- enquanto cada Control era um deploy. Agora um único `control-app` atende
|
|
22
|
+
-- restaurante, clínica e escola na mesma sessão — e um tema compilado só sabe
|
|
23
|
+
-- ser um. `app.apps` guardava doze colunas sobre um aplicativo e NENHUMA sobre
|
|
24
|
+
-- como ele se apresenta: `accent_color` pinta o ladrilho do switcher e mais
|
|
25
|
+
-- nada.
|
|
26
|
+
--
|
|
27
|
+
-- Então o tema desce para a linha. Mesmo movimento que a 077 fez com o rail: o
|
|
28
|
+
-- que é conhecimento de produto vira dado, e trocar a cor de um Control passa a
|
|
29
|
+
-- ser um UPDATE em vez de um release.
|
|
30
|
+
--
|
|
31
|
+
-- ── As três camadas, e quem arbitra ────────────────────────────────────────
|
|
32
|
+
--
|
|
33
|
+
-- 1. PRODUTO app.apps.theme ouro no Chef, ameixa no Studio
|
|
34
|
+
-- 2. CONTA tenants.settings.branding logo e nome sempre; cor, só se…
|
|
35
|
+
-- 3. DECISÃO plan.entitlements …o plano disser que sim
|
|
36
|
+
--
|
|
37
|
+
-- A regra em uma frase: IDENTIDADE (logo, nome, favicon) é de toda conta —
|
|
38
|
+
-- ninguém abre um sistema sem a própria marca no alto. PELE (cor, rail, papel)
|
|
39
|
+
-- é de quem paga por ela. Um restaurante no plano `pro` abre ouro com a logo
|
|
40
|
+
-- dele; o Espaço Facial, no `business`, continua preto com bege.
|
|
41
|
+
--
|
|
42
|
+
-- O ponto 3 fica no cliente, junto de todos os outros tetos de plano
|
|
43
|
+
-- (`config.billing.plans`), e não aqui: é cosmético, e duplicar a tabela de
|
|
44
|
+
-- planos no banco só para pintar um botão criaria uma segunda fonte da verdade
|
|
45
|
+
-- para a mesma pergunta. O banco ENTREGA as duas camadas; quem escolhe qual
|
|
46
|
+
-- pintar é o `ThemeInitializer`.
|
|
47
|
+
--
|
|
48
|
+
-- ── O formato ──────────────────────────────────────────────────────────────
|
|
49
|
+
-- `theme` é um `SaasTheme` — exatamente o objeto que o shell já consome, com
|
|
50
|
+
-- as cores em triplas HSL sem `hsl()` porque o SDK as compõe como
|
|
51
|
+
-- `hsl(var(--x) / <alpha>)`. O que o banco grava é o que a casca já sabe
|
|
52
|
+
-- pintar, sem tradutor no meio. Mesma disciplina da 093, que trouxe o tema do
|
|
53
|
+
-- inquilino no vocabulário do `SaasTheme.sidebar`.
|
|
54
|
+
--
|
|
55
|
+
-- Os sete temas abaixo são cópia fiel do que cada aplicativo tinha compilado —
|
|
56
|
+
-- esta migração não redesenha nada, ela só muda o tema de lugar. `crm` e
|
|
57
|
+
-- `control-admin` ficam sem tema: o primeiro está inativo, o segundo é a mesa
|
|
58
|
+
-- da casa e herda o do bundle, que é o certo para uma ferramenta interna.
|
|
59
|
+
--
|
|
60
|
+
-- Idempotente: uma coluna com IF NOT EXISTS, sete UPDATEs por id e uma função
|
|
61
|
+
-- reemitida.
|
|
62
|
+
-- ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
-- ── §1 a coluna ───────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
ALTER TABLE app.apps ADD COLUMN IF NOT EXISTS theme jsonb;
|
|
67
|
+
|
|
68
|
+
COMMENT ON COLUMN app.apps.theme IS
|
|
69
|
+
'O tema do PRODUTO (138): um SaasTheme completo — brand, sidebar, colors, darkColors, perception —, no mesmo vocabulário que o shell consome. A conta ESTENDE isto com tenants.settings.branding, e só até onde o plano dela permitir. NULL = este aplicativo não reivindica cor e herda a do bundle.';
|
|
70
|
+
|
|
71
|
+
-- ── §2 os sete produtos ───────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
UPDATE app.apps SET theme = $theme$
|
|
74
|
+
{
|
|
75
|
+
"name": "ChefControl",
|
|
76
|
+
"__kind": "saas-theme",
|
|
77
|
+
"brand": "41 100% 49.2%",
|
|
78
|
+
"colors": {
|
|
79
|
+
"background": "42 41.9% 93.9%",
|
|
80
|
+
"content": "42 41.9% 93.9%",
|
|
81
|
+
"card": "43 100% 98.6%",
|
|
82
|
+
"popover": "43 100% 98.6%",
|
|
83
|
+
"muted": "44 24.4% 91.2%",
|
|
84
|
+
"foreground": "80 6.7% 8.8%",
|
|
85
|
+
"cardForeground": "80 6.7% 8.8%",
|
|
86
|
+
"popoverForeground": "80 6.7% 8.8%",
|
|
87
|
+
"mutedForeground": "68 4% 39.6%",
|
|
88
|
+
"primary": "41 100% 49.2%",
|
|
89
|
+
"primaryForeground": "80 6.7% 8.8%",
|
|
90
|
+
"secondary": "44 24.4% 91.2%",
|
|
91
|
+
"secondaryForeground": "75 4.3% 18.4%",
|
|
92
|
+
"accent": "44 24.4% 91.2%",
|
|
93
|
+
"accentForeground": "72 3.9% 24.9%",
|
|
94
|
+
"border": "39 17.9% 81.4%",
|
|
95
|
+
"input": "39 17.9% 81.4%",
|
|
96
|
+
"ring": "208 70.8% 44.3%",
|
|
97
|
+
"destructive": "4 76.4% 39.8%",
|
|
98
|
+
"destructiveForeground": "0 0% 100%",
|
|
99
|
+
"success": "151 59.5% 30%",
|
|
100
|
+
"successForeground": "0 0% 100%",
|
|
101
|
+
"warning": "41 70.7% 55.9%",
|
|
102
|
+
"warningForeground": "80 6.7% 8.8%",
|
|
103
|
+
"sidebar": "80 6.7% 8.8%",
|
|
104
|
+
"sidebarForeground": "42 41.9% 93.9%",
|
|
105
|
+
"sidebarBorder": "60 3.5% 22.4%",
|
|
106
|
+
"sidebarAccent": "44 53.3% 15.1%",
|
|
107
|
+
"sidebarAccentForeground": "42 73.1% 69.4%",
|
|
108
|
+
"sidebarMuted": "60 4.3% 63.9%"
|
|
109
|
+
},
|
|
110
|
+
"darkColors": {
|
|
111
|
+
"primary": "41 100% 49.2%",
|
|
112
|
+
"primaryForeground": "80 6.7% 8.8%",
|
|
113
|
+
"ring": "208 70.8% 44.3%",
|
|
114
|
+
"accent": "72 5.6% 17.5%",
|
|
115
|
+
"accentForeground": "42 41.9% 93.9%",
|
|
116
|
+
"background": "80 6.7% 8.8%",
|
|
117
|
+
"content": "80 6.7% 8.8%",
|
|
118
|
+
"card": "80 4.3% 13.5%",
|
|
119
|
+
"popover": "80 4.3% 13.5%",
|
|
120
|
+
"muted": "72 5.6% 17.5%",
|
|
121
|
+
"mutedForeground": "60 4.3% 63.9%",
|
|
122
|
+
"foreground": "42 41.9% 93.9%",
|
|
123
|
+
"cardForeground": "42 41.9% 93.9%",
|
|
124
|
+
"popoverForeground": "42 41.9% 93.9%",
|
|
125
|
+
"border": "60 3.5% 22.4%",
|
|
126
|
+
"input": "48 2.9% 34.3%",
|
|
127
|
+
"destructive": "4 76.4% 39.8%",
|
|
128
|
+
"destructiveForeground": "0 0% 100%",
|
|
129
|
+
"success": "151 59.5% 30%",
|
|
130
|
+
"successForeground": "0 0% 100%",
|
|
131
|
+
"warning": "41 70.7% 55.9%",
|
|
132
|
+
"warningForeground": "80 6.7% 8.8%",
|
|
133
|
+
"sidebar": "80 6.7% 8.8%",
|
|
134
|
+
"sidebarForeground": "42 41.9% 93.9%",
|
|
135
|
+
"sidebarBorder": "60 3.5% 22.4%",
|
|
136
|
+
"sidebarAccent": "44 53.3% 15.1%",
|
|
137
|
+
"sidebarAccentForeground": "42 73.1% 69.4%",
|
|
138
|
+
"sidebarMuted": "60 4.3% 63.9%"
|
|
139
|
+
},
|
|
140
|
+
"perception": {
|
|
141
|
+
"buttonRadius": "0.5rem",
|
|
142
|
+
"inputRadius": "0.5rem",
|
|
143
|
+
"cardRadius": "0.75rem",
|
|
144
|
+
"modalRadius": "1rem",
|
|
145
|
+
"fontFamily": "\"Hanken Grotesk\", ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
146
|
+
"fontFamilyMono": "\"JetBrains Mono\", ui-monospace, SFMono-Regular, monospace",
|
|
147
|
+
"shadowSm": "0 1px 2px rgba(23, 24, 21, 0.06), 0 1px 1px rgba(23, 24, 21, 0.04)",
|
|
148
|
+
"shadowMd": "0 2px 4px rgba(23, 24, 21, 0.07), 0 4px 12px rgba(23, 24, 21, 0.06)",
|
|
149
|
+
"shadowLg": "0 8px 24px rgba(23, 24, 21, 0.1), 0 2px 6px rgba(23, 24, 21, 0.06)"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
$theme$::jsonb WHERE id = 'resto';
|
|
153
|
+
|
|
154
|
+
UPDATE app.apps SET theme = $theme$
|
|
155
|
+
{
|
|
156
|
+
"__kind": "saas-theme",
|
|
157
|
+
"name": "StudioControl",
|
|
158
|
+
"preset": "classic_admin",
|
|
159
|
+
"brand": "260 34% 47%",
|
|
160
|
+
"shadow": "subtle",
|
|
161
|
+
"font": "dm-sans",
|
|
162
|
+
"sidebar": {
|
|
163
|
+
"background": "258 33% 10%",
|
|
164
|
+
"foreground": "260 20% 90%",
|
|
165
|
+
"border": "0 0% 100% / 0.1",
|
|
166
|
+
"accent": "257 57% 76% / 0.16",
|
|
167
|
+
"accentForeground": "257 57% 76%",
|
|
168
|
+
"muted": "0 0% 100% / 0.55"
|
|
169
|
+
},
|
|
170
|
+
"content": {
|
|
171
|
+
"background": "60 12% 95%"
|
|
172
|
+
},
|
|
173
|
+
"colors": {
|
|
174
|
+
"background": "60 12% 95%",
|
|
175
|
+
"foreground": "156 12% 8%",
|
|
176
|
+
"card": "0 0% 100%",
|
|
177
|
+
"cardForeground": "156 12% 8%",
|
|
178
|
+
"popover": "0 0% 100%",
|
|
179
|
+
"popoverForeground": "156 12% 8%",
|
|
180
|
+
"secondary": "60 11% 91%",
|
|
181
|
+
"secondaryForeground": "156 12% 8%",
|
|
182
|
+
"muted": "60 11% 91%",
|
|
183
|
+
"mutedForeground": "60 3% 35%",
|
|
184
|
+
"border": "60 11% 91%",
|
|
185
|
+
"input": "60 8% 84%",
|
|
186
|
+
"ring": "260 34% 47%",
|
|
187
|
+
"accent": "260 56% 95%",
|
|
188
|
+
"accentForeground": "260 34% 47%"
|
|
189
|
+
},
|
|
190
|
+
"perception": {
|
|
191
|
+
"buttonRadius": "12px",
|
|
192
|
+
"cardRadius": "16px",
|
|
193
|
+
"inputRadius": "12px",
|
|
194
|
+
"modalRadius": "20px",
|
|
195
|
+
"fontFamily": "'DM Sans', system-ui, sans-serif",
|
|
196
|
+
"fontFamilyMono": "'JetBrains Mono', ui-monospace, monospace",
|
|
197
|
+
"shadowSm": "0 1px 3px rgba(11,15,14,0.06), 0 1px 2px rgba(11,15,14,0.04)",
|
|
198
|
+
"shadowMd": "0 4px 12px rgba(11,15,14,0.06), 0 2px 4px rgba(11,15,14,0.04)",
|
|
199
|
+
"shadowLg": "0 12px 32px rgba(11,15,14,0.08), 0 4px 12px rgba(11,15,14,0.04)"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
$theme$::jsonb WHERE id = 'beauty';
|
|
203
|
+
|
|
204
|
+
UPDATE app.apps SET theme = $theme$
|
|
205
|
+
{
|
|
206
|
+
"__kind": "saas-theme",
|
|
207
|
+
"name": "StoreControl",
|
|
208
|
+
"brand": "130 80% 34%",
|
|
209
|
+
"font": "dm-sans",
|
|
210
|
+
"sidebar": {
|
|
211
|
+
"background": "151 16% 7%",
|
|
212
|
+
"foreground": "0 0% 100%",
|
|
213
|
+
"border": "0 0% 100% / 0.09",
|
|
214
|
+
"accent": "130 72% 53% / 0.14",
|
|
215
|
+
"accentForeground": "130 72% 53%",
|
|
216
|
+
"muted": "0 0% 100% / 0.6"
|
|
217
|
+
},
|
|
218
|
+
"content": {
|
|
219
|
+
"background": "60 12% 95%"
|
|
220
|
+
},
|
|
221
|
+
"colors": {
|
|
222
|
+
"background": "60 12% 95%",
|
|
223
|
+
"foreground": "155 11% 8%",
|
|
224
|
+
"card": "0 0% 100%",
|
|
225
|
+
"cardForeground": "155 11% 8%",
|
|
226
|
+
"popover": "0 0% 100%",
|
|
227
|
+
"popoverForeground": "155 11% 8%",
|
|
228
|
+
"border": "60 11% 91%",
|
|
229
|
+
"input": "60 8% 84%",
|
|
230
|
+
"muted": "60 11% 91%",
|
|
231
|
+
"mutedForeground": "60 3% 35%",
|
|
232
|
+
"secondary": "60 11% 91%",
|
|
233
|
+
"secondaryForeground": "155 11% 8%",
|
|
234
|
+
"accent": "129 78% 93%",
|
|
235
|
+
"accentForeground": "130 80% 34%",
|
|
236
|
+
"ring": "130 80% 34%",
|
|
237
|
+
"success": "130 80% 34%",
|
|
238
|
+
"successForeground": "0 0% 100%",
|
|
239
|
+
"destructive": "6 100% 68%",
|
|
240
|
+
"destructiveForeground": "0 0% 100%",
|
|
241
|
+
"warning": "54 90% 50%",
|
|
242
|
+
"warningForeground": "155 11% 8%"
|
|
243
|
+
},
|
|
244
|
+
"perception": {
|
|
245
|
+
"buttonRadius": "12px",
|
|
246
|
+
"cardRadius": "16px",
|
|
247
|
+
"inputRadius": "12px",
|
|
248
|
+
"modalRadius": "18px",
|
|
249
|
+
"fontFamily": "\"DM Sans\", ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
250
|
+
"fontFamilyMono": "\"JetBrains Mono\", ui-monospace, SFMono-Regular, monospace",
|
|
251
|
+
"shadowSm": "0 1px 3px rgba(11,15,14,0.06), 0 1px 2px rgba(11,15,14,0.04)",
|
|
252
|
+
"shadowMd": "0 4px 12px rgba(11,15,14,0.06), 0 2px 4px rgba(11,15,14,0.04)",
|
|
253
|
+
"shadowLg": "0 12px 32px rgba(11,15,14,0.08), 0 4px 12px rgba(11,15,14,0.04)"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
$theme$::jsonb WHERE id = 'shop';
|
|
257
|
+
|
|
258
|
+
UPDATE app.apps SET theme = $theme$
|
|
259
|
+
{
|
|
260
|
+
"__kind": "saas-theme",
|
|
261
|
+
"name": "CourseControl",
|
|
262
|
+
"brand": "175 84% 32%",
|
|
263
|
+
"font": "dm-sans",
|
|
264
|
+
"sidebar": {
|
|
265
|
+
"background": "185 30% 8%",
|
|
266
|
+
"foreground": "0 0% 100%",
|
|
267
|
+
"border": "0 0% 100% / 0.09",
|
|
268
|
+
"accent": "175 77% 41% / 0.14",
|
|
269
|
+
"accentForeground": "175 77% 41%",
|
|
270
|
+
"muted": "0 0% 100% / 0.6"
|
|
271
|
+
},
|
|
272
|
+
"content": {
|
|
273
|
+
"background": "60 12% 95%"
|
|
274
|
+
},
|
|
275
|
+
"colors": {
|
|
276
|
+
"background": "60 12% 95%",
|
|
277
|
+
"foreground": "155 11% 8%",
|
|
278
|
+
"card": "0 0% 100%",
|
|
279
|
+
"cardForeground": "155 11% 8%",
|
|
280
|
+
"popover": "0 0% 100%",
|
|
281
|
+
"popoverForeground": "155 11% 8%",
|
|
282
|
+
"border": "60 11% 91%",
|
|
283
|
+
"input": "60 8% 84%",
|
|
284
|
+
"muted": "60 11% 91%",
|
|
285
|
+
"mutedForeground": "60 3% 35%",
|
|
286
|
+
"secondary": "60 11% 91%",
|
|
287
|
+
"secondaryForeground": "155 11% 8%",
|
|
288
|
+
"accent": "172 60% 93%",
|
|
289
|
+
"accentForeground": "175 84% 32%",
|
|
290
|
+
"ring": "175 84% 32%",
|
|
291
|
+
"success": "130 80% 34%",
|
|
292
|
+
"successForeground": "0 0% 100%",
|
|
293
|
+
"destructive": "6 100% 68%",
|
|
294
|
+
"destructiveForeground": "0 0% 100%",
|
|
295
|
+
"warning": "54 90% 50%",
|
|
296
|
+
"warningForeground": "155 11% 8%"
|
|
297
|
+
},
|
|
298
|
+
"perception": {
|
|
299
|
+
"buttonRadius": "12px",
|
|
300
|
+
"cardRadius": "16px",
|
|
301
|
+
"inputRadius": "12px",
|
|
302
|
+
"modalRadius": "18px",
|
|
303
|
+
"fontFamily": "\"DM Sans\", ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
304
|
+
"fontFamilyMono": "\"JetBrains Mono\", ui-monospace, SFMono-Regular, monospace",
|
|
305
|
+
"shadowSm": "0 1px 3px rgba(11,15,14,0.06), 0 1px 2px rgba(11,15,14,0.04)",
|
|
306
|
+
"shadowMd": "0 4px 12px rgba(11,15,14,0.06), 0 2px 4px rgba(11,15,14,0.04)",
|
|
307
|
+
"shadowLg": "0 12px 32px rgba(11,15,14,0.08), 0 4px 12px rgba(11,15,14,0.04)"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
$theme$::jsonb WHERE id = 'school';
|
|
311
|
+
|
|
312
|
+
UPDATE app.apps SET theme = $theme$
|
|
313
|
+
{
|
|
314
|
+
"__kind": "saas-theme",
|
|
315
|
+
"name": "TicketControl",
|
|
316
|
+
"brand": "276 91% 43%",
|
|
317
|
+
"font": "dm-sans",
|
|
318
|
+
"sidebar": {
|
|
319
|
+
"background": "266 30% 8%",
|
|
320
|
+
"foreground": "0 0% 100%",
|
|
321
|
+
"border": "0 0% 100% / 0.09",
|
|
322
|
+
"accent": "276 91% 55% / 0.20",
|
|
323
|
+
"accentForeground": "276 90% 72%",
|
|
324
|
+
"muted": "0 0% 100% / 0.6"
|
|
325
|
+
},
|
|
326
|
+
"content": {
|
|
327
|
+
"background": "270 20% 96%"
|
|
328
|
+
},
|
|
329
|
+
"colors": {
|
|
330
|
+
"background": "270 20% 96%",
|
|
331
|
+
"foreground": "266 25% 10%",
|
|
332
|
+
"card": "0 0% 100%",
|
|
333
|
+
"cardForeground": "266 25% 10%",
|
|
334
|
+
"popover": "0 0% 100%",
|
|
335
|
+
"popoverForeground": "266 25% 10%",
|
|
336
|
+
"border": "261 22% 92%",
|
|
337
|
+
"input": "263 18% 86%",
|
|
338
|
+
"muted": "261 22% 92%",
|
|
339
|
+
"mutedForeground": "263 12% 40%",
|
|
340
|
+
"secondary": "261 22% 92%",
|
|
341
|
+
"secondaryForeground": "266 25% 10%",
|
|
342
|
+
"accent": "276 90% 95%",
|
|
343
|
+
"accentForeground": "276 91% 43%",
|
|
344
|
+
"ring": "276 91% 43%",
|
|
345
|
+
"primary": "276 91% 43%",
|
|
346
|
+
"primaryForeground": "0 0% 100%",
|
|
347
|
+
"success": "152 76% 32%",
|
|
348
|
+
"successForeground": "0 0% 100%",
|
|
349
|
+
"destructive": "2 80% 55%",
|
|
350
|
+
"destructiveForeground": "0 0% 100%",
|
|
351
|
+
"warning": "38 92% 50%",
|
|
352
|
+
"warningForeground": "266 25% 10%"
|
|
353
|
+
},
|
|
354
|
+
"perception": {
|
|
355
|
+
"buttonRadius": "12px",
|
|
356
|
+
"cardRadius": "16px",
|
|
357
|
+
"inputRadius": "12px",
|
|
358
|
+
"modalRadius": "18px",
|
|
359
|
+
"fontFamily": "\"DM Sans\", ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
360
|
+
"fontFamilyMono": "\"JetBrains Mono\", ui-monospace, SFMono-Regular, monospace",
|
|
361
|
+
"shadowSm": "0 1px 3px rgba(23,17,32,0.06), 0 1px 2px rgba(23,17,32,0.04)",
|
|
362
|
+
"shadowMd": "0 4px 12px rgba(23,17,32,0.06), 0 2px 4px rgba(23,17,32,0.04)",
|
|
363
|
+
"shadowLg": "0 12px 32px rgba(23,17,32,0.08), 0 4px 12px rgba(23,17,32,0.04)"
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
$theme$::jsonb WHERE id = 'ticket';
|
|
367
|
+
|
|
368
|
+
UPDATE app.apps SET theme = $theme$
|
|
369
|
+
{
|
|
370
|
+
"__kind": "saas-theme",
|
|
371
|
+
"name": "FiscalControl",
|
|
372
|
+
"brand": "215 25% 27%",
|
|
373
|
+
"font": "dm-sans",
|
|
374
|
+
"sidebar": {
|
|
375
|
+
"background": "222 47% 11%",
|
|
376
|
+
"foreground": "0 0% 100%",
|
|
377
|
+
"border": "0 0% 100% / 0.09",
|
|
378
|
+
"accent": "213 27% 70% / 0.18",
|
|
379
|
+
"accentForeground": "213 27% 84%",
|
|
380
|
+
"muted": "0 0% 100% / 0.6"
|
|
381
|
+
},
|
|
382
|
+
"content": {
|
|
383
|
+
"background": "210 30% 97%"
|
|
384
|
+
},
|
|
385
|
+
"colors": {
|
|
386
|
+
"background": "210 30% 97%",
|
|
387
|
+
"foreground": "222 47% 11%",
|
|
388
|
+
"card": "0 0% 100%",
|
|
389
|
+
"cardForeground": "222 47% 11%",
|
|
390
|
+
"popover": "0 0% 100%",
|
|
391
|
+
"popoverForeground": "222 47% 11%",
|
|
392
|
+
"border": "214 32% 91%",
|
|
393
|
+
"input": "213 27% 84%",
|
|
394
|
+
"muted": "214 32% 91%",
|
|
395
|
+
"mutedForeground": "215 16% 47%",
|
|
396
|
+
"secondary": "214 32% 91%",
|
|
397
|
+
"secondaryForeground": "222 47% 11%",
|
|
398
|
+
"accent": "210 40% 96%",
|
|
399
|
+
"accentForeground": "215 25% 27%",
|
|
400
|
+
"ring": "215 25% 27%",
|
|
401
|
+
"primary": "215 25% 27%",
|
|
402
|
+
"primaryForeground": "0 0% 100%",
|
|
403
|
+
"success": "152 76% 32%",
|
|
404
|
+
"successForeground": "0 0% 100%",
|
|
405
|
+
"destructive": "2 80% 55%",
|
|
406
|
+
"destructiveForeground": "0 0% 100%",
|
|
407
|
+
"warning": "38 92% 50%",
|
|
408
|
+
"warningForeground": "222 47% 11%"
|
|
409
|
+
},
|
|
410
|
+
"perception": {
|
|
411
|
+
"buttonRadius": "12px",
|
|
412
|
+
"cardRadius": "16px",
|
|
413
|
+
"inputRadius": "12px",
|
|
414
|
+
"modalRadius": "18px",
|
|
415
|
+
"fontFamily": "\"DM Sans\", ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
416
|
+
"fontFamilyMono": "\"JetBrains Mono\", ui-monospace, SFMono-Regular, monospace",
|
|
417
|
+
"shadowSm": "0 1px 3px rgba(15,23,42,0.06), 0 1px 2px rgba(15,23,42,0.04)",
|
|
418
|
+
"shadowMd": "0 4px 12px rgba(15,23,42,0.06), 0 2px 4px rgba(15,23,42,0.04)",
|
|
419
|
+
"shadowLg": "0 12px 32px rgba(15,23,42,0.08), 0 4px 12px rgba(15,23,42,0.04)"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
$theme$::jsonb WHERE id = 'fiscal';
|
|
423
|
+
|
|
424
|
+
UPDATE app.apps SET theme = $theme$
|
|
425
|
+
{
|
|
426
|
+
"__kind": "saas-theme",
|
|
427
|
+
"name": "FullControl",
|
|
428
|
+
"brand": "224 64% 33%",
|
|
429
|
+
"font": "dm-sans",
|
|
430
|
+
"sidebar": {
|
|
431
|
+
"background": "222 30% 8%",
|
|
432
|
+
"foreground": "0 0% 100%",
|
|
433
|
+
"border": "0 0% 100% / 0.09",
|
|
434
|
+
"accent": "213 94% 68% / 0.16",
|
|
435
|
+
"accentForeground": "213 94% 68%",
|
|
436
|
+
"muted": "0 0% 100% / 0.6"
|
|
437
|
+
},
|
|
438
|
+
"content": {
|
|
439
|
+
"background": "210 20% 96%"
|
|
440
|
+
},
|
|
441
|
+
"colors": {
|
|
442
|
+
"background": "210 20% 96%",
|
|
443
|
+
"foreground": "222 25% 10%",
|
|
444
|
+
"card": "0 0% 100%",
|
|
445
|
+
"cardForeground": "222 25% 10%",
|
|
446
|
+
"popover": "0 0% 100%",
|
|
447
|
+
"popoverForeground": "222 25% 10%",
|
|
448
|
+
"border": "214 18% 92%",
|
|
449
|
+
"input": "214 15% 85%",
|
|
450
|
+
"muted": "214 18% 92%",
|
|
451
|
+
"mutedForeground": "215 14% 35%",
|
|
452
|
+
"secondary": "214 18% 92%",
|
|
453
|
+
"secondaryForeground": "222 25% 10%",
|
|
454
|
+
"accent": "214 95% 93%",
|
|
455
|
+
"accentForeground": "224 64% 33%",
|
|
456
|
+
"ring": "221 83% 53%",
|
|
457
|
+
"success": "152 76% 32%",
|
|
458
|
+
"successForeground": "0 0% 100%",
|
|
459
|
+
"destructive": "6 100% 68%",
|
|
460
|
+
"destructiveForeground": "0 0% 100%",
|
|
461
|
+
"warning": "38 92% 50%",
|
|
462
|
+
"warningForeground": "222 25% 10%"
|
|
463
|
+
},
|
|
464
|
+
"perception": {
|
|
465
|
+
"buttonRadius": "12px",
|
|
466
|
+
"cardRadius": "16px",
|
|
467
|
+
"inputRadius": "12px",
|
|
468
|
+
"modalRadius": "18px",
|
|
469
|
+
"fontFamily": "\"DM Sans\", ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
470
|
+
"fontFamilyMono": "\"JetBrains Mono\", ui-monospace, SFMono-Regular, monospace",
|
|
471
|
+
"shadowSm": "0 1px 3px rgba(13,17,26,0.06), 0 1px 2px rgba(13,17,26,0.04)",
|
|
472
|
+
"shadowMd": "0 4px 12px rgba(13,17,26,0.06), 0 2px 4px rgba(13,17,26,0.04)",
|
|
473
|
+
"shadowLg": "0 12px 32px rgba(13,17,26,0.08), 0 4px 12px rgba(13,17,26,0.04)"
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
$theme$::jsonb WHERE id = 'full';
|
|
477
|
+
|
|
478
|
+
-- ── §3 app_config() passa a entregar o tema ───────────────────────────────
|
|
479
|
+
--
|
|
480
|
+
-- Reemitida a partir do ARQUIVO da 115, e não da definição viva do cluster: o
|
|
481
|
+
-- comentário dela conta que reemitir a função copiando do banco já perdeu uma
|
|
482
|
+
-- chave TRÊS vezes. A única mudança é `'theme', a.theme` nos dois ramos da
|
|
483
|
+
-- união.
|
|
484
|
+
|
|
485
|
+
CREATE OR REPLACE FUNCTION public.app_config()
|
|
486
|
+
RETURNS jsonb
|
|
487
|
+
LANGUAGE plpgsql
|
|
488
|
+
STABLE SECURITY DEFINER
|
|
489
|
+
SET search_path TO ''
|
|
490
|
+
AS $function$
|
|
491
|
+
DECLARE
|
|
492
|
+
v_tenant uuid := app.current_tenant_id();
|
|
493
|
+
v_staff text;
|
|
494
|
+
BEGIN
|
|
495
|
+
-- `platform` é do CLUSTER, não da cadeia canônica: um banco montado do zero
|
|
496
|
+
-- (o db-from-zero, um pool novo) não tem esse schema, e uma chamada direta
|
|
497
|
+
-- estoura na inicialização da variável — antes de qualquer linha do corpo.
|
|
498
|
+
IF pg_catalog.to_regproc('platform.staff_role(uuid)') IS NOT NULL THEN
|
|
499
|
+
EXECUTE 'SELECT platform.staff_role($1)' INTO v_staff USING (SELECT auth.uid());
|
|
500
|
+
END IF;
|
|
501
|
+
|
|
502
|
+
IF v_tenant IS NULL THEN
|
|
503
|
+
RETURN jsonb_build_object('tenant_id', NULL, 'plugins', '[]'::jsonb, 'nav', '[]'::jsonb,
|
|
504
|
+
'apps', '[]'::jsonb, 'permissions', '[]'::jsonb, 'generated_at', now());
|
|
505
|
+
END IF;
|
|
506
|
+
|
|
507
|
+
RETURN jsonb_build_object(
|
|
508
|
+
'tenant_id', v_tenant,
|
|
509
|
+
'branding', coalesce(app.brand_of(v_tenant), '{}'::jsonb),
|
|
510
|
+
'settings', public.tenant_settings(),
|
|
511
|
+
'plugins', coalesce((
|
|
512
|
+
SELECT jsonb_agg(jsonb_build_object('plugin', t.plugin_id, 'facet', nullif(t.facet, ''),
|
|
513
|
+
'status', CASE WHEN t.expires_at IS NOT NULL AND t.expires_at <= now() THEN 'disabled' ELSE t.status END,
|
|
514
|
+
'source', t.source, 'config', t.config, 'expires_at', t.expires_at)
|
|
515
|
+
ORDER BY t.plugin_id, t.facet)
|
|
516
|
+
FROM app.tenant_plugins t WHERE t.tenant_id = v_tenant), '[]'::jsonb),
|
|
517
|
+
-- `trade` ordena a lista: quem roda dois aplicativos tem UM ofício, e o
|
|
518
|
+
-- outro é retaguarda (084). Entre dois SEM ofício, quem declara
|
|
519
|
+
-- `preset.rail` vence quem não declara (115).
|
|
520
|
+
--
|
|
521
|
+
-- O PRIMEIRO desta lista é quem empresta o rail (`useAppPreset`) e agora
|
|
522
|
+
-- também quem empresta a COR (`useAppTheme`) — as duas leituras têm de ser
|
|
523
|
+
-- o mesmo aplicativo, senão a casa abre com o menu de um produto e a cara
|
|
524
|
+
-- de outro.
|
|
525
|
+
'apps', coalesce((
|
|
526
|
+
SELECT jsonb_agg(x ORDER BY x ->> 'house',
|
|
527
|
+
(x ->> 'trade')::boolean DESC,
|
|
528
|
+
(x -> 'preset' -> 'rail' IS NOT NULL) DESC,
|
|
529
|
+
x ->> 'name') FROM (
|
|
530
|
+
SELECT jsonb_build_object('id', a.id, 'name', a.name, 'icon', a.icon, 'accent_color', a.accent_color,
|
|
531
|
+
'url', a.url, 'house', false, 'preset', a.preset, 'theme', a.theme, 'vertical_id', a.vertical_id,
|
|
532
|
+
'trade', a.vertical_id IS NOT NULL AND a.vertical_id NOT IN ('accounting','generic')) AS x
|
|
533
|
+
FROM app.tenant_apps ta JOIN app.apps a ON a.id = ta.app_id
|
|
534
|
+
WHERE ta.tenant_id = v_tenant AND ta.status = 'active' AND a.active
|
|
535
|
+
UNION ALL
|
|
536
|
+
SELECT jsonb_build_object('id', a.id, 'name', a.name, 'icon', a.icon, 'accent_color', a.accent_color,
|
|
537
|
+
'url', a.url, 'house', true, 'preset', a.preset, 'theme', a.theme, 'vertical_id', a.vertical_id,
|
|
538
|
+
'trade', false) AS x
|
|
539
|
+
FROM app.apps a WHERE v_staff IS NOT NULL AND a.id = 'control-admin' AND a.active AND a.url IS NOT NULL) apps), '[]'::jsonb),
|
|
540
|
+
'nav', coalesce((
|
|
541
|
+
SELECT jsonb_agg(jsonb_build_object('key', n.entry_key, 'hidden', n.hidden, 'label', n.label,
|
|
542
|
+
'section', n.section, 'position', n.position) ORDER BY n.entry_key)
|
|
543
|
+
FROM app.tenant_nav n WHERE n.tenant_id = v_tenant), '[]'::jsonb),
|
|
544
|
+
'permissions', coalesce((
|
|
545
|
+
SELECT jsonb_agg(DISTINCT rp.permission)
|
|
546
|
+
FROM app.memberships m
|
|
547
|
+
JOIN app.role_permissions rp ON rp.role_id = m.role_id AND rp.tenant_id = m.tenant_id
|
|
548
|
+
WHERE m.tenant_id = v_tenant AND m.user_id = (SELECT auth.uid()) AND m.active
|
|
549
|
+
), '[]'::jsonb),
|
|
550
|
+
'generated_at', now());
|
|
551
|
+
END $function$;
|
|
552
|
+
|
|
553
|
+
-- ── §4 a porta ANÔNIMA: quem é este aplicativo, antes de haver sessão ─────
|
|
554
|
+
--
|
|
555
|
+
-- A tela de login acontece antes de existir tenant, e `app.apps` é legível só
|
|
556
|
+
-- para `authenticated` (001). Sem isto o `.env` do bundle não teria como ler a
|
|
557
|
+
-- linha que ele reivindica, e a tela de entrada do ChefControl continuaria
|
|
558
|
+
-- azul até alguém digitar a senha.
|
|
559
|
+
--
|
|
560
|
+
-- Devolve só o que uma porta de entrada mostra — nome, marca, cor. Não devolve
|
|
561
|
+
-- `preset` (a árvore do menu não é assunto de quem ainda não entrou) nem `url`
|
|
562
|
+
-- (a lista de origens do handoff de sessão continua atrás do login).
|
|
563
|
+
CREATE OR REPLACE FUNCTION public.app_identity(p_app text)
|
|
564
|
+
RETURNS jsonb
|
|
565
|
+
LANGUAGE sql
|
|
566
|
+
STABLE SECURITY DEFINER
|
|
567
|
+
SET search_path TO ''
|
|
568
|
+
AS $function$
|
|
569
|
+
SELECT jsonb_build_object('id', a.id, 'name', a.name, 'icon', a.icon,
|
|
570
|
+
'accent_color', a.accent_color, 'theme', a.theme)
|
|
571
|
+
FROM app.apps a WHERE a.id = p_app AND a.active;
|
|
572
|
+
$function$;
|
|
573
|
+
|
|
574
|
+
REVOKE ALL ON FUNCTION public.app_identity(text) FROM PUBLIC;
|
|
575
|
+
GRANT EXECUTE ON FUNCTION public.app_identity(text) TO anon, authenticated;
|
|
576
|
+
|
|
577
|
+
COMMENT ON FUNCTION public.app_identity(text) IS
|
|
578
|
+
'Como este aplicativo se apresenta antes de haver sessão (138): nome, ícone, cor e tema. A tela de login lê por aqui, a partir do VITE_APP_ID do bundle. Deliberadamente SEM preset e SEM url — o menu e a lista de origens do handoff continuam atrás do login.';
|
|
579
|
+
|
|
580
|
+
-- ── §5 a pele que ninguém escolheu sai da frente ──────────────────────────
|
|
581
|
+
--
|
|
582
|
+
-- `Espaço Renova Rio` está no `business`, então o teto de plano NÃO o segura —
|
|
583
|
+
-- e é isso que torna este UPDATE necessário em vez de automático. O marrom
|
|
584
|
+
-- #935427 que ele veste não foi decisão da casa: é `onboarding/submit.ts`
|
|
585
|
+
-- gravando uma paleta no cadastro. A casa quer o StudioControl como ele é.
|
|
586
|
+
--
|
|
587
|
+
-- Sai a PELE, fica a IDENTIDADE: nome e logo continuam, porque quem abre o
|
|
588
|
+
-- sistema abre o sistema DELES. É a mesma linha que a camada 2 desenha — e
|
|
589
|
+
-- aqui ela é traçada à mão porque não há como perguntar retroativamente a uma
|
|
590
|
+
-- conta se a cor dela foi escolha ou sobra de formulário.
|
|
591
|
+
--
|
|
592
|
+
-- As outras contas `business` do cluster (Artorius, Clínica Sol, Espaço Facial)
|
|
593
|
+
-- ficam como estão: são marcas de verdade, e a 093 existe por causa delas.
|
|
594
|
+
-- As contas `free` e `pro` não precisam de UPDATE nenhum — o teto de plano já
|
|
595
|
+
-- deixa de ler os tokens delas, e o dado fica no lugar para o dia em que
|
|
596
|
+
-- assinarem.
|
|
597
|
+
UPDATE public.tenants
|
|
598
|
+
SET settings = jsonb_set(
|
|
599
|
+
settings,
|
|
600
|
+
'{branding,tokens}',
|
|
601
|
+
'{}'::jsonb,
|
|
602
|
+
true
|
|
603
|
+
)
|
|
604
|
+
WHERE lower(name) = 'espaco renova rio'
|
|
605
|
+
AND settings #> '{branding,tokens}' IS NOT NULL
|
|
606
|
+
AND settings #>> '{branding,tokens,color,primary}' = '#935427';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------------
|
|
2
|
+
-- 139_a_equipe_e_um_agrupamento_declarado.sql — a equipe entra, e três
|
|
3
|
+
-- agrupamentos que já estavam na tela param de ser indeclarados.
|
|
4
|
+
-- ---------------------------------------------------------------------------
|
|
5
|
+
-- Os Cadastros agrupam pelo arquétipo `category`: categoria de serviço, grupo
|
|
6
|
+
-- de locais, origem. Desde a 147 a espécie de uma categoria é uma LINHA em
|
|
7
|
+
-- `category_kinds`, e `categories.kind` tem chave estrangeira para ela
|
|
8
|
+
-- (`categories_kind_registered`). Só que as três espécies que o
|
|
9
|
+
-- `plugin-registry` escreve nunca foram declaradas por ninguém: o seed traz
|
|
10
|
+
-- `service`, `good`, `product`, `shop`, `collection`, `menu_category` e
|
|
11
|
+
-- `unclassified`, e mais nada.
|
|
12
|
+
--
|
|
13
|
+
-- O efeito é o pior tipo de defeito: a tela existe, o formulário abre, e o
|
|
14
|
+
-- salvar volta 23503 — violação de chave estrangeira — numa tela de cadastro,
|
|
15
|
+
-- que é onde ninguém procura por erro de integridade. Quem viu isso concluiu
|
|
16
|
+
-- "os Cadastros não funcionam", e não estava errado.
|
|
17
|
+
--
|
|
18
|
+
-- ── a equipe ────────────────────────────────────────────────────────────────
|
|
19
|
+
--
|
|
20
|
+
-- `staff_team` é a espécie nova: um agrupamento de FICHAS da equipe (people
|
|
21
|
+
-- kind='staff'), não de logins. A distinção importa e é a mesma que o
|
|
22
|
+
-- `v_team_members` já confunde por nome — aquilo são os membros do tenant
|
|
23
|
+
-- (app.memberships), quem entra no sistema. Uma casa tem recepção, estética e
|
|
24
|
+
-- vendas muito antes de ter três logins, e é a ficha que responde por turno,
|
|
25
|
+
-- comissão e escala.
|
|
26
|
+
--
|
|
27
|
+
-- O vínculo mora na ficha (`people.custom_fields->>'teamId'`), como a profissão
|
|
28
|
+
-- e a comissão: é uma escolha do cadastro, não uma terceira tabela de ligação
|
|
29
|
+
-- para uma pessoa que está em uma equipe.
|
|
30
|
+
--
|
|
31
|
+
-- As tabelas `public.teams` e `public.team_members` do baseline NÃO são isto e
|
|
32
|
+
-- não são tocadas aqui: nenhuma linha de código as lê, `team_members` aponta
|
|
33
|
+
-- para `user_id`, e reaproveitá-las por terem o nome certo seria herdar um
|
|
34
|
+
-- modelo que ninguém escolheu.
|
|
35
|
+
--
|
|
36
|
+
-- Idempotente: `register_kind` é upsert por espécie.
|
|
37
|
+
-- ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
SELECT public.register_kind('category', 'staff_team', 'registry', NULL, NULL,
|
|
40
|
+
'Uma equipe da casa — o agrupamento das fichas da equipe (recepção, estética, vendas).');
|
|
41
|
+
|
|
42
|
+
-- As três que a tela já escrevia sem declarar.
|
|
43
|
+
SELECT public.register_kind('category', 'service_category', 'registry', NULL, NULL,
|
|
44
|
+
'Categoria de serviço — como o catálogo de serviços se divide para quem marca.');
|
|
45
|
+
|
|
46
|
+
SELECT public.register_kind('category', 'location_group', 'registry', NULL, NULL,
|
|
47
|
+
'Grupo de locais — a região ou o franqueado que recolhe várias filiais.');
|
|
48
|
+
|
|
49
|
+
SELECT public.register_kind('category', 'origin', 'registry', NULL, NULL,
|
|
50
|
+
'Origem — por onde o cliente chegou até a casa.');
|