@fayz-ai/plugin-agenda 0.3.0 → 0.8.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.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/bridges/create-financial-bridge.d.ts.map +1 -1
  3. package/dist/config.d.ts +2 -2
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/data/supabase.d.ts.map +1 -1
  6. package/dist/index.cjs +77 -65
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +77 -65
  9. package/dist/index.js.map +1 -1
  10. package/dist/migrations/index.d.ts +8 -1
  11. package/dist/migrations/index.d.ts.map +1 -1
  12. package/dist/public/createPublicBookingPlugin.d.ts.map +1 -1
  13. package/dist/public/index.cjs +16 -16
  14. package/dist/public/index.cjs.map +1 -1
  15. package/dist/public/index.js +17 -17
  16. package/dist/public/index.js.map +1 -1
  17. package/dist/registries.d.ts.map +1 -1
  18. package/dist/types.d.ts +3 -3
  19. package/dist/types.d.ts.map +1 -1
  20. package/package.json +20 -14
  21. package/src/bridges/create-financial-bridge.ts +6 -8
  22. package/src/components/AppointmentModal.tsx +4 -4
  23. package/src/config.ts +2 -2
  24. package/src/data/supabase.ts +26 -25
  25. package/src/index.ts +1 -1
  26. package/src/integrations/google-calendar/index.ts +1 -1
  27. package/src/integrations/google-calendar/migrations/001_google_calendar.sql +6 -6
  28. package/src/migrations/000b_v_bookings_rename.sql +21 -0
  29. package/src/migrations/000c_dedupe_customer_phones.sql +43 -0
  30. package/src/migrations/001_public_booking.sql +40 -27
  31. package/src/migrations/002_v_bookings_compat.sql +14 -0
  32. package/src/migrations/index.ts +134 -29
  33. package/src/public/createPublicBookingPlugin.ts +23 -20
  34. package/src/public/data.supabase.ts +4 -4
  35. package/src/registries.ts +1 -2
  36. package/src/types.ts +4 -4
package/dist/index.js CHANGED
@@ -1721,7 +1721,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1721
1721
  for (const [k, v] of Object.entries(data)) {
1722
1722
  dbData[k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase())] = v;
1723
1723
  }
1724
- await supabase.schema("saas_core").from("persons").update(dbData).eq("id", clientId);
1724
+ await supabase.from("people").update(dbData).eq("id", clientId);
1725
1725
  setClientData({ ...clientData, ...data });
1726
1726
  setClientEditing(false);
