SecurityPredicate
parent
2398bd6aca
commit
bf9a5e1c8d
|
|
@ -29,28 +29,34 @@ class KufrikConnectionFactory
|
||||||
|
|
||||||
$identity = $this->user->getIdentity();
|
$identity = $this->user->getIdentity();
|
||||||
|
|
||||||
// Server i Databáze se načtou z Identity přihlášeného uživatele (z tabulky LOGIN)
|
// Server, Databáze i FirmaId se načtou z Identity přihlášeného uživatele
|
||||||
$host = $identity->server ?? null;
|
$host = $identity->server ?? null;
|
||||||
$databaseName = $identity->database ?? null;
|
$databaseName = $identity->database ?? null;
|
||||||
|
$firmaId = $identity->firma_id ?? null;
|
||||||
|
|
||||||
if (empty($host) || empty($databaseName)) {
|
if (empty($host) || empty($databaseName) || empty($firmaId)) {
|
||||||
throw new \LogicException('Přihlášený uživatel nemá v tabulce LOGIN definovaný SERVER nebo DATABASE.');
|
throw new \LogicException('Přihlášený uživatel nemá v tabulce LOGIN definovaný SERVER, DATABASE nebo FIRMA_ID.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamický DSN řetězec
|
// Dynamický DSN řetězec
|
||||||
$dsn = "sqlsrv:Server={$host};Database={$databaseName};LoginTimeout=3;";
|
$dsn = "sqlsrv:Server={$host};Database={$databaseName};LoginTimeout=3;";
|
||||||
|
|
||||||
return new Connection(
|
$connection = new Connection(
|
||||||
$dsn,
|
$dsn,
|
||||||
$this->dbUser,
|
$this->dbUser,
|
||||||
$this->dbPassword,
|
$this->dbPassword,
|
||||||
[
|
[
|
||||||
'lazy' => false,
|
'lazy' => false, // Spojení musí vzniknout ihned
|
||||||
'driverOptions' => [
|
'driverOptions' => [
|
||||||
\PDO::SQLSRV_ATTR_ENCODING => \PDO::SQLSRV_ENCODING_UTF8,
|
\PDO::SQLSRV_ATTR_ENCODING => \PDO::SQLSRV_ENCODING_UTF8,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// NASTAVENÍ RLS KONTEXTU PRO MS SQL SERVER
|
||||||
|
$connection->query('EXEC sp_set_session_context @key = N\'FirmaId\', @value = ?', (int)$firmaId);
|
||||||
|
|
||||||
|
return $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createExplorer(): Explorer
|
public function createExplorer(): Explorer
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,10 @@
|
||||||
<div n:class="tab-pane, fade, $presenter->isLinkCurrent('Faktura:*') ? 'show active'" id="toolbar-invoices" role="tabpanel">
|
<div n:class="tab-pane, fade, $presenter->isLinkCurrent('Faktura:*') ? 'show active'" id="toolbar-invoices" role="tabpanel">
|
||||||
<div class="toolbar-group">
|
<div class="toolbar-group">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button type="button" class="btn btn-outline-primary btn-action">
|
<a n:href="Faktura:" type="button" class="btn btn-outline-primary btn-action">
|
||||||
<i class="bi bi-folder-fill"></i>
|
<i class="bi bi-folder-fill"></i>
|
||||||
<span>Seznam faktur</span>
|
<span>Seznam faktur</span>
|
||||||
</button>
|
</a>
|
||||||
<button type="button" class="btn btn-success btn-action">
|
<button type="button" class="btn btn-success btn-action">
|
||||||
<i class="bi bi-plus-square-fill"></i>
|
<i class="bi bi-plus-square-fill"></i>
|
||||||
<span>Přidat fakturu</span>
|
<span>Přidat fakturu</span>
|
||||||
|
|
@ -134,17 +134,36 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filtr pro Faktury -->
|
<!-- Filtr pro Faktury v Toolbaru -->
|
||||||
<form class="filter-panel ms-auto ms-lg-0">
|
<form n:attr="method => 'get', action => $presenter->link('Faktura:default')" class="filter-panel ms-auto ms-lg-0">
|
||||||
<i class="bi bi-funnel-fill text-muted fs-5 ms-1"></i>
|
<i class="bi bi-funnel-fill text-muted fs-5 ms-1"></i>
|
||||||
<select class="form-select form-select-sm" style="width: 85px;">
|
|
||||||
<option selected>2026</option>
|
{var $aktualniRok = (int) date('Y')}
|
||||||
<option>2025</option>
|
{var $vybranyRok = $presenter->rok ?? $aktualniRok}
|
||||||
|
{var $vybranyMesic = $presenter->mesic ?? ''}
|
||||||
|
|
||||||
|
<!-- Výběr Roku -->
|
||||||
|
<select name="rok" class="form-select form-select-sm" style="width: 90px;" onchange="this.form.submit()">
|
||||||
|
{for $r = $aktualniRok; $r >= $aktualniRok - 3; $r--}
|
||||||
|
<option value="{$r}" {if $vybranyRok == $r}selected{/if}>{$r}</option>
|
||||||
|
{/for}
|
||||||
</select>
|
</select>
|
||||||
<select class="form-select form-select-sm" style="width: 140px;">
|
|
||||||
<option selected>(všechny měsíce)</option>
|
<!-- Výběr Měsíce -->
|
||||||
<option>Leden</option>
|
<select name="mesic" class="form-select form-select-sm" style="width: 140px;" onchange="this.form.submit()">
|
||||||
<option>Únor</option>
|
<option value="" {if $vybranyMesic === ''}selected{/if}>(všechny měsíce)</option>
|
||||||
|
<option value="1" {if $vybranyMesic === '1'}selected{/if}>Leden</option>
|
||||||
|
<option value="2" {if $vybranyMesic === '2'}selected{/if}>Únor</option>
|
||||||
|
<option value="3" {if $vybranyMesic === '3'}selected{/if}>Březen</option>
|
||||||
|
<option value="4" {if $vybranyMesic === '4'}selected{/if}>Duben</option>
|
||||||
|
<option value="5" {if $vybranyMesic === '5'}selected{/if}>Květen</option>
|
||||||
|
<option value="6" {if $vybranyMesic === '6'}selected{/if}>Červen</option>
|
||||||
|
<option value="7" {if $vybranyMesic === '7'}selected{/if}>Červenec</option>
|
||||||
|
<option value="8" {if $vybranyMesic === '8'}selected{/if}>Srpen</option>
|
||||||
|
<option value="9" {if $vybranyMesic === '9'}selected{/if}>Září</option>
|
||||||
|
<option value="10" {if $vybranyMesic === '10'}selected{/if}>Říjen</option>
|
||||||
|
<option value="11" {if $vybranyMesic === '11'}selected{/if}>Listopad</option>
|
||||||
|
<option value="12" {if $vybranyMesic === '12'}selected{/if}>Prosinec</option>
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,113 @@ use Nette\Database\Explorer;
|
||||||
|
|
||||||
class FakturaPresenter extends BasePresenter
|
class FakturaPresenter extends BasePresenter
|
||||||
{
|
{
|
||||||
|
/** @persistent */
|
||||||
|
public int $rok;
|
||||||
|
|
||||||
|
/** @persistent */
|
||||||
|
public string $mesic = '';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private Explorer $kufrikExplorer
|
private Explorer $kufrikExplorer
|
||||||
) {
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
$this->rok = (int) date('Y');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function actionDefault(): void
|
public function renderDefault(): void
|
||||||
{
|
{
|
||||||
// Seznam faktur
|
$firmaId = $this->getUser()->getIdentity()->firma_id ?? null;
|
||||||
|
|
||||||
|
if (!$firmaId) {
|
||||||
|
$this->flashMessage('K účtu není přiřazena žádná aktivní firma.', 'danger');
|
||||||
|
$this->template->faktury = [];
|
||||||
|
$this->template->celkemSum = 0;
|
||||||
|
$this->template->neuhrazenoCount = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function actionAdd(): void
|
// Dynamické sestavení podmínek WHERE pro T-SQL
|
||||||
|
$where = ['F.FIRMA_ID = ?'];
|
||||||
|
$params = [$firmaId];
|
||||||
|
|
||||||
|
if ($this->rok) {
|
||||||
|
$where[] = 'YEAR(F.DATUM_VYSTAVENI) = ?';
|
||||||
|
$params[] = $this->rok;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->mesic !== '') {
|
||||||
|
$where[] = 'MONTH(F.DATUM_VYSTAVENI) = ?';
|
||||||
|
$params[] = (int) $this->mesic;
|
||||||
|
}
|
||||||
|
|
||||||
|
$whereSql = implode(' AND ', $where);
|
||||||
|
|
||||||
|
// Čisté T-SQL zaručující 100% kompatibilitu s MS SQL Serverem bez magických Nette relací
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
F.*,
|
||||||
|
ISNULL((SELECT SUM(P.CENA) FROM dbo.POLOZKA P WHERE P.FAKTURA = F.ID), 0) AS CASTKA_CELKEM
|
||||||
|
FROM dbo.FAKTURA F
|
||||||
|
WHERE {$whereSql}
|
||||||
|
ORDER BY F.DATUM_VYSTAVENI DESC, F.ID DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
// Provedeme dotaz přímo na Explorer connection
|
||||||
|
$faktury = $this->kufrikExplorer->query($sql, ...$params)->fetchAll();
|
||||||
|
|
||||||
|
// Součty pro karty
|
||||||
|
$celkemSum = 0;
|
||||||
|
$neuhrazenoCount = 0;
|
||||||
|
|
||||||
|
foreach ($faktury as $f) {
|
||||||
|
$suma = (float) ($f->CASTKA_CELKEM ?? 0);
|
||||||
|
$celkemSum += $suma;
|
||||||
|
if ($f->STAV !== 'PAID') {
|
||||||
|
$neuhrazenoCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->template->faktury = $faktury;
|
||||||
|
$this->template->celkemSum = $celkemSum;
|
||||||
|
$this->template->neuhrazenoCount = $neuhrazenoCount;
|
||||||
|
$this->template->vybranyRok = $this->rok;
|
||||||
|
$this->template->vybranyMesic = $this->mesic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUhradit(int $id): void
|
||||||
{
|
{
|
||||||
// Přidání faktury
|
$firmaId = $this->getUser()->getIdentity()->firma_id;
|
||||||
|
$faktura = $this->kufrikExplorer->table('FAKTURA')
|
||||||
|
->where('ID', $id)
|
||||||
|
->where('FIRMA_ID', $firmaId)
|
||||||
|
->fetch();
|
||||||
|
|
||||||
|
if ($faktura) {
|
||||||
|
$novyStav = ($faktura->STAV === 'PAID') ? 'ISSUED' : 'PAID';
|
||||||
|
$faktura->update(['STAV' => $novyStav]);
|
||||||
|
|
||||||
|
$this->flashMessage(
|
||||||
|
$novyStav === 'PAID' ? 'Faktura byla označena jako zaplacená.' : 'Stav faktury byl změněn na neuhrazeno.',
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect('this');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleDelete(int $id): void
|
||||||
|
{
|
||||||
|
$firmaId = $this->getUser()->getIdentity()->firma_id;
|
||||||
|
|
||||||
|
$deleted = $this->kufrikExplorer->table('FAKTURA')
|
||||||
|
->where('ID', $id)
|
||||||
|
->where('FIRMA_ID', $firmaId)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
if ($deleted) {
|
||||||
|
$this->flashMessage('Faktura byla smazána.', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect('this');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
{block content}
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<!-- Flash zprávy / Toast -->
|
||||||
|
<div n:foreach="$flashes as $flash" class="alert alert-{$flash->type} alert-dismissible fade show auto-dismiss my-3 shadow-sm" role="alert">
|
||||||
|
<i class="bi bi-check-circle-fill me-2"></i>
|
||||||
|
<strong>{$flash->message}</strong>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Souhrnné statistiky -->
|
||||||
|
<div class="row g-3 my-2">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="content-card d-flex align-items-center p-3 border-start border-primary border-4">
|
||||||
|
<div class="fs-1 text-primary me-3"><i class="bi bi-cash-stack"></i></div>
|
||||||
|
<div>
|
||||||
|
<div class="text-muted small">Celkem fakturováno ({$vybranyRok}{if $vybranyMesic}/{$vybranyMesic}{/if})</div>
|
||||||
|
<div class="fs-4 fw-bold">{$celkemSum|number:2, ',', ' '} CZK</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="content-card d-flex align-items-center p-3 border-start border-warning border-4">
|
||||||
|
<div class="fs-1 text-warning me-3"><i class="bi bi-exclamation-triangle"></i></div>
|
||||||
|
<div>
|
||||||
|
<div class="text-muted small">Neuhrazené faktury v období</div>
|
||||||
|
<div class="fs-4 fw-bold">{$neuhrazenoCount} ks</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hlavní tabulka faktur -->
|
||||||
|
<div class="content-card my-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h2 class="h4 mb-0" style="color: var(--header-bg);">
|
||||||
|
<i class="bi bi-folder2-open me-2"></i>Vydané faktury
|
||||||
|
</h2>
|
||||||
|
<a class="btn btn-success btn-sm">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i>Vystavit novou fakturu
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Číslo faktury</th>
|
||||||
|
<th>Odběratel</th>
|
||||||
|
<th>Vystaveno</th>
|
||||||
|
<th>Splatnost</th>
|
||||||
|
<th class="text-end">Celková částka</th>
|
||||||
|
<th class="text-center">Stav</th>
|
||||||
|
<th class="text-end">Akce</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{foreach $faktury as $faktura}
|
||||||
|
<tr>
|
||||||
|
<td class="fw-bold">
|
||||||
|
<a n:href="FakturaDetail:default $faktura->ID" class="text-decoration-none">
|
||||||
|
{$faktura->CISLO}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-semibold">{$faktura->O_PRAJM ?: 'Nespecifikovaný odběratel'}</div>
|
||||||
|
<div class="text-muted small" n:if="$faktura->O_IC">IČ: {$faktura->O_IC}</div>
|
||||||
|
</td>
|
||||||
|
<td>{$faktura->DATUM_VYSTAVENI|date:'d.m.Y'}</td>
|
||||||
|
<td>
|
||||||
|
<span n:class="$faktura->DATUM_SPLATNOSTI < new \DateTime() && $faktura->STAV !== 'PAID' ? 'text-danger fw-bold'">
|
||||||
|
{$faktura->DATUM_SPLATNOSTI|date:'d.m.Y'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end fw-bold fs-6">
|
||||||
|
{($faktura->CASTKA_CELKEM ?? 0)|number:2, ',', ' '} CZK
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
{if $faktura->STAV === 'PAID'}
|
||||||
|
<span class="badge bg-success-subtle text-success border border-success">
|
||||||
|
<i class="bi bi-check-circle me-1"></i>Uhrazeno
|
||||||
|
</span>
|
||||||
|
{else}
|
||||||
|
<span class="badge bg-danger-subtle text-danger border border-danger">
|
||||||
|
<i class="bi bi-clock me-1"></i>Neuhrazeno
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
|
<!-- Detail / Náhled -->
|
||||||
|
<a n:href="FakturaDetail:default $faktura->ID"
|
||||||
|
class="btn btn-outline-info"
|
||||||
|
title="Detail faktury">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Úprava -->
|
||||||
|
<a n:href="FakturaEdit:edit $faktura->ID"
|
||||||
|
class="btn btn-outline-primary"
|
||||||
|
title="Upravit fakturu">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Přepnutí úhrady -->
|
||||||
|
<a n:href="uhradit! $faktura->ID"
|
||||||
|
class="btn {$faktura->STAV === 'PAID' ? 'btn-outline-secondary' : 'btn-outline-success'}"
|
||||||
|
title="Změnit stav úhrady">
|
||||||
|
<i n:class="bi, $faktura->STAV === 'PAID' ? 'bi-x-circle' : 'bi-check2-all'"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Smazání -->
|
||||||
|
<a n:href="delete! $faktura->ID"
|
||||||
|
class="btn btn-outline-danger"
|
||||||
|
onclick="return confirm('Opravdu chcete smazat tuto fakturu včetně všech položek?');"
|
||||||
|
title="Smazat fakturu">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{else}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-4 text-muted">
|
||||||
|
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
|
||||||
|
Pro zadaný filtr (rok {$vybranyRok}{if $vybranyMesic}, měsíc {$vybranyMesic}{/if}) nebyly nalezeny žádné faktury.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/foreach}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const alerts = document.querySelectorAll('.alert.auto-dismiss');
|
||||||
|
alerts.forEach(alert => {
|
||||||
|
setTimeout(() => {
|
||||||
|
alert.classList.remove('show');
|
||||||
|
alert.classList.add('fade');
|
||||||
|
setTimeout(() => alert.remove(), 500);
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -41,7 +41,7 @@ class SignPresenter extends Presenter
|
||||||
$this->flashMessage('Přihlášení proběhlo úspěšně.', 'success');
|
$this->flashMessage('Přihlášení proběhlo úspěšně.', 'success');
|
||||||
|
|
||||||
// Přesměrování do hlavní části aplikace (např. HomePresenter)
|
// Přesměrování do hlavní části aplikace (např. HomePresenter)
|
||||||
$this->redirect('Home:');
|
$this->redirect('Faktura:');
|
||||||
|
|
||||||
} catch (AuthenticationException $e) {
|
} catch (AuthenticationException $e) {
|
||||||
$form->addError('Nespravný e-mail nebo heslo.');
|
$form->addError('Nespravný e-mail nebo heslo.');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue