Files
RAPPORT/supabase/migrations/0003_seed_defaults.sql
T
karim 27b1057cd4 Release 0.8.0: Cloud-Variante (Supabase, Multi-Studio, Realtime, Web-Deploy)
Rapport ist jetzt dual: lokal (wie bisher) ODER Cloud auf eigenem Supabase-Server.
Beide Modi haben dieselben Funktionen, Cloud zusätzlich Multi-User + Live-Sync.

Storage-Architektur
- src/storage/adapter.js: einheitliche Promise-API, LocalStorage- und SupabaseAdapter
- src/storage/migrations.js: applyMigrations als reine Funktion, für beide Backends
- Konfig-driven: VITE_SUPABASE_URL im Production-Build → automatisch Cloud-Modus

Postgres-Schema (supabase/migrations/0001–0010)
- 29 Tabellen, multi-tenant via studio_id + Row-Level-Security
- Audit-Spalten (created_by/updated_by/at) + Trigger
- Seed-Trigger pro neuem Studio (Rollen, Templates, Absenz-Typen)
- Realtime-Publication für Live-Sync
- RPCs: ensure_profile, create_studio_with_admin (mit Personen-Sharing),
  list_studios, load_persons_for_studio, attach_user_to_studio

Cloud-Features (App)
- BackendChoice.jsx als Erst-Screen «Lokal oder Cloud»
- CloudSetup.jsx: 3-Schritt-Wizard für Erst-Einrichtung
- Login.jsx: Modus-Switcher + Server-URL + Studio-Dropdown + Passwort-Vergessen
- ResetPassword.jsx: empfängt Mail-Link-Klick via PASSWORD_RECOVERY-Event
- Realtime: Änderungen zwischen Browsern ohne Reload sichtbar
- Settings → System: Cloud-Verbindung, Studio-Switcher, weiteres Studio anlegen
- Settings → Team: Mitarbeiter via Email einladen (Admin-Aktion)
- Personen-Sharing: bei neuem Studio Personen aus anderen Studios übernehmen
- Reload-Resume: studio_id in sessionStorage, kein erneuter Login nötig

Web-Deploy
- deploy/docker-compose.yml + nginx.conf: dist/ via nginx-Container, Port 8080
- .env.production.example: Build-time Cloud-URL
- DEPLOY.md: Anleitung für LAN-only und extern via Nginx Proxy Manager

Doku
- README.md: Cloud-Variante prominent erklärt
- ARCHITECTURE.md: Storage-Adapter, Migrations, neue Views in Risiko-Tabelle
- DEPLOY.md: Schritt-für-Schritt für Mac Mini + NPM

Version-Bump auf 0.8.0 in package.json, src-tauri/tauri.conf.json, Cargo.toml.
Changelog-Entry im App.jsx-Modal (Karim sieht ihn beim ersten Start).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 19:08:00 +02:00

117 lines
5.1 KiB
PL/PgSQL

-- ============================================================================
-- RAPPORT — Default-Stammdaten pro neuem Studio
-- ============================================================================
-- Wenn ein neues Studio angelegt wird (INSERT INTO studios), bekommt es
-- automatisch:
-- - eine studio_settings-Zeile (alle Defaults aus CREATE TABLE)
-- - 4 studio_roles (PL, TS, BL, AS)
-- - 3 dashboard_templates (admin, projektleiter, mitarbeiter)
-- - 3 app_roles (r-admin, r-projektleiter, r-mitarbeiter)
-- - 7 absence_types (Krankheit, Unfall, …)
-- - 2 letter_templates (Offerte, Zahlungserinnerung)
--
-- Quelle der Werte: src/constants.js (`defaultData`) + DEFAULT_ABSENZ_TYPES.
-- Wenn dort etwas geändert wird, hier nachziehen — und umgekehrt.
--
-- SECURITY DEFINER: Funktion läuft mit Postgres-Owner-Rechten, umgeht damit
-- die RLS-Policies (der gerade anlegende User ist noch nicht studio_member,
-- könnte sonst nichts einfügen).
-- ============================================================================
create or replace function seed_studio_defaults(s_id uuid)
returns void
language plpgsql
security definer
as $$
begin
-- 1. studio_settings (1 Zeile, alle Defaults aus CREATE TABLE)
insert into studio_settings (studio_id) values (s_id);
-- 2. studio_roles (Rate-Profile)
insert into studio_roles (studio_id, id, label, rate, sort) values
(s_id, 'PL', 'Projektleiter/in', 140, 1),
(s_id, 'TS', 'Technischer Support', 120, 2),
(s_id, 'BL', 'Bauleiter/in', 135, 3),
(s_id, 'AS', 'Administrativer Support', 120, 4);
-- 3. dashboard_templates (vor app_roles wegen FK)
insert into dashboard_templates (studio_id, id, name, is_public, layout) values
(s_id, 'tpl-admin', 'Administrator', true, $j$[
{"id":"dw-a1","cols":4,"minH":0,"widgets":["kpi-projekte","kpi-stunden","kpi-ausstehend","kpi-umsatz"]},
{"id":"dw-a2","cols":1,"minH":0,"widgets":["warnungen"]},
{"id":"dw-a3","cols":2,"minH":0,"widgets":["aktive-projekte","unverrechnete-stunden"]},
{"id":"dw-a4","cols":2,"minH":0,"widgets":["umsatz-sparkline","offene-offerten"]},
{"id":"dw-a5","cols":1,"minH":0,"widgets":["letzte-zeiteintraege"]}
]$j$::jsonb),
(s_id, 'tpl-projektleiter', 'Projektleiter', true, $j$[
{"id":"dw-p1","cols":2,"minH":0,"widgets":["kpi-projekte","kpi-stunden"]},
{"id":"dw-p2","cols":1,"minH":0,"widgets":["warnungen"]},
{"id":"dw-p3","cols":3,"minH":0,"widgets":["meine-projekte","team-auslastung","offene-offerten"]},
{"id":"dw-p4","cols":1,"minH":0,"widgets":["letzte-zeiteintraege"]}
]$j$::jsonb),
(s_id, 'tpl-mitarbeiter', 'Mitarbeiter', true, $j$[
{"id":"dw-m1","cols":3,"minH":0,"widgets":["kpi-stunden","ueberstunden","meine-ferien"]},
{"id":"dw-m2","cols":2,"minH":0,"widgets":["meine-projekte","stunden-woche"]},
{"id":"dw-m3","cols":1,"minH":0,"widgets":["meine-zeiteintraege"]}
]$j$::jsonb);
-- 4. app_roles (permissions=NULL bedeutet "alle erlaubt")
insert into app_roles (studio_id, id, name, permissions, dashboard_template_id) values
(s_id, 'r-admin', 'Administrator',
null,
'tpl-admin'),
(s_id, 'r-projektleiter', 'Projektleiter',
array['dashboard','projects','time','quotes','personen','mitarbeiter','settings'],
'tpl-projektleiter'),
(s_id, 'r-mitarbeiter', 'Mitarbeiter',
array['dashboard','projects','time','personen','settings'],
'tpl-mitarbeiter');
-- 5. absence_types (aus DEFAULT_ABSENZ_TYPES in constants.js)
insert into absence_types (studio_id, id, label, color) values
(s_id, 'krankheit', 'Krankheit', '#8a1a1a'),
(s_id, 'unfall', 'Unfall', '#b5621e'),
(s_id, 'intern', 'Intern', '#1a4e8a'),
(s_id, 'informatik', 'Informatik', '#555'),
(s_id, 'rechnungswesen', 'Rechnungswesen', '#7a6a00'),
(s_id, 'weiterbildung', 'Weiterbildung', '#2d6a4f'),
(s_id, 'militaer', 'Militär / Zivildienst', '#3d3d38');
-- 6. letter_templates
insert into letter_templates (studio_id, id, name, body) values
(s_id, 'offer', 'Offerte',
$b$Sehr geehrte/r {{client}}
Gerne unterbreiten wir Ihnen die Offerte für das Projekt «{{project}}».
[Leistungsumfang]
Honorar: CHF [Betrag]
Wir freuen uns auf die Zusammenarbeit.
Freundliche Grüsse$b$),
(s_id, 'reminder', 'Zahlungserinnerung',
$b$Sehr geehrte/r {{client}}
Bei einer Überprüfung unserer Buchhaltung stellen wir fest, dass die Rechnung [Nr.] vom [Datum] über CHF [Betrag] noch nicht beglichen ist.
Wir bitten Sie höflich, den offenen Betrag innert 10 Tagen zu überweisen.
Freundliche Grüsse$b$);
end$$;
-- ─── Trigger: bei jedem Studio-Insert die Defaults reinkippen ──────────────
create or replace function trg_studios_seed_defaults()
returns trigger
language plpgsql
as $$
begin
perform seed_studio_defaults(new.id);
return new;
end$$;
create trigger studios_seed_defaults
after insert on studios
for each row execute function trg_studios_seed_defaults();