SecurityPredicate
parent
2398bd6aca
commit
bf9a5e1c8d
|
|
@ -29,28 +29,34 @@ class KufrikConnectionFactory
|
|||
|
||||
$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;
|
||||
$databaseName = $identity->database ?? null;
|
||||
$firmaId = $identity->firma_id ?? null;
|
||||
|
||||
if (empty($host) || empty($databaseName)) {
|
||||
throw new \LogicException('Přihlášený uživatel nemá v tabulce LOGIN definovaný SERVER nebo DATABASE.');
|
||||
if (empty($host) || empty($databaseName) || empty($firmaId)) {
|
||||
throw new \LogicException('Přihlášený uživatel nemá v tabulce LOGIN definovaný SERVER, DATABASE nebo FIRMA_ID.');
|
||||
}
|
||||
|
||||
// Dynamický DSN řetězec
|
||||
$dsn = "sqlsrv:Server={$host};Database={$databaseName};LoginTimeout=3;";
|
||||
|
||||
return new Connection(
|
||||
$connection = new Connection(
|
||||
$dsn,
|
||||
$this->dbUser,
|
||||
$this->dbPassword,
|
||||
[
|
||||
'lazy' => false,
|
||||
'lazy' => false, // Spojení musí vzniknout ihned
|
||||
'driverOptions' => [
|
||||
\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
|
||||
|
|
|
|||
|
|
@ -109,10 +109,10 @@
|
|||
<div n:class="tab-pane, fade, $presenter->isLinkCurrent('Faktura:*') ? 'show active'" id="toolbar-invoices" role="tabpanel">
|
||||
<div class="toolbar-group">
|
||||
<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>
|
||||
<span>Seznam faktur</span>
|
||||
</button>
|
||||
</a>
|
||||
<button type="button" class="btn btn-success btn-action">
|
||||
<i class="bi bi-plus-square-fill"></i>
|
||||
<span>Přidat fakturu</span>
|
||||
|
|
@ -134,17 +134,36 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filtr pro Faktury -->
|
||||
<form class="filter-panel ms-auto ms-lg-0">
|
||||
<!-- Filtr pro Faktury v Toolbaru -->
|
||||
<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>
|
||||
<select class="form-select form-select-sm" style="width: 85px;">
|
||||
<option selected>2026</option>
|
||||
<option>2025</option>
|
||||
|
||||
{var $aktualniRok = (int) date('Y')}
|
||||
{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 class="form-select form-select-sm" style="width: 140px;">
|
||||
<option selected>(všechny měsíce)</option>
|
||||
<option>Leden</option>
|
||||
<option>Únor</option>
|
||||
|
||||
<!-- Výběr Měsíce -->
|
||||
<select name="mesic" class="form-select form-select-sm" style="width: 140px;" onchange="this.form.submit()">
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,19 +7,113 @@ use Nette\Database\Explorer;
|
|||
|
||||
class FakturaPresenter extends BasePresenter
|
||||
{
|
||||
/** @persistent */
|
||||
public int $rok;
|
||||
|
||||
/** @persistent */
|
||||
public string $mesic = '';
|
||||
|
||||
public function __construct(
|
||||
private Explorer $kufrikExplorer
|
||||
) {
|
||||
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');
|
||||
|
||||
// Přesměrování do hlavní části aplikace (např. HomePresenter)
|
||||
$this->redirect('Home:');
|
||||
$this->redirect('Faktura:');
|
||||
|
||||
} catch (AuthenticationException $e) {
|
||||
$form->addError('Nespravný e-mail nebo heslo.');
|
||||
|
|
|
|||
Loading…
Reference in New Issue