1727
1727
  } catch {
@@ -1881,7 +1881,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1881
1881
  const supabase = getSupabaseClientOptional4();
1882
1882
  const tenantId = getAgendaTenantId2();
1883
1883
  if (!supabase || !tenantId) throw new Error("Not initialized");
1884
- const { data: personRow, error: personError } = await supabase.schema("saas_core").from("persons").insert({
1884
+ const { data: personRow, error: personError } = await supabase.from("people").insert({
1885
1885
  tenant_id: tenantId,
1886
1886
  kind: config.clientKind,
1887
1887
  name: newClientName.trim(),
@@ -1894,7 +1894,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1894
1894
  tenant_id: tenantId
1895
1895
  });
1896
1896
  if (clientError) {
1897
- await supabase.schema("saas_core").from("persons").delete().eq("id", personRow.id);
1897
+ await supabase.from("people").delete().eq("id", personRow.id);
1898
1898
  throw clientError;
1899
1899
  }
1900
1900
  setClientId(personRow.id);
@@ -3474,7 +3474,7 @@ function snakeToCamel(obj) {
3474
3474
  function getClients() {
3475
3475
  const supabase = getSupabaseClientOptional();
3476
3476
  if (!supabase) throw new Error("Supabase not initialized");
3477
- return { core: supabase.schema("saas_core"), pub: supabase };
3477
+ return { core: supabase, pub: supabase };
3478
3478
  }
3479
3479
  function mapBooking(row) {
3480
3480
  return snakeToCamel(row);
@@ -3531,7 +3531,7 @@ async function hydrateCanonicalBookings(core, bookings) {
3531
3531
  { data: orders, error: ordersError },
3532
3532
  { data: items, error: itemsError }
3533
3533
  ] = await Promise.all([
3534
- personIds.length ? core.from("persons").select("id, name, phone, email, avatar_url").in("id", personIds) : Promise.resolve({ data: [], error: null }),
3534
+ personIds.length ? core.from("people").select("id, name, phone, email, avatar_url").in("id", personIds) : Promise.resolve({ data: [], error: null }),
3535
3535
  locationIds.length ? core.from("locations").select("id, name").in("id", locationIds) : Promise.resolve({ data: [], error: null }),
3536
3536
  orderIds.length ? core.from("orders").select("id, total, status").in("id", orderIds) : Promise.resolve({ data: [], error: null }),
3537
3537
  orderIds.length ? core.from("order_items").select("id, order_id, service_id, name, duration_minutes, unit_price, assignee_id, sort_order").in("order_id", orderIds) : Promise.resolve({ data: [], error: null })
@@ -3589,7 +3589,7 @@ async function hydrateCanonicalBookings(core, bookings) {
3589
3589
  });
3590
3590
  }
3591
3591
  async function getCanonicalBookings(core, query) {
3592
- let q = core.from("bookings").select("id, tenant_id, kind, starts_at, ends_at, status, notes, order_id, location_id, metadata, party_id, assignee_id, created_at, updated_at").gte("starts_at", query.dateRange.start).lte("starts_at", query.dateRange.end);
3592
+ let q = core.from("appointments").select("id, tenant_id, kind, starts_at, ends_at, status, notes, order_id, location_id, metadata, party_id, assignee_id, created_at, updated_at").gte("starts_at", query.dateRange.start).lte("starts_at", query.dateRange.end);
3593
3593
  if (query.kind) q = q.eq("kind", query.kind);
3594
3594
  if (query.statuses?.length) q = q.in("status", query.statuses);
3595
3595
  if (query.professionalIds?.length) q = q.in("assignee_id", query.professionalIds);
@@ -3600,10 +3600,10 @@ async function getCanonicalBookings(core, query) {
3600
3600
  }
3601
3601
  async function getCanonicalBookingById(core, id) {
3602
3602
  const columns = "id, tenant_id, kind, starts_at, ends_at, status, notes, order_id, location_id, metadata, party_id, assignee_id, created_at, updated_at";
3603
- let { data, error } = await core.from("bookings").select(columns).eq("id", id).maybeSingle();
3603
+ let { data, error } = await core.from("appointments").select(columns).eq("id", id).maybeSingle();
3604
3604
  if (error) throw error;
3605
3605
  if (!data) {
3606
- const fallback = await core.from("bookings").select(columns).eq("order_id", id).maybeSingle();
3606
+ const fallback = await core.from("appointments").select(columns).eq("order_id", id).maybeSingle();
3607
3607
  if (fallback.error) throw fallback.error;
3608
3608
  data = fallback.data;
3609
3609
  }
@@ -3613,10 +3613,10 @@ async function getCanonicalBookingById(core, id) {
3613
3613
  }
3614
3614
  function createSupabaseAgendaProvider() {
3615
3615
  return {
3616
- // v_bookings is in public schema (PostgREST default)
3616
+ // v_appointments is in public schema (PostgREST default)
3617
3617
  async getBookings(query) {
3618
3618
  const { core, pub } = getClients();
3619
- let q = pub.from("v_bookings").select("*").gte("starts_at", query.dateRange.start).lte("starts_at", query.dateRange.end);
3619
+ let q = pub.from("v_appointments").select("*").gte("starts_at", query.dateRange.start).lte("starts_at", query.dateRange.end);
3620
3620
  if (query.kind) q = q.eq("kind", query.kind);
3621
3621
  if (query.statuses?.length) q = q.in("status", query.statuses);
3622
3622
  if (query.professionalIds?.length) q = q.in("professional_id", query.professionalIds);
@@ -3630,7 +3630,7 @@ function createSupabaseAgendaProvider() {
3630
3630
  },
3631
3631
  async getBookingById(id) {
3632
3632
  const { core, pub } = getClients();
3633
- const { data, error } = await pub.from("v_bookings").select("*").eq("id", id).maybeSingle();
3633
+ const { data, error } = await pub.from("v_appointments").select("*").eq("id", id).maybeSingle();
3634
3634
  if (error) throw error;
3635
3635
  if (data) return mapBooking(data);
3636
3636
  return getCanonicalBookingById(core, id);
@@ -3646,7 +3646,7 @@ function createSupabaseAgendaProvider() {
3646
3646
  const serviceNames = input.services.map((s) => s.name).join(", ");
3647
3647
  let contactName;
3648
3648
  if (input.clientId) {
3649
- const { data: person } = await core.from("persons").select("name").eq("id", input.clientId).single();
3649
+ const { data: person } = await core.from("people").select("name").eq("id", input.clientId).single();
3650
3650
  contactName = person?.name;
3651
3651
  }
3652
3652
  const eventTitle = input.title?.trim() || void 0;
@@ -3668,7 +3668,7 @@ function createSupabaseAgendaProvider() {
3668
3668
  }
3669
3669
  }).select("id").single();
3670
3670
  if (orderErr) throw orderErr;
3671
- const { data: booking, error: bookingErr } = await core.from("bookings").insert({
3671
+ const { data: booking, error: bookingErr } = await core.from("appointments").insert({
3672
3672
  tenant_id: tenantId,
3673
3673
  kind: input.kind ?? "appointment",
3674
3674
  party_id: input.clientId || null,
@@ -3708,7 +3708,7 @@ function createSupabaseAgendaProvider() {
3708
3708
  },
3709
3709
  async updateBooking(id, data) {
3710
3710
  const { core } = getClients();
3711
- const { data: existing, error: existingErr } = await core.from("bookings").select("id, order_id, starts_at, assignee_id, metadata").eq("id", id).single();
3711
+ const { data: existing, error: existingErr } = await core.from("appointments").select("id, order_id, starts_at, assignee_id, metadata").eq("id", id).single();
3712
3712
  if (existingErr) throw existingErr;
3713
3713
  const updates = {};
3714
3714
  if (data.startsAt) updates.starts_at = data.startsAt;
@@ -3733,7 +3733,7 @@ function createSupabaseAgendaProvider() {
3733
3733
  }
3734
3734
  }
3735
3735
  const effectiveAssigneeId = data.professionalId ?? existing?.assignee_id ?? null;
3736
- const { error } = await core.from("bookings").update(updates).eq("id", id);
3736
+ const { error } = await core.from("appointments").update(updates).eq("id", id);
3737
3737
  if (error) throw error;
3738
3738
  const orderId = existing?.order_id;
3739
3739
  if (orderId) {
@@ -3774,21 +3774,21 @@ function createSupabaseAgendaProvider() {
3774
3774
  },
3775
3775
  async deleteBooking(id) {
3776
3776
  const { core, pub } = getClients();
3777
- const { data: booking, error: bookingErr } = await core.from("bookings").select("order_id").eq("id", id).single();
3777
+ const { data: booking, error: bookingErr } = await core.from("appointments").select("order_id").eq("id", id).single();
3778
3778
  if (bookingErr) throw bookingErr;
3779
3779
  const orderId = booking?.order_id;
3780
- const { count, error: movementErr } = orderId ? await pub.from("financial_movements").select("id", { count: "exact", head: true }).eq("invoice_id", orderId) : { count: 0, error: null };
3780
+ const { count, error: movementErr } = orderId ? await pub.from("plg_financial_movements").select("id", { count: "exact", head: true }).eq("invoice_id", orderId) : { count: 0, error: null };
3781
3781
  if (movementErr) throw movementErr;
3782
3782
  if (orderId && (count ?? 0) > 0) {
3783
- await pub.from("financial_movements").update({ status: "cancelled" }).eq("invoice_id", orderId);
3783
+ await pub.from("plg_financial_movements").update({ status: "cancelled" }).eq("invoice_id", orderId);
3784
3784
  await core.from("orders").update({ status: "cancelled" }).eq("id", orderId);
3785
- const { error } = await core.from("bookings").update({ status: "cancelled" }).eq("id", id);
3785
+ const { error } = await core.from("appointments").update({ status: "cancelled" }).eq("id", id);
3786
3786
  if (error) throw error;
3787
3787
  } else {
3788
3788
  if (orderId) {
3789
- await pub.from("financial_movements").delete().eq("invoice_id", orderId);
3789
+ await pub.from("plg_financial_movements").delete().eq("invoice_id", orderId);
3790
3790
  }
3791
- const { error } = await core.from("bookings").delete().eq("id", id);
3791
+ const { error } = await core.from("appointments").delete().eq("id", id);
3792
3792
  if (error) throw error;
3793
3793
  if (orderId) {
3794
3794
  const { error: orderErr } = await core.from("orders").delete().eq("id", orderId);
@@ -3801,7 +3801,7 @@ function createSupabaseAgendaProvider() {
3801
3801
  },
3802
3802
  async checkConflict(params) {
3803
3803
  const { core } = getClients();
3804
- let q = core.from("bookings").select("id", { count: "exact", head: true }).eq("assignee_id", params.assigneeId).eq("kind", "appointment").not("status", "in", '("cancelled","no_show")').not("starts_at", "is", null).lt("starts_at", params.endsAt).gt("ends_at", params.startsAt);
3804
+ let q = core.from("appointments").select("id", { count: "exact", head: true }).eq("assignee_id", params.assigneeId).eq("kind", "appointment").not("status", "in", '("cancelled","no_show")').not("starts_at", "is", null).lt("starts_at", params.endsAt).gt("ends_at", params.startsAt);
3805
3805
  if (params.excludeBookingId) {
3806
3806
  q = q.neq("id", params.excludeBookingId);
3807
3807
  }
@@ -3856,7 +3856,7 @@ function createSupabaseAgendaProvider() {
3856
3856
  },
3857
3857
  async getProfessionals() {
3858
3858
  const { core } = getClients();
3859
- const { data, error } = await core.from("persons").select("id, name, avatar_url, is_active").eq("kind", "staff").eq("is_active", true).order("name");
3859
+ const { data, error } = await core.from("people").select("id, name, avatar_url, is_active").eq("kind", "staff").eq("is_active", true).order("name");
3860
3860
  if (error) throw error;
3861
3861
  return (data ?? []).map((p) => ({
3862
3862
  id: p.id,
@@ -3878,23 +3878,23 @@ function createSupabaseAgendaProvider() {
3878
3878
  const now = (/* @__PURE__ */ new Date()).toISOString();
3879
3879
  const cutoff = /* @__PURE__ */ new Date();
3880
3880
  cutoff.setDate(cutoff.getDate() + daysAhead);
3881
- const { data, error } = await pub.from("v_bookings").select("*").eq("status", "scheduled").gte("starts_at", now).lte("starts_at", cutoff.toISOString()).order("starts_at");
3881
+ const { data, error } = await pub.from("v_appointments").select("*").eq("status", "scheduled").gte("starts_at", now).lte("starts_at", cutoff.toISOString()).order("starts_at");
3882
3882
  if (error) throw error;
3883
3883
  return (data ?? []).map(mapBooking);
3884
3884
  },
3885
3885
  async sendConfirmation(bookingId, channel) {
3886
3886
  const { core } = getClients();
3887
- const { data: booking } = await core.from("bookings").select("metadata").eq("id", bookingId).single();
3887
+ const { data: booking } = await core.from("appointments").select("metadata").eq("id", bookingId).single();
3888
3888
  const metadata = booking?.metadata ?? {};
3889
3889
  const confirmations = metadata.confirmations ?? [];
3890
3890
  confirmations.push({ channel, sentAt: (/* @__PURE__ */ new Date()).toISOString(), status: "sent" });
3891
- await core.from("bookings").update({ metadata: { ...metadata, confirmations } }).eq("id", bookingId);
3891
+ await core.from("appointments").update({ metadata: { ...metadata, confirmations } }).eq("id", bookingId);
3892
3892
  },
3893
3893
  async completeBooking(bookingId) {
3894
3894
  const { core } = getClients();
3895
- const { data: existing, error: existingErr } = await core.from("bookings").select("order_id").eq("id", bookingId).single();
3895
+ const { data: existing, error: existingErr } = await core.from("appointments").select("order_id").eq("id", bookingId).single();
3896
3896
  if (existingErr) throw existingErr;
3897
- const { error } = await core.from("bookings").update({ status: "completed" }).eq("id", bookingId);
3897
+ const { error } = await core.from("appointments").update({ status: "completed" }).eq("id", bookingId);
3898
3898
  if (error) throw error;
3899
3899
  const orderId = existing?.order_id;
3900
3900
  const booking = await this.getBookingById(bookingId);
@@ -4123,7 +4123,6 @@ var holidayEntity = {
4123
4123
  ],
4124
4124
  data: {
4125
4125
  table: "holidays",
4126
- schema: "saas_core",
4127
4126
  tenantScoped: true
4128
4127
  }
4129
4128
  };
@@ -4657,36 +4656,49 @@ var MIGRATION_001_PUBLIC_BOOKING = `-- =========================================
4657
4656
  -- plugin-agenda 001: booking read model + public (anon) booking surface
4658
4657
  --
4659
4658
  -- Ships the objects the agenda providers reference but the spine (packages/db
4660
- -- migrations 001\u2013008) does not define:
4659
+ -- migrations 001\u2013010) does not define:
4660
+ -- \xA70 converted-pool cleanup \u2014 drop the pre-rename v_bookings view / indexes
4661
4661
  -- \xA71 order_items columns the admin provider writes (duration/assignee)
4662
- -- \xA72 public.v_bookings \u2014 admin read model (authenticated, RLS applies)
4662
+ -- \xA72 public.v_appointments \u2014 admin read model (authenticated, RLS applies)
4663
4663
  -- \xA73 public.v_public_services \u2014 anon-readable service catalog
4664
4664
  -- \xA74 public.get_available_slots \u2014 anon RPC (mirrors the mock algorithm)
4665
4665
  -- \xA75 public.create_public_booking \u2014 anon RPC (validated, race-safe write)
4666
4666
  --
4667
+ -- Core v1 lives directly in PUBLIC (no separate core schema): people (was
4668
+ -- persons), appointments (was bookings). This migration is written against public
4669
+ -- names and is idempotent + safe on both fresh and converted pools.
4670
+ --
4667
4671
  -- Anon-access design: public booking pages run with the publishable key and no
4668
4672
  -- session, so the canonical tenant RLS yields nothing. Anon access is granted
4669
4673
  -- ONLY via the column-whitelisted view (\xA73) and SECURITY DEFINER RPCs (\xA74, \xA75)
4670
4674
  -- with tight input validation and hardcoded statuses.
4671
4675
  -- ============================================================================
4672
4676
 
4677
+ -- \xA70 \u2014 converted-pool cleanup. The pre-industry-pool shape shipped a v_bookings
4678
+ -- view and persons_/bookings_-named indexes; drop them so this file re-emits the
4679
+ -- canonical v_appointments view + people_/appointments_ indexes without leaving
4680
+ -- duplicates. All guarded with IF EXISTS \u2192 no-ops on a fresh pool.
4681
+ DROP VIEW IF EXISTS public.v_bookings;
4682
+ DROP INDEX IF EXISTS public.persons_tenant_customer_phone;
4683
+ DROP INDEX IF EXISTS public.bookings_assignee_time;
4684
+
4673
4685
  -- \xA71 \u2014 columns the agenda provider writes but 004_archetypes lacks
4674
- ALTER TABLE saas_core.order_items
4686
+ ALTER TABLE public.order_items
4675
4687
  ADD COLUMN IF NOT EXISTS duration_minutes integer,
4676
- ADD COLUMN IF NOT EXISTS assignee_id uuid REFERENCES saas_core.persons(id) ON DELETE SET NULL;
4688
+ ADD COLUMN IF NOT EXISTS assignee_id uuid REFERENCES public.people(id) ON DELETE SET NULL;
4677
4689
 
4678
4690
  -- Upsert key for public customers (per tenant, by normalized phone)
4679
- CREATE UNIQUE INDEX IF NOT EXISTS persons_tenant_customer_phone
4680
- ON saas_core.persons(tenant_id, phone)
4691
+ CREATE UNIQUE INDEX IF NOT EXISTS people_tenant_customer_phone
4692
+ ON public.people(tenant_id, phone)
4681
4693
  WHERE kind = 'customer' AND phone IS NOT NULL;
4682
4694
 
4683
4695
  -- Slot-conflict scan
4684
- CREATE INDEX IF NOT EXISTS bookings_assignee_time
4685
- ON saas_core.bookings(assignee_id, starts_at, ends_at)
4696
+ CREATE INDEX IF NOT EXISTS appointments_assignee_time
4697
+ ON public.appointments(assignee_id, starts_at, ends_at)
4686
4698
  WHERE status NOT IN ('cancelled', 'no_show');
4687
4699
 
4688
4700
  -- \xA72 \u2014 admin read model (security_invoker \u2192 underlying RLS applies; anon sees 0 rows)
4689
- CREATE OR REPLACE VIEW public.v_bookings
4701
+ CREATE OR REPLACE VIEW public.v_appointments
4690
4702
  WITH (security_invoker = true) AS
4691
4703
  SELECT
4692
4704
  b.id, b.tenant_id, b.kind, b.starts_at, b.ends_at, b.status, b.notes,
@@ -4704,14 +4716,14 @@ SELECT
4704
4716
  o.status AS order_status,
4705
4717
  (EXTRACT(EPOCH FROM (COALESCE(b.ends_at, b.starts_at) - b.starts_at))::int / 60)
4706
4718
  AS total_duration_minutes
4707
- FROM saas_core.bookings b
4708
- LEFT JOIN saas_core.persons pc ON pc.id = b.party_id
4709
- LEFT JOIN saas_core.persons ps ON ps.id = b.assignee_id
4710
- LEFT JOIN saas_core.locations l ON l.id = b.location_id
4711
- LEFT JOIN saas_core.orders o ON o.id = b.order_id;
4719
+ FROM public.appointments b
4720
+ LEFT JOIN public.people pc ON pc.id = b.party_id
4721
+ LEFT JOIN public.people ps ON ps.id = b.assignee_id
4722
+ LEFT JOIN public.locations l ON l.id = b.location_id
4723
+ LEFT JOIN public.orders o ON o.id = b.order_id;
4712
4724
 
4713
- GRANT SELECT ON public.v_bookings TO authenticated, service_role;
4714
- REVOKE SELECT ON public.v_bookings FROM anon;
4725
+ GRANT SELECT ON public.v_appointments TO authenticated, service_role;
4726
+ REVOKE SELECT ON public.v_appointments FROM anon;
4715
4727
 
4716
4728
  -- \xA73 \u2014 anon-readable public catalog (owner-rights view = deliberate, whitelisted)
4717
4729
  CREATE OR REPLACE VIEW public.v_public_services AS
@@ -4720,7 +4732,7 @@ SELECT
4720
4732
  COALESCE(s.duration_minutes, 30) AS duration_minutes,
4721
4733
  s.image_url,
4722
4734
  COALESCE((s.metadata->>'sort_order')::int, 0) AS sort_order
4723
- FROM saas_core.services s
4735
+ FROM public.services s
4724
4736
  WHERE s.is_active AND s.status = 'active';
4725
4737
 
4726
4738
  GRANT SELECT ON public.v_public_services TO anon, authenticated, service_role;
@@ -4734,7 +4746,7 @@ CREATE OR REPLACE FUNCTION public.get_available_slots(
4734
4746
  p_slot_interval integer DEFAULT 30
4735
4747
  ) RETURNS TABLE (slot_start timestamptz, slot_end timestamptz)
4736
4748
  LANGUAGE plpgsql STABLE SECURITY DEFINER
4737
- SET search_path = saas_core, public
4749
+ SET search_path = public
4738
4750
  AS $$
4739
4751
  DECLARE
4740
4752
  v_tz text;
@@ -4782,7 +4794,7 @@ BEGIN
4782
4794
  SELECT DISTINCT c.s_start, c.s_end
4783
4795
  FROM candidate c
4784
4796
  WHERE NOT EXISTS (
4785
- SELECT 1 FROM bookings b
4797
+ SELECT 1 FROM appointments b
4786
4798
  WHERE b.tenant_id = p_tenant_id
4787
4799
  AND b.assignee_id = c.assignee_id
4788
4800
  AND b.status NOT IN ('cancelled', 'no_show')
@@ -4808,7 +4820,7 @@ CREATE OR REPLACE FUNCTION public.create_public_booking(
4808
4820
  p_notes text DEFAULT NULL
4809
4821
  ) RETURNS TABLE (booking_id uuid, starts_at timestamptz, ends_at timestamptz)
4810
4822
  LANGUAGE plpgsql SECURITY DEFINER
4811
- SET search_path = saas_core, public
4823
+ SET search_path = public
4812
4824
  AS $$
4813
4825
  DECLARE
4814
4826
  v_service record;
@@ -4870,7 +4882,7 @@ BEGIN
4870
4882
  PERFORM pg_advisory_xact_lock(hashtextextended(p_tenant_id::text || v_assignee::text, 0));
4871
4883
 
4872
4884
  IF EXISTS (
4873
- SELECT 1 FROM bookings b
4885
+ SELECT 1 FROM appointments b
4874
4886
  WHERE b.tenant_id = p_tenant_id
4875
4887
  AND b.assignee_id = v_assignee
4876
4888
  AND b.status NOT IN ('cancelled', 'no_show')
@@ -4882,8 +4894,8 @@ BEGIN
4882
4894
 
4883
4895
  -- anti-spam: cap open future public bookings per phone per tenant
4884
4896
  IF (
4885
- SELECT count(*) FROM bookings b
4886
- JOIN persons p ON p.id = b.party_id
4897
+ SELECT count(*) FROM appointments b
4898
+ JOIN people p ON p.id = b.party_id
4887
4899
  WHERE b.tenant_id = p_tenant_id
4888
4900
  AND p.phone = v_phone
4889
4901
  AND b.starts_at > now()
@@ -4892,14 +4904,14 @@ BEGIN
4892
4904
  RAISE EXCEPTION 'too many open bookings for this phone';
4893
4905
  END IF;
4894
4906
 
4895
- -- upsert the customer by (tenant, phone) \u2014 matches persons_tenant_customer_phone
4896
- INSERT INTO persons (tenant_id, kind, name, phone, email, metadata)
4907
+ -- upsert the customer by (tenant, phone) \u2014 matches people_tenant_customer_phone
4908
+ INSERT INTO people (tenant_id, kind, name, phone, email, metadata)
4897
4909
  VALUES (p_tenant_id, 'customer', trim(p_name), v_phone, p_email,
4898
4910
  jsonb_build_object('source', 'public_booking'))
4899
4911
  ON CONFLICT (tenant_id, phone) WHERE kind = 'customer' AND phone IS NOT NULL
4900
4912
  DO UPDATE SET
4901
4913
  name = EXCLUDED.name,
4902
- email = COALESCE(EXCLUDED.email, persons.email),
4914
+ email = COALESCE(EXCLUDED.email, people.email),
4903
4915
  updated_at = now()
4904
4916
  RETURNING id INTO v_person_id;
4905
4917
 
@@ -4912,7 +4924,7 @@ BEGIN
4912
4924
  'contactName', trim(p_name)))
4913
4925
  RETURNING id INTO v_order;
4914
4926
 
4915
- INSERT INTO bookings (tenant_id, kind, party_id, assignee_id, order_id,
4927
+ INSERT INTO appointments (tenant_id, kind, party_id, assignee_id, order_id,
4916
4928
  starts_at, ends_at, status, notes, metadata)
4917
4929
  VALUES (p_tenant_id, 'appointment', v_person_id, v_assignee, v_order,
4918
4930
  p_starts_at, v_ends, 'scheduled', v_notes,
@@ -4959,8 +4971,8 @@ function createFinancialBridge(provider2) {
4959
4971
  if (!supabase) return result;
4960
4972
  try {
4961
4973
  const [{ data: orders }, { data: movements }] = await Promise.all([
4962
- supabase.schema("saas_core").from("orders").select("id, kind, status, total, reference_number, metadata").in("id", orderIds),
4963
- supabase.from("financial_movements").select("invoice_id, status, paid_amount").in("invoice_id", orderIds)
4974
+ supabase.from("orders").select("id, kind, status, total, reference_number, metadata").in("id", orderIds),
4975
+ supabase.from("plg_financial_movements").select("invoice_id, status, paid_amount").in("invoice_id", orderIds)
4964
4976
  ]);
4965
4977
  const movementsByOrderId = /* @__PURE__ */ new Map();
4966
4978
  for (const movement of movements ?? []) {
@@ -4988,8 +5000,8 @@ function createFinancialBridge(provider2) {
4988
5000
  const supabase = getSupabaseClientOptional();
4989
5001
  if (!supabase) return null;
4990
5002
  const [orderRes, movRes] = await Promise.all([
4991
- supabase.schema("saas_core").from("orders").select("id, status, total, reference_number, metadata").eq("id", orderId).single(),
4992
- supabase.from("financial_movements").select("*").eq("invoice_id", orderId).order("due_date")
5003
+ supabase.from("orders").select("id, status, total, reference_number, metadata").eq("id", orderId).single(),
5004
+ supabase.from("plg_financial_movements").select("*").eq("invoice_id", orderId).order("due_date")
4993
5005
  ]);
4994
5006
  const order = orderRes.data;
4995
5007
  if (!order) return null;
@@ -4997,7 +5009,7 @@ function createFinancialBridge(provider2) {
4997
5009
  const typeIds = [...new Set((movRes.data ?? []).map((m) => m.payment_method_type_id).filter(Boolean))];
4998
5010
  let typeNames = {};
4999
5011
  if (typeIds.length > 0) {
5000
- const { data: types } = await supabase.from("payment_method_types").select("id, name").in("id", typeIds);
5012
+ const { data: types } = await supabase.from("plg_financial_payment_method_types").select("id, name").in("id", typeIds);
5001
5013
  typeNames = Object.fromEntries((types ?? []).map((t) => [t.id, t.name]));
5002
5014
  }
5003
5015
  const movements = (movRes.data ?? []).map((m) => ({
@@ -5049,17 +5061,17 @@ function createFinancialBridge(provider2) {
5049
5061
  async createCommissionMovement(orderId, options) {
5050
5062
  const supabase = getSupabaseClientOptional();
5051
5063
  if (!supabase) return null;
5052
- const { data: order } = await supabase.schema("saas_core").from("orders").select("id, tenant_id, assignee_id, total").eq("id", orderId).single();
5064
+ const { data: order } = await supabase.from("orders").select("id, tenant_id, assignee_id, total").eq("id", orderId).single();
5053
5065
  if (!order || !order.assignee_id) return null;
5054
5066
  const { data: staff } = await supabase.from("v_staff").select("commission_rate").eq("id", order.assignee_id).single();
5055
5067
  const rate = Number(staff?.commission_rate) || 0;
5056
5068
  const base = Number(order.total) || 0;
5057
5069
  const amount = computeCommissionAmount(base, rate);
5058
5070
  if (amount <= 0) return null;
5059
- const { data: existing } = await supabase.from("financial_movements").select("id").eq("invoice_id", orderId).eq("movement_kind", "commission").limit(1);
5071
+ const { data: existing } = await supabase.from("plg_financial_movements").select("id").eq("invoice_id", orderId).eq("movement_kind", "commission").limit(1);
5060
5072
  if (existing && existing.length > 0) return existing[0].id;
5061
5073
  const dueDate = options?.dueDate ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5062
- const { data: inserted } = await supabase.from("financial_movements").insert({
5074
+ const { data: inserted } = await supabase.from("plg_financial_movements").insert({
5063
5075
  tenant_id: order.tenant_id,
5064
5076
  invoice_id: orderId,
5065
5077
  direction: "debit",
@@ -5496,7 +5508,7 @@ function createAgendaPlugin(options) {
5496
5508
  id: "agenda-001-public-booking",
5497
5509
  version: "0.3.0",
5498
5510
  sql: MIGRATION_001_PUBLIC_BOOKING,
5499
- description: "Booking read model (v_bookings) + anon public booking surface (v_public_services, get_available_slots, create_public_booking) + order_items duration/assignee columns"
5511
+ description: "Booking read model (v_appointments) + anon public booking surface (v_public_services, get_available_slots, create_public_booking) + order_items duration/assignee columns"
5500
5512
  }
5501
5513
  ],
5502
5514
  locales: agendaLocales