Admin menu.
parent
fa02f23d7f
commit
497708b5bb
|
|
@ -2,35 +2,188 @@
|
||||||
|
|
||||||
namespace App\Model\Facade;
|
namespace App\Model\Facade;
|
||||||
|
|
||||||
|
use App\Model\Database\KufrikConnectionFactory;
|
||||||
use Nette\Database\Connection;
|
use Nette\Database\Connection;
|
||||||
use Nette\Http\FileUpload;
|
use Nette\Http\FileUpload;
|
||||||
|
use Nette\Security\Passwords;
|
||||||
use Nette\Utils\FileSystem;
|
use Nette\Utils\FileSystem;
|
||||||
use Nette\Utils\Json;
|
use Nette\Utils\Json;
|
||||||
|
use Nette\Utils\Validators;
|
||||||
|
|
||||||
class DatabazeFacade
|
class DatabazeFacade
|
||||||
{
|
{
|
||||||
|
private const MASTER_DB = 'kufrik_master';
|
||||||
|
private const SYSTEM_DBS = ['master', 'tempdb', 'model', 'msdb', self::MASTER_DB];
|
||||||
|
|
||||||
|
/** Extended property na datové DB — jen takto označené se vypisují v Admin/Databáze. */
|
||||||
|
private const APP_PROPERTY = 'kufrik_app';
|
||||||
|
private const APP_PROPERTY_VALUE = '1';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private Connection $masterDb,
|
private Connection $masterDb,
|
||||||
private string $sdfStorageDir
|
private string $sdfStorageDir,
|
||||||
|
private Passwords $passwords,
|
||||||
|
private KufrikConnectionFactory $kufrikConnectionFactory,
|
||||||
|
private string $tenantServer,
|
||||||
|
private string $tenantDatabase
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seznam databází z master tabulky LOGIN (jeden login = jedna databáze).
|
* Seznam datových databází Kufříku (označené extended property kufrik_app=1).
|
||||||
*
|
*
|
||||||
* @return \stdClass[]
|
* @return \stdClass[]
|
||||||
*/
|
*/
|
||||||
public function getAll(): array
|
public function getDatabases(): array
|
||||||
|
{
|
||||||
|
$rows = $this->masterDb->fetchAll(
|
||||||
|
"SELECT name AS [DATABASE]
|
||||||
|
FROM sys.databases
|
||||||
|
WHERE state = 0
|
||||||
|
AND name NOT IN (?, ?, ?, ?, ?)
|
||||||
|
ORDER BY name",
|
||||||
|
...self::SYSTEM_DBS
|
||||||
|
);
|
||||||
|
|
||||||
|
$stats = [];
|
||||||
|
foreach ($this->masterDb->fetchAll(
|
||||||
|
"SELECT [DATABASE], [SERVER],
|
||||||
|
COUNT(*) AS USERS,
|
||||||
|
COUNT(DISTINCT FIRMA_ID) AS FIRMY
|
||||||
|
FROM dbo.LOGIN
|
||||||
|
WHERE [DATABASE] <> ?
|
||||||
|
GROUP BY [DATABASE], [SERVER]",
|
||||||
|
self::MASTER_DB
|
||||||
|
) as $row) {
|
||||||
|
$stats[(string) $row->DATABASE] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$name = (string) $row->DATABASE;
|
||||||
|
if (!$this->isKufrikDatabase($name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$stat = $stats[$name] ?? null;
|
||||||
|
$result[] = (object) [
|
||||||
|
'DATABASE' => $name,
|
||||||
|
'SERVER' => $stat->SERVER ?? $this->tenantServer,
|
||||||
|
'USERS' => (int) ($stat->USERS ?? 0),
|
||||||
|
'FIRMY' => (int) ($stat->FIRMY ?? 0),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string> database => database
|
||||||
|
*/
|
||||||
|
public function getDatabaseSelectItems(): array
|
||||||
|
{
|
||||||
|
$items = [];
|
||||||
|
foreach ($this->getDatabases() as $db) {
|
||||||
|
$items[$db->DATABASE] = $db->DATABASE;
|
||||||
|
}
|
||||||
|
if ($items === [] && $this->tenantDatabase !== '') {
|
||||||
|
$items[$this->tenantDatabase] = $this->tenantDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Firmy z vybrané datové DB.
|
||||||
|
*
|
||||||
|
* @return \stdClass[]
|
||||||
|
*/
|
||||||
|
public function getFirmy(string $database, ?string $server = null): array
|
||||||
|
{
|
||||||
|
$server ??= $this->tenantServer;
|
||||||
|
$this->assertTenantDatabase($database);
|
||||||
|
|
||||||
|
$explorer = $this->kufrikConnectionFactory->createTenantExplorer($server, $database, 1);
|
||||||
|
$userCounts = [];
|
||||||
|
foreach ($this->masterDb->fetchAll(
|
||||||
|
'SELECT FIRMA_ID, COUNT(*) AS C
|
||||||
|
FROM dbo.LOGIN
|
||||||
|
WHERE [DATABASE] = ? AND [SERVER] = ?
|
||||||
|
GROUP BY FIRMA_ID',
|
||||||
|
$database,
|
||||||
|
$server
|
||||||
|
) as $row) {
|
||||||
|
$userCounts[(int) $row->FIRMA_ID] = (int) $row->C;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($explorer->table('FIRMA')->order('NAZEV ASC') as $firma) {
|
||||||
|
$firmaId = (int) $firma->ID;
|
||||||
|
$loginId = $this->findLoginIdForFirma($database, $firmaId, $server);
|
||||||
|
$item = (object) [
|
||||||
|
'ID' => $firmaId,
|
||||||
|
'NAZEV' => (string) $firma->NAZEV,
|
||||||
|
'IC' => $firma->IC,
|
||||||
|
'DIC' => $firma->DIC,
|
||||||
|
'EMAIL' => $firma->EMAIL,
|
||||||
|
'TELEFON' => $firma->TELEFON,
|
||||||
|
'MESTO' => $firma->MESTO,
|
||||||
|
'DATABASE' => $database,
|
||||||
|
'SERVER' => $server,
|
||||||
|
'USERS' => $userCounts[$firmaId] ?? 0,
|
||||||
|
'LOGIN_ID' => $loginId,
|
||||||
|
];
|
||||||
|
if ($loginId) {
|
||||||
|
$meta = $this->readSdfMeta($loginId);
|
||||||
|
$item->SDF_SOUBOR = $meta['originalName'] ?? null;
|
||||||
|
$item->SDF_NAHRANO = isset($meta['uploadedAt']) ? new \DateTimeImmutable($meta['uploadedAt']) : null;
|
||||||
|
$item->SDF_IMPORTOVANO = isset($meta['importedAt']) ? new \DateTimeImmutable($meta['importedAt']) : null;
|
||||||
|
} else {
|
||||||
|
$item->SDF_SOUBOR = null;
|
||||||
|
$item->SDF_NAHRANO = null;
|
||||||
|
$item->SDF_IMPORTOVANO = null;
|
||||||
|
}
|
||||||
|
$result[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function getFirmaSelectItems(string $database, ?string $server = null): array
|
||||||
|
{
|
||||||
|
$items = [];
|
||||||
|
foreach ($this->getFirmy($database, $server) as $firma) {
|
||||||
|
$items[(int) $firma->ID] = $firma->NAZEV . ' (#' . $firma->ID . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uživatelé z master tabulky LOGIN.
|
||||||
|
*
|
||||||
|
* @return \stdClass[]
|
||||||
|
*/
|
||||||
|
public function getUzivatele(): array
|
||||||
{
|
{
|
||||||
$rows = $this->masterDb->fetchAll(
|
$rows = $this->masterDb->fetchAll(
|
||||||
'SELECT ID, EMAIL, POSLEDNI_PRIHLASENI, AKTIVNI, [DATABASE], [SERVER], FIRMA_ID, IS_ADMIN
|
'SELECT ID, EMAIL, POSLEDNI_PRIHLASENI, AKTIVNI, [DATABASE], [SERVER], FIRMA_ID, IS_ADMIN
|
||||||
FROM dbo.LOGIN
|
FROM dbo.LOGIN
|
||||||
ORDER BY [DATABASE], EMAIL'
|
ORDER BY [DATABASE], FIRMA_ID, EMAIL'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$firmaNamesByDb = [];
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$result[] = $this->hydrate($row);
|
$item = $this->hydrateLogin($row);
|
||||||
|
$db = (string) $item->DATABASE;
|
||||||
|
if (!isset($firmaNamesByDb[$db])) {
|
||||||
|
$firmaNamesByDb[$db] = $this->loadFirmaNames($db, (string) $item->SERVER);
|
||||||
|
}
|
||||||
|
$item->FIRMA_NAZEV = $firmaNamesByDb[$db][(int) $item->FIRMA_ID] ?? null;
|
||||||
|
$result[] = $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
|
@ -44,8 +197,129 @@ class DatabazeFacade
|
||||||
WHERE ID = ?',
|
WHERE ID = ?',
|
||||||
$id
|
$id
|
||||||
);
|
);
|
||||||
|
if (!$row) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return $row ? $this->hydrate($row) : null;
|
$item = $this->hydrateLogin($row);
|
||||||
|
$names = $this->loadFirmaNames((string) $item->DATABASE, (string) $item->SERVER);
|
||||||
|
$item->FIRMA_NAZEV = $names[(int) $item->FIRMA_ID] ?? null;
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFirma(string $firmaNazev, string $database, ?string $server = null): int
|
||||||
|
{
|
||||||
|
$server ??= $this->tenantServer;
|
||||||
|
$firmaNazev = trim($firmaNazev);
|
||||||
|
$this->assertTenantDatabase($database);
|
||||||
|
|
||||||
|
if ($firmaNazev === '') {
|
||||||
|
throw new \InvalidArgumentException('Zadejte název firmy.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$explorer = $this->kufrikConnectionFactory->createTenantExplorer($server, $database, 1);
|
||||||
|
$firmaRow = $explorer->table('FIRMA')->insert([
|
||||||
|
'NAZEV' => mb_substr($firmaNazev, 0, 255),
|
||||||
|
'PLATCE_DPH' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int) $firmaRow->ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Založí uživatele (vždy role User, nikdy admin).
|
||||||
|
*/
|
||||||
|
public function createUzivatel(
|
||||||
|
string $email,
|
||||||
|
string $password,
|
||||||
|
string $database,
|
||||||
|
int $firmaId,
|
||||||
|
?string $server = null
|
||||||
|
): int {
|
||||||
|
$server ??= $this->tenantServer;
|
||||||
|
$email = mb_strtolower(trim($email));
|
||||||
|
$this->assertTenantDatabase($database);
|
||||||
|
|
||||||
|
if (!Validators::isEmail($email)) {
|
||||||
|
throw new \InvalidArgumentException('Zadejte platný e-mail uživatele.');
|
||||||
|
}
|
||||||
|
if (mb_strlen($password) < 6) {
|
||||||
|
throw new \InvalidArgumentException('Heslo musí mít alespoň 6 znaků.');
|
||||||
|
}
|
||||||
|
if ($this->emailExists($email)) {
|
||||||
|
throw new \InvalidArgumentException('Uživatel s tímto e-mailem už existuje.');
|
||||||
|
}
|
||||||
|
if (!$this->firmaExists($database, $firmaId, $server)) {
|
||||||
|
throw new \InvalidArgumentException('Vybraná firma v databázi neexistuje.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->masterDb->query(
|
||||||
|
'INSERT INTO dbo.LOGIN (EMAIL, HESLO_HASH, AKTIVNI, [DATABASE], [SERVER], FIRMA_ID, IS_ADMIN)
|
||||||
|
VALUES (?, ?, 1, ?, ?, ?, 0)',
|
||||||
|
$email,
|
||||||
|
$this->passwords->hash($password),
|
||||||
|
$database,
|
||||||
|
$server,
|
||||||
|
$firmaId
|
||||||
|
);
|
||||||
|
|
||||||
|
$loginId = (int) $this->masterDb->fetchField('SELECT ID FROM dbo.LOGIN WHERE EMAIL = ?', $email);
|
||||||
|
if (!$loginId) {
|
||||||
|
throw new \RuntimeException('Uživatele se nepodařilo založit.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $loginId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateUserCredentials(int $loginId, string $email, ?string $password = null): void
|
||||||
|
{
|
||||||
|
$login = $this->getById($loginId);
|
||||||
|
if (!$login) {
|
||||||
|
throw new \InvalidArgumentException('Uživatel nebyl nalezen.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = mb_strtolower(trim($email));
|
||||||
|
if (!Validators::isEmail($email)) {
|
||||||
|
throw new \InvalidArgumentException('Zadejte platný e-mail uživatele.');
|
||||||
|
}
|
||||||
|
if ($this->emailExists($email, $loginId)) {
|
||||||
|
throw new \InvalidArgumentException('Uživatel s tímto e-mailem už existuje.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($password !== null && $password !== '') {
|
||||||
|
if (mb_strlen($password) < 6) {
|
||||||
|
throw new \InvalidArgumentException('Heslo musí mít alespoň 6 znaků.');
|
||||||
|
}
|
||||||
|
$this->masterDb->query(
|
||||||
|
'UPDATE dbo.LOGIN SET EMAIL = ?, HESLO_HASH = ? WHERE ID = ?',
|
||||||
|
$email,
|
||||||
|
$this->passwords->hash($password),
|
||||||
|
$loginId
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->masterDb->query(
|
||||||
|
'UPDATE dbo.LOGIN SET EMAIL = ? WHERE ID = ?',
|
||||||
|
$email,
|
||||||
|
$loginId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findLoginIdForFirma(string $database, int $firmaId, ?string $server = null): ?int
|
||||||
|
{
|
||||||
|
$server ??= $this->tenantServer;
|
||||||
|
$id = $this->masterDb->fetchField(
|
||||||
|
'SELECT TOP 1 ID FROM dbo.LOGIN
|
||||||
|
WHERE [DATABASE] = ? AND [SERVER] = ? AND FIRMA_ID = ?
|
||||||
|
ORDER BY IS_ADMIN DESC, ID ASC',
|
||||||
|
$database,
|
||||||
|
$server,
|
||||||
|
$firmaId
|
||||||
|
);
|
||||||
|
|
||||||
|
return $id ? (int) $id : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSdfFilePath(int $loginId): ?string
|
public function getSdfFilePath(int $loginId): ?string
|
||||||
|
|
@ -66,29 +340,11 @@ class DatabazeFacade
|
||||||
FileSystem::write($this->sdfMetaPath($loginId), Json::encode($meta, pretty: true));
|
FileSystem::write($this->sdfMetaPath($loginId), Json::encode($meta, pretty: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Položky selectu: ID => „databáze — e-mail“.
|
|
||||||
*
|
|
||||||
* @return array<int, string>
|
|
||||||
*/
|
|
||||||
public function getSelectItems(): array
|
|
||||||
{
|
|
||||||
$items = [];
|
|
||||||
foreach ($this->getAll() as $login) {
|
|
||||||
$items[(int) $login->ID] = $login->DATABASE . ' — ' . $login->EMAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uloží nahraný .sdf k loginu.
|
|
||||||
*/
|
|
||||||
public function saveSdf(int $loginId, FileUpload $file): void
|
public function saveSdf(int $loginId, FileUpload $file): void
|
||||||
{
|
{
|
||||||
$exists = $this->masterDb->fetchField('SELECT ID FROM dbo.LOGIN WHERE ID = ?', $loginId);
|
$exists = $this->masterDb->fetchField('SELECT ID FROM dbo.LOGIN WHERE ID = ?', $loginId);
|
||||||
if (!$exists) {
|
if (!$exists) {
|
||||||
throw new \InvalidArgumentException('Vybraná databáze (login) neexistuje.');
|
throw new \InvalidArgumentException('Uživatel / login pro import neexistuje.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$file->isOk()) {
|
if (!$file->isOk()) {
|
||||||
|
|
@ -101,7 +357,6 @@ class DatabazeFacade
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSystem::createDir($this->sdfStorageDir);
|
FileSystem::createDir($this->sdfStorageDir);
|
||||||
|
|
||||||
$file->move($this->sdfPath($loginId));
|
$file->move($this->sdfPath($loginId));
|
||||||
$this->clearSdfImportMeta($loginId);
|
$this->clearSdfImportMeta($loginId);
|
||||||
|
|
||||||
|
|
@ -111,10 +366,95 @@ class DatabazeFacade
|
||||||
'size' => $file->getSize(),
|
'size' => $file->getSize(),
|
||||||
'loginId' => $loginId,
|
'loginId' => $loginId,
|
||||||
];
|
];
|
||||||
|
|
||||||
FileSystem::write($this->sdfMetaPath($loginId), Json::encode($meta, pretty: true));
|
FileSystem::write($this->sdfMetaPath($loginId), Json::encode($meta, pretty: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDefaultTenantDatabase(): string
|
||||||
|
{
|
||||||
|
return $this->tenantDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefaultTenantServer(): string
|
||||||
|
{
|
||||||
|
return $this->tenantServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertTenantDatabase(string $database): void
|
||||||
|
{
|
||||||
|
if ($database === '' || in_array($database, self::SYSTEM_DBS, true)) {
|
||||||
|
throw new \InvalidArgumentException('Neplatná datová databáze.');
|
||||||
|
}
|
||||||
|
if (!$this->isKufrikDatabase($database)) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
"Databáze „{$database}“ není označená jako Kufřík (chybí metadata kufrik_app)."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databáze patří do Kufříku, pokud má extended property kufrik_app = 1.
|
||||||
|
*/
|
||||||
|
private function isKufrikDatabase(string $database): bool
|
||||||
|
{
|
||||||
|
if (!preg_match('/^[A-Za-z0-9_]+$/', $database)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// sqlsrv + Nette fetchField nefunguje spolehlivě (column '0') → pojmenovaný sloupec
|
||||||
|
$row = $this->masterDb->fetch(
|
||||||
|
"SELECT TOP 1 CONVERT(nvarchar(100), value) AS prop_value
|
||||||
|
FROM [{$database}].sys.extended_properties
|
||||||
|
WHERE class = 0 AND major_id = 0 AND minor_id = 0 AND name = ?",
|
||||||
|
self::APP_PROPERTY
|
||||||
|
);
|
||||||
|
|
||||||
|
return $row !== null && (string) $row->prop_value === self::APP_PROPERTY_VALUE;
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function firmaExists(string $database, int $firmaId, string $server): bool
|
||||||
|
{
|
||||||
|
$explorer = $this->kufrikConnectionFactory->createTenantExplorer($server, $database, 1);
|
||||||
|
return (bool) $explorer->table('FIRMA')->get($firmaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function emailExists(string $email, ?int $exceptId = null): bool
|
||||||
|
{
|
||||||
|
if ($exceptId) {
|
||||||
|
return (bool) $this->masterDb->fetchField(
|
||||||
|
'SELECT ID FROM dbo.LOGIN WHERE EMAIL = ? AND ID <> ?',
|
||||||
|
$email,
|
||||||
|
$exceptId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) $this->masterDb->fetchField(
|
||||||
|
'SELECT ID FROM dbo.LOGIN WHERE EMAIL = ?',
|
||||||
|
$email
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
private function loadFirmaNames(string $database, string $server): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->assertTenantDatabase($database);
|
||||||
|
$explorer = $this->kufrikConnectionFactory->createTenantExplorer($server, $database, 1);
|
||||||
|
$names = [];
|
||||||
|
foreach ($explorer->table('FIRMA')->select('ID, NAZEV') as $firma) {
|
||||||
|
$names[(int) $firma->ID] = (string) $firma->NAZEV;
|
||||||
|
}
|
||||||
|
return $names;
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{originalName?: string, uploadedAt?: string, size?: int}|array{}
|
* @return array{originalName?: string, uploadedAt?: string, size?: int}|array{}
|
||||||
*/
|
*/
|
||||||
|
|
@ -143,7 +483,7 @@ class DatabazeFacade
|
||||||
return $meta;
|
return $meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function hydrate(object $row): \stdClass
|
private function hydrateLogin(object $row): \stdClass
|
||||||
{
|
{
|
||||||
$item = (object) (array) $row;
|
$item = (object) (array) $row;
|
||||||
$meta = $this->readSdfMeta((int) $item->ID);
|
$meta = $this->readSdfMeta((int) $item->ID);
|
||||||
|
|
|
||||||
|
|
@ -235,9 +235,13 @@
|
||||||
<i class="bi bi-database-fill"></i>
|
<i class="bi bi-database-fill"></i>
|
||||||
<span>Databáze</span>
|
<span>Databáze</span>
|
||||||
</a>
|
</a>
|
||||||
<a n:href="Admin:statistika" n:class="btn, btn-action, $presenter->isLinkCurrent('Admin:statistika') ? 'btn-primary active' : 'btn-outline-primary'">
|
<a n:href="Admin:firmy" n:class="btn, btn-action, $presenter->isLinkCurrent('Admin:firmy') ? 'btn-primary active' : 'btn-outline-primary'">
|
||||||
<i class="bi bi-graph-up"></i>
|
<i class="bi bi-building"></i>
|
||||||
<span>Statistika</span>
|
<span>Firmy</span>
|
||||||
|
</a>
|
||||||
|
<a n:href="Admin:uzivatele" n:class="btn, btn-action, $presenter->isLinkCurrent('Admin:uzivatele') ? 'btn-primary active' : 'btn-outline-primary'">
|
||||||
|
<i class="bi bi-people"></i>
|
||||||
|
<span>Uživatelé</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ class AdminPresenter extends BasePresenter
|
||||||
{
|
{
|
||||||
private const SDF_MAX_BYTES = 50 * 1024 * 1024;
|
private const SDF_MAX_BYTES = 50 * 1024 * 1024;
|
||||||
|
|
||||||
|
/** @persistent */
|
||||||
|
public string $db = '';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private DatabazeFacade $databazeFacade,
|
private DatabazeFacade $databazeFacade,
|
||||||
private SdfKufrikImporter $sdfImporter
|
private SdfKufrikImporter $sdfImporter
|
||||||
|
|
@ -29,6 +32,10 @@ class AdminPresenter extends BasePresenter
|
||||||
if (!$this->getUser()->isInRole('admin')) {
|
if (!$this->getUser()->isInRole('admin')) {
|
||||||
$this->error('Sekce Admin je dostupná pouze administrátorovi systému.', 403);
|
$this->error('Sekce Admin je dostupná pouze administrátorovi systému.', 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->db === '') {
|
||||||
|
$this->db = $this->databazeFacade->getDefaultTenantDatabase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function actionDefault(): void
|
public function actionDefault(): void
|
||||||
|
|
@ -38,22 +45,192 @@ class AdminPresenter extends BasePresenter
|
||||||
|
|
||||||
public function renderDatabaze(): void
|
public function renderDatabaze(): void
|
||||||
{
|
{
|
||||||
$loginy = $this->databazeFacade->getAll();
|
$databases = $this->databazeFacade->getDatabases();
|
||||||
$aktivniCount = 0;
|
$this->template->databases = $databases;
|
||||||
|
$this->template->celkemCount = count($databases);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($loginy as $login) {
|
public function renderFirmy(): void
|
||||||
if ($login->AKTIVNI) {
|
{
|
||||||
|
$databases = $this->databazeFacade->getDatabaseSelectItems();
|
||||||
|
if ($databases !== [] && !isset($databases[$this->db])) {
|
||||||
|
$this->db = (string) array_key_first($databases);
|
||||||
|
}
|
||||||
|
|
||||||
|
$firmy = $this->db !== ''
|
||||||
|
? $this->databazeFacade->getFirmy($this->db, $this->databazeFacade->getDefaultTenantServer())
|
||||||
|
: [];
|
||||||
|
|
||||||
|
$this->template->databases = $databases;
|
||||||
|
$this->template->selectedDb = $this->db;
|
||||||
|
$this->template->firmy = $firmy;
|
||||||
|
$this->template->celkemCount = count($firmy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderUzivatele(): void
|
||||||
|
{
|
||||||
|
$uzivatele = $this->databazeFacade->getUzivatele();
|
||||||
|
$aktivniCount = 0;
|
||||||
|
foreach ($uzivatele as $u) {
|
||||||
|
if ($u->AKTIVNI) {
|
||||||
$aktivniCount++;
|
$aktivniCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->template->loginy = $loginy;
|
$this->template->uzivatele = $uzivatele;
|
||||||
$this->template->celkemCount = count($loginy);
|
$this->template->celkemCount = count($uzivatele);
|
||||||
$this->template->aktivniCount = $aktivniCount;
|
$this->template->aktivniCount = $aktivniCount;
|
||||||
|
$this->template->databases = $this->databazeFacade->getDatabaseSelectItems();
|
||||||
|
$this->template->defaultDb = $this->db !== ''
|
||||||
|
? $this->db
|
||||||
|
: $this->databazeFacade->getDefaultTenantDatabase();
|
||||||
|
$this->template->firmaItems = $this->databazeFacade->getFirmaSelectItems(
|
||||||
|
$this->template->defaultDb,
|
||||||
|
$this->databazeFacade->getDefaultTenantServer()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renderStatistika(): void
|
protected function createComponentAddFirmaForm(): Form
|
||||||
{
|
{
|
||||||
|
$form = new Form();
|
||||||
|
|
||||||
|
$form->addText('firmaNazev', 'Název firmy / OSVČ:')
|
||||||
|
->setRequired('Zadejte název firmy.')
|
||||||
|
->addRule($form::MaxLength, 'Název může mít nejvýše 255 znaků.', 255);
|
||||||
|
|
||||||
|
$form->addSubmit('send', 'Přidat firmu');
|
||||||
|
$form->onSuccess[] = [$this, 'addFirmaFormSucceeded'];
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFirmaFormSucceeded(Form $form, \stdClass $data): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->db === '') {
|
||||||
|
throw new \InvalidArgumentException('Nejdřív vyberte databázi.');
|
||||||
|
}
|
||||||
|
$firmaId = $this->databazeFacade->createFirma(
|
||||||
|
$data->firmaNazev,
|
||||||
|
$this->db,
|
||||||
|
$this->databazeFacade->getDefaultTenantServer()
|
||||||
|
);
|
||||||
|
$this->flashMessage("Firma „{$data->firmaNazev}“ (#{$firmaId}) byla založena.", 'success');
|
||||||
|
} catch (AbortException $e) {
|
||||||
|
throw $e;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$form->addError($e->getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect('this');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createComponentAddUzivatelForm(): Form
|
||||||
|
{
|
||||||
|
$form = new Form();
|
||||||
|
$databases = $this->databazeFacade->getDatabaseSelectItems();
|
||||||
|
$defaultDb = isset($databases[$this->db])
|
||||||
|
? $this->db
|
||||||
|
: ($databases !== [] ? (string) array_key_first($databases) : '');
|
||||||
|
|
||||||
|
$form->addSelect('database', 'Databáze:')
|
||||||
|
->setItems($databases)
|
||||||
|
->setDefaultValue($defaultDb)
|
||||||
|
->setRequired('Vyberte databázi.');
|
||||||
|
|
||||||
|
$form->addSelect('firmaId', 'Firma:')
|
||||||
|
->setItems($defaultDb !== ''
|
||||||
|
? $this->databazeFacade->getFirmaSelectItems($defaultDb)
|
||||||
|
: [])
|
||||||
|
->setPrompt('— vyberte firmu —')
|
||||||
|
->setRequired('Vyberte firmu.');
|
||||||
|
|
||||||
|
$form->addEmail('email', 'E-mail:')
|
||||||
|
->setRequired('Zadejte e-mail.')
|
||||||
|
->addRule($form::MaxLength, 'E-mail může mít nejvýše 100 znaků.', 100);
|
||||||
|
|
||||||
|
$form->addPassword('password', 'Heslo:')
|
||||||
|
->setRequired('Zadejte heslo.')
|
||||||
|
->addRule($form::MinLength, 'Heslo musí mít alespoň 6 znaků.', 6);
|
||||||
|
|
||||||
|
$form->addPassword('passwordVerify', 'Heslo znovu:')
|
||||||
|
->setRequired('Zadejte heslo znovu.')
|
||||||
|
->addRule($form::Equal, 'Hesla se neshodují.', $form['password']);
|
||||||
|
|
||||||
|
$form->addSubmit('send', 'Přidat uživatele');
|
||||||
|
$form->onSuccess[] = [$this, 'addUzivatelFormSucceeded'];
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addUzivatelFormSucceeded(Form $form, \stdClass $data): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->databazeFacade->createUzivatel(
|
||||||
|
$data->email,
|
||||||
|
$data->password,
|
||||||
|
$data->database,
|
||||||
|
(int) $data->firmaId,
|
||||||
|
$this->databazeFacade->getDefaultTenantServer()
|
||||||
|
);
|
||||||
|
$this->flashMessage('Uživatel byl založen s rolí User.', 'success');
|
||||||
|
} catch (AbortException $e) {
|
||||||
|
throw $e;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$form->addError($e->getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect('this');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createComponentUserForm(): Form
|
||||||
|
{
|
||||||
|
$form = new Form();
|
||||||
|
|
||||||
|
$form->addHidden('loginId')
|
||||||
|
->addRule($form::Integer)
|
||||||
|
->setRequired();
|
||||||
|
|
||||||
|
$form->addEmail('email', 'E-mail / přihlašovací jméno:')
|
||||||
|
->setRequired('Zadejte e-mail.')
|
||||||
|
->addRule($form::MaxLength, 'E-mail může mít nejvýše 100 znaků.', 100);
|
||||||
|
|
||||||
|
$form->addPassword('password', 'Nové heslo:')
|
||||||
|
->setNullable()
|
||||||
|
->addCondition($form::Filled)
|
||||||
|
->addRule($form::MinLength, 'Heslo musí mít alespoň 6 znaků.', 6);
|
||||||
|
|
||||||
|
$form->addPassword('passwordVerify', 'Heslo znovu:')
|
||||||
|
->setNullable()
|
||||||
|
->addConditionOn($form['password'], $form::Filled)
|
||||||
|
->setRequired('Zadejte heslo znovu.')
|
||||||
|
->addRule($form::Equal, 'Hesla se neshodují.', $form['password']);
|
||||||
|
|
||||||
|
$form->addSubmit('send', 'Uložit uživatele');
|
||||||
|
$form->onSuccess[] = [$this, 'userFormSucceeded'];
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userFormSucceeded(Form $form, \stdClass $data): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$password = $data->password !== '' ? $data->password : null;
|
||||||
|
$this->databazeFacade->updateUserCredentials(
|
||||||
|
(int) $data->loginId,
|
||||||
|
$data->email,
|
||||||
|
$password
|
||||||
|
);
|
||||||
|
$this->flashMessage('Údaje uživatele byly uloženy.', 'success');
|
||||||
|
} catch (AbortException $e) {
|
||||||
|
throw $e;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->flashMessage('Uložení uživatele selhalo: ' . $e->getMessage(), 'danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect('this');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createComponentSdfImportForm(): Form
|
protected function createComponentSdfImportForm(): Form
|
||||||
|
|
@ -61,8 +238,8 @@ class AdminPresenter extends BasePresenter
|
||||||
$form = new Form();
|
$form = new Form();
|
||||||
|
|
||||||
$form->addHidden('loginId')
|
$form->addHidden('loginId')
|
||||||
->addRule($form::Integer, 'Vyberte databázi.')
|
->addRule($form::Integer, 'Vyberte firmu s uživatelem.')
|
||||||
->setRequired('Vyberte databázi.');
|
->setRequired('Vyberte firmu s uživatelem.');
|
||||||
|
|
||||||
$iniLimits = array_filter([
|
$iniLimits = array_filter([
|
||||||
Helpers::iniGetSize('upload_max_filesize'),
|
Helpers::iniGetSize('upload_max_filesize'),
|
||||||
|
|
@ -102,13 +279,11 @@ class AdminPresenter extends BasePresenter
|
||||||
$stats = $this->sdfImporter->import($loginId);
|
$stats = $this->sdfImporter->import($loginId);
|
||||||
$this->flashMessage(
|
$this->flashMessage(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Import ze SDF do databáze %s dokončen. Původní data byla smazána. Zákazníci: %d. Faktury: %d. Položky: %d. Platby: %d. Nastavení: %s.',
|
'Import ze SDF dokončen. Původní data firmy byla smazána. Zákazníci: %d. Faktury: %d. Položky: %d. Platby: %d.',
|
||||||
$stats['database'],
|
|
||||||
$stats['zakazniciInserted'],
|
$stats['zakazniciInserted'],
|
||||||
$stats['fakturyInserted'],
|
$stats['fakturyInserted'],
|
||||||
$stats['polozkyInserted'],
|
$stats['polozkyInserted'],
|
||||||
$stats['platbyInserted'],
|
$stats['platbyInserted']
|
||||||
!empty($stats['nastaveniImported']) ? 'přeneseno' : 'bez změn'
|
|
||||||
),
|
),
|
||||||
'success'
|
'success'
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,22 @@
|
||||||
{block content}
|
{block content}
|
||||||
|
|
||||||
<!-- DataTables CSS s Bootstrap 5 vzhledem -->
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.6/css/dataTables.bootstrap5.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.6/css/dataTables.bootstrap5.min.css">
|
||||||
|
|
||||||
<div class="container-fluid py-3">
|
<div class="container-fluid py-3">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="mb-3">
|
||||||
<h2 class="h3 mb-0">Přehled databází</h2>
|
<h2 class="h3 mb-1">Databáze</h2>
|
||||||
</div>
|
<p class="text-muted small mb-0">
|
||||||
|
Jen databáze označené metadaty <code>kufrik_app=1</code> (bez systémových DB a <code>kufrik_master</code>).
|
||||||
<form n:name="sdfImportForm" class="d-none">
|
</p>
|
||||||
<input n:name="loginId">
|
|
||||||
<input n:name="sdf" accept=".sdf">
|
|
||||||
<button n:name="send">Import</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="alert alert-danger" n:if="$presenter['sdfImportForm']->hasErrors()">
|
|
||||||
<ul class="mb-0 ps-3">
|
|
||||||
<li n:foreach="$presenter['sdfImportForm']->errors as $error">{$error}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
<div class="col-md-6 col-lg-4">
|
<div class="col-md-4 col-lg-3">
|
||||||
<div class="card border-0 shadow-sm p-3 bg-white border-start border-primary border-4">
|
<div class="card border-0 shadow-sm p-3 bg-white border-start border-primary border-4">
|
||||||
<div class="text-muted small">Celkem databází</div>
|
<div class="text-muted small">Databází</div>
|
||||||
<div class="fs-4 fw-bold text-primary">{$celkemCount} ks</div>
|
<div class="fs-4 fw-bold text-primary">{$celkemCount} ks</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-lg-4">
|
|
||||||
<div class="card border-0 shadow-sm p-3 bg-white border-start border-success border-4">
|
|
||||||
<div class="text-muted small">Aktivní</div>
|
|
||||||
<div class="fs-4 fw-bold text-success">{$aktivniCount} ks</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm border-0 p-3 bg-white">
|
<div class="card shadow-sm border-0 p-3 bg-white">
|
||||||
|
|
@ -42,71 +26,27 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>Databáze</th>
|
<th>Databáze</th>
|
||||||
<th>Server</th>
|
<th>Server</th>
|
||||||
<th>Login</th>
|
<th class="text-center">Firem</th>
|
||||||
<th class="text-center">Firma ID</th>
|
<th class="text-center">Uživatelů</th>
|
||||||
<th>Poslední přihlášení</th>
|
|
||||||
<th class="text-center">Stav</th>
|
|
||||||
<th class="text-center">Role</th>
|
|
||||||
<th>Poslední import SDF</th>
|
|
||||||
<th class="text-end" data-orderable="false">Akce</th>
|
<th class="text-end" data-orderable="false">Akce</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{foreach $loginy as $login}
|
{foreach $databases as $db}
|
||||||
<tr n:class="!$login->AKTIVNI ? 'table-light text-muted'">
|
<tr>
|
||||||
<td data-label="Databáze" class="fw-bold" data-order="{$login->DATABASE}">
|
<td data-label="Databáze" class="fw-bold">
|
||||||
<i class="bi bi-database me-1 text-muted"></i>{$login->DATABASE}
|
<i class="bi bi-database me-1 text-muted"></i>{$db->DATABASE}
|
||||||
</td>
|
|
||||||
<td data-label="Server">{$login->SERVER}</td>
|
|
||||||
<td data-label="Login">
|
|
||||||
<a href="mailto:{$login->EMAIL}" class="text-decoration-none">{$login->EMAIL}</a>
|
|
||||||
</td>
|
|
||||||
<td data-label="Firma ID" class="text-center">{$login->FIRMA_ID}</td>
|
|
||||||
<td data-label="Poslední přihlášení" data-order="{if $login->POSLEDNI_PRIHLASENI}{$login->POSLEDNI_PRIHLASENI|date:'YmdHis'}{/if}">
|
|
||||||
{if $login->POSLEDNI_PRIHLASENI}
|
|
||||||
{$login->POSLEDNI_PRIHLASENI|date:'d.m.Y H:i'}
|
|
||||||
{else}
|
|
||||||
<span class="text-muted">—</span>
|
|
||||||
{/if}
|
|
||||||
</td>
|
|
||||||
<td data-label="Stav" class="text-center" data-order="{$login->AKTIVNI}">
|
|
||||||
{if $login->AKTIVNI}
|
|
||||||
<span class="badge bg-success">Aktivní</span>
|
|
||||||
{else}
|
|
||||||
<span class="badge bg-secondary">Neaktivní</span>
|
|
||||||
{/if}
|
|
||||||
</td>
|
|
||||||
<td data-label="Role" class="text-center" data-order="{$login->IS_ADMIN}">
|
|
||||||
{if $login->IS_ADMIN}
|
|
||||||
<span class="badge bg-dark">Admin</span>
|
|
||||||
{else}
|
|
||||||
<span class="badge bg-light text-dark border">Uživatel</span>
|
|
||||||
{/if}
|
|
||||||
</td>
|
|
||||||
<td data-label="Poslední import SDF" data-order="{$login->SDF_SOUBOR}">
|
|
||||||
{if $login->SDF_SOUBOR}
|
|
||||||
<div class="fw-semibold">{$login->SDF_SOUBOR}</div>
|
|
||||||
<div class="small text-muted">
|
|
||||||
nahráno {$login->SDF_NAHRANO|date:'d.m.Y H:i'}
|
|
||||||
{if $login->SDF_VELIKOST_TEXT}
|
|
||||||
· {$login->SDF_VELIKOST_TEXT}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="small text-success" n:if="$login->SDF_IMPORTOVANO">
|
|
||||||
importováno {$login->SDF_IMPORTOVANO|date:'d.m.Y H:i'}
|
|
||||||
</div>
|
|
||||||
{else}
|
|
||||||
<span class="text-muted">—</span>
|
|
||||||
{/if}
|
|
||||||
</td>
|
</td>
|
||||||
|
<td data-label="Server">{$db->SERVER}</td>
|
||||||
|
<td data-label="Firem" class="text-center">{$db->FIRMY}</td>
|
||||||
|
<td data-label="Uživatelů" class="text-center">{$db->USERS}</td>
|
||||||
<td class="text-end text-nowrap cell-actions">
|
<td class="text-end text-nowrap cell-actions">
|
||||||
<button type="button"
|
<a n:href="Admin:firmy, db => $db->DATABASE" class="btn btn-sm btn-outline-primary me-1" title="Firmy v databázi">
|
||||||
class="btn btn-sm btn-success js-sdf-import"
|
<i class="bi bi-building"></i>
|
||||||
data-login-id="{$login->ID}"
|
</a>
|
||||||
data-database="{$login->DATABASE}"
|
<a n:href="Admin:uzivatele, db => $db->DATABASE" class="btn btn-sm btn-outline-secondary" title="Uživatelé databáze">
|
||||||
title="Smaže stávající zákazníky, faktury, položky a platby a nahradí je daty ze SDF">
|
<i class="bi bi-people"></i>
|
||||||
<i class="bi bi-box-arrow-in-down me-1"></i>Import
|
</a>
|
||||||
</button>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/foreach}
|
{/foreach}
|
||||||
|
|
@ -123,33 +63,11 @@
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#databaze-table').DataTable({
|
$('#databaze-table').DataTable({
|
||||||
language: {
|
language: { url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/cs.json' },
|
||||||
url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/cs.json'
|
|
||||||
},
|
|
||||||
order: [[0, 'asc']],
|
order: [[0, 'asc']],
|
||||||
pageLength: 25,
|
pageLength: 25,
|
||||||
autoWidth: false
|
autoWidth: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const $form = $('form[id$="-sdfImportForm"], #frm-sdfImportForm').first();
|
|
||||||
const $loginId = $form.find('[name="loginId"]');
|
|
||||||
const $file = $form.find('input[type="file"]');
|
|
||||||
|
|
||||||
$(document).on('click', '.js-sdf-import', function() {
|
|
||||||
const database = $(this).data('database');
|
|
||||||
if (!confirm('VAROVÁNÍ: Import ze SDF do databáze ' + database + ' SMAŽE všechna stávající data této firmy (zákazníky, faktury, položky a platby) a nahradí je obsahem souboru.\n\nOpravdu chcete pokračovat?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$loginId.val($(this).data('login-id'));
|
|
||||||
$file.val('');
|
|
||||||
$file.trigger('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
$file.on('change', function() {
|
|
||||||
if (this.files && this.files.length) {
|
|
||||||
$form.trigger('submit');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{/syntax}
|
{/syntax}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
{block content}
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.6/css/dataTables.bootstrap5.min.css">
|
||||||
|
|
||||||
|
<div class="container-fluid py-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<h2 class="h3 mb-1">Firmy</h2>
|
||||||
|
<p class="text-muted small mb-0">
|
||||||
|
Tabulka <code>FIRMA</code> ve vybrané datové databázi.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm p-3 bg-white mb-4">
|
||||||
|
<div class="mb-2" n:if="$presenter['addFirmaForm']->hasErrors()">
|
||||||
|
<div class="alert alert-danger mb-0">
|
||||||
|
<ul class="mb-0 ps-3">
|
||||||
|
<li n:foreach="$presenter['addFirmaForm']->errors as $error">{$error}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-2 align-items-end">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<form method="get" action="{link this}">
|
||||||
|
<label class="form-label" for="dbFilter">Databáze</label>
|
||||||
|
<select name="db" id="dbFilter" class="form-select" onchange="this.form.submit()">
|
||||||
|
<option n:foreach="$databases as $name => $label" value="{$name}" {if $selectedDb === $name}selected{/if}>{$label}</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<form n:name="addFirmaForm" class="row g-2 align-items-end">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label n:name="firmaNazev" class="form-label">Název firmy / OSVČ</label>
|
||||||
|
<input n:name="firmaNazev" class="form-control" placeholder="např. Jan Novák">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<button n:name="send" class="btn btn-primary w-100">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i>Přidat
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form n:name="sdfImportForm" class="d-none">
|
||||||
|
<input n:name="loginId">
|
||||||
|
<input n:name="sdf" accept=".sdf">
|
||||||
|
<button n:name="send">Import</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-4 col-lg-3">
|
||||||
|
<div class="card border-0 shadow-sm p-3 bg-white border-start border-primary border-4">
|
||||||
|
<div class="text-muted small">Firem v {$selectedDb}</div>
|
||||||
|
<div class="fs-4 fw-bold text-primary">{$celkemCount} ks</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0 p-3 bg-white">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-cards align-middle w-100" id="firmy-table">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Název</th>
|
||||||
|
<th class="text-center">ID</th>
|
||||||
|
<th>IČ / DIČ</th>
|
||||||
|
<th>Kontakt</th>
|
||||||
|
<th class="text-center">Uživatelů</th>
|
||||||
|
<th>SDF</th>
|
||||||
|
<th class="text-end" data-orderable="false">Akce</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{foreach $firmy as $firma}
|
||||||
|
<tr>
|
||||||
|
<td data-label="Název" class="fw-bold">
|
||||||
|
<i class="bi bi-building me-1 text-muted"></i>{$firma->NAZEV}
|
||||||
|
</td>
|
||||||
|
<td data-label="ID" class="text-center">{$firma->ID}</td>
|
||||||
|
<td data-label="IČ / DIČ">
|
||||||
|
{if $firma->IC || $firma->DIC}
|
||||||
|
<div n:if="$firma->IC">IČ: {$firma->IC}</div>
|
||||||
|
<div class="small text-muted" n:if="$firma->DIC">DIČ: {$firma->DIC}</div>
|
||||||
|
{else}
|
||||||
|
<span class="text-muted">—</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td data-label="Kontakt">
|
||||||
|
{if $firma->EMAIL || $firma->TELEFON || $firma->MESTO}
|
||||||
|
<div n:if="$firma->EMAIL">{$firma->EMAIL}</div>
|
||||||
|
<div class="small text-muted" n:if="$firma->TELEFON">{$firma->TELEFON}</div>
|
||||||
|
<div class="small text-muted" n:if="$firma->MESTO">{$firma->MESTO}</div>
|
||||||
|
{else}
|
||||||
|
<span class="text-muted">—</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td data-label="Uživatelů" class="text-center">{$firma->USERS}</td>
|
||||||
|
<td data-label="SDF">
|
||||||
|
{if $firma->SDF_SOUBOR}
|
||||||
|
<div class="fw-semibold">{$firma->SDF_SOUBOR}</div>
|
||||||
|
<div class="small text-success" n:if="$firma->SDF_IMPORTOVANO">
|
||||||
|
importováno {$firma->SDF_IMPORTOVANO|date:'d.m.Y H:i'}
|
||||||
|
</div>
|
||||||
|
{else}
|
||||||
|
<span class="text-muted">—</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td class="text-end text-nowrap cell-actions">
|
||||||
|
{if $firma->LOGIN_ID}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-success js-sdf-import"
|
||||||
|
data-login-id="{$firma->LOGIN_ID}"
|
||||||
|
data-firma="{$firma->NAZEV}"
|
||||||
|
title="Import ze SDF (smaže data firmy)">
|
||||||
|
<i class="bi bi-box-arrow-in-down me-1"></i>Import
|
||||||
|
</button>
|
||||||
|
{else}
|
||||||
|
<span class="small text-muted">Nejdřív založte uživatele</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/foreach}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/datatables.net@1.13.6/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.6/js/dataTables.bootstrap5.min.js"></script>
|
||||||
|
|
||||||
|
{syntax off}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#firmy-table').DataTable({
|
||||||
|
language: { url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/cs.json' },
|
||||||
|
order: [[0, 'asc']],
|
||||||
|
pageLength: 25,
|
||||||
|
autoWidth: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const $importForm = $('form[id$="-sdfImportForm"], #frm-sdfImportForm').first();
|
||||||
|
const $importLoginId = $importForm.find('[name="loginId"]');
|
||||||
|
const $importFile = $importForm.find('input[type="file"]');
|
||||||
|
|
||||||
|
$(document).on('click', '.js-sdf-import', function() {
|
||||||
|
const firma = $(this).data('firma');
|
||||||
|
if (!confirm('VAROVÁNÍ: Import ze SDF pro firmu ' + firma + ' SMAŽE všechna stávající data této firmy.\n\nOpravdu pokračovat?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$importLoginId.val($(this).data('login-id'));
|
||||||
|
$importFile.val('');
|
||||||
|
$importFile.trigger('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
$importFile.on('change', function() {
|
||||||
|
if (this.files && this.files.length) {
|
||||||
|
$importForm.trigger('submit');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{/syntax}
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
{block content}
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.6/css/dataTables.bootstrap5.min.css">
|
||||||
|
|
||||||
|
<div class="container-fluid py-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<h2 class="h3 mb-1">Uživatelé</h2>
|
||||||
|
<p class="text-muted small mb-0">
|
||||||
|
Tabulka <code>LOGIN</code> v <code>kufrik_master</code>. Každý uživatel míří na databázi a firmu. Noví uživatelé mají vždy roli User.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm p-3 bg-white mb-4">
|
||||||
|
<h3 class="h6 text-muted mb-2">Přidat uživatele</h3>
|
||||||
|
<form n:name="addUzivatelForm" class="row g-3 align-items-end">
|
||||||
|
<div class="col-12" n:if="$form->hasErrors()">
|
||||||
|
<div class="alert alert-danger mb-0">
|
||||||
|
<ul class="mb-0 ps-3">
|
||||||
|
<li n:foreach="$form->errors as $error">{$error}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label n:name="database" class="form-label">Databáze</label>
|
||||||
|
<select n:name="database" class="form-select" id="addUserDatabase"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label n:name="firmaId" class="form-label">Firma</label>
|
||||||
|
<select n:name="firmaId" class="form-select" id="addUserFirma"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label n:name="email" class="form-label">E-mail</label>
|
||||||
|
<input n:name="email" class="form-control" placeholder="jan@firma.cz">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label n:name="password" class="form-label">Heslo</label>
|
||||||
|
<input n:name="password" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label n:name="passwordVerify" class="form-label">Heslo znovu</label>
|
||||||
|
<input n:name="passwordVerify" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-auto">
|
||||||
|
<button n:name="send" class="btn btn-primary">
|
||||||
|
<i class="bi bi-person-plus me-1"></i>Přidat
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<p class="small text-muted mt-2 mb-0">
|
||||||
|
Po změně databáze obnovte stránku s parametrem <code>?db=...</code>, aby se načetly firmy, nebo přidejte firmu nejdřív v agendě Firmy.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-4 col-lg-3">
|
||||||
|
<div class="card border-0 shadow-sm p-3 bg-white border-start border-primary border-4">
|
||||||
|
<div class="text-muted small">Uživatelů</div>
|
||||||
|
<div class="fs-4 fw-bold text-primary">{$celkemCount} ks</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-lg-3">
|
||||||
|
<div class="card border-0 shadow-sm p-3 bg-white border-start border-success border-4">
|
||||||
|
<div class="text-muted small">Aktivní</div>
|
||||||
|
<div class="fs-4 fw-bold text-success">{$aktivniCount} ks</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0 p-3 bg-white">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-cards align-middle w-100" id="uzivatele-table">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>E-mail</th>
|
||||||
|
<th>Databáze</th>
|
||||||
|
<th>Firma</th>
|
||||||
|
<th class="text-center">Role</th>
|
||||||
|
<th>Poslední přihlášení</th>
|
||||||
|
<th class="text-center">Stav</th>
|
||||||
|
<th class="text-end" data-orderable="false">Akce</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{foreach $uzivatele as $u}
|
||||||
|
<tr n:class="!$u->AKTIVNI ? 'table-light text-muted'">
|
||||||
|
<td data-label="E-mail" class="fw-bold">
|
||||||
|
<a href="mailto:{$u->EMAIL}" class="text-decoration-none">{$u->EMAIL}</a>
|
||||||
|
</td>
|
||||||
|
<td data-label="Databáze">{$u->DATABASE}</td>
|
||||||
|
<td data-label="Firma">
|
||||||
|
{$u->FIRMA_NAZEV ?: ('#' . $u->FIRMA_ID)}
|
||||||
|
<span class="small text-muted">(#{$u->FIRMA_ID})</span>
|
||||||
|
</td>
|
||||||
|
<td data-label="Role" class="text-center">
|
||||||
|
{if $u->IS_ADMIN}
|
||||||
|
<span class="badge bg-dark">Admin</span>
|
||||||
|
{else}
|
||||||
|
<span class="badge bg-light text-dark border">User</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td data-label="Poslední přihlášení">
|
||||||
|
{if $u->POSLEDNI_PRIHLASENI}
|
||||||
|
{$u->POSLEDNI_PRIHLASENI|date:'d.m.Y H:i'}
|
||||||
|
{else}
|
||||||
|
<span class="text-muted">—</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td data-label="Stav" class="text-center">
|
||||||
|
{if $u->AKTIVNI}
|
||||||
|
<span class="badge bg-success">Aktivní</span>
|
||||||
|
{else}
|
||||||
|
<span class="badge bg-secondary">Neaktivní</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td class="text-end text-nowrap cell-actions">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline-primary js-user-edit"
|
||||||
|
data-login-id="{$u->ID}"
|
||||||
|
data-email="{$u->EMAIL}"
|
||||||
|
data-label="{$u->EMAIL}"
|
||||||
|
title="Upravit e-mail / heslo">
|
||||||
|
<i class="bi bi-person-gear"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/foreach}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="userEditModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form n:name="userForm">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="bi bi-person-gear me-1"></i>Uživatel — <span id="userEditLabel"></span>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zavřít"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input n:name="loginId">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label n:name="email" class="form-label">E-mail / přihlašovací jméno</label>
|
||||||
|
<input n:name="email" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label n:name="password" class="form-label">Nové heslo</label>
|
||||||
|
<input n:name="password" class="form-control" placeholder="ponechte prázdné = beze změny">
|
||||||
|
</div>
|
||||||
|
<div class="mb-0">
|
||||||
|
<label n:name="passwordVerify" class="form-label">Heslo znovu</label>
|
||||||
|
<input n:name="passwordVerify" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Zrušit</button>
|
||||||
|
<button n:name="send" class="btn btn-primary">Uložit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/datatables.net@1.13.6/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.6/js/dataTables.bootstrap5.min.js"></script>
|
||||||
|
|
||||||
|
{syntax off}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#uzivatele-table').DataTable({
|
||||||
|
language: { url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/cs.json' },
|
||||||
|
order: [[0, 'asc']],
|
||||||
|
pageLength: 25,
|
||||||
|
autoWidth: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const userModalEl = document.getElementById('userEditModal');
|
||||||
|
const userModal = userModalEl ? new bootstrap.Modal(userModalEl) : null;
|
||||||
|
const $userForm = $('form[id$="-userForm"], #frm-userForm').first();
|
||||||
|
|
||||||
|
$(document).on('click', '.js-user-edit', function() {
|
||||||
|
$userForm.find('[name="loginId"]').val($(this).data('login-id'));
|
||||||
|
$userForm.find('[name="email"]').val($(this).data('email'));
|
||||||
|
$userForm.find('[name="password"]').val('');
|
||||||
|
$userForm.find('[name="passwordVerify"]').val('');
|
||||||
|
$('#userEditLabel').text($(this).data('label'));
|
||||||
|
if (userModal) {
|
||||||
|
userModal.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Při změně databáze v add form načti firmy přes redirect s ?db=
|
||||||
|
$('#addUserDatabase').on('change', function() {
|
||||||
|
const db = $(this).val();
|
||||||
|
if (db) {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set('db', db);
|
||||||
|
window.location.href = url.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{/syntax}
|
||||||
|
|
@ -4,6 +4,11 @@ parameters:
|
||||||
user: kufrik
|
user: kufrik
|
||||||
password: 'giteaKufrik_2026!'
|
password: 'giteaKufrik_2026!'
|
||||||
|
|
||||||
|
# Sdílená tenant DB (zatím jen localhost / jedna databáze)
|
||||||
|
tenantDb:
|
||||||
|
server: localhost
|
||||||
|
database: kufrik
|
||||||
|
|
||||||
# Nahrané SDF soubory (SQL Server Compact) a heslo staré databáze
|
# Nahrané SDF soubory (SQL Server Compact) a heslo staré databáze
|
||||||
sdfStorageDir: %appDir%/../data/sdf
|
sdfStorageDir: %appDir%/../data/sdf
|
||||||
sdfPassword: 'Kmetix2016_'
|
sdfPassword: 'Kmetix2016_'
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,15 @@ services:
|
||||||
# Authenticator si vyžádá připojení k Master DB
|
# Authenticator si vyžádá připojení k Master DB
|
||||||
- App\Model\Security\Authenticator(@database.default.connection)
|
- App\Model\Security\Authenticator(@database.default.connection)
|
||||||
|
|
||||||
# Seznam databází (LOGIN) z Master DB
|
# Firmy / přístupy (LOGIN) ve sdílené tenant DB
|
||||||
- App\Model\Facade\DatabazeFacade(@database.default.connection, %sdfStorageDir%)
|
- App\Model\Facade\DatabazeFacade(
|
||||||
|
@database.default.connection,
|
||||||
|
%sdfStorageDir%,
|
||||||
|
@security.passwords,
|
||||||
|
@kufrikConnectionFactory,
|
||||||
|
%tenantDb.server%,
|
||||||
|
%tenantDb.database%
|
||||||
|
)
|
||||||
|
|
||||||
- App\Model\Sdf\SdfCompactExporter(%sdfExportScript%, %sdfPassword%)
|
- App\Model\Sdf\SdfCompactExporter(%sdfExportScript%, %sdfPassword%)
|
||||||
- App\Model\Sdf\SdfKufrikImporter
|
- App\Model\Sdf\SdfKufrikImporter
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,20 @@ GO
|
||||||
ALTER ROLE [db_owner] ADD MEMBER [kufrik]
|
ALTER ROLE [db_owner] ADD MEMBER [kufrik]
|
||||||
GO
|
GO
|
||||||
|
|
||||||
|
-- Metadata: označení datové DB aplikace Kufřík (Admin vypisuje jen takto označené)
|
||||||
|
-- Property na databázi = sp_addextendedproperty bez level parametrů (v kontextu USE [kufrik])
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM sys.extended_properties
|
||||||
|
WHERE class = 0 AND major_id = 0 AND minor_id = 0 AND name = N'kufrik_app'
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
EXEC sys.sp_addextendedproperty
|
||||||
|
@name = N'kufrik_app',
|
||||||
|
@value = N'1';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
-- ==========================================
|
-- ==========================================
|
||||||
-- 1. FIRMA HLAVNÍ TABULKA - vázaná na LOGIN z master_DB (taky bude tenant - izolace dat)
|
-- 1. FIRMA HLAVNÍ TABULKA - vázaná na LOGIN z master_DB (taky bude tenant - izolace dat)
|
||||||
-- ==========================================
|
-- ==========================================
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
-- Označí existující datovou databázi jako Kufřík (extended property).
|
||||||
|
-- Admin → Databáze vypisuje jen DB s kufrik_app = 1.
|
||||||
|
-- Property se nastavuje UVNITŘ databáze (bez @level0type = DATABASE).
|
||||||
|
|
||||||
|
USE [kufrik]
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM sys.extended_properties
|
||||||
|
WHERE class = 0 AND major_id = 0 AND minor_id = 0 AND name = N'kufrik_app'
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
EXEC sys.sp_addextendedproperty
|
||||||
|
@name = N'kufrik_app',
|
||||||
|
@value = N'1';
|
||||||
|
PRINT N'Databáze [kufrik] byla označena (kufrik_app=1).';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
PRINT N'Databáze [kufrik] už je označená.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
SELECT name, CONVERT(nvarchar(100), value) AS value
|
||||||
|
FROM sys.extended_properties
|
||||||
|
WHERE class = 0 AND major_id = 0 AND minor_id = 0;
|
||||||
|
GO
|
||||||
Loading…
Reference in New Issue