DB a Factory pro databázi.
parent
5c5b5b81d8
commit
ea6ada8ce6
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,136 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\UI\Sign;
|
||||
|
||||
use App\Core\MujAutentifikator;
|
||||
use App\Model\FormData\LoginFormData;
|
||||
use App\Model\Login\ServerCredentials;
|
||||
use Nette;
|
||||
use Nette\Application\UI\Presenter;
|
||||
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
|
||||
{
|
||||
$form = new Form();
|
||||
$form->addText('server', 'Server:')
|
||||
->setRequired('Prosím vyplňte server.')
|
||||
->setValue(@$_COOKIE['server']);
|
||||
|
||||
$form->addText('username', 'Uživatelské jméno:')
|
||||
->setRequired('Prosím vyplňte své uživatelské jméno.')
|
||||
->setValue(@$_COOKIE['user']);
|
||||
$form->addEmail('email', 'E-mail:')
|
||||
->setRequired('Zadejte prosím váš e-mail.')
|
||||
->setHtmlAttribute('placeholder', 'jan@novak.cz');
|
||||
|
||||
$form->addPassword('password', 'Heslo:');
|
||||
// setRequired nepoužívat!! kvůli soft logoutu!!!
|
||||
//->setRequired('Prosím vyplňte své heslo.');
|
||||
$form->addPassword('password', 'Heslo:')
|
||||
->setRequired('Zadejte prosím heslo.');
|
||||
|
||||
$form->addSubmit('send', 'Přihlásit');
|
||||
//$form->addProtection(); //ochrana pomocí TOKENu
|
||||
$form->addSubmit('send', 'Přihlásit se');
|
||||
|
||||
$form->onSuccess[] = [$this, 'signInFormSucceeded'];
|
||||
|
||||
$form->onSuccess[] = $this->signInFormSucceeded(...);
|
||||
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 {
|
||||
// kontrola serverů:
|
||||
if (!Funkce::is_cloud_ip($data->server)) {
|
||||
error_log("QR Login - neplatny server.", 0); //log je ve var/log/apache2/error.log (fail2ban)
|
||||
$form->addError('Neznámý server.');
|
||||
return;
|
||||
}
|
||||
// Zavolá náš Authenticator spojený s Master DB
|
||||
$this->getUser()->login($data->email, $data->password);
|
||||
|
||||
// sestav Credentials:
|
||||
$credentials = new ServerCredentials($data->server);
|
||||
$this->flashMessage('Přihlášení proběhlo úspěšně.', 'success');
|
||||
|
||||
$autentifikator = new MujAutentifikator($credentials, $this->session); // naše vlastní autentifikace!
|
||||
$this->getUser()->setAuthenticator($autentifikator); // musíme ji registrovat!
|
||||
// Přesměrování do hlavní části aplikace (např. HomePresenter)
|
||||
$this->redirect('Home:');
|
||||
|
||||
$blacklist_check = $this->blackList->blacklist_check($data->server);
|
||||
bdump($blacklist_check);
|
||||
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
|
||||
} catch (AuthenticationException $e) {
|
||||
$form->addError('Nespravný e-mail nebo heslo.');
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
* @return void
|
||||
* Akce pro odhlášení
|
||||
*/
|
||||
public function actionOut(): void
|
||||
{
|
||||
$this->getUser()->logout();
|
||||
$this->flashMessage('Odhlášení bylo úspěšné.');
|
||||
$this->redirect('Sign: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');
|
||||
$this->getUser()->logout(true); // smaže i identitu
|
||||
$this->flashMessage('Byli jste úspěšně odhlášeni.', 'info');
|
||||
$this->redirect('in');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,10 @@
|
|||
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:
|
||||
|
|
@ -9,10 +15,10 @@ application:
|
|||
|
||||
|
||||
database:
|
||||
dsn: 'sqlsrv:server=kmetix.cz;Database=kufrik;LoginTimeout=6;'
|
||||
dsn: 'sqlsrv:server=localhost;Database=kufrik;LoginTimeout=3;'
|
||||
user: 'kufrik'
|
||||
password: 'giteaKufrik_2024'
|
||||
|
||||
password: 'giteaKufrik_2026!'
|
||||
autowired: false # Vypnuto, abychom jako výchozí Explorer používali kufrikExplorer
|
||||
|
||||
latte:
|
||||
strictTypes: yes
|
||||
|
|
|
|||
|
|
@ -3,6 +3,20 @@ services:
|
|||
|
||||
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:
|
||||
- in: %appDir%
|
||||
classes:
|
||||
|
|
|
|||
349
sql/KufrikDB.sql
349
sql/KufrikDB.sql
|
|
@ -1,46 +1,134 @@
|
|||
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 )
|
||||
use master
|
||||
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
|
||||
|
||||
--user:
|
||||
USE [master]
|
||||
GO
|
||||
CREATE LOGIN [kufrik] WITH PASSWORD=N'giteaKufrik_2024', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
|
||||
GO
|
||||
use [master];
|
||||
CREATE LOGIN [kufrik] WITH PASSWORD=N'giteaKufrik_2026!', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
|
||||
GO
|
||||
USE [kufrik]
|
||||
GO
|
||||
CREATE USER [kufrik] FOR LOGIN [kufrik]
|
||||
GO
|
||||
USE [kufrik]
|
||||
GO
|
||||
ALTER USER [kufrik] WITH DEFAULT_SCHEMA=[dbo]
|
||||
GO
|
||||
USE [kufrik]
|
||||
GO
|
||||
ALTER ROLE [db_owner] ADD MEMBER [kufrik]
|
||||
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
|
||||
/****** 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
|
||||
SET QUOTED_IDENTIFIER ON
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_ZAKAZNIK_FIRMA_ID] ON [dbo].[ZAKAZNIK]([FIRMA_ID] ASC);
|
||||
GO
|
||||
|
||||
-- ==========================================
|
||||
-- 2. HLAVIČKA FAKTURY
|
||||
-- ==========================================
|
||||
CREATE TABLE [dbo].[FAKTURA](
|
||||
[ID] [int] IDENTITY(15,1) NOT NULL,
|
||||
[DATUM_VYSTAVENI] [datetime] NOT NULL,
|
||||
[DATUM_SPLATNOSTI] [datetime] NOT NULL,
|
||||
[ZAKAZNIK] [int] NOT NULL,
|
||||
[FIRMA_ID] [int] NOT NULL, -- Izolace dat (Tenant)
|
||||
[ZAKAZNIK] [int] NULL, -- Vazba na adresář (volitelná, pro historii)
|
||||
|
||||
[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_ULICE] [nvarchar](50) NULL,
|
||||
[D_MESTO] [nvarchar](50) NULL,
|
||||
|
|
@ -51,156 +139,101 @@ CREATE TABLE [dbo].[FAKTURA](
|
|||
[D_TELEFON] [nvarchar](15) NULL,
|
||||
[D_EMAIL] [nvarchar](100) NULL,
|
||||
[D_WEB] [nvarchar](100) NULL,
|
||||
|
||||
-- Snapshot Odběratele (Zákazník v čase vystavení)
|
||||
[O_PRAJM] [nvarchar](511) NULL,
|
||||
[O_ULICE] [nvarchar](50) NULL,
|
||||
[O_MESTO] [nvarchar](50) NULL,
|
||||
[O_PSC] [nvarchar](5) NULL,
|
||||
[O_IC] [nvarchar](8) NULL,
|
||||
[O_DIC] [nvarchar](10) NULL,
|
||||
[FORMA_UHRADY] [tinyint] NOT NULL,
|
||||
[CISLO_ABS] [int] NOT NULL,
|
||||
[CINNOSTI] [ntext] NULL,
|
||||
CONSTRAINT [PK_FAKTURA] 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]
|
||||
|
||||
[CINNOSTI] [nvarchar](max) NULL, -- Nahrazeno ntext -> nvarchar(max)
|
||||
[STAV] [nvarchar](20) NOT NULL CONSTRAINT [DF_FAKTURA_STAV] DEFAULT ('ISSUED'),
|
||||
|
||||
CONSTRAINT [PK_FAKTURA] PRIMARY KEY CLUSTERED ([ID] ASC),
|
||||
CONSTRAINT [FK_FAKTURA_FIRMA] FOREIGN KEY ([FIRMA_ID]) REFERENCES [dbo].[FIRMA] ([ID]),
|
||||
CONSTRAINT [FK_FAKTURA_ZAKAZNIK] FOREIGN KEY ([ZAKAZNIK]) REFERENCES [dbo].[ZAKAZNIK] ([ID]),
|
||||
|
||||
-- Číslo faktury je unikátní vždy jen v rámci jedné firmy
|
||||
CONSTRAINT [UQ_FAKTURA_FIRMA_CISLO] UNIQUE ([FIRMA_ID], [CISLO])
|
||||
);
|
||||
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
|
||||
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
|
||||
CREATE TABLE [dbo].[KONFIGURACE](
|
||||
[ID] [int] IDENTITY(2,1) NOT NULL,
|
||||
[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
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_POLOZKA_FAKTURA] ON [dbo].[POLOZKA]([FAKTURA] ASC);
|
||||
GO
|
||||
|
||||
-- ==========================================
|
||||
-- 4. ÚHRADY / PLATBY FAKTUR
|
||||
-- ==========================================
|
||||
CREATE TABLE [dbo].[PLATBA](
|
||||
[ID] [int] IDENTITY(11,1) NOT NULL,
|
||||
[FAKTURA] [int] NOT NULL,
|
||||
[CASTKA] [money] NOT NULL,
|
||||
[DATUM] [datetime] NOT NULL,
|
||||
[POZNAMKA] [ntext] NULL,
|
||||
CONSTRAINT [PK_PLATBA] 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].[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]
|
||||
[DATUM] [datetime2](7) NOT NULL DEFAULT SYSDATETIME(),
|
||||
[POZNAMKA] [nvarchar](max) NULL, -- Nahrazeno ntext -> nvarchar(max)
|
||||
|
||||
CONSTRAINT [PK_PLATBA] PRIMARY KEY CLUSTERED ([ID] ASC),
|
||||
CONSTRAINT [FK_PLATBA_FAKTURA] FOREIGN KEY ([FAKTURA]) REFERENCES [dbo].[FAKTURA] ([ID]) ON DELETE CASCADE
|
||||
);
|
||||
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
|
||||
Loading…
Reference in New Issue