@fayz-ai/plugin-reservations 0.2.31 → 0.2.33

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.
@@ -0,0 +1,421 @@
1
+ // src/migrations/index.ts
2
+ var MIGRATION_001_RESERVATIONS = `-- plugin-reservations \u2014 a mesa guardada para mais tarde.
3
+ --
4
+ -- WHY THIS IS NOT THE AGENDA. \`plugin-agenda\` schedules a PERSON's time: a
5
+ -- column per professional, services with durations, an assignee. A restaurant
6
+ -- reservation has neither professional nor service \u2014 it spends the SEATS OF A
7
+ -- ROOM inside a time window. resto-saas shipped \`createAgendaPlugin({
8
+ -- bookingKind: 'reservation' })\` with the labels swapped, and the screen came
9
+ -- out with nine staff columns over the word "Reservas". A booking kind cannot
10
+ -- change which resource is scarce.
11
+ --
12
+ -- The V1 (~/dev/beautyplace/docs/v1-guide/restaurante/reservas) models this
13
+ -- correctly and we keep its model \u2014 local \xD7 faixa \xD7 dia da semana, with
14
+ -- exceptions that override the weekly grid \u2014 with three deliberate changes,
15
+ -- each written where it happens below.
16
+ --
17
+ -- Idempotent.
18
+
19
+ -- ---------------------------------------------------------------------------
20
+ -- Policy \u2014 one row per tenant.
21
+ -- ---------------------------------------------------------------------------
22
+ create table if not exists public.plg_reservations_settings (
23
+ tenant_id uuid primary key references public.tenants(id) on delete cascade,
24
+ -- V1's three modes, same words. \`no_duration\` is the V1 default and the one
25
+ -- its own legend explains best: "a capacidade \xE9 o teto de pessoas que pode
26
+ -- iniciar dentro da faixa, sem rolagem entre faixas" \u2014 the reservation spends
27
+ -- the slot it STARTS in.
28
+ capacity_mode text not null default 'no_duration'
29
+ check (capacity_mode in ('fixed_duration','per_slot_duration','no_duration')),
30
+ default_duration_minutes integer not null default 120 check (default_duration_minutes > 0),
31
+ -- CHANGE 2 of 3: the V1 asks for minimum notice in MINUTES (1440) and maximum
32
+ -- in DAYS (90), in the same pair of fields. Both are hours/days here and the
33
+ -- UI writes the equivalent out beside them.
34
+ min_advance_hours integer not null default 24 check (min_advance_hours >= 0),
35
+ max_advance_days integer not null default 90 check (max_advance_days > 0),
36
+ auto_confirm boolean not null default false,
37
+ require_phone boolean not null default true,
38
+ require_email boolean not null default false,
39
+ created_at timestamptz not null default now(),
40
+ updated_at timestamptz not null default now()
41
+ );
42
+
43
+ -- ---------------------------------------------------------------------------
44
+ -- The weekly grid \u2014 one row per zone \xD7 window \xD7 set of weekdays.
45
+ -- ---------------------------------------------------------------------------
46
+ create table if not exists public.plg_reservations_capacity_rules (
47
+ id uuid primary key default gen_random_uuid(),
48
+ tenant_id uuid not null references public.tenants(id) on delete cascade,
49
+ -- CHANGE 1 of 3: capacity is per PRA\xC7A, not per a cadastro of its own.
50
+ --
51
+ -- The V1 splits these: capacity lives on \`service_locations\` (Cadastros \u2192
52
+ -- Locais Operacionais) while the mesa lives on \`restaurant_sections\` (the
53
+ -- Sal\xE3o screen). Two cadastros with similar names, and its own document says
54
+ -- "nada na tela avisa" \u2014 whoever registered the pra\xE7as does not find those
55
+ -- names when configuring the lota\xE7\xE3o. We already own zones with mesas inside
56
+ -- them; a third cadastro would re-import the confusion and buy nothing.
57
+ --
58
+ -- Nullable = the house as a whole, for a restaurant with one room.
59
+ zone_id uuid references public.plg_tables_zones(id) on delete cascade,
60
+ capacity integer not null check (capacity > 0),
61
+ start_time time not null,
62
+ end_time time not null,
63
+ -- 0=Sunday..6=Saturday, matching Date#getDay and plugin-menu's availability.
64
+ weekdays smallint[] not null default '{0,1,2,3,4,5,6}',
65
+ -- Only read in \`per_slot_duration\` mode; null falls back to the default.
66
+ duration_minutes integer check (duration_minutes is null or duration_minutes > 0),
67
+ is_active boolean not null default true,
68
+ created_at timestamptz not null default now(),
69
+ updated_at timestamptz not null default now(),
70
+ check (end_time > start_time)
71
+ );
72
+
73
+ -- ---------------------------------------------------------------------------
74
+ -- Exceptions \u2014 "Exce\xE7\xF5es sobrep\xF5em a grade semanal para o(s) dia(s) cobertos
75
+ -- no mesmo local." (V1, literal). A row with capacity 0 closes the zone.
76
+ -- ---------------------------------------------------------------------------
77
+ create table if not exists public.plg_reservations_capacity_exceptions (
78
+ id uuid primary key default gen_random_uuid(),
79
+ tenant_id uuid not null references public.tenants(id) on delete cascade,
80
+ zone_id uuid references public.plg_tables_zones(id) on delete cascade,
81
+ starts_on date not null,
82
+ ends_on date not null,
83
+ -- Null start/end = the whole day. Zero capacity = closed.
84
+ start_time time,
85
+ end_time time,
86
+ capacity integer not null default 0 check (capacity >= 0),
87
+ reason text,
88
+ created_at timestamptz not null default now(),
89
+ updated_at timestamptz not null default now(),
90
+ check (ends_on >= starts_on)
91
+ );
92
+
93
+ -- ---------------------------------------------------------------------------
94
+ -- The reservations.
95
+ -- ---------------------------------------------------------------------------
96
+ create table if not exists public.plg_reservations (
97
+ id uuid primary key default gen_random_uuid(),
98
+ tenant_id uuid not null references public.tenants(id) on delete cascade,
99
+ person_id uuid references public.people(id) on delete set null,
100
+ -- Denormalised on purpose: a reservation taken over the phone from someone
101
+ -- who is not a client yet still has a name and a number to call back.
102
+ guest_name text not null,
103
+ phone text,
104
+ email text,
105
+ zone_id uuid references public.plg_tables_zones(id) on delete set null,
106
+ table_id uuid references public.plg_tables_tables(id) on delete set null,
107
+ reserved_on date not null,
108
+ start_time time not null,
109
+ end_time time,
110
+ party_size integer not null check (party_size > 0),
111
+ occasion text,
112
+ notes text,
113
+ -- V1's five states, same words: Pendente \xB7 Confirmada \xB7 Acomodada \xB7
114
+ -- N\xE3o compareceu \xB7 Cancelada.
115
+ status text not null default 'pending'
116
+ check (status in ('pending','confirmed','seated','no_show','cancelled')),
117
+ -- CHANGE 3 of 3: \`seated\` opens the mesa.
118
+ --
119
+ -- In the V1, reservation and comanda are separate universes \u2014 marking
120
+ -- Acomodada creates no comanda at all, and the night's money never knows a
121
+ -- reservation happened. Here \`seated\` is the moment the party becomes a
122
+ -- comanda, and this column is the join. Written by the app, not a trigger:
123
+ -- opening a comanda is the tables plugin's job and it numbers the order.
124
+ order_id uuid references public.orders(id) on delete set null,
125
+ seated_at timestamptz,
126
+ created_at timestamptz not null default now(),
127
+ updated_at timestamptz not null default now()
128
+ );
129
+
130
+ create index if not exists plg_reservations_day_idx
131
+ on public.plg_reservations (tenant_id, reserved_on, start_time);
132
+ create index if not exists plg_reservations_zone_day_idx
133
+ on public.plg_reservations (tenant_id, zone_id, reserved_on);
134
+ create index if not exists plg_reservations_rules_zone_idx
135
+ on public.plg_reservations_capacity_rules (tenant_id, zone_id);
136
+ create index if not exists plg_reservations_exceptions_day_idx
137
+ on public.plg_reservations_capacity_exceptions (tenant_id, starts_on, ends_on);
138
+
139
+ do $$ begin
140
+ create trigger plg_reservations_settings_updated_at before update on public.plg_reservations_settings
141
+ for each row execute function public.handle_updated_at();
142
+ exception when duplicate_object then null; end $$;
143
+ do $$ begin
144
+ create trigger plg_reservations_rules_updated_at before update on public.plg_reservations_capacity_rules
145
+ for each row execute function public.handle_updated_at();
146
+ exception when duplicate_object then null; end $$;
147
+ do $$ begin
148
+ create trigger plg_reservations_exceptions_updated_at before update on public.plg_reservations_capacity_exceptions
149
+ for each row execute function public.handle_updated_at();
150
+ exception when duplicate_object then null; end $$;
151
+ do $$ begin
152
+ create trigger plg_reservations_updated_at before update on public.plg_reservations
153
+ for each row execute function public.handle_updated_at();
154
+ exception when duplicate_object then null; end $$;
155
+
156
+ -- ---------------------------------------------------------------------------
157
+ -- RLS \u2014 the ACTIVE account, never the union.
158
+ --
159
+ -- \`user_tenant_ids()\` honours only a signed JWT claim; switching workspace
160
+ -- happens through the \`x-fayz-tenant\` header, so it does not see the switch and
161
+ -- answers with the UNION of the caller's accounts. plugin-tables learned this
162
+ -- the hard way \u2014 Artorius drew Papa L\xE9guas' mesas. \`app.current_tenant_id()\`
163
+ -- reads the header and validates it against app.memberships: it chooses among
164
+ -- accounts you already belong to and can never widen.
165
+ -- ---------------------------------------------------------------------------
166
+ alter table public.plg_reservations_settings enable row level security;
167
+ alter table public.plg_reservations_capacity_rules enable row level security;
168
+ alter table public.plg_reservations_capacity_exceptions enable row level security;
169
+ alter table public.plg_reservations enable row level security;
170
+
171
+ do $$
172
+ declare t text;
173
+ begin
174
+ foreach t in array array['plg_reservations_settings','plg_reservations_capacity_rules',
175
+ 'plg_reservations_capacity_exceptions','plg_reservations'] loop
176
+ execute format('drop policy if exists %I on public.%I', t || '_select', t);
177
+ execute format('drop policy if exists %I on public.%I', t || '_insert', t);
178
+ execute format('drop policy if exists %I on public.%I', t || '_update', t);
179
+ execute format('drop policy if exists %I on public.%I', t || '_delete', t);
180
+ execute format('create policy %I on public.%I for select using (tenant_id = (select app.current_tenant_id()))', t || '_select', t);
181
+ execute format('create policy %I on public.%I for insert with check (tenant_id = (select app.current_tenant_id()))', t || '_insert', t);
182
+ execute format('create policy %I on public.%I for update using (tenant_id = (select app.current_tenant_id()))', t || '_update', t);
183
+ execute format('create policy %I on public.%I for delete using (tenant_id = (select app.current_tenant_id()))', t || '_delete', t);
184
+ execute format('grant select, insert, update, delete on public.%I to authenticated', t);
185
+ end loop;
186
+ end $$;
187
+
188
+ -- ---------------------------------------------------------------------------
189
+ -- The reservation with the names already resolved. The list and the calendar
190
+ -- read this, so nobody joins four tables in TypeScript to render a row.
191
+ -- ---------------------------------------------------------------------------
192
+ create or replace view public.v_reservations with (security_invoker = true) as
193
+ select
194
+ r.id, r.tenant_id, r.person_id, r.guest_name, r.phone, r.email,
195
+ r.zone_id, z.name as zone_name, z.color as zone_color,
196
+ r.table_id, t.number as table_number,
197
+ r.reserved_on, r.start_time, r.end_time, r.party_size,
198
+ r.occasion, r.notes, r.status, r.order_id, r.seated_at,
199
+ o.reference_number as order_reference,
200
+ r.created_at, r.updated_at
201
+ from public.plg_reservations r
202
+ left join public.plg_tables_zones z on z.id = r.zone_id
203
+ left join public.plg_tables_tables t on t.id = r.table_id
204
+ left join public.orders o on o.id = r.order_id;
205
+
206
+ grant select on public.v_reservations to authenticated;
207
+
208
+ -- ---------------------------------------------------------------------------
209
+ -- The slots of one day, with what is left in each.
210
+ --
211
+ -- THIS IS THE FUNCTION THE V1 NEVER GOT RIGHT. With three rules covering all
212
+ -- seven weekdays, its "Capacidade do dia" answered "Nenhuma grade configurada"
213
+ -- on every date tested (V1-041), and its reservation form never called the
214
+ -- availability function at all \u2014 the lota\xE7\xE3o check, if it existed, did not
215
+ -- happen while anyone was deciding. Both are the reason this screen exists.
216
+ --
217
+ -- Returns one row per zone \xD7 window that applies to \`p_date\`, with the seats
218
+ -- already spent by non-cancelled reservations STARTING inside that window \u2014
219
+ -- which is what "sem rolagem entre faixas" means in the V1's own legend.
220
+ --
221
+ -- Exceptions OVERRIDE, they do not add: a zone with an exception covering the
222
+ -- date returns the exception's windows and none of the weekly ones. A capacity
223
+ -- of 0 therefore closes the zone for the day without deleting the grid.
224
+ -- ---------------------------------------------------------------------------
225
+ create or replace function public.reservations_day_capacity(p_date date)
226
+ returns table (
227
+ zone_id uuid,
228
+ zone_name text,
229
+ zone_color text,
230
+ start_time time,
231
+ end_time time,
232
+ capacity integer,
233
+ booked integer,
234
+ remaining integer,
235
+ is_exception boolean
236
+ )
237
+ language sql
238
+ stable
239
+ security invoker
240
+ set search_path = public
241
+ as $$
242
+ with tenant as (select app.current_tenant_id() as id),
243
+ -- Which zones had their day overridden. Checked per zone, so a holiday in the
244
+ -- Varanda does not silence the grid of the Sal\xE3o.
245
+ overridden as (
246
+ select distinct e.zone_id
247
+ from public.plg_reservations_capacity_exceptions e, tenant
248
+ where e.tenant_id = tenant.id
249
+ and p_date between e.starts_on and e.ends_on
250
+ ),
251
+ slots as (
252
+ select e.zone_id, e.start_time, e.end_time, e.capacity, true as is_exception
253
+ from public.plg_reservations_capacity_exceptions e, tenant
254
+ where e.tenant_id = tenant.id
255
+ and p_date between e.starts_on and e.ends_on
256
+ and e.capacity > 0
257
+ -- A whole-day exception still has to draw SOMETHING, so it borrows the
258
+ -- house's widest window rather than a made-up one.
259
+ and coalesce(e.start_time, time '00:00') < coalesce(e.end_time, time '23:59')
260
+ union all
261
+ select r.zone_id, r.start_time, r.end_time, r.capacity, false
262
+ from public.plg_reservations_capacity_rules r, tenant
263
+ where r.tenant_id = tenant.id
264
+ and r.is_active
265
+ -- extract(dow) is 0=Sunday..6=Saturday, the same axis \`weekdays\` uses.
266
+ and extract(dow from p_date)::smallint = any (r.weekdays)
267
+ and (r.zone_id is null or r.zone_id not in (select o.zone_id from overridden o where o.zone_id is not null))
268
+ )
269
+ select
270
+ s.zone_id,
271
+ z.name as zone_name,
272
+ z.color as zone_color,
273
+ coalesce(s.start_time, time '00:00') as start_time,
274
+ coalesce(s.end_time, time '23:59') as end_time,
275
+ s.capacity,
276
+ coalesce(b.booked, 0)::integer as booked,
277
+ greatest(s.capacity - coalesce(b.booked, 0), 0)::integer as remaining,
278
+ s.is_exception
279
+ from slots s
280
+ left join public.plg_tables_zones z on z.id = s.zone_id
281
+ left join lateral (
282
+ select sum(v.party_size)::integer as booked
283
+ from public.plg_reservations v, tenant
284
+ where v.tenant_id = tenant.id
285
+ and v.reserved_on = p_date
286
+ and v.status <> 'cancelled'
287
+ and (s.zone_id is null or v.zone_id is not distinct from s.zone_id)
288
+ -- The reservation spends the window it STARTS in.
289
+ and v.start_time >= coalesce(s.start_time, time '00:00')
290
+ and v.start_time < coalesce(s.end_time, time '23:59')
291
+ ) b on true
292
+ order by coalesce(s.start_time, time '00:00'), z.name nulls first;
293
+ $$;
294
+
295
+ grant execute on function public.reservations_day_capacity(date) to authenticated;
296
+ `;
297
+ var MIGRATION_002_A_RESERVA_E_DE_UMA_CASA = `-- A reserva \xE9 de uma casa.
298
+ --
299
+ -- \`plg_reservations\`, \`plg_reservations_settings\` e
300
+ -- \`plg_reservations_capacity_rules\` n\xE3o t\xEAm \`unit_id\`. O V1 tem, nas tr\xEAs:
301
+ -- \`reservations.unit_id\`, \`reservation_settings.unit_id\`,
302
+ -- \`reservation_capacity_rules.unit_id\`.
303
+ --
304
+ -- Uma reserva sem unidade aparece na agenda de todas as filiais, e a regra de
305
+ -- capacidade de uma casa passa a limitar a outra \u2014 que \xE9 o pior dos dois, porque
306
+ -- recusa cliente em sil\xEAncio.
307
+ --
308
+ -- A configura\xE7\xE3o \xE9 o caso mais claro: \`reservation_settings\` existe PARA ser
309
+ -- por unidade. Sem a coluna, a casa que abre \xE0s 19h e a que abre \xE0s 12h t\xEAm de
310
+ -- concordar num hor\xE1rio s\xF3.
311
+ --
312
+ -- Idempotente.
313
+
314
+ DO $$
315
+ DECLARE t text;
316
+ BEGIN
317
+ FOREACH t IN ARRAY ARRAY['plg_reservations', 'plg_reservations_settings',
318
+ 'plg_reservations_capacity_rules',
319
+ 'plg_reservations_capacity_exceptions'] LOOP
320
+ IF to_regclass('public.' || t) IS NULL THEN CONTINUE; END IF;
321
+ EXECUTE format('ALTER TABLE public.%I ADD COLUMN IF NOT EXISTS unit_id uuid', t);
322
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = t || '_unit_fkey') THEN
323
+ EXECUTE format('ALTER TABLE public.%I ADD CONSTRAINT %I FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL', t, t || '_unit_fkey');
324
+ END IF;
325
+ EXECUTE format('COMMENT ON COLUMN public.%I.unit_id IS %L', t,
326
+ 'A casa a que a reserva pertence. V1: reservations.unit_id / reservation_settings.unit_id / reservation_capacity_rules.unit_id (002).');
327
+ END LOOP;
328
+ END $$;
329
+
330
+ -- "quais reservas desta casa, neste dia" \xE9 a consulta do ma\xEEtre, e ela roda a
331
+ -- cada abertura de turno.
332
+ DO $$ BEGIN
333
+ IF EXISTS (SELECT 1 FROM pg_attribute a
334
+ WHERE a.attrelid = 'public.plg_reservations'::regclass
335
+ AND a.attname = 'starts_at' AND NOT a.attisdropped) THEN
336
+ CREATE INDEX IF NOT EXISTS plg_reservations_unit_day_idx
337
+ ON public.plg_reservations USING btree (tenant_id, unit_id, starts_at);
338
+ END IF;
339
+ END $$;
340
+
341
+ -- Uma configura\xE7\xE3o por casa. Duas seriam duas respostas para "a que horas
342
+ -- abrimos" \u2014 e a tela leria a primeira que viesse.
343
+ CREATE UNIQUE INDEX IF NOT EXISTS plg_reservations_settings_unit_uidx
344
+ ON public.plg_reservations_settings USING btree (tenant_id, unit_id)
345
+ WHERE unit_id IS NOT NULL;
346
+ `;
347
+ var MIGRATION_003_QUEM_CHEGOU_JA_ESTA_NA_CASA_ANTES_DE_SENTAR = `-- Quem chegou j\xE1 est\xE1 na casa, antes de sentar.
348
+ --
349
+ -- A reserva tinha cinco estados e nenhum deles respondia \xE0 pergunta que o
350
+ -- anfitri\xE3o faz a noite inteira: **essa gente j\xE1 est\xE1 aqui?** Entre \xABConfirmada\xBB
351
+ -- (prometeu vir) e \xABAcomodada\xBB (est\xE1 na mesa) existe um terceiro fato \u2014 o grupo
352
+ -- est\xE1 de p\xE9 na recep\xE7\xE3o esperando a mesa vagar \u2014, e sem ele o anfitri\xE3o marca
353
+ -- a reserva como acomodada s\xF3 para tir\xE1-la da lista, o que abre uma comanda
354
+ -- numa mesa que ningu\xE9m ocupou.
355
+ --
356
+ -- N\xC3O \xE9 porte: o V1 tamb\xE9m tem cinco estados
357
+ -- (\`pending,confirmed,seated,no_show,cancelled\`, em
358
+ -- \`20260622220606_*.sql\`) e nunca teve check-in. \xC9 desenho novo, pedido pelo
359
+ -- founder para a tela de controle de entrada, onde reserva e fila de espera
360
+ -- passam a conviver: chegar \xE9 o gesto que p\xF5e a reserva NA FILA, e sentar
361
+ -- continua sendo o gesto que abre a comanda.
362
+ --
363
+ -- \`arrived\` entra ANTES de \`seated\` na ordem do CHECK para deixar a leitura do
364
+ -- ciclo \xF3bvia para quem abrir esta tabela depois.
365
+ --
366
+ -- Idempotente.
367
+
368
+ ALTER TABLE public.plg_reservations
369
+ ADD COLUMN IF NOT EXISTS arrived_at timestamptz;
370
+
371
+ -- O CHECK \xE9 reescrito, n\xE3o somado: um \`ADD CONSTRAINT\` com outro nome deixaria
372
+ -- os dois valendo, e o antigo recusaria \`arrived\` de qualquer jeito \u2014 Postgres
373
+ -- exige que TODOS passem.
374
+ ALTER TABLE public.plg_reservations
375
+ DROP CONSTRAINT IF EXISTS plg_reservations_status_check;
376
+ ALTER TABLE public.plg_reservations
377
+ ADD CONSTRAINT plg_reservations_status_check
378
+ CHECK (status IN ('pending','confirmed','arrived','seated','no_show','cancelled'));
379
+
380
+ COMMENT ON COLUMN public.plg_reservations.arrived_at IS
381
+ 'Quando o grupo se apresentou na recep\xE7\xE3o. Chegar N\xC3O abre comanda: quem abre \xE9 o seat.';
382
+
383
+ -- A VISTA precisa carregar a coluna nova, sen\xE3o o motor existe e a tela n\xE3o o
384
+ -- enxerga: \`v_reservations\` lista coluna a coluna (n\xE3o \xE9 \`r.*\`), e toda leitura
385
+ -- do plugin passa por ela. Sem esta parte, \`arrived_at\` seria gravado e nunca
386
+ -- lido \u2014 o defeito de "ler de A e escrever em B", que s\xF3 aparece na tela.
387
+ --
388
+ -- \`security_invoker=true\` \xE9 repetido de prop\xF3sito: um \`CREATE OR REPLACE\` sem
389
+ -- ele devolveria a vista para o dono e a RLS deixaria de recortar por conta.
390
+ --
391
+ -- E \`arrived_at\` vai no FIM da lista, n\xE3o ao lado de \`seated_at\` onde ele
392
+ -- faria sentido para quem l\xEA: \`CREATE OR REPLACE VIEW\` s\xF3 sabe ACRESCENTAR
393
+ -- coluna no fim \u2014 p\xF4-la no meio renomeia as seguintes e o Postgres recusa
394
+ -- (\xABuse ALTER VIEW ... RENAME COLUMN\xBB). Trocar por DROP + CREATE derrubaria
395
+ -- as permiss\xF5es junto.
396
+ CREATE OR REPLACE VIEW public.v_reservations
397
+ WITH (security_invoker = true) AS
398
+ SELECT r.id, r.tenant_id, r.person_id, r.guest_name, r.phone, r.email,
399
+ r.zone_id, z.name AS zone_name, z.color AS zone_color,
400
+ r.table_id, t.number AS table_number,
401
+ r.reserved_on, r.start_time, r.end_time, r.party_size,
402
+ r.occasion, r.notes, r.status, r.order_id, r.seated_at,
403
+ o.reference_number AS order_reference,
404
+ r.created_at, r.updated_at,
405
+ r.arrived_at
406
+ FROM public.plg_reservations r
407
+ LEFT JOIN public.plg_tables_zones z ON z.id = r.zone_id
408
+ LEFT JOIN public.plg_tables_tables t ON t.id = r.table_id
409
+ LEFT JOIN public.orders o ON o.id = r.order_id;
410
+
411
+ GRANT SELECT ON public.v_reservations TO authenticated;
412
+ `;
413
+ var MIGRATIONS = [
414
+ { id: "001_reservations", sql: MIGRATION_001_RESERVATIONS },
415
+ { id: "002_a_reserva_e_de_uma_casa", sql: MIGRATION_002_A_RESERVA_E_DE_UMA_CASA },
416
+ { id: "003_quem_chegou_ja_esta_na_casa_antes_de_sentar", sql: MIGRATION_003_QUEM_CHEGOU_JA_ESTA_NA_CASA_ANTES_DE_SENTAR }
417
+ ];
418
+
419
+ export { MIGRATIONS, MIGRATION_001_RESERVATIONS, MIGRATION_002_A_RESERVA_E_DE_UMA_CASA, MIGRATION_003_QUEM_CHEGOU_JA_ESTA_NA_CASA_ANTES_DE_SENTAR };
420
+ //# sourceMappingURL=main.js.map
421
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/migrations/index.ts"],"names":[],"mappings":";AAIO,IAAM,0BAAA,GAA6B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAwSnC,IAAM,qCAAA,GAAwC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmD9C,IAAM,yDAAA,GAA4D,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAmElE,IAAM,UAAA,GAAiD;AAAA,EAC5D,EAAE,EAAA,EAAI,kBAAA,EAAoB,GAAA,EAAK,0BAAA,EAA2B;AAAA,EAC1D,EAAE,EAAA,EAAI,6BAAA,EAA+B,GAAA,EAAK,qCAAA,EAAsC;AAAA,EAChF,EAAE,EAAA,EAAI,iDAAA,EAAmD,GAAA,EAAK,yDAAA;AAChE","file":"main.js","sourcesContent":["// AUTO-GENERATED from 001_reservations.sql, 002_a_reserva_e_de_uma_casa.sql, 003_quem_chegou_ja_esta_na_casa_antes_de_sentar.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_001_RESERVATIONS = `-- plugin-reservations — a mesa guardada para mais tarde.\n--\n-- WHY THIS IS NOT THE AGENDA. \\`plugin-agenda\\` schedules a PERSON's time: a\n-- column per professional, services with durations, an assignee. A restaurant\n-- reservation has neither professional nor service — it spends the SEATS OF A\n-- ROOM inside a time window. resto-saas shipped \\`createAgendaPlugin({\n-- bookingKind: 'reservation' })\\` with the labels swapped, and the screen came\n-- out with nine staff columns over the word \"Reservas\". A booking kind cannot\n-- change which resource is scarce.\n--\n-- The V1 (~/dev/beautyplace/docs/v1-guide/restaurante/reservas) models this\n-- correctly and we keep its model — local × faixa × dia da semana, with\n-- exceptions that override the weekly grid — with three deliberate changes,\n-- each written where it happens below.\n--\n-- Idempotent.\n\n-- ---------------------------------------------------------------------------\n-- Policy — one row per tenant.\n-- ---------------------------------------------------------------------------\ncreate table if not exists public.plg_reservations_settings (\n tenant_id uuid primary key references public.tenants(id) on delete cascade,\n -- V1's three modes, same words. \\`no_duration\\` is the V1 default and the one\n -- its own legend explains best: \"a capacidade é o teto de pessoas que pode\n -- iniciar dentro da faixa, sem rolagem entre faixas\" — the reservation spends\n -- the slot it STARTS in.\n capacity_mode text not null default 'no_duration'\n check (capacity_mode in ('fixed_duration','per_slot_duration','no_duration')),\n default_duration_minutes integer not null default 120 check (default_duration_minutes > 0),\n -- CHANGE 2 of 3: the V1 asks for minimum notice in MINUTES (1440) and maximum\n -- in DAYS (90), in the same pair of fields. Both are hours/days here and the\n -- UI writes the equivalent out beside them.\n min_advance_hours integer not null default 24 check (min_advance_hours >= 0),\n max_advance_days integer not null default 90 check (max_advance_days > 0),\n auto_confirm boolean not null default false,\n require_phone boolean not null default true,\n require_email boolean not null default false,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now()\n);\n\n-- ---------------------------------------------------------------------------\n-- The weekly grid — one row per zone × window × set of weekdays.\n-- ---------------------------------------------------------------------------\ncreate table if not exists public.plg_reservations_capacity_rules (\n id uuid primary key default gen_random_uuid(),\n tenant_id uuid not null references public.tenants(id) on delete cascade,\n -- CHANGE 1 of 3: capacity is per PRAÇA, not per a cadastro of its own.\n --\n -- The V1 splits these: capacity lives on \\`service_locations\\` (Cadastros →\n -- Locais Operacionais) while the mesa lives on \\`restaurant_sections\\` (the\n -- Salão screen). Two cadastros with similar names, and its own document says\n -- \"nada na tela avisa\" — whoever registered the praças does not find those\n -- names when configuring the lotação. We already own zones with mesas inside\n -- them; a third cadastro would re-import the confusion and buy nothing.\n --\n -- Nullable = the house as a whole, for a restaurant with one room.\n zone_id uuid references public.plg_tables_zones(id) on delete cascade,\n capacity integer not null check (capacity > 0),\n start_time time not null,\n end_time time not null,\n -- 0=Sunday..6=Saturday, matching Date#getDay and plugin-menu's availability.\n weekdays smallint[] not null default '{0,1,2,3,4,5,6}',\n -- Only read in \\`per_slot_duration\\` mode; null falls back to the default.\n duration_minutes integer check (duration_minutes is null or duration_minutes > 0),\n is_active boolean not null default true,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n check (end_time > start_time)\n);\n\n-- ---------------------------------------------------------------------------\n-- Exceptions — \"Exceções sobrepõem a grade semanal para o(s) dia(s) cobertos\n-- no mesmo local.\" (V1, literal). A row with capacity 0 closes the zone.\n-- ---------------------------------------------------------------------------\ncreate table if not exists public.plg_reservations_capacity_exceptions (\n id uuid primary key default gen_random_uuid(),\n tenant_id uuid not null references public.tenants(id) on delete cascade,\n zone_id uuid references public.plg_tables_zones(id) on delete cascade,\n starts_on date not null,\n ends_on date not null,\n -- Null start/end = the whole day. Zero capacity = closed.\n start_time time,\n end_time time,\n capacity integer not null default 0 check (capacity >= 0),\n reason text,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n check (ends_on >= starts_on)\n);\n\n-- ---------------------------------------------------------------------------\n-- The reservations.\n-- ---------------------------------------------------------------------------\ncreate table if not exists public.plg_reservations (\n id uuid primary key default gen_random_uuid(),\n tenant_id uuid not null references public.tenants(id) on delete cascade,\n person_id uuid references public.people(id) on delete set null,\n -- Denormalised on purpose: a reservation taken over the phone from someone\n -- who is not a client yet still has a name and a number to call back.\n guest_name text not null,\n phone text,\n email text,\n zone_id uuid references public.plg_tables_zones(id) on delete set null,\n table_id uuid references public.plg_tables_tables(id) on delete set null,\n reserved_on date not null,\n start_time time not null,\n end_time time,\n party_size integer not null check (party_size > 0),\n occasion text,\n notes text,\n -- V1's five states, same words: Pendente · Confirmada · Acomodada ·\n -- Não compareceu · Cancelada.\n status text not null default 'pending'\n check (status in ('pending','confirmed','seated','no_show','cancelled')),\n -- CHANGE 3 of 3: \\`seated\\` opens the mesa.\n --\n -- In the V1, reservation and comanda are separate universes — marking\n -- Acomodada creates no comanda at all, and the night's money never knows a\n -- reservation happened. Here \\`seated\\` is the moment the party becomes a\n -- comanda, and this column is the join. Written by the app, not a trigger:\n -- opening a comanda is the tables plugin's job and it numbers the order.\n order_id uuid references public.orders(id) on delete set null,\n seated_at timestamptz,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now()\n);\n\ncreate index if not exists plg_reservations_day_idx\n on public.plg_reservations (tenant_id, reserved_on, start_time);\ncreate index if not exists plg_reservations_zone_day_idx\n on public.plg_reservations (tenant_id, zone_id, reserved_on);\ncreate index if not exists plg_reservations_rules_zone_idx\n on public.plg_reservations_capacity_rules (tenant_id, zone_id);\ncreate index if not exists plg_reservations_exceptions_day_idx\n on public.plg_reservations_capacity_exceptions (tenant_id, starts_on, ends_on);\n\ndo $$ begin\n create trigger plg_reservations_settings_updated_at before update on public.plg_reservations_settings\n for each row execute function public.handle_updated_at();\nexception when duplicate_object then null; end $$;\ndo $$ begin\n create trigger plg_reservations_rules_updated_at before update on public.plg_reservations_capacity_rules\n for each row execute function public.handle_updated_at();\nexception when duplicate_object then null; end $$;\ndo $$ begin\n create trigger plg_reservations_exceptions_updated_at before update on public.plg_reservations_capacity_exceptions\n for each row execute function public.handle_updated_at();\nexception when duplicate_object then null; end $$;\ndo $$ begin\n create trigger plg_reservations_updated_at before update on public.plg_reservations\n for each row execute function public.handle_updated_at();\nexception when duplicate_object then null; end $$;\n\n-- ---------------------------------------------------------------------------\n-- RLS — the ACTIVE account, never the union.\n--\n-- \\`user_tenant_ids()\\` honours only a signed JWT claim; switching workspace\n-- happens through the \\`x-fayz-tenant\\` header, so it does not see the switch and\n-- answers with the UNION of the caller's accounts. plugin-tables learned this\n-- the hard way — Artorius drew Papa Léguas' mesas. \\`app.current_tenant_id()\\`\n-- reads the header and validates it against app.memberships: it chooses among\n-- accounts you already belong to and can never widen.\n-- ---------------------------------------------------------------------------\nalter table public.plg_reservations_settings enable row level security;\nalter table public.plg_reservations_capacity_rules enable row level security;\nalter table public.plg_reservations_capacity_exceptions enable row level security;\nalter table public.plg_reservations enable row level security;\n\ndo $$\ndeclare t text;\nbegin\n foreach t in array array['plg_reservations_settings','plg_reservations_capacity_rules',\n 'plg_reservations_capacity_exceptions','plg_reservations'] loop\n execute format('drop policy if exists %I on public.%I', t || '_select', t);\n execute format('drop policy if exists %I on public.%I', t || '_insert', t);\n execute format('drop policy if exists %I on public.%I', t || '_update', t);\n execute format('drop policy if exists %I on public.%I', t || '_delete', t);\n execute format('create policy %I on public.%I for select using (tenant_id = (select app.current_tenant_id()))', t || '_select', t);\n execute format('create policy %I on public.%I for insert with check (tenant_id = (select app.current_tenant_id()))', t || '_insert', t);\n execute format('create policy %I on public.%I for update using (tenant_id = (select app.current_tenant_id()))', t || '_update', t);\n execute format('create policy %I on public.%I for delete using (tenant_id = (select app.current_tenant_id()))', t || '_delete', t);\n execute format('grant select, insert, update, delete on public.%I to authenticated', t);\n end loop;\nend $$;\n\n-- ---------------------------------------------------------------------------\n-- The reservation with the names already resolved. The list and the calendar\n-- read this, so nobody joins four tables in TypeScript to render a row.\n-- ---------------------------------------------------------------------------\ncreate or replace view public.v_reservations with (security_invoker = true) as\nselect\n r.id, r.tenant_id, r.person_id, r.guest_name, r.phone, r.email,\n r.zone_id, z.name as zone_name, z.color as zone_color,\n r.table_id, t.number as table_number,\n r.reserved_on, r.start_time, r.end_time, r.party_size,\n r.occasion, r.notes, r.status, r.order_id, r.seated_at,\n o.reference_number as order_reference,\n r.created_at, r.updated_at\nfrom public.plg_reservations r\nleft join public.plg_tables_zones z on z.id = r.zone_id\nleft join public.plg_tables_tables t on t.id = r.table_id\nleft join public.orders o on o.id = r.order_id;\n\ngrant select on public.v_reservations to authenticated;\n\n-- ---------------------------------------------------------------------------\n-- The slots of one day, with what is left in each.\n--\n-- THIS IS THE FUNCTION THE V1 NEVER GOT RIGHT. With three rules covering all\n-- seven weekdays, its \"Capacidade do dia\" answered \"Nenhuma grade configurada\"\n-- on every date tested (V1-041), and its reservation form never called the\n-- availability function at all — the lotação check, if it existed, did not\n-- happen while anyone was deciding. Both are the reason this screen exists.\n--\n-- Returns one row per zone × window that applies to \\`p_date\\`, with the seats\n-- already spent by non-cancelled reservations STARTING inside that window —\n-- which is what \"sem rolagem entre faixas\" means in the V1's own legend.\n--\n-- Exceptions OVERRIDE, they do not add: a zone with an exception covering the\n-- date returns the exception's windows and none of the weekly ones. A capacity\n-- of 0 therefore closes the zone for the day without deleting the grid.\n-- ---------------------------------------------------------------------------\ncreate or replace function public.reservations_day_capacity(p_date date)\nreturns table (\n zone_id uuid,\n zone_name text,\n zone_color text,\n start_time time,\n end_time time,\n capacity integer,\n booked integer,\n remaining integer,\n is_exception boolean\n)\nlanguage sql\nstable\nsecurity invoker\nset search_path = public\nas $$\n with tenant as (select app.current_tenant_id() as id),\n -- Which zones had their day overridden. Checked per zone, so a holiday in the\n -- Varanda does not silence the grid of the Salão.\n overridden as (\n select distinct e.zone_id\n from public.plg_reservations_capacity_exceptions e, tenant\n where e.tenant_id = tenant.id\n and p_date between e.starts_on and e.ends_on\n ),\n slots as (\n select e.zone_id, e.start_time, e.end_time, e.capacity, true as is_exception\n from public.plg_reservations_capacity_exceptions e, tenant\n where e.tenant_id = tenant.id\n and p_date between e.starts_on and e.ends_on\n and e.capacity > 0\n -- A whole-day exception still has to draw SOMETHING, so it borrows the\n -- house's widest window rather than a made-up one.\n and coalesce(e.start_time, time '00:00') < coalesce(e.end_time, time '23:59')\n union all\n select r.zone_id, r.start_time, r.end_time, r.capacity, false\n from public.plg_reservations_capacity_rules r, tenant\n where r.tenant_id = tenant.id\n and r.is_active\n -- extract(dow) is 0=Sunday..6=Saturday, the same axis \\`weekdays\\` uses.\n and extract(dow from p_date)::smallint = any (r.weekdays)\n and (r.zone_id is null or r.zone_id not in (select o.zone_id from overridden o where o.zone_id is not null))\n )\n select\n s.zone_id,\n z.name as zone_name,\n z.color as zone_color,\n coalesce(s.start_time, time '00:00') as start_time,\n coalesce(s.end_time, time '23:59') as end_time,\n s.capacity,\n coalesce(b.booked, 0)::integer as booked,\n greatest(s.capacity - coalesce(b.booked, 0), 0)::integer as remaining,\n s.is_exception\n from slots s\n left join public.plg_tables_zones z on z.id = s.zone_id\n left join lateral (\n select sum(v.party_size)::integer as booked\n from public.plg_reservations v, tenant\n where v.tenant_id = tenant.id\n and v.reserved_on = p_date\n and v.status <> 'cancelled'\n and (s.zone_id is null or v.zone_id is not distinct from s.zone_id)\n -- The reservation spends the window it STARTS in.\n and v.start_time >= coalesce(s.start_time, time '00:00')\n and v.start_time < coalesce(s.end_time, time '23:59')\n ) b on true\n order by coalesce(s.start_time, time '00:00'), z.name nulls first;\n$$;\n\ngrant execute on function public.reservations_day_capacity(date) to authenticated;\n`\n\nexport const MIGRATION_002_A_RESERVA_E_DE_UMA_CASA = `-- A reserva é de uma casa.\n--\n-- \\`plg_reservations\\`, \\`plg_reservations_settings\\` e\n-- \\`plg_reservations_capacity_rules\\` não têm \\`unit_id\\`. O V1 tem, nas três:\n-- \\`reservations.unit_id\\`, \\`reservation_settings.unit_id\\`,\n-- \\`reservation_capacity_rules.unit_id\\`.\n--\n-- Uma reserva sem unidade aparece na agenda de todas as filiais, e a regra de\n-- capacidade de uma casa passa a limitar a outra — que é o pior dos dois, porque\n-- recusa cliente em silêncio.\n--\n-- A configuração é o caso mais claro: \\`reservation_settings\\` existe PARA ser\n-- por unidade. Sem a coluna, a casa que abre às 19h e a que abre às 12h têm de\n-- concordar num horário só.\n--\n-- Idempotente.\n\nDO $$\nDECLARE t text;\nBEGIN\n FOREACH t IN ARRAY ARRAY['plg_reservations', 'plg_reservations_settings',\n 'plg_reservations_capacity_rules',\n 'plg_reservations_capacity_exceptions'] LOOP\n IF to_regclass('public.' || t) IS NULL THEN CONTINUE; END IF;\n EXECUTE format('ALTER TABLE public.%I ADD COLUMN IF NOT EXISTS unit_id uuid', t);\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = t || '_unit_fkey') THEN\n EXECUTE format('ALTER TABLE public.%I ADD CONSTRAINT %I FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL', t, t || '_unit_fkey');\n END IF;\n EXECUTE format('COMMENT ON COLUMN public.%I.unit_id IS %L', t,\n 'A casa a que a reserva pertence. V1: reservations.unit_id / reservation_settings.unit_id / reservation_capacity_rules.unit_id (002).');\n END LOOP;\nEND $$;\n\n-- \"quais reservas desta casa, neste dia\" é a consulta do maître, e ela roda a\n-- cada abertura de turno.\nDO $$ BEGIN\n IF EXISTS (SELECT 1 FROM pg_attribute a\n WHERE a.attrelid = 'public.plg_reservations'::regclass\n AND a.attname = 'starts_at' AND NOT a.attisdropped) THEN\n CREATE INDEX IF NOT EXISTS plg_reservations_unit_day_idx\n ON public.plg_reservations USING btree (tenant_id, unit_id, starts_at);\n END IF;\nEND $$;\n\n-- Uma configuração por casa. Duas seriam duas respostas para \"a que horas\n-- abrimos\" — e a tela leria a primeira que viesse.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_reservations_settings_unit_uidx\n ON public.plg_reservations_settings USING btree (tenant_id, unit_id)\n WHERE unit_id IS NOT NULL;\n`\n\nexport const MIGRATION_003_QUEM_CHEGOU_JA_ESTA_NA_CASA_ANTES_DE_SENTAR = `-- Quem chegou já está na casa, antes de sentar.\n--\n-- A reserva tinha cinco estados e nenhum deles respondia à pergunta que o\n-- anfitrião faz a noite inteira: **essa gente já está aqui?** Entre «Confirmada»\n-- (prometeu vir) e «Acomodada» (está na mesa) existe um terceiro fato — o grupo\n-- está de pé na recepção esperando a mesa vagar —, e sem ele o anfitrião marca\n-- a reserva como acomodada só para tirá-la da lista, o que abre uma comanda\n-- numa mesa que ninguém ocupou.\n--\n-- NÃO é porte: o V1 também tem cinco estados\n-- (\\`pending,confirmed,seated,no_show,cancelled\\`, em\n-- \\`20260622220606_*.sql\\`) e nunca teve check-in. É desenho novo, pedido pelo\n-- founder para a tela de controle de entrada, onde reserva e fila de espera\n-- passam a conviver: chegar é o gesto que põe a reserva NA FILA, e sentar\n-- continua sendo o gesto que abre a comanda.\n--\n-- \\`arrived\\` entra ANTES de \\`seated\\` na ordem do CHECK para deixar a leitura do\n-- ciclo óbvia para quem abrir esta tabela depois.\n--\n-- Idempotente.\n\nALTER TABLE public.plg_reservations\n ADD COLUMN IF NOT EXISTS arrived_at timestamptz;\n\n-- O CHECK é reescrito, não somado: um \\`ADD CONSTRAINT\\` com outro nome deixaria\n-- os dois valendo, e o antigo recusaria \\`arrived\\` de qualquer jeito — Postgres\n-- exige que TODOS passem.\nALTER TABLE public.plg_reservations\n DROP CONSTRAINT IF EXISTS plg_reservations_status_check;\nALTER TABLE public.plg_reservations\n ADD CONSTRAINT plg_reservations_status_check\n CHECK (status IN ('pending','confirmed','arrived','seated','no_show','cancelled'));\n\nCOMMENT ON COLUMN public.plg_reservations.arrived_at IS\n 'Quando o grupo se apresentou na recepção. Chegar NÃO abre comanda: quem abre é o seat.';\n\n-- A VISTA precisa carregar a coluna nova, senão o motor existe e a tela não o\n-- enxerga: \\`v_reservations\\` lista coluna a coluna (não é \\`r.*\\`), e toda leitura\n-- do plugin passa por ela. Sem esta parte, \\`arrived_at\\` seria gravado e nunca\n-- lido — o defeito de \"ler de A e escrever em B\", que só aparece na tela.\n--\n-- \\`security_invoker=true\\` é repetido de propósito: um \\`CREATE OR REPLACE\\` sem\n-- ele devolveria a vista para o dono e a RLS deixaria de recortar por conta.\n--\n-- E \\`arrived_at\\` vai no FIM da lista, não ao lado de \\`seated_at\\` onde ele\n-- faria sentido para quem lê: \\`CREATE OR REPLACE VIEW\\` só sabe ACRESCENTAR\n-- coluna no fim — pô-la no meio renomeia as seguintes e o Postgres recusa\n-- («use ALTER VIEW ... RENAME COLUMN»). Trocar por DROP + CREATE derrubaria\n-- as permissões junto.\nCREATE OR REPLACE VIEW public.v_reservations\nWITH (security_invoker = true) AS\n SELECT r.id, r.tenant_id, r.person_id, r.guest_name, r.phone, r.email,\n r.zone_id, z.name AS zone_name, z.color AS zone_color,\n r.table_id, t.number AS table_number,\n r.reserved_on, r.start_time, r.end_time, r.party_size,\n r.occasion, r.notes, r.status, r.order_id, r.seated_at,\n o.reference_number AS order_reference,\n r.created_at, r.updated_at,\n r.arrived_at\n FROM public.plg_reservations r\n LEFT JOIN public.plg_tables_zones z ON z.id = r.zone_id\n LEFT JOIN public.plg_tables_tables t ON t.id = r.table_id\n LEFT JOIN public.orders o ON o.id = r.order_id;\n\nGRANT SELECT ON public.v_reservations TO authenticated;\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"001_reservations\", sql: MIGRATION_001_RESERVATIONS },\n { id: \"002_a_reserva_e_de_uma_casa\", sql: MIGRATION_002_A_RESERVA_E_DE_UMA_CASA },\n { id: \"003_quem_chegou_ja_esta_na_casa_antes_de_sentar\", sql: MIGRATION_003_QUEM_CHEGOU_JA_ESTA_NA_CASA_ANTES_DE_SENTAR },\n]\n"]}
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "tables"
7
7
  ]
8
8
  },
9
- "version": "0.2.31",
9
+ "version": "0.2.33",
10
10
  "description": "Fayz SDK — plugin-reservations plugin",
11
11
  "type": "module",
12
12
  "sideEffects": false,
@@ -19,6 +19,12 @@
19
19
  "import": "./dist/index.js"
20
20
  }
21
21
  },
22
+ "imports": {
23
+ "#migrations/*": {
24
+ "browser": "./dist/migrations/*.browser.js",
25
+ "default": "./dist/migrations/*.js"
26
+ }
27
+ },
22
28
  "files": [
23
29
  "dist"
24
30
  ],
@@ -30,10 +36,10 @@
30
36
  "sonner": "^2.0.7"
31
37
  },
32
38
  "dependencies": {
33
- "@fayz-ai/core": "^0.61.0",
39
+ "@fayz-ai/core": "^0.63.0",
34
40
  "@fayz-ai/sdk": "^0.61.0",
35
- "@fayz-ai/admin": "^0.61.0",
36
- "@fayz-ai/ui": "^0.61.0"
41
+ "@fayz-ai/admin": "^0.63.0",
42
+ "@fayz-ai/ui": "^0.63.0"
37
43
  },
38
44
  "devDependencies": {
39
45
  "@types/react": "^18.3.0",