DB a Factory pro databázi.

master
Kowalski 2026-07-31 15:50:00 +02:00
parent 5c5b5b81d8
commit ea6ada8ce6
7 changed files with 408 additions and 312 deletions

View File

@ -0,0 +1,69 @@
<?php
namespace App\Model\Database;
use Nette\Database\Connection;
use Nette\Database\Explorer;
use Nette\Database\Structure;
use Nette\Caching\Storages\MemoryStorage;
use Nette\Security\User;
class KufrikConnectionFactory
{
private User $user;
private array $baseConfig;
public function __construct(array $baseConfig, User $user)
{
$this->baseConfig = $baseConfig;
$this->user = $user;
}
public function createConnection(): Connection
{
if (!$this->user->isLoggedIn()) {
throw new \LogicException('Uživatel není přihlášen.');
}
$identity = $this->user->getIdentity();
// 1. Dynamické načtení z identity (každý uživatel může mít jiný server i DB!)
$host = $identity->db_host ?? $this->baseConfig['host'];
$databaseName = $identity->db_name ?? $this->baseConfig['default_db'];
$firmaId = $identity->firma_id ?? null;
if (!$firmaId) {
throw new \LogicException('Uživatel nemá zvolenou žádnou aktivní firmu.');
}
// 2. Sestavení DSN pro konkrétní SQL Server a DB
$dsn = "sqlsrv:Server={$host};Database={$databaseName}";
// Přihlašovací údaje k SQL serveru (buď společné ze souboru, nebo dynamické)
$connection = new Connection(
$dsn,
$this->baseConfig['username'],
$this->baseConfig['password'],
[
'lazy' => false,
'driverOptions' => [
\PDO::SQLSRV_ATTR_ENCODING => \PDO::SQLSRV_ENCODING_UTF8,
]
]
);
// 3. Nastavení RLS kontextu pro firmu
$connection->query('EXEC sp_set_session_context @key = N\'FirmaId\', @value = ?', $firmaId);
return $connection;
}
public function createExplorer(): Explorer
{
$connection = $this->createConnection();
$storage = new MemoryStorage();
$structure = new Structure($connection, $storage);
return new Explorer($connection, $structure);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Model\Security;
use Nette\Security\IAuthenticator;
use Nette\Security\SimpleIdentity;
use Nette\Security\AuthenticationException;
use Nette\Security\Passwords;
use Nette\Database\Connection;
class Authenticator implements IAuthenticator
{
private Connection $masterDb;
private Passwords $passwords;
public function __construct(Connection $masterDb, Passwords $passwords)
{
$this->masterDb = $masterDb;
$this->passwords = $passwords;
}
public function authenticate(string $user, string $password): SimpleIdentity
{
// 1. Dotaz do centrální Master DB
$row = $this->masterDb->fetch(
'SELECT ID, EMAIL, HESLO_HASH, JMENO, PRIJMENI, DATABASE_HOST, DATABASE_NAME, DEFAULT_FIRMA_ID
FROM dbo.UZIVATEL
WHERE EMAIL = ? AND AKTIVNI = 1',
$user
);
if (!$row || !$this->passwords->verify($password, $row->HESLO_HASH)) {
throw new AuthenticationException('Nespravný e-mail nebo heslo.');
}
// 2. Všechny potřebné parametry pro KufrikConnectionFactory zabalíme do Nette Identity
return new SimpleIdentity(
$row->ID,
null, // role (volitelně)
[
'email' => $row->EMAIL,
'jmeno' => $row->JMENO . ' ' . $row->PRIJMENI,
// Kufrik parametry:
'db_host' => $row->DATABASE_HOST ?? '127.0.0.1', // Umožňuje i RŮZNÉ SERVERY!
'db_name' => $row->DATABASE_NAME ?? 'Fakturace_MainDB',
'firma_id' => $row->DEFAULT_FIRMA_ID,
]
);
}
}

View File

@ -1,136 +1,60 @@
<?php <?php
namespace App\UI\Sign; namespace App\UI\Sign;
use App\Core\MujAutentifikator; use Nette\Application\UI\Presenter;
use App\Model\FormData\LoginFormData;
use App\Model\Login\ServerCredentials;
use Nette;
use Nette\Application\UI\Form; use Nette\Application\UI\Form;
use Nette\Application\Attributes\Persistent; use Nette\Security\AuthenticationException;
final class SignPresenter extends Nette\Application\UI\Presenter class SignPresenter extends Presenter
{ {
/** /**
* Stores the previous page hash to redirect back after successful login. * Vytvoření přihlašovacího formuláře
*/ */
#[Persistent]
public string $backlink = '';
public function __construct(
) {
}
protected function createComponentSignInForm(): Form protected function createComponentSignInForm(): Form
{ {
$form = new Form(); $form = new Form();
$form->addText('server', 'Server:')
->setRequired('Prosím vyplňte server.')
->setValue(@$_COOKIE['server']);
$form->addText('username', 'Uživatelské jméno:') $form->addEmail('email', 'E-mail:')
->setRequired('Prosím vyplňte své uživatelské jméno.') ->setRequired('Zadejte prosím váš e-mail.')
->setValue(@$_COOKIE['user']); ->setHtmlAttribute('placeholder', 'jan@novak.cz');
$form->addPassword('password', 'Heslo:'); $form->addPassword('password', 'Heslo:')
// setRequired nepoužívat!! kvůli soft logoutu!!! ->setRequired('Zadejte prosím heslo.');
//->setRequired('Prosím vyplňte své heslo.');
$form->addSubmit('send', 'Přihlásit'); $form->addSubmit('send', 'Přihlásit se');
//$form->addProtection(); //ochrana pomocí TOKENu
$form->onSuccess[] = [$this, 'signInFormSucceeded'];
$form->onSuccess[] = $this->signInFormSucceeded(...);
return $form; return $form;
} }
private function signInFormSucceeded(Form $form, LoginFormData $data): void //moje vlastní třída LoginFormData /**
* Zpracování formuláře po odeslání
*/
public function signInFormSucceeded(Form $form, \stdClass $data): void
{ {
try { try {
// kontrola serverů: // Zavolá náš Authenticator spojený s Master DB
if (!Funkce::is_cloud_ip($data->server)) { $this->getUser()->login($data->email, $data->password);
error_log("QR Login - neplatny server.", 0); //log je ve var/log/apache2/error.log (fail2ban)
$form->addError('Neznámý server.');
return;
}
// sestav Credentials: $this->flashMessage('Přihlášení proběhlo úspěšně.', 'success');
$credentials = new ServerCredentials($data->server);
$autentifikator = new MujAutentifikator($credentials, $this->session); // naše vlastní autentifikace! // Přesměrování do hlavní části aplikace (např. HomePresenter)
$this->getUser()->setAuthenticator($autentifikator); // musíme ji registrovat! $this->redirect('Home:');
$blacklist_check = $this->blackList->blacklist_check($data->server); } catch (AuthenticationException $e) {
bdump($blacklist_check); $form->addError('Nespravný e-mail nebo heslo.');
if (is_null($blacklist_check)) {
//nic, jako by prošel
} else {
// $blacklist_check->vysledek Returns 1, pokud není blacklistu. Když se tam nachází, tak 0.
// $blacklist_check->expire_date Kdy končí.
// $blacklist_check->expired_text Co jim zahlásíš.
if ($blacklist_check->vysledek == 0) {
setcookie("salt", "", time() - 3600); // smažeme v tomto případě kukínu ...
if (empty($blacklist_check->expired_text))
throw new BlackListException("Licence pro použití PečAppky vypršela, nebo nám dlužíte peníze.");
else
throw new BlackListException($blacklist_check->expired_text); // custom text z blacklist DB
} }
} }
// uložené heslo?
if (empty($data->password) and isset($_COOKIE['salt'])) {
$password = Funkce::decrypt_password($_COOKIE['salt']);
} else {
$password = $data->password;
}
// validace přihlášení je v třídě MujAutentifikator->authenticate(...)
$this->getUser()->login($data->username, $password);
// kam potom?
$this->restoreRequest($this->backlink); // vrátit se na požadovanou stránku
$this->redirect(':Ctecka:'); // jinak přejdi sem
} catch (Nette\Database\ConnectionException) {
error_log("QR Login - neplatne prihlaseni.", 0); //log je ve var/log/apache2/error.log (fail2ban)
$form->addError('Neplatné přihlášení.');
} catch (Nette\Security\AuthenticationException $e) {
error_log("QR Login - neplatne prihlaseni.", 0); //log je ve var/log/apache2/error.log (fail2ban)
$form->addError($e->getMessage());
} catch (BlackListException $e2) {
$form->addError($e2->getMessage());
}
}
public function renderIn(): void
{
// ukázat či neukázat pole pro heslo?
$this->template->salt = isset($_COOKIE['salt']);
}
/** /**
* Normalní logout. * Akce pro odhlášení
* @return void
*/ */
public function actionOut(): void public function actionOut(): void
{ {
$this->getUser()->logout(); $this->getUser()->logout(true); // smaže i identitu
$this->flashMessage('Odhlášení bylo úspěšné.'); $this->flashMessage('Byli jste úspěšně odhlášeni.', 'info');
$this->redirect('Sign:in'); $this->redirect('in');
}
/**
* Tvrdý logout.
* @return void
*/
public function actionHardOut(): void
{
$this->getUser()->logout();
setcookie("salt", "", time() - 3600); // smažeme v tomto případě kukínu ...
// smažeme vybranou DB ze session:
if ($this->session->hasSection(Konstanty::sectionDatabase)) {
$section = $this->session->getSection(Konstanty::sectionDatabase);
$section->remove(Konstanty::sessionCurrentDatabase);
}
$this->flashMessage('Odhlášení bylo úspěšné.');
$this->redirect('Sign:in');
} }
} }

View File

@ -1,4 +1,10 @@
parameters: parameters:
# Přihlašovací údaje ke klientským DB serverům
mssql:
host: "localhost" # Výchozí záložní host
username: "kufrik"
password: "giteaKufrik_2026!"
default_db: "kufrik"
application: application:
@ -9,10 +15,10 @@ application:
database: database:
dsn: 'sqlsrv:server=kmetix.cz;Database=kufrik;LoginTimeout=6;' dsn: 'sqlsrv:server=localhost;Database=kufrik;LoginTimeout=3;'
user: 'kufrik' user: 'kufrik'
password: 'giteaKufrik_2024' password: 'giteaKufrik_2026!'
autowired: false # Vypnuto, abychom jako výchozí Explorer používali kufrikExplorer
latte: latte:
strictTypes: yes strictTypes: yes

View File

@ -3,6 +3,20 @@ services:
security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12])
# Authenticator si vyžádá připojení k Master DB
- App\Model\Security\Authenticator(@database.default.connection)
# Konfigurace pro klientskou KufrikConnectionFactory
kufrikConnectionFactory:
factory: App\Model\Database\KufrikConnectionFactory
arguments:
baseConfig: %mssql%
# Hlavní databázový Explorer pro aplikaci
kufrikExplorer:
type: Nette\Database\Explorer
factory: @App\Model\Database\KufrikConnectionFactory::createExplorer()
search: search:
- in: %appDir% - in: %appDir%
classes: classes:

