Přeskočit obsah

Správa portálu — Specifikace

Účel

Samostatná aplikace přístupná výhradně firmě Avaxis (superfirma). Slouží ke správě celé platformy — schvalování registrací, správa firem a jejich uživatelů, katalog aplikací, monitoring, vzdálená podpora, fakturace. Dostupná jako webová aplikace i záložka v desktop main app pro super_admin.

Očekávaná škála: ~500 firem, ~100 000 uživatelů (40 000 aktivních)

Superfirma Avaxis

Avaxis je první a speciální firma v systému:
  • IČO: [IČO firmy Avaxis — doplnit při nasazení]
  • system_role uživatelů: dle přiřazených rolí (viz sekce Role)
  • Plný přístup ke Správě portálu dle role
  • Jako první se zaregistruje při nasazení (seed script)

Seed při nasazení

Při prvním spuštění backendu (prázdná databáze):
  1. Vytvoří se firma Avaxis (IČO, název z ENV proměnných)
  2. Vytvoří se první super_admin účet (e-mail + heslo z ENV)
  3. Veškerá další správa probíhá přes UI portálu

Forma aplikace

• Web: admin.avaxis.cz — přístup jen pro uživatele Avaxis s rolí
• Desktop: záložka "Správa portálu" viditelná jen pro Avaxis uživatele
• API vrátí 403 Forbidden pro jakéhokoli jiného uživatele

Role uživatelů Avaxis

Uživatel může mít více rolí současně — role jsou kumulativní.

super_admin         → plný přístup ke všemu
support_agent       → support tikety (viz support.md)
catalog_admin       → správa aplikací + operační statistiky (bez financí)
payments_admin      → finanční přehledy + správa předplatných (plné změny)
accounting_readonly → finanční přehledy jen pro čtení (bez změn)

Příklady kombinací:
  Jan   [support_agent + catalog_admin]  → tikety + katalog, ne finance
  Marie [payments_admin]                 → veškeré finance, ne tikety
  Petr  [super_admin]                    → vše
  Lucie [accounting_readonly]            → vidí příjmy, nemůže měnit
avaxis_user_roles (
  user_id UUID REFERENCES users(id),
  role    VARCHAR(50),
  PRIMARY KEY (user_id, role)
)

Sekce 1 — Registrace: schvalování nových firem

Nová firma → potvrdí e-mail → žádost v "Čekající registrace"

┌────────────────────────────────────────────────────────────────┐
│ Firma A (IČO: 12345678)              2026-04-21 09:14         │
│ jan.novak@firmaa.cz                                            │
│                               [Schválit] [Zamítnout] [Detail] │
└────────────────────────────────────────────────────────────────┘

Po schválení → firma aktivní, company admin dostane e-mail
Po zamítnutí → e-mail s důvodem, žádost v archivu

Notifikace: badge + záložka Notifikace v detailu firmy. Žádný e-mail na super admina.

Automatické schvalování

platform_settings: 'auto_approve_registrations' = 'true'/'false'

Pokud zapnuto: firma aktivní ihned po potvrzení e-mailu
Doporučení: vypnout v produkci, zapnout jen pro testování

Sekce 2 — Správa firem

Seznam firem

Vyhledávání: [ název firmy nebo IČO nebo e-mail uživatele... ]
Filtry: Stav / Plán / Datum registrace

┌──────────────────┬──────────┬──────────┬──────────┬──────────┐
│ Firma            │ IČO      │ Uživatelé│ Plán     │ Stav     │
├──────────────────┼──────────┼──────────┼──────────┼──────────┤
│ Firma A          │ 12345678 │ 12       │ Standard │ Aktivní  │
│ Firma D          │ 55667788 │  5       │ Premium  │ Grace 3d │
└──────────────────┴──────────┴──────────┴──────────┴──────────┘

Detail firmy — záložky

┌─────────┬───────────┬──────────┬───────────┬─────────────┬────────────┐
│ Přehled │ Uživatelé │ Aplikace │ Předplatné│ Notifikace  │ Audit log  │
└─────────┴───────────┴──────────┴───────────┴─────────────┴────────────┘

