-- ============================================================================
-- RAPPORT — Initial Migration (Draft v1)
-- ============================================================================
-- Zweck: Übersetzt das localStorage-Schema (studio_data_v1) nach Postgres,
--        Multi-Tenant von Anfang an (jede Tabelle hat studio_id),
--        Auth via Supabase (auth.users), Files via Supabase Storage.
--
-- Stand: Draft auf Basis von constants.js + View-Audit (Projects, Quotes,
--        Invoices, Expenses, Employees, Persons, Protocols, Time).
--        Noch NICHT auf eine echte Supabase-Instanz angewendet.
--
-- Konventionen:
--   - snake_case für Spalten, camelCase-Felder aus dem Frontend werden
--     beim Adapter-Mapping übersetzt (z.B. ferienWochen → ferien_wochen).
--   - status/kind/mode als CHECK-Constraints (statt Postgres-ENUM), weil
--     leichter zu erweitern.
--   - Volatile / formverteilte Strukturen als JSONB (z.B. quote.sia,
--     protocol.participants), klassisch normalisiert wo joinable.
--   - RLS-Policies am Ende; bis dahin sind Tabellen offen — für Self-Hosted
--     Supabase muss `alter table … enable row level security` aktiv sein.
-- ============================================================================

-- ─── EXTENSIONS ────────────────────────────────────────────────────────────
create extension if not exists "pgcrypto";   -- gen_random_uuid()
create extension if not exists "citext";     -- case-insensitive email

-- ════════════════════════════════════════════════════════════════════════════
-- TENANT-LAYER: Studios + Membership
-- ════════════════════════════════════════════════════════════════════════════

create table studios (
  id          uuid primary key default gen_random_uuid(),
  name        text not null,
  slug        text unique not null,
  created_at  timestamptz not null default now()
);

-- Verlinkung User (Supabase auth.users) ↔ Studio mit Rolle pro Studio
create table studio_members (
  studio_id      uuid not null references studios(id) on delete cascade,
  user_id        uuid not null references auth.users(id) on delete cascade,
  app_role_id    text,                                -- FK später, app_roles ist studio-spezifisch
  active         boolean not null default true,
  joined_at      timestamptz not null default now(),
  primary key (studio_id, user_id)
);

-- Profil-Erweiterung zu auth.users (Anzeigename etc.) — global, nicht studio-spezifisch
create table profiles (
  id            uuid primary key references auth.users(id) on delete cascade,
  username      citext unique not null,
  display_name  text not null,
  created_at    timestamptz not null default now()
);

-- ════════════════════════════════════════════════════════════════════════════
-- STUDIO-WEITE STAMMDATEN (Settings, Rollen, Templates)
-- ════════════════════════════════════════════════════════════════════════════

-- Eine Zeile pro Studio (ersetzt das Singleton-`settings`-Objekt)
create table studio_settings (
  studio_id              uuid primary key references studios(id) on delete cascade,
  name                   text not null default 'Mein Studio',
  address                text,
  street                 text, zip text, city text, country text default 'CH',
  email                  citext, phone text,
  iban                   text,
  iban_type              text default 'qr' check (iban_type in ('qr','normal')),
  mwst_nr                text,
  mwst_rate              numeric(5,2) default 8.1,
  default_hourly_rate    numeric(10,2) default 120,
  default_wochenstunden  numeric(5,2) default 35,
  default_ferien_wochen  numeric(4,1) default 5,

  -- Volatile / formverteilte Konfig als JSONB:
  formats                jsonb not null default '{}'::jsonb,  -- projectNumberFormat, invoiceNumberFormat, …
  page_margins           jsonb not null default '{}'::jsonb,  -- pageMarginTop/Bottom/Left/Right
  ui                     jsonb not null default '{}'::jsonb,  -- autoPrint, logoSize, qrNewPage, pdfNameFormat
  protokoll_type_abbr    jsonb not null default '{}'::jsonb,  -- {"Bausitzung":"BS", …}

  closed_months          int[] default '{}',
  block_mai_tag          boolean default true,
  setup_completed        boolean default false,
  logo_url               text,                                -- Supabase Storage Pfad
  updated_at             timestamptz not null default now()
);

-- Rate-Profile (PL/TS/BL/AS) — pro Studio, weil Stundensätze unterschiedlich
create table studio_roles (
  studio_id  uuid not null references studios(id) on delete cascade,
  id         text not null,                          -- "PL", "TS", … (innerhalb Studio eindeutig)
  label      text not null,
  rate       numeric(10,2) not null default 0,
  sort       int default 0,
  primary key (studio_id, id)
);

-- App-Rollen (Permissions/Dashboard-Zuordnung) — pro Studio
create table app_roles (
  studio_id              uuid not null references studios(id) on delete cascade,
  id                     text not null,              -- "r-admin", "r-projektleiter", …
  name                   text not null,
  permissions            text[],                     -- null = alle; sonst ["dashboard","projects",…]
  dashboard_template_id  text,
  primary key (studio_id, id)
);

create table dashboard_templates (
  studio_id  uuid not null references studios(id) on delete cascade,
  id         text not null,                          -- "tpl-admin", …
  name       text not null,
  is_public  boolean default true,
  layout     jsonb not null,                         -- Row-/Widget-Struktur
  primary key (studio_id, id)
);

alter table app_roles
  add constraint app_roles_dashboard_fk
  foreign key (studio_id, dashboard_template_id)
  references dashboard_templates(studio_id, id) on delete set null;

alter table studio_members
  add constraint studio_members_role_fk
  foreign key (studio_id, app_role_id)
  references app_roles(studio_id, id) on delete set null;

-- Absenz-Typen (Krankheit/Unfall/…) — pro Studio
create table absence_types (
  studio_id  uuid not null references studios(id) on delete cascade,
  id         text not null,                          -- "krankheit", "unfall", …
  label      text not null,
  color      text,
  primary key (studio_id, id)
);

-- Brieftemplates ("Offerte", "Zahlungserinnerung") — pro Studio
create table letter_templates (
  studio_id  uuid not null references studios(id) on delete cascade,
  id         text not null,                          -- "offer", "reminder"
  name       text not null,
  body       text not null,
  primary key (studio_id, id)
);

-- Feiertage — pro Studio (kantonal-spezifisch)
create table holidays (
  studio_id  uuid not null references studios(id) on delete cascade,
  date       date not null,
  label      text not null,
  half_day   boolean default false,
  primary key (studio_id, date)
);

-- ════════════════════════════════════════════════════════════════════════════
-- PERSONEN (Kunden + Partner vereint, seit v0.5)
-- ────────────────────────────────────────────────────────────────────────────
-- Zwei Modi pro Person:
--   a) Studio-lokal: studio_id IS NOT NULL  → klassisch pro Studio
--   b) Geteilt:      studio_id IS NULL      → lebt in person_studio_links,
--                                             sichtbar in allen verlinkten Studios
-- Default ist (a). Umstellung auf (b) ist eine User-Aktion in Stammdaten.
-- ════════════════════════════════════════════════════════════════════════════

create table persons (
  id                uuid primary key default gen_random_uuid(),
  studio_id         uuid references studios(id) on delete cascade,  -- NULL = geteilt
  name              text not null,
  person_type       text,                            -- Planer-Typ: "Elektroplaner", "HLKSE-Planer", …
  is_auftraggeber   boolean not null default false,
  is_partner        boolean not null default false,
  street            text, zip text, city text, country text default 'CH',
  email             citext, phone text, website text,
  note              text,
  contacts          jsonb not null default '[]'::jsonb,  -- [{id,name,position,phone,email}]
  honorar_offers    jsonb not null default '[]'::jsonb,
  created_at        timestamptz not null default now()
  -- updated_at + created_by/updated_by werden im Audit-Block am Ende ergänzt
);
create index on persons (studio_id);

-- Sichtbarkeit für geteilte Personen (studio_id IS NULL).
-- Nur relevant, wenn ein User die Person später "globalisiert".
create table person_studio_links (
  person_id  uuid not null references persons(id) on delete cascade,
  studio_id  uuid not null references studios(id) on delete cascade,
  primary_studio boolean default false,             -- in welchem Studio wurde sie ursprünglich angelegt
  linked_at  timestamptz not null default now(),
  primary key (person_id, studio_id)
);
create index on person_studio_links (studio_id);

-- ════════════════════════════════════════════════════════════════════════════
-- PROJEKTE
-- ════════════════════════════════════════════════════════════════════════════

create table projects (
  id                 uuid primary key default gen_random_uuid(),
  studio_id          uuid not null references studios(id) on delete cascade,
  number             text,                            -- "2025/03" via Format-Template
  name               text not null,
  client_id          uuid references persons(id) on delete set null,
  category           text,                            -- "Wettbewerb", "Direktauftrag", … (siehe constants.PROJECT_TYPES)
  billing_type       text check (billing_type in ('stundensatz','pauschal')),
  hourly_rate        numeric(10,2),
  budget             numeric(14,2),
  budget_hours       numeric(10,2),
  status             text default 'aktiv' check (status in ('aktiv','pausiert','abgeschlossen')),
  description        text,
  start_date         date,

  -- Aktivierte SIA-Phasen als Array (z.B. ["31","32","41","51","52","53"])
  enabled_phases     text[] not null default '{}',

  -- Komplexe / formverteilte Strukturen als JSONB (siehe View-Audit):
  positions          jsonb not null default '[]'::jsonb,    -- [{phaseId, …}]
  custom_phases      jsonb not null default '[]'::jsonb,    -- [{id, label}]
  project_contacts   jsonb not null default '[]'::jsonb,    -- [{contactId, personIds[]}]
  internal_members   jsonb not null default '[]'::jsonb,    -- [{userId, role, …}]

  created_at         timestamptz not null default now(),
  unique (studio_id, number)
);
create index on projects (studio_id, status);
create index on projects (client_id);

-- Projekt ↔ Offerten (mit Rolle aus linkedQuotes-Eintrag)
create table project_quote_links (
  project_id  uuid not null references projects(id) on delete cascade,
  quote_id    uuid not null,                          -- FK nach quotes (siehe unten)
  role        text,
  primary key (project_id, quote_id)
);

-- ════════════════════════════════════════════════════════════════════════════
-- OFFERTEN
-- ════════════════════════════════════════════════════════════════════════════

create table quotes (
  id              uuid primary key default gen_random_uuid(),
  studio_id       uuid not null references studios(id) on delete cascade,
  number          text,
  client_id       uuid references persons(id) on delete set null,
  project_id      uuid references projects(id) on delete set null,
  project_name    text,                               -- snapshot wenn Projekt nicht (mehr) verlinkt
  date            date,
  valid_until     date,
  mode            text check (mode in ('sia','manual','free')),
  mwst            boolean default true,
  notes           text,
  status          text default 'entwurf' check (status in
                  ('entwurf','gesendet','angenommen','abgelehnt','abgelaufen')),

  -- Drei Kalkulations-Pfade — je nach mode wird einer befüllt:
  sia_config      jsonb,                              -- {baukosten, schwierigkeit, stundenansatz, phases[]}
  manual_phases   jsonb,                              -- [{phaseId, …}]
  free_items      jsonb,                              -- [{id, desc, qty, price}]
  quote_roles     jsonb,                              -- [{id, label, rate}] — Rate-Overrides pro Offerte

  created_at      timestamptz not null default now(),
  unique (studio_id, number)
);
create index on quotes (studio_id, status);
create index on quotes (project_id);

alter table project_quote_links
  add constraint project_quote_links_quote_fk
  foreign key (quote_id) references quotes(id) on delete cascade;

-- ════════════════════════════════════════════════════════════════════════════
-- RECHNUNGEN
-- ════════════════════════════════════════════════════════════════════════════

create table invoices (
  id               uuid primary key default gen_random_uuid(),
  studio_id        uuid not null references studios(id) on delete cascade,
  number           text,
  client_id        uuid references persons(id) on delete set null,
  contact_id       uuid,                              -- optional: Kontaktperson aus persons.contacts (JSONB)
  project_id       uuid references projects(id) on delete set null,
  quote_id         uuid references quotes(id) on delete set null,

  date             date,
  due_date         date,
  sent_date        date,
  paid_date        date,                              -- gesetzt wenn status = 'bezahlt'

  items            jsonb not null default '[]'::jsonb,  -- [{id,desc,qty,price,discount}]
  mwst             boolean default true,
  mwst_rate        numeric(5,2),
  notes            text,

  status           text default 'entwurf' check (status in
                   ('entwurf','gesendet','bezahlt','überfällig')),

  invoice_kind     text check (invoice_kind in ('akonto','teilrechnung','schluss','voll')),

  discount_type    text default 'none' check (discount_type in ('none','percent','amount')),
  discount_value   numeric(14,2) default 0,
  discount_label   text,

  -- Welche Zeit-/Spesen-Einträge in diese Rechnung gehen
  entry_selections jsonb not null default '{}'::jsonb,

  qr_reference     text,                              -- 27-stellige Schweizer QR-Ref

  created_at       timestamptz not null default now(),
  unique (studio_id, number)
);
create index on invoices (studio_id, status);
create index on invoices (project_id);
create index on invoices (client_id);

-- Mahnungen-Historie. Verifiziert: Frontend speichert inv.reminders[] mit
-- {nr, date, sentDate, daysPast}. Wird hier 1:1 abgebildet, damit jeder mit
-- Buchhaltungs-Zugriff den letzten Mahnungs-Stand sieht (z.B. "3× Mahnung,
-- zuletzt 15.03.2025"). UI-Hinweis-Box ("schick eine Mahnung") bleibt
-- localStorage — wie Dark Mode, per-Device-Setting.
create table invoice_reminders (
  id          uuid primary key default gen_random_uuid(),
  invoice_id  uuid not null references invoices(id) on delete cascade,
  nr          int not null check (nr between 1 and 9),  -- 1. Erinnerung, 2./3. Mahnung
  date        date not null,                            -- Erstell-/Druckdatum
  sent_date   date,                                     -- editierbar im Mahnungs-Modal
  days_past   int,                                      -- Snapshot Tage überfällig zum Zeitpunkt
  note        text,
  created_at  timestamptz not null default now()
);
create index on invoice_reminders (invoice_id, nr);

-- ════════════════════════════════════════════════════════════════════════════
-- ZEITERFASSUNG
-- ════════════════════════════════════════════════════════════════════════════

create table time_entries (
  id           uuid primary key default gen_random_uuid(),
  studio_id    uuid not null references studios(id) on delete cascade,
  employee_id  uuid,                                  -- FK nach employees (siehe unten)
  project_id   uuid references projects(id) on delete set null,
  phase_id     text,                                  -- SIA-Phase z.B. "32"
  position_id  text,                                  -- optional, sub-position
  date         date not null,
  minutes      int not null,
  start_time   time,
  end_time     time,
  description  text,
  created_at   timestamptz not null default now()
);
create index on time_entries (studio_id, date);
create index on time_entries (employee_id, date);
create index on time_entries (project_id, date);

-- ════════════════════════════════════════════════════════════════════════════
-- SPESEN & AUSGABEN
-- ════════════════════════════════════════════════════════════════════════════

create table expenses (              -- Mitarbeiterspesen (zur Rückerstattung)
  id              uuid primary key default gen_random_uuid(),
  studio_id       uuid not null references studios(id) on delete cascade,
  employee_id     uuid,                              -- FK nach employees
  project_id      uuid references projects(id) on delete set null,
  date            date not null,
  category        text,                              -- aus studio_settings.expense_categories
  description     text,
  amount          numeric(14,2) not null,
  mwst_rate       numeric(5,2),
  incl_mwst       boolean default true,
  status          text default 'offen' check (status in
                  ('offen','genehmigt','auf nächsten Lohn','ausbezahlt')),
  receipt_url     text,                              -- Supabase Storage Pfad (statt Base64)
  receipt_name    text,
  lohn_entry_id   uuid,                              -- FK nach payroll_entries (gesetzt bei ausbezahlt)
  created_at      timestamptz not null default now()
);
create index on expenses (studio_id, status);
create index on expenses (employee_id, date);

create table internal_expenses (     -- Studio-Ausgaben (Miete, Software, …)
  id                  uuid primary key default gen_random_uuid(),
  studio_id           uuid not null references studios(id) on delete cascade,
  date                date not null,
  category            text,
  description         text,
  amount              numeric(14,2) not null,
  mwst_rate           numeric(5,2),
  incl_mwst           boolean default true,
  recurring           boolean default false,
  recurring_interval  text check (recurring_interval in ('monatlich','quartalsweise','jährlich')),
  receipt_url         text,
  created_at          timestamptz not null default now()
);
create index on internal_expenses (studio_id, date);

-- ════════════════════════════════════════════════════════════════════════════
-- MITARBEITER (HR)
-- ════════════════════════════════════════════════════════════════════════════

create table employees (
  id                        uuid primary key default gen_random_uuid(),
  studio_id                 uuid not null references studios(id) on delete cascade,
  name                      text not null,
  personal_nr               text,
  pensum                    int check (pensum between 0 and 100),  -- in Prozent
  wochenstunden             numeric(5,2),
  ferien_wochen             numeric(4,1),
  pk_ag_satz                numeric(5,2),                          -- Pensionskasse-AG-Satz
  ferien_uebertrag_vorjahr  jsonb not null default '{}'::jsonb,    -- {year: days}

  -- Brücke zu App-Login (optional — nicht jeder Mitarbeiter braucht Cloud-Account)
  app_user_id               uuid references profiles(id) on delete set null,

  active                    boolean default true,
  created_at                timestamptz not null default now()
);
create index on employees (studio_id, active);

-- Jetzt die zirkulären FKs nachreichen:
alter table time_entries
  add constraint time_entries_employee_fk
  foreign key (employee_id) references employees(id) on delete set null;

alter table expenses
  add constraint expenses_employee_fk
  foreign key (employee_id) references employees(id) on delete set null;

create table absences (
  id           uuid primary key default gen_random_uuid(),
  studio_id    uuid not null references studios(id) on delete cascade,
  employee_id  uuid not null references employees(id) on delete cascade,
  type_id      text,                                  -- FK auf absence_types(studio_id, id)
  date         date,                                  -- Einzeltag-Variante
  date_from    date,                                  -- Mehrtages-Variante
  date_to      date,
  start_time   time,
  end_time     time,
  hours        int,
  minutes      int,
  note         text,
  status       text default 'pending' check (status in ('pending','approved','rejected')),
  created_at   timestamptz not null default now(),
  constraint absences_type_fk foreign key (studio_id, type_id)
    references absence_types(studio_id, id) on delete set null
);
create index on absences (employee_id, date);

create table vacation_entries (             -- ferienEntries
  id            uuid primary key default gen_random_uuid(),
  studio_id     uuid not null references studios(id) on delete cascade,
  employee_id   uuid not null references employees(id) on delete cascade,
  date_from     date not null,
  date_to       date not null,
  note          text,
  status        text default 'pending' check (status in ('pending','approved','rejected')),
  original_data jsonb,                                -- für pending-Anträge: Snapshot der Eingabe
  created_at    timestamptz not null default now()
);
create index on vacation_entries (employee_id, date_from);

create table payroll_entries (              -- lohnEntries
  id            uuid primary key default gen_random_uuid(),
  studio_id     uuid not null references studios(id) on delete cascade,
  employee_id   uuid not null references employees(id) on delete cascade,
  year          int not null,
  month         int not null check (month between 1 and 12),
  brutto        numeric(14,2),
  ahv           numeric(14,2),
  alv           numeric(14,2),
  bvg           numeric(14,2),
  nbu           numeric(14,2),
  ktg           numeric(14,2),
  quellensteuer numeric(14,2),
  spesen        numeric(14,2),
  bonus         numeric(14,2),
  netto         numeric(14,2),
  status        text default 'entwurf',
  paid_at       date,
  created_at    timestamptz not null default now(),
  unique (employee_id, year, month)
);

-- expenses.lohn_entry_id zeigt jetzt auf existierende Tabelle:
alter table expenses
  add constraint expenses_lohn_entry_fk
  foreign key (lohn_entry_id) references payroll_entries(id) on delete set null;

create table overtime_closings (            -- uberstundenAbschluss
  id           uuid primary key default gen_random_uuid(),
  studio_id    uuid not null references studios(id) on delete cascade,
  employee_id  uuid not null references employees(id) on delete cascade,
  date         date not null,
  saldo_hours  numeric(8,2),
  created_at   timestamptz not null default now()
);

-- ════════════════════════════════════════════════════════════════════════════
-- DOKUMENTE: Protokolle, Lieferscheine
-- ════════════════════════════════════════════════════════════════════════════

create table protocols (
  id              uuid primary key default gen_random_uuid(),
  studio_id       uuid not null references studios(id) on delete cascade,
  number          text,                                -- "2025-BS-01"
  type            text check (type in (
                    'Bausitzung','Planungssitzung','Baubesprechung','Jour fixe',
                    'Interne Sitzung','Kundensitzung','Abnahme','Sonstiges')),
  location        text,
  project_id      uuid references projects(id) on delete set null,
  project_manual  text,                                -- freie Eingabe falls kein verlinktes Projekt
  participants    jsonb not null default '[]'::jsonb,  -- [{id,name,role,source,status}]
  traktanden      jsonb not null default '[]'::jsonb,  -- [{id,nr,title,items:[{kind,text,…}]}]
  next_date       date,
  verteiler       text,
  created_at      timestamptz not null default now(),
  unique (studio_id, number)
);
create index on protocols (studio_id, type);
create index on protocols (project_id);

create table delivery_notes (
  id          uuid primary key default gen_random_uuid(),
  studio_id   uuid not null references studios(id) on delete cascade,
  number      text,
  date        date,
  client_id   uuid references persons(id) on delete set null,
  project_id  uuid references projects(id) on delete set null,
  notes       text,
  created_at  timestamptz not null default now(),
  unique (studio_id, number)
);

create table delivery_note_items (
  id                uuid primary key default gen_random_uuid(),
  delivery_note_id  uuid not null references delivery_notes(id) on delete cascade,
  sort              int default 0,
  description       text,
  qty               numeric(12,3) default 1,
  unit              text default 'Stk.',
  note              text
);
create index on delivery_note_items (delivery_note_id);

-- ════════════════════════════════════════════════════════════════════════════
-- PINNWAND (Blog-Posts)
-- ════════════════════════════════════════════════════════════════════════════

create table blog_posts (
  id          uuid primary key default gen_random_uuid(),
  studio_id   uuid not null references studios(id) on delete cascade,
  author_id   uuid references profiles(id) on delete set null,
  category    text,
  title       text,
  body        text,
  pinned      boolean default false,
  created_at  timestamptz not null default now()
);
create index on blog_posts (studio_id, created_at desc);

-- ════════════════════════════════════════════════════════════════════════════
-- AUDIT-SPALTEN: created_by / updated_by + Auto-Update via Trigger
-- ----------------------------------------------------------------------------
-- Bewusst nur auf "Daten"-Tabellen, nicht auf Stammdaten-Konfig (studio_roles,
-- app_roles, absence_types, holidays, letter_templates, dashboard_templates) —
-- die ändern sich selten, der Audit-Overhead lohnt nicht. Für Stammdaten
-- reicht `updated_at`.
--
-- Beim Insert/Update wird updated_at = now(), updated_by = auth.uid() gesetzt.
-- created_by wird beim Insert einmalig gesetzt und nie überschrieben.
-- ════════════════════════════════════════════════════════════════════════════

do $$
declare t text;
begin
  for t in
    select unnest(array[
      'studios',
      'persons','projects','quotes','invoices','invoice_reminders',
      'time_entries','expenses','internal_expenses',
      'employees','absences','vacation_entries','payroll_entries','overtime_closings',
      'protocols','delivery_notes','delivery_note_items','blog_posts'
    ])
  loop
    execute format('alter table %I add column created_by uuid references auth.users(id);', t);
    execute format('alter table %I add column updated_by uuid references auth.users(id);', t);
    execute format('alter table %I add column updated_at timestamptz not null default now();', t);
  end loop;
end$$;

create or replace function set_audit_fields() returns trigger
language plpgsql as $$
begin
  if tg_op = 'INSERT' then
    new.created_by := coalesce(new.created_by, auth.uid());
    new.updated_by := coalesce(new.updated_by, auth.uid());
    new.updated_at := now();
  elsif tg_op = 'UPDATE' then
    new.created_by := old.created_by;   -- nie ändern
    new.updated_by := auth.uid();
    new.updated_at := now();
  end if;
  return new;
end$$;

do $$
declare t text;
begin
  for t in
    select unnest(array[
      'studios',
      'persons','projects','quotes','invoices','invoice_reminders',
      'time_entries','expenses','internal_expenses',
      'employees','absences','vacation_entries','payroll_entries','overtime_closings',
      'protocols','delivery_notes','delivery_note_items','blog_posts'
    ])
  loop
    execute format(
      'create trigger %I_audit before insert or update on %I
         for each row execute function set_audit_fields();',
      t, t
    );
  end loop;
end$$;

-- ════════════════════════════════════════════════════════════════════════════
-- ROW-LEVEL-SECURITY (RLS)
-- ============================================================================
-- Globale Policy: "User darf Zeile lesen/schreiben, wenn er Mitglied im
-- Studio (studio_id) der Zeile ist." Funktioniert für alle Tabellen mit
-- studio_id-Spalte. studios/studio_members/persons brauchen eigene Policies.
-- ════════════════════════════════════════════════════════════════════════════

-- Helper: prüft Mitgliedschaft des aktuellen Users in einem Studio
create or replace function is_studio_member(s_id uuid) returns boolean
language sql stable security definer as $$
  select exists (
    select 1 from studio_members
    where studio_id = s_id
      and user_id = auth.uid()
      and active = true
  );
$$;

-- Alle studio-bezogenen Tabellen
do $$
declare t text;
begin
  for t in
    select unnest(array[
      'studio_settings','studio_roles','app_roles','dashboard_templates',
      'absence_types','letter_templates','holidays',
      'persons','projects','project_quote_links','quotes',
      'invoices','invoice_reminders','time_entries',
      'expenses','internal_expenses',
      'employees','absences','vacation_entries','payroll_entries','overtime_closings',
      'protocols','delivery_notes','delivery_note_items','blog_posts'
    ])
  loop
    execute format('alter table %I enable row level security;', t);
  end loop;
end$$;

-- Tabellen mit direkter studio_id-Spalte: einheitliche Policy
-- (persons ist hier ausgenommen, weil studio_id NULL sein darf — siehe unten)
do $$
declare t text;
begin
  for t in
    select unnest(array[
      'studio_settings','studio_roles','app_roles','dashboard_templates',
      'absence_types','letter_templates','holidays',
      'projects','quotes',
      'invoices','time_entries',
      'expenses','internal_expenses',
      'employees','absences','vacation_entries','payroll_entries','overtime_closings',
      'protocols','delivery_notes','blog_posts'
    ])
  loop
    execute format($f$
      create policy %I_member_access on %I
        for all
        using (is_studio_member(studio_id))
        with check (is_studio_member(studio_id));
    $f$, t, t);
  end loop;
end$$;

-- persons: zwei Sichtbarkeitspfade (studio-lokal ODER via person_studio_links)
alter table person_studio_links enable row level security;
create policy persons_member_access on persons
  for all
  using (
    (studio_id is not null and is_studio_member(studio_id))
    or exists (
      select 1 from person_studio_links psl
      where psl.person_id = persons.id
        and is_studio_member(psl.studio_id)
    )
  )
  with check (
    (studio_id is not null and is_studio_member(studio_id))
    or exists (
      select 1 from person_studio_links psl
      where psl.person_id = persons.id
        and is_studio_member(psl.studio_id)
    )
  );

create policy person_studio_links_member_access on person_studio_links
  for all
  using (is_studio_member(studio_id))
  with check (is_studio_member(studio_id));

-- Sub-Tabellen ohne eigene studio_id: Zugriff via Parent
create policy project_quote_links_member_access on project_quote_links
  for all
  using (
    exists (select 1 from projects p
            where p.id = project_quote_links.project_id
              and is_studio_member(p.studio_id))
  );

create policy invoice_reminders_member_access on invoice_reminders
  for all
  using (
    exists (select 1 from invoices i
            where i.id = invoice_reminders.invoice_id
              and is_studio_member(i.studio_id))
  );

create policy delivery_note_items_member_access on delivery_note_items
  for all
  using (
    exists (select 1 from delivery_notes dn
            where dn.id = delivery_note_items.delivery_note_id
              and is_studio_member(dn.studio_id))
  );

-- studios: User sieht nur Studios, in denen er Mitglied ist
alter table studios enable row level security;
create policy studios_member_access on studios
  for select
  using (is_studio_member(id));

-- studio_members: User sieht eigene Mitgliedschaften
alter table studio_members enable row level security;
create policy studio_members_self_access on studio_members
  for select
  using (user_id = auth.uid() or is_studio_member(studio_id));

-- profiles: Jeder authentifizierte User sieht alle Profile (Anzeigenamen)
alter table profiles enable row level security;
create policy profiles_authenticated_read on profiles
  for select
  using (auth.role() = 'authenticated');
create policy profiles_self_write on profiles
  for update using (id = auth.uid());

-- ════════════════════════════════════════════════════════════════════════════
-- Nächste Migrations:
--   0002_storage.sql        — Supabase Storage Buckets (receipts, logos) + Policies
--   0003_seed_defaults.sql  — Pro neues Studio die Default-Rollen, Absenz-Typen,
--                              Letter-Templates, Dashboard-Templates anlegen
-- ════════════════════════════════════════════════════════════════════════════