View File

@ -1,46 +1,134 @@
CREATE DATABASE [kufrik] use master
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'kufrik', FILENAME = N'/var/opt/mssql/data/kufrik.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
LOG ON
( NAME = N'kufrik_log', FILENAME = N'/var/opt/mssql/data/kufrik_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
GO GO
ALTER DATABASE [kufrik] SET COMPATIBILITY_LEVEL = 150
DROP TABLE [kufrik]
GO
CREATE DATABASE [kufrik]
-- CONTAINMENT = NONE
-- ON PRIMARY
--( NAME = N'kufrik', FILENAME = N'/var/opt/mssql/data/kufrik.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
-- LOG ON
--( NAME = N'kufrik_log', FILENAME = N'/var/opt/mssql/data/kufrik_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
GO
ALTER DATABASE [kufrik] SET COMPATIBILITY_LEVEL = 160
GO
--Pokud je AUTO_CLOSE zapnuté (ON), databáze se po odpojení posledního uživatele kompletně uzavře a uvolní paměť. Při dalším požadavku se pak musí znovu otevírat, což způsobuje výrazné zpoždění (latenci) prvního dotazu a zbytečnou zátěž na disk a CPU.
ALTER DATABASE [kufrik] SET AUTO_CLOSE OFF WITH NO_WAIT;
GO GO
--user: --user:
USE [master] USE [master]
GO GO
CREATE LOGIN [kufrik] WITH PASSWORD=N'giteaKufrik_2024', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF CREATE LOGIN [kufrik] WITH PASSWORD=N'giteaKufrik_2026!', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO
use [master];
GO GO
USE [kufrik] USE [kufrik]
GO GO
CREATE USER [kufrik] FOR LOGIN [kufrik] CREATE USER [kufrik] FOR LOGIN [kufrik]
GO GO
USE [kufrik]
GO
ALTER USER [kufrik] WITH DEFAULT_SCHEMA=[dbo] ALTER USER [kufrik] WITH DEFAULT_SCHEMA=[dbo]
GO GO
USE [kufrik]
GO
ALTER ROLE [db_owner] ADD MEMBER [kufrik] ALTER ROLE [db_owner] ADD MEMBER [kufrik]
GO GO
USE [kufrik] -- 1. PŘIHLAŠOVACÍ ÚČET (Fyzická osoba)
CREATE TABLE [dbo].[UZIVATEL] (
[ID] [int] IDENTITY(1,1) NOT NULL,
[EMAIL] [nvarchar](100) NOT NULL,
[HESLO_HASH] [nvarchar](255) NOT NULL,
[JMENO] [nvarchar](100) NULL,
[PRIJMENI] [nvarchar](100) NULL,
[TELEFON] [nvarchar](20) NULL,
[POSLEDNI_PRIHLASENI] [datetime2](7) NULL,
[AKTIVNI] [bit] NOT NULL DEFAULT 1,
CONSTRAINT [PK_UZIVATEL] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [UQ_UZIVATEL_EMAIL] UNIQUE ([EMAIL])
);
ALTER TABLE [dbo].[UZIVATEL] ADD
-- Identifikátor nebo název cílové databáze
[DATABASE_NAME] [nvarchar](128) NULL,
-- Volitelně celý connection string (nebo klíč do konfigurace aplikace)
[CONNECTION_STRING_KEY] [nvarchar](100) NULL;
GO GO
/****** Object: Table [dbo].[FAKTURA] Script Date: 31.07.2026 9:05:46 ******/
SET ANSI_NULLS ON -- 2. FIRMA / OSVČ (Fakturační jednotka = TENANT)
CREATE TABLE [dbo].[FIRMA] (
[ID] [int] IDENTITY(1,1) NOT NULL,
[NAZEV] [nvarchar](255) NOT NULL, -- Jméno a příjmení OSVČ nebo název s.r.o.
[IC] [nvarchar](8) NULL,
[DIC] [nvarchar](12) NULL,
[PLATCE_DPH] [bit] NOT NULL DEFAULT 1,
-- Sídlo
[ULICE] [nvarchar](100) NULL,
[MESTO] [nvarchar](100) NULL,
[PSC] [nvarchar](10) NULL,
[ZEME_KOD] [nvarchar](2) NOT NULL DEFAULT 'CZ',
-- Banka pro fakturaci
[CISLO_UCTU] [nvarchar](30) NULL,
[BANKA_KOD] [nvarchar](10) NULL,
[IBAN] [nvarchar](34) NULL,
[BIC] [nvarchar](11) NULL,
[DATUM_VYTVORENI] [datetime2](7) NOT NULL DEFAULT SYSDATETIME(),
CONSTRAINT [PK_FIRMA] PRIMARY KEY CLUSTERED ([ID] ASC)
);
-- 3. VAZBA UŽIVATEL <-> FIRMA (Práva k firmám)
CREATE TABLE [dbo].[UZIVATEL_FIRMA] (
[UZIVATEL_ID] [int] NOT NULL,
[FIRMA_ID] [int] NOT NULL,
[ROLE] [nvarchar](20) NOT NULL DEFAULT 'OWNER', -- 'OWNER', 'ACCOUNTANT', 'READONLY'
CONSTRAINT [PK_UZIVATEL_FIRMA] PRIMARY KEY CLUSTERED ([UZIVATEL_ID], [FIRMA_ID]),
CONSTRAINT [FK_UF_UZIVATEL] FOREIGN KEY ([UZIVATEL_ID]) REFERENCES [dbo].[UZIVATEL] ([ID]),
CONSTRAINT [FK_UF_FIRMA] FOREIGN KEY ([FIRMA_ID]) REFERENCES [dbo].[FIRMA] ([ID])
);
-- ==========================================
-- 1. ZÁKAZNÍCI / ODBĚRATELÉ (vázaní na Firmu)
-- ==========================================
CREATE TABLE [dbo].[ZAKAZNIK](
[ID] [int] IDENTITY(2,1) NOT NULL,
[FIRMA_ID] [int] NOT NULL, -- Izolace dat (Tenant)
[JMENO] [nvarchar](255) NULL,
[PRIJMENI] [nvarchar](255) NOT NULL,
[ULICE] [nvarchar](50) NULL,
[MESTO] [nvarchar](50) NULL,
[PSC] [nvarchar](5) NULL,
[IC] [nvarchar](8) NULL,
[DIC] [nvarchar](10) NULL,
[TELEFON] [nvarchar](15) NULL,
[EMAIL] [nvarchar](100) NULL,
[AKTIVNI] [bit] NOT NULL CONSTRAINT [DF_ZAKAZNIK_AKTIVNI] DEFAULT ((1)),
CONSTRAINT [PK_ZAKAZNIK] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_ZAKAZNIK_FIRMA] FOREIGN KEY ([FIRMA_ID]) REFERENCES [dbo].[FIRMA] ([ID])
);
GO GO
SET QUOTED_IDENTIFIER ON
CREATE NONCLUSTERED INDEX [IX_ZAKAZNIK_FIRMA_ID] ON [dbo].[ZAKAZNIK]([FIRMA_ID] ASC);
GO GO
-- ==========================================
-- 2. HLAVIČKA FAKTURY
-- ==========================================
CREATE TABLE [dbo].[FAKTURA]( CREATE TABLE [dbo].[FAKTURA](
[ID] [int] IDENTITY(15,1) NOT NULL, [ID] [int] IDENTITY(15,1) NOT NULL,
[DATUM_VYSTAVENI] [datetime] NOT NULL, [FIRMA_ID] [int] NOT NULL, -- Izolace dat (Tenant)
[DATUM_SPLATNOSTI] [datetime] NOT NULL, [ZAKAZNIK] [int] NULL, -- Vazba na adresář (volitelná, pro historii)
[ZAKAZNIK] [int] NOT NULL,
[CISLO] [nvarchar](20) NOT NULL, [CISLO] [nvarchar](20) NOT NULL,
[CISLO_ABS] [int] NOT NULL, -- Sekvenční pořadové číslo v roce
[FORMA_UHRADY] [tinyint] NOT NULL CONSTRAINT [DF_FAKTURA_FORMA_UHRADY] DEFAULT ((1)),
[DATUM_VYSTAVENI] [date] NOT NULL,
[DATUM_SPLATNOSTI] [date] NOT NULL,
[DATUM_DUZP] [date] NULL, -- Doplněno pro DPH plnění
-- Snapshot Dodavatele (OSVČ / Firma v čase vystavení)
[D_PRAJM] [nvarchar](511) NULL, [D_PRAJM] [nvarchar](511) NULL,
[D_ULICE] [nvarchar](50) NULL, [D_ULICE] [nvarchar](50) NULL,
[D_MESTO] [nvarchar](50) NULL, [D_MESTO] [nvarchar](50) NULL,
@ -51,156 +139,101 @@ CREATE TABLE [dbo].[FAKTURA](
[D_TELEFON] [nvarchar](15) NULL, [D_TELEFON] [nvarchar](15) NULL,
[D_EMAIL] [nvarchar](100) NULL, [D_EMAIL] [nvarchar](100) NULL,
[D_WEB] [nvarchar](100) NULL, [D_WEB] [nvarchar](100) NULL,
-- Snapshot Odběratele (Zákazník v čase vystavení)
[O_PRAJM] [nvarchar](511) NULL, [O_PRAJM] [nvarchar](511) NULL,
[O_ULICE] [nvarchar](50) NULL, [O_ULICE] [nvarchar](50) NULL,
[O_MESTO] [nvarchar](50) NULL, [O_MESTO] [nvarchar](50) NULL,
[O_PSC] [nvarchar](5) NULL, [O_PSC] [nvarchar](5) NULL,
[O_IC] [nvarchar](8) NULL, [O_IC] [nvarchar](8) NULL,
[O_DIC] [nvarchar](10) NULL, [O_DIC] [nvarchar](10) NULL,
[FORMA_UHRADY] [tinyint] NOT NULL,
[CISLO_ABS] [int] NOT NULL, [CINNOSTI] [nvarchar](max) NULL, -- Nahrazeno ntext -> nvarchar(max)
[CINNOSTI] [ntext] NULL, [STAV] [nvarchar](20) NOT NULL CONSTRAINT [DF_FAKTURA_STAV] DEFAULT ('ISSUED'),
CONSTRAINT [PK_FAKTURA] PRIMARY KEY CLUSTERED
( CONSTRAINT [PK_FAKTURA] PRIMARY KEY CLUSTERED ([ID] ASC),
[ID] ASC CONSTRAINT [FK_FAKTURA_FIRMA] FOREIGN KEY ([FIRMA_ID]) REFERENCES [dbo].[FIRMA] ([ID]),
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] CONSTRAINT [FK_FAKTURA_ZAKAZNIK] FOREIGN KEY ([ZAKAZNIK]) REFERENCES [dbo].[ZAKAZNIK] ([ID]),
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
-- Číslo faktury je unikátní vždy jen v rámci jedné firmy
CONSTRAINT [UQ_FAKTURA_FIRMA_CISLO] UNIQUE ([FIRMA_ID], [CISLO])
);
GO GO
/****** Object: Table [dbo].[KONFIGURACE] Script Date: 31.07.2026 9:05:46 ******/
SET ANSI_NULLS ON CREATE NONCLUSTERED INDEX [IX_FAKTURA_FIRMA_DATUM] ON [dbo].[FAKTURA]([FIRMA_ID] ASC, [DATUM_VYSTAVENI] DESC);
GO GO
SET QUOTED_IDENTIFIER ON
-- ==========================================
-- 3. POLOŽKY FAKTURY
-- ==========================================
CREATE TABLE [dbo].[POLOZKA](
[ID] [int] IDENTITY(24,1) NOT NULL,
[FAKTURA] [int] NOT NULL,
[FIRMA_ID] [int] NOT NULL, -- Duplikováno pro rychlé filtry a RLS
[NAZEV] [nvarchar](255) NOT NULL,
[POCET] [float] NOT NULL DEFAULT 1,
[JEDNOTKA] [nvarchar](10) NULL DEFAULT 'ks',
[CENA_JEDNOTKA] [money] NOT NULL,
[SLEVA] [real] NULL DEFAULT 0,
[CENA] [money] NOT NULL, -- Celková cena položky po slevě
CONSTRAINT [PK_POLOZKA] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_POLOZKA_FAKTURA] FOREIGN KEY ([FAKTURA]) REFERENCES [dbo].[FAKTURA] ([ID]) ON DELETE CASCADE,
CONSTRAINT [FK_POLOZKA_FIRMA] FOREIGN KEY ([FIRMA_ID]) REFERENCES [dbo].[FIRMA] ([ID])
);
GO GO
CREATE TABLE [dbo].[KONFIGURACE](
[ID] [int] IDENTITY(2,1) NOT NULL, CREATE NONCLUSTERED INDEX [IX_POLOZKA_FAKTURA] ON [dbo].[POLOZKA]([FAKTURA] ASC);
[DATA] [ntext] NULL,
CONSTRAINT [PK_KONFIGURACE] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[PLATBA] Script Date: 31.07.2026 9:05:46 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO GO
-- ==========================================
-- 4. ÚHRADY / PLATBY FAKTUR
-- ==========================================
CREATE TABLE [dbo].[PLATBA]( CREATE TABLE [dbo].[PLATBA](
[ID] [int] IDENTITY(11,1) NOT NULL, [ID] [int] IDENTITY(11,1) NOT NULL,
[FAKTURA] [int] NOT NULL, [FAKTURA] [int] NOT NULL,
[CASTKA] [money] NOT NULL, [CASTKA] [money] NOT NULL,
[DATUM] [datetime] NOT NULL, [DATUM] [datetime2](7) NOT NULL DEFAULT SYSDATETIME(),
[POZNAMKA] [ntext] NULL, [POZNAMKA] [nvarchar](max) NULL, -- Nahrazeno ntext -> nvarchar(max)
CONSTRAINT [PK_PLATBA] PRIMARY KEY CLUSTERED
( CONSTRAINT [PK_PLATBA] PRIMARY KEY CLUSTERED ([ID] ASC),
[ID] ASC CONSTRAINT [FK_PLATBA_FAKTURA] FOREIGN KEY ([FAKTURA]) REFERENCES [dbo].[FAKTURA] ([ID]) ON DELETE CASCADE
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] );
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[POLOZKA] Script Date: 31.07.2026 9:05:46 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[POLOZKA](
[ID] [int] IDENTITY(24,1) NOT NULL,
[FAKTURA] [int] NOT NULL,
[NAZEV] [nvarchar](255) NOT NULL,
[POCET] [float] NOT NULL,
[CENA_JEDNOTKA] [money] NOT NULL,
[SLEVA] [real] NULL,
[CENA] [money] NOT NULL,
[JEDNOTKA] [nvarchar](5) NULL,
CONSTRAINT [PK_POLOZKA] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[UZIVATEL] Script Date: 31.07.2026 9:05:46 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UZIVATEL](
[ID] [int] IDENTITY(2,1) NOT NULL,
[JMENO] [nvarchar](255) NULL,
[PRIJMENI] [nvarchar](255) NOT NULL,
[ULICE] [nvarchar](50) NULL,
[MESTO] [nvarchar](50) NULL,
[PSC] [nvarchar](5) NULL,
[IC] [nvarchar](8) NULL,
[DIC] [nvarchar](10) NULL,
[TELEFON] [nvarchar](15) NULL,
[EMAIL] [nvarchar](100) NULL,
[BANKA] [nvarchar](100) NULL,
[WEB] [nvarchar](100) NULL,
CONSTRAINT [PK_UZIVATEL] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[VERZE] Script Date: 31.07.2026 9:05:46 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[VERZE](
[ID] [int] IDENTITY(15,1) NOT NULL,
[VERZE] [smallint] NOT NULL,
[DATUM] [datetime] NOT NULL,
CONSTRAINT [PK_VERZE] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
CONSTRAINT [UQ__VERZE__ID] UNIQUE NONCLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
CONSTRAINT [UQ__VERZE__VERZE] UNIQUE NONCLUSTERED
(
[VERZE] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[ZAKAZNIK] Script Date: 31.07.2026 9:05:46 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[ZAKAZNIK](
[ID] [int] IDENTITY(2,1) NOT NULL,
[JMENO] [nvarchar](255) NULL,
[PRIJMENI] [nvarchar](255) NOT NULL,
[ULICE] [nvarchar](50) NULL,
[MESTO] [nvarchar](50) NULL,
[PSC] [nvarchar](5) NULL,
[IC] [nvarchar](8) NULL,
[DIC] [nvarchar](10) NULL,
[TELEFON] [nvarchar](15) NULL,
[EMAIL] [nvarchar](100) NULL,
CONSTRAINT [PK_ZAKAZNIK] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[FAKTURA] ADD DEFAULT ((1)) FOR [FORMA_UHRADY]
GO
ALTER TABLE [dbo].[FAKTURA] WITH CHECK ADD CONSTRAINT [FK_ZAKAZNIK] FOREIGN KEY([ZAKAZNIK])
REFERENCES [dbo].[ZAKAZNIK] ([ID])
GO
ALTER TABLE [dbo].[FAKTURA] CHECK CONSTRAINT [FK_ZAKAZNIK]
GO
ALTER TABLE [dbo].[PLATBA] WITH CHECK ADD CONSTRAINT [FK_PLATBA_FAKTURA] FOREIGN KEY([FAKTURA])
REFERENCES [dbo].[FAKTURA] ([ID])
GO
ALTER TABLE [dbo].[PLATBA] CHECK CONSTRAINT [FK_PLATBA_FAKTURA]
GO
ALTER TABLE [dbo].[POLOZKA] WITH CHECK ADD CONSTRAINT [FK_PLOZKA_FAKTURA] FOREIGN KEY([FAKTURA])
REFERENCES [dbo].[FAKTURA] ([ID])
GO
ALTER TABLE [dbo].[POLOZKA] CHECK CONSTRAINT [FK_PLOZKA_FAKTURA]
GO GO
CREATE NONCLUSTERED INDEX [IX_PLATBA_FAKTURA] ON [dbo].[PLATBA]([FAKTURA] ASC);
GO
-- ==========================================
-- 5. SECURITY
-- ==========================================
-- 1. Vytvoření schématu pro bezpečnostní objekty
CREATE SCHEMA Security;
GO
-- 2. Predikátová funkce pro kontrolu FirmaId
CREATE FUNCTION Security.fn_SecurityPredicate(@FirmaId INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_SecurityPredicate_result
WHERE
-- Vyhodnotí se jako pravda pouze pokud se FIRMA_ID řádku shoduje se SESSION_CONTEXTu
@FirmaId = CAST(SESSION_CONTEXT(N'FirmaId') AS INT);
GO
-- 3. Vytvoření Security Policy pro tabulky
CREATE SECURITY POLICY Security.FirmaSecurityPolicy
ADD FILTER PREDICATE Security.fn_SecurityPredicate(FIRMA_ID) ON dbo.FAKTURA,
ADD BLOCK PREDICATE Security.fn_SecurityPredicate(FIRMA_ID) ON dbo.FAKTURA,
ADD FILTER PREDICATE Security.fn_SecurityPredicate(FIRMA_ID) ON dbo.ZAKAZNIK,
ADD BLOCK PREDICATE Security.fn_SecurityPredicate(FIRMA_ID) ON dbo.ZAKAZNIK,
ADD FILTER PREDICATE Security.fn_SecurityPredicate(FIRMA_ID) ON dbo.POLOZKA,
ADD BLOCK PREDICATE Security.fn_SecurityPredicate(FIRMA_ID) ON dbo.POLOZKA
WITH (STATE = ON);
GO