Přehled: základní info, stav, trial/grace stav, tlačítka akcí

Aplikace: aktivní aplikace, počty instalací, stav (aktivní/read-only/deaktivovaná)

Předplatné: aktivní předplatná, expirace, trial/grace nastavení per firma, ruční operace

Notifikace: chronologický seznam (selhání platby, expirace, registrace), přečteno/nepřečteno

Audit log: akce super adminů + impersonace, viditelné i company adminovi firmy


Sekce 2a — Správa uživatelů (škála 100 000 uživatelů)

Správa uživatelů je klíčová funkce pro vzdálenou podporu. Systém musí být výkonný při 500 firmách a 100 000 uživatelích.

Globální vyhledávání uživatelů (napříč firmami)

Vyhledávání: [ jméno / e-mail / IČO firmy... ]
Filtry: Stav (aktivní / neaktivní / smazaný) / Firma / Role / Poslední přihlášení

Výsledky stránkované (50 na stránku), full-text search přes PostgreSQL
nebo Elasticsearch pro rychlost při 100k+ záznamech.

┌──────────────────┬──────────────────────┬─────────────┬──────────────┐
│ Jméno            │ E-mail               │ Firma       │ Posl. přih. │
├──────────────────┼──────────────────────┼─────────────┼──────────────┤
│ Jan Novák        │ jan.novak@firmaa.cz  │ Firma A     │ dnes 09:14   │
│ Jana Nováková    │ j.novakova@firmab.cz │ Firma B     │ 3 dny        │
│ [Smazaný uživ.]  │ [anonymizováno]      │ Firma C     │ 6 měsíců     │
└──────────────────┴──────────────────────┴─────────────┴──────────────┘

Detail uživatele (vzdálená podpora)

Základní info:
  Jméno, e-mail, firma, role v firmě, datum registrace, stav

Přiřazené aplikace: seznam s možností přidání/odebrání

Historie přihlášení (posledních 50):
  Datum/čas | IP adresa | Zařízení (device_id) | Typ (přihlášení / impersonace)

Akce vzdálené podpory:
  [Reset hesla]         → odešle reset e-mail
  [Deaktivovat]         → zablokuje přístup (soft, reverzibilní)
  [Smazat / Anonymizovat] → viz GDPR sekce
  [Přihlásit se jako uživatel] → Impersonace (s audit logem)
  [Oživit uživatele]    → pouze pro stav 'deleted' (anonymizovaný)

Stavy uživatele

active    → normální přístup
inactive  → deaktivován adminem (firma nebo Avaxis), lze reaktivovat
deleted   → anonymizován (GDPR), jméno a e-mail nahrazeny zástupnými hodnotami
             ID zůstává pro konzistenci cizích klíčů v datech
             lze "oživit" — vytvoří nový účet propojený se starým ID

Fyzické smazání záznamu NIKDY neprobíhá — zajišťuje konzistenci dat
(faktury, audit logy, zprávy v chatu jsou vázány na user_id)

Anonymizace (GDPR výmaz)

users tabulka po anonymizaci:
  name       → "Smazaný uživatel"
  email      → deleted_{uuid}@avaxis.deleted
  phone      → NULL
  is_deleted → true
  deleted_at → timestamp
  gdpr_request_id → odkaz na žádost

Co se smaže fyzicky ze S3:
  • Zálohy dat (users/{id}/data/)
  • Zálohy nastavení (users/{id}/settings/)
  • Přílohy chatu odeslané uživatelem

Co zůstane (pro konzistenci):
  • Zprávy v chatu → "Smazaný uživatel: [obsah zprávy]"
  • Záznamy v audit logu → "Smazaný uživatel (ID: xxx)"
  • Faktury a platební záznamy (daňová povinnost)

Hromadné operace

Pro efektivní správu při 100k uživatelích:

Hromadný výběr (checkbox) + akce:
  • Deaktivovat vybrané uživatele
  • Resetovat hesla vybraným
  • Export vybraných do CSV

Filtry pro hromadný výběr:
  • Všichni uživatelé firmy X
  • Neaktivní déle než N dní
  • Bez přihlášení za posledních 90 dní

Výkon a indexování

-- Kritické indexy pro 100k uživatelů:
CREATE INDEX idx_users_company    ON users(company_id);
CREATE INDEX idx_users_email      ON users(email);
CREATE INDEX idx_users_status     ON users(is_active, is_deleted);
CREATE INDEX idx_users_last_login ON users(last_login_at DESC);

-- Full-text search (pokud bez Elasticsearch):
CREATE INDEX idx_users_fts ON users
  USING GIN(to_tsvector('czech', name || ' ' || email));

-- Login history partitioning (může narůst na miliony řádků):
login_history PARTITION BY RANGE (created_at)
   měsíční partition, starší než 1 rok přesunout do S3 archivu

Impersonace uživatele

Super admin klikne [Přihlásit se jako uživatel]:
  → Audit záznam: { actor, action:'impersonate', target_user, company, started_at }
  → Super admin vidí aplikaci očima uživatele
  → Banner v UI: "Prohlížíte jako: Jan Novák (Firma A) — [Ukončit]"
  → Po ukončení: { ended_at }

Kdo vidí záznam:
  ✓ Super admini Avaxis — globální audit log
  ✓ Company admin dané firmy — záložka Audit log firmy
    "Administrátor Avaxis přistoupil k účtu Jana Nováka 21.4.2026 09:32–09:58"
  ✗ Běžní uživatelé firmy

Sekce 3 — Správa aplikací v katalogu

(přístup: catalog_admin, super_admin)

Seznam aplikací: název, slug, kategorie, viditelnost, verze, instalace

Nahrání nové verze:
  1. Vybrat aplikaci, zadat semver verzi
  2. Nahrát Win .exe + Linux .AppImage → S3, auto SHA-256 checksum
  3. Changelog (Markdown, nepovinné)
  4. Označit jako latest nebo preview (beta)

Vytvoření nové aplikace:
  Název, slug, popis, kategorie, ikona
  Viditelnost: public / custom (vybrat firmu)

Statistiky pro catalog_admin

(operační data pro správu katalogu, bez finančních informací)

Per aplikace:
  • Celkový počet instalací (Win / Linux)
  • Počet aktivních instalací (spuštěno za posledních 30 dní)
  • Graf stahování v čase
  • Distribuce verzí (kolik uživatelů má kterou verzi)
  • Firmy s největším počtem instalací (název firmy, počet)

Sekce 4 — Předplatné, Trial a Grace period

(přístup: payments_admin, super_admin)

Stavy předplatného

active   → plný přístup
trial    → zkušební období, plný přístup bez platby
grace    → expirováno/nezaplaceno, READ-ONLY režim (viz níže)
expired  → grace period uplynula, přístup zablokován
cancelled→ zrušeno firmou

Grace period — business logika

Kontext: ekonomické aplikace zpracovávají data předchozího měsíce
v měsíci aktuálním. Uživatel musí mít přístup k datům za zaplacené období.

Grace period chování:
  → Aplikace přístupná, placené komponenty v READ-ONLY:
      • Vidí všechna data z období aktivního předplatného
      • Může tisknout a exportovat (PDF, XLSX, CSV)
      • Nemůže vytvářet ani editovat záznamy
      • Banner: "Vaše předplatné expiralo. Data jsou dostupná pouze
        pro čtení a export. Obnovte předplatné pro plný přístup."

Po uplynutí grace period:
  → Přístup zcela zablokován, data zachována na S3

Po zaplacení (kdykoliv v grace period):
  → API přijme příznak od Stripe (invoice.paid webhook)
  → Okamžitá automatická obnova plného přístupu
  → E-mail + in-app notifikace potvrzení obnovy

Časová databáze předplatného

Základ pro určení, ke kterým datům má firma přístup v read-only režimu.

subscription_history (
  id           UUID PRIMARY KEY,
  company_id   UUID REFERENCES companies(id),
  app_id       UUID REFERENCES apps(id),
  status       VARCHAR(20),
  period_start TIMESTAMPTZ,
  period_end   TIMESTAMPTZ,   -- NULL = stále aktivní
  plan         VARCHAR(50),
  changed_by   VARCHAR(50),   -- 'stripe_webhook' | 'super_admin' | 'system'
  created_at   TIMESTAMPTZ
)

Aplikace v read-only: "Ukaž data z období kdy status = 'active' pro tuto firmu."

Trial a grace period nastavení

Globální defaulty (platform_settings):
  default_trial_days = 7
  default_grace_days = 7
  notification_days_before_expiry = '7,1'

Per firma (přepíše global):
  company_trial_config: trial_days, grace_days

Notifikace při expiraci (obojí — e-mail + in-app):
  trial: 7 dní před, 1 den před, v den expirace → přechod do grace
  grace: okamžitě při přechodu, každé 3 dny připomínka
  zaplacení: okamžité potvrzení obnovy

Ruční operace super admina:
  • Prodloužit trial (firma potřebuje více času)
  • Prodloužit grace period (řeší se platba, sporná faktura)
  • Okamžitě aktivovat / deaktivovat

Sekce 5 — Uživatelé Avaxis

Správa interních uživatelů s přiřazením rolí (více rolí na osobu):
  super_admin, support_agent, catalog_admin, payments_admin, accounting_readonly

Akce: přidat, přiřadit/odebrat roli, deaktivovat

Sekce 6 — Finanční přehled

(přístup: payments_admin — čtení + změny; accounting_readonly — jen čtení)

• MRR, ARR, graf příjmů v čase
• Počet předplatných per aplikace per plán
• Firmy v grace period (riziko churn)
• Seznam firem s fakturací + stav platby
• Propojení Stripe API — faktury, platební metody, detail transakcí
• Export CSV / XLSX pro účetnictví

accounting_readonly: vše viditelné, tlačítka pro změny skryta

Sekce 7 — Monitoring

(přístup: catalog_admin, super_admin)

• Počet aktivních firem / uživatelů / instalací
• Nové registrace za 30 dní
• Stav S3 (GB, náklady), Uptime API
• Aktivní support tikety

Sekce 8 — Audit log (globální)

Záznamy nelze editovat ani mazat.
Filtry: datum / actor / typ / firma
Export: CSV

Retention: smluvní vztah + 3 roky → starší záznamy do S3 archivu

GDPR

Při registraci: GDPR souhlas firma + každý uživatel zvlášť
Nová verze dokumentu → znovu souhlas při přihlášení

Právo na výmaz — workflow v portálu:
  1. Žádost přijde přes support nebo přímo do portálu
  2. Super admin schválí → systém anonymizuje (viz sekce Anonymizace)
  3. Zákonná lhůta 30 dní → portál hlídá a upozorní při překročení
  4. Záznam žádosti uložen pro compliance

gdpr_requests (
  id           UUID PRIMARY KEY,
  user_id      UUID REFERENCES users(id),
  type         VARCHAR(20),  -- 'erasure' | 'export' | 'correction'
  status       VARCHAR(20),  -- 'pending' | 'completed' | 'rejected'
  requested_at TIMESTAMPTZ,
  deadline_at  TIMESTAMPTZ,  -- requested_at + 30 dní
  completed_at TIMESTAMPTZ,
  handled_by   UUID REFERENCES users(id)
)

Datový model

company_registrations (
  id            UUID PRIMARY KEY,
  ico           VARCHAR(8),
  name          VARCHAR(255),
  address       TEXT,
  contact_email VARCHAR(255),
  status        VARCHAR(20),
  auto_approved BOOLEAN DEFAULT false,
  reviewed_by   UUID REFERENCES users(id),
  review_note   TEXT,
  created_at    TIMESTAMPTZ,
  reviewed_at   TIMESTAMPTZ
)

platform_settings (
  key        VARCHAR(100) PRIMARY KEY,
  value      TEXT,
  updated_by UUID REFERENCES users(id),
  updated_at TIMESTAMPTZ
)

company_trial_config (
  company_id UUID REFERENCES companies(id) PRIMARY KEY,
  trial_days INTEGER,
  grace_days INTEGER,
  updated_by UUID REFERENCES users(id),
  updated_at TIMESTAMPTZ
)

subscription_history (
  id           UUID PRIMARY KEY,
  company_id   UUID REFERENCES companies(id),
  app_id       UUID REFERENCES apps(id),
  status       VARCHAR(20),
  period_start TIMESTAMPTZ,
  period_end   TIMESTAMPTZ,
  plan         VARCHAR(50),
  changed_by   VARCHAR(50),
  created_at   TIMESTAMPTZ
)

avaxis_user_roles (
  user_id UUID REFERENCES users(id),
  role    VARCHAR(50),
  PRIMARY KEY (user_id, role)
)

gdpr_consents (
  id           UUID PRIMARY KEY,
  subject_type VARCHAR(20),  -- 'company' | 'user'
  subject_id   UUID,
  version      VARCHAR(20),
  ip_address   INET,
  signed_at    TIMESTAMPTZ
)

gdpr_requests (
  id           UUID PRIMARY KEY,
  user_id      UUID REFERENCES users(id),
  type         VARCHAR(20),
  status       VARCHAR(20),
  requested_at TIMESTAMPTZ,
  deadline_at  TIMESTAMPTZ,
  completed_at TIMESTAMPTZ,
  handled_by   UUID REFERENCES users(id)
)

portal_notifications (
  id         UUID PRIMARY KEY,
  company_id UUID REFERENCES companies(id),
  type       VARCHAR(50),
  message    TEXT,
  is_read    BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ
)

-- login_history partitioned by month (může mít miliony řádků)
login_history (
  id         UUID,
  user_id    UUID REFERENCES users(id),
  ip_address INET,
  device_id  VARCHAR(255),
  type       VARCHAR(20),  -- 'login' | 'impersonate_start' | 'impersonate_end'
  created_at TIMESTAMPTZ
) PARTITION BY RANGE (created_at)

Rozhodnuté otázky

Otázka Rozhodnutí
Forma aplikace Samostatná webová aplikace (admin.avaxis.cz) + záložka v desktop
Škála ~500 firem, ~100 000 uživatelů, ~40 000 aktivních
Role Avaxis super_admin, support_agent, catalog_admin, payments_admin, accounting_readonly
Multi-role Uživatel může mít více rolí současně
Finance — přístup payments_admin (změny) + accounting_readonly (čtení)
catalog_admin finance Žádné — vidí jen operační statistiky katalogů
catalog_admin statistiky Instalace, aktivní uživatelé, graf stahování, distribuce verzí
Impersonace — záznam Vidí super admini Avaxis + company admin dané firmy
Grace period — obnova Okamžitě automaticky po platbě (Stripe webhook → API příznak)
Grace period — business Zpracování dat předchozího měsíce → read-only je dostatečné
Trial default 7 dní, nastavitelný per firma
Grace default 7 dní, nastavitelný per firma (prodloužení pro sporné platby)
Grace chování Read-only + tisk + export za zaplacené období
Notifikace expirace E-mail + in-app: 7 dní před, 1 den před, v den expirace, každé 3 dny v grace
Fyzické mazání uživatelů Nikdy — pouze anonymizace (GDPR), ID zachováno pro konzistenci
Smazaní uživatelé Viditelní v seznamu jako "[Smazaný uživatel]", lze oživit
GDPR workflow Formální žádost v portálu, 30denní lhůta, hlídáno systémem
Výkon uživatelů PostgreSQL full-text index, login_history partitioning po měsících

Otevřené otázky

Všechny otázky k portal-management.md jsou vyřešeny.


Poslední aktualizace: 2026-04-21