Platby + import plateb a nastavení

master
Kowalski 2026-10-17 10:50:46 +02:00
parent 99dfc9770b
commit 4b4e3acafe
12 changed files with 583 additions and 277 deletions

View File

@ -7,11 +7,39 @@ use Nette\Database\Explorer;
class FakturaFacade class FakturaFacade
{ {
public const STAV_VYSTAVENO = 'ISSUED';
public const STAV_CASTECNE = 'PARTIAL';
public const STAV_ZAPLACENO = 'PAID';
public function __construct( public function __construct(
private Explorer $kufrikExplorer private Explorer $kufrikExplorer
) { ) {
} }
/**
* Stav úhrady podle součtu plateb vůči částce faktury.
*/
public static function stavZCastek(float $celkem, float $zaplaceno): string
{
if ($zaplaceno <= 0.004) {
return self::STAV_VYSTAVENO;
}
if ($celkem > 0 && $zaplaceno + 0.004 >= $celkem) {
return self::STAV_ZAPLACENO;
}
return self::STAV_CASTECNE;
}
public static function stavPopis(string $stav): string
{
return match ($stav) {
self::STAV_ZAPLACENO => 'Zaplaceno',
self::STAV_CASTECNE => 'Částečně uhrazeno',
default => 'Neuhrazeno',
};
}
/** /**
* Vrátí seznam faktur s dopočítanou celkovou částkou z položek. * Vrátí seznam faktur s dopočítanou celkovou částkou z položek.
*/ */
@ -36,7 +64,8 @@ class FakturaFacade
$sql = " $sql = "
SELECT SELECT
F.*, F.*,
ISNULL((SELECT SUM(P.CENA) FROM dbo.POLOZKA P WHERE P.FAKTURA = F.ID), 0) AS CASTKA_CELKEM ISNULL((SELECT SUM(P.CENA) FROM dbo.POLOZKA P WHERE P.FAKTURA = F.ID), 0) AS CASTKA_CELKEM,
ISNULL((SELECT SUM(PL.CASTKA) FROM dbo.PLATBA PL WHERE PL.FAKTURA = F.ID), 0) AS ZAPLACENO
FROM dbo.FAKTURA F FROM dbo.FAKTURA F
{$whereSql} {$whereSql}
ORDER BY F.DATUM_VYSTAVENI DESC, F.ID DESC ORDER BY F.DATUM_VYSTAVENI DESC, F.ID DESC
@ -181,7 +210,6 @@ class FakturaFacade
$values = [ $values = [
'DATUM_SPLATNOSTI' => $data['DATUM_SPLATNOSTI'], 'DATUM_SPLATNOSTI' => $data['DATUM_SPLATNOSTI'],
'DATUM_DUZP' => !empty($data['DATUM_DUZP']) ? $data['DATUM_DUZP'] : null, 'DATUM_DUZP' => !empty($data['DATUM_DUZP']) ? $data['DATUM_DUZP'] : null,
'STAV' => $data['STAV'] ?? 'ISSUED',
'CINNOSTI' => !empty($data['CINNOSTI']) ? $data['CINNOSTI'] : null, 'CINNOSTI' => !empty($data['CINNOSTI']) ? $data['CINNOSTI'] : null,
]; ];
@ -296,20 +324,171 @@ class FakturaFacade
return $this->getNextCislo($firmaId, $datum)['cisloAbs']; return $this->getNextCislo($firmaId, $datum)['cisloAbs'];
} }
public function toggleUhrada(int $id): string /**
* Faktury dřív označené jen stavem Zaplaceno dostanou jednu platbu,
* a stav všech faktur se srovná se součtem plateb.
*/
public function reconcileUhrady(): void
{ {
$faktura = $this->getById($id); $this->kufrikExplorer->query("
INSERT INTO dbo.PLATBA (FAKTURA, CASTKA, DATUM, POZNAMKA)
SELECT
F.ID,
ISNULL((SELECT SUM(P.CENA) FROM dbo.POLOZKA P WHERE P.FAKTURA = F.ID), 0),
CAST(ISNULL(F.DATUM_SPLATNOSTI, F.DATUM_VYSTAVENI) AS datetime2),
N'Doplněno podle původního stavu Zaplaceno'
FROM dbo.FAKTURA F
WHERE F.STAV = 'PAID'
AND NOT EXISTS (SELECT 1 FROM dbo.PLATBA PL WHERE PL.FAKTURA = F.ID)
AND ISNULL((SELECT SUM(P.CENA) FROM dbo.POLOZKA P WHERE P.FAKTURA = F.ID), 0) > 0
");
$this->kufrikExplorer->query("
UPDATE F
SET STAV = CASE
WHEN CAST(ISNULL(U.ZAPLACENO, 0) AS decimal(19,4)) <= 0 THEN 'ISSUED'
WHEN CAST(ISNULL(C.CELKEM, 0) AS decimal(19,4)) > 0
AND CAST(ISNULL(U.ZAPLACENO, 0) AS decimal(19,4)) + 0.004 >= CAST(ISNULL(C.CELKEM, 0) AS decimal(19,4)) THEN 'PAID'
ELSE 'PARTIAL'
END
FROM dbo.FAKTURA F
LEFT JOIN (
SELECT FAKTURA, SUM(CASTKA) AS ZAPLACENO
FROM dbo.PLATBA
GROUP BY FAKTURA
) U ON U.FAKTURA = F.ID
LEFT JOIN (
SELECT FAKTURA, SUM(CENA) AS CELKEM
FROM dbo.POLOZKA
GROUP BY FAKTURA
) C ON C.FAKTURA = F.ID
WHERE F.STAV <> CASE
WHEN CAST(ISNULL(U.ZAPLACENO, 0) AS decimal(19,4)) <= 0 THEN 'ISSUED'
WHEN CAST(ISNULL(C.CELKEM, 0) AS decimal(19,4)) > 0
AND CAST(ISNULL(U.ZAPLACENO, 0) AS decimal(19,4)) + 0.004 >= CAST(ISNULL(C.CELKEM, 0) AS decimal(19,4)) THEN 'PAID'
ELSE 'PARTIAL'
END
");
}
public function getPlatby(int $fakturaId): array
{
return $this->kufrikExplorer->query('
SELECT PL.*
FROM dbo.PLATBA PL
INNER JOIN dbo.FAKTURA F ON F.ID = PL.FAKTURA
WHERE PL.FAKTURA = ?
ORDER BY PL.DATUM, PL.ID
', $fakturaId)->fetchAll();
}
public function sumaPlateb(int $fakturaId): float
{
$row = $this->kufrikExplorer->query('
SELECT ISNULL(SUM(PL.CASTKA), 0) AS ZAPLACENO
FROM dbo.PLATBA PL
INNER JOIN dbo.FAKTURA F ON F.ID = PL.FAKTURA
WHERE PL.FAKTURA = ?
', $fakturaId)->fetch();
return (float) ($row->ZAPLACENO ?? 0);
}
/**
* @return string Nový stav faktury
*/
public function addPlatba(int $fakturaId, float $castka, \DateTimeInterface $datum, ?string $poznamka): string
{
if (!$this->getById($fakturaId)) {
throw new \InvalidArgumentException('Faktura nebyla nalezena.');
}
if ($castka <= 0) {
throw new \InvalidArgumentException('Částka platby musí být větší než 0.');
}
$poznamka = trim((string) $poznamka);
if (mb_strlen($poznamka) > 2000) {
$poznamka = mb_substr($poznamka, 0, 2000);
}
$this->kufrikExplorer->table('PLATBA')->insert([
'FAKTURA' => $fakturaId,
'CASTKA' => round($castka, 2),
'DATUM' => $datum->format('Y-m-d'),
'POZNAMKA' => $poznamka !== '' ? $poznamka : null,
]);
return $this->syncStav($fakturaId);
}
public function updatePlatba(int $id, float $castka, \DateTimeInterface $datum, ?string $poznamka): ?int
{
$row = $this->kufrikExplorer->query('
SELECT PL.ID, PL.FAKTURA
FROM dbo.PLATBA PL
INNER JOIN dbo.FAKTURA F ON F.ID = PL.FAKTURA
WHERE PL.ID = ?
', $id)->fetch();
if (!$row) {
return null;
}
if ($castka <= 0) {
throw new \InvalidArgumentException('Částka platby musí být větší než 0.');
}
$poznamka = trim((string) $poznamka);
if (mb_strlen($poznamka) > 2000) {
$poznamka = mb_substr($poznamka, 0, 2000);
}
$this->kufrikExplorer->table('PLATBA')->where('ID', (int) $row->ID)->update([
'CASTKA' => round($castka, 2),
'DATUM' => $datum->format('Y-m-d'),
'POZNAMKA' => $poznamka !== '' ? $poznamka : null,
]);
$this->syncStav((int) $row->FAKTURA);
return (int) $row->FAKTURA;
}
public function deletePlatba(int $id): ?int
{
$row = $this->kufrikExplorer->query('
SELECT PL.ID, PL.FAKTURA
FROM dbo.PLATBA PL
INNER JOIN dbo.FAKTURA F ON F.ID = PL.FAKTURA
WHERE PL.ID = ?
', $id)->fetch();
if (!$row) {
return null;
}
$this->kufrikExplorer->table('PLATBA')->where('ID', (int) $row->ID)->delete();
$this->syncStav((int) $row->FAKTURA);
return (int) $row->FAKTURA;
}
public function syncStav(int $fakturaId): string
{
$faktura = $this->getById($fakturaId);
if (!$faktura) { if (!$faktura) {
return ''; return '';
} }
$novyStav = ($faktura->STAV === 'PAID') ? 'ISSUED' : 'PAID'; $celkem = 0.0;
foreach ($this->getPolozky($fakturaId) as $polozka) {
$celkem += (float) $polozka->CENA;
}
$stav = self::stavZCastek($celkem, $this->sumaPlateb($fakturaId));
$this->kufrikExplorer->table('FAKTURA') $this->kufrikExplorer->table('FAKTURA')
->where('ID', $id) ->where('ID', $fakturaId)
->update(['STAV' => $novyStav]); ->update(['STAV' => $stav]);
return $novyStav; return $stav;
} }
public function delete(int $id): void public function delete(int $id): void

View File

@ -16,7 +16,7 @@ class SdfCompactExporter
/** /**
* Přečte SQL Compact .sdf a vrátí tabulky jako pole. * Přečte SQL Compact .sdf a vrátí tabulky jako pole.
* *
* @return array{uzivatel: array, zakaznik: array, faktura: array, polozka: array, platba: array} * @return array{uzivatel: array, zakaznik: array, faktura: array, polozka: array, platba: array, konfigurace: array}
*/ */
public function export(string $sdfPath): array public function export(string $sdfPath): array
{ {
@ -45,6 +45,7 @@ class SdfCompactExporter
'faktura' => $data['faktura'] ?? [], 'faktura' => $data['faktura'] ?? [],
'polozka' => $data['polozka'] ?? [], 'polozka' => $data['polozka'] ?? [],
'platba' => $data['platba'] ?? [], 'platba' => $data['platba'] ?? [],
'konfigurace' => $data['konfigurace'] ?? [],
]; ];
} finally { } finally {
FileSystem::delete($workDir); FileSystem::delete($workDir);

View File

@ -4,6 +4,8 @@ namespace App\Model\Sdf;
use App\Model\Database\KufrikConnectionFactory; use App\Model\Database\KufrikConnectionFactory;
use App\Model\Facade\DatabazeFacade; use App\Model\Facade\DatabazeFacade;
use App\Model\Facade\FakturaFacade;
use App\Model\Faktura\FakturaCislovani;
use Nette\Database\Explorer; use Nette\Database\Explorer;
class SdfKufrikImporter class SdfKufrikImporter
@ -24,7 +26,8 @@ class SdfKufrikImporter
* fakturyInserted: int, * fakturyInserted: int,
* fakturySkipped: int, * fakturySkipped: int,
* polozkyInserted: int, * polozkyInserted: int,
* platbyInserted: int * platbyInserted: int,
* nastaveniImported: bool
* } * }
*/ */
public function import(int $loginId): array public function import(int $loginId): array
@ -46,6 +49,7 @@ class SdfKufrikImporter
'faktura' => $this->rows($exported['faktura']), 'faktura' => $this->rows($exported['faktura']),
'polozka' => $this->rows($exported['polozka']), 'polozka' => $this->rows($exported['polozka']),
'platba' => $this->rows($exported['platba']), 'platba' => $this->rows($exported['platba']),
'konfigurace' => $this->rows($exported['konfigurace'] ?? []),
]; ];
$firmaId = (int) $login->FIRMA_ID; $firmaId = (int) $login->FIRMA_ID;
$explorer = $this->kufrikConnectionFactory->createTenantExplorer( $explorer = $this->kufrikConnectionFactory->createTenantExplorer(
@ -58,6 +62,7 @@ class SdfKufrikImporter
try { try {
$this->wipeTenantData($explorer, $firmaId); $this->wipeTenantData($explorer, $firmaId);
$firmaState = $this->importFirma($explorer, $data['uzivatel'][0] ?? null, $firmaId); $firmaState = $this->importFirma($explorer, $data['uzivatel'][0] ?? null, $firmaId);
$nastaveniImported = $this->importNastaveni($explorer, $data['konfigurace'], $firmaId);
$zakaznikMap = $this->importZakaznici($explorer, $data['zakaznik'], $firmaId); $zakaznikMap = $this->importZakaznici($explorer, $data['zakaznik'], $firmaId);
$fakturaResult = $this->importFaktury($explorer, $data, $firmaId, $zakaznikMap); $fakturaResult = $this->importFaktury($explorer, $data, $firmaId, $zakaznikMap);
$explorer->getConnection()->commit(); $explorer->getConnection()->commit();
@ -75,6 +80,7 @@ class SdfKufrikImporter
'fakturySkipped' => $fakturaResult['skipped'], 'fakturySkipped' => $fakturaResult['skipped'],
'polozkyInserted' => $fakturaResult['polozky'], 'polozkyInserted' => $fakturaResult['polozky'],
'platbyInserted' => $fakturaResult['platby'], 'platbyInserted' => $fakturaResult['platby'],
'nastaveniImported' => $nastaveniImported,
]; ];
$this->databazeFacade->markSdfImported($loginId, $stats); $this->databazeFacade->markSdfImported($loginId, $stats);
@ -139,6 +145,79 @@ class SdfKufrikImporter
return 'inserted'; return 'inserted';
} }
/**
* Maska, období číslování a splatnost ze staré tabulky KONFIGURACE.
*
* @param array<int, array<string, mixed>> $rows
*/
private function importNastaveni(Explorer $explorer, array $rows, int $firmaId): bool
{
$xml = null;
foreach ($rows as $row) {
$data = $row['DATA'] ?? null;
if (is_string($data) && trim($data) !== '') {
$xml = $data;
break;
}
}
if ($xml === null) {
return false;
}
$nastaveni = $this->parseKonfigurace($xml);
if ($nastaveni === null) {
return false;
}
$updated = $explorer->table('FIRMA')->where('ID', $firmaId)->update([
'FAKTURA_MASKA' => $nastaveni['maska'],
'FAKTURA_CISLOVAT' => $nastaveni['obdobi'],
'FAKTURA_SPLATNOST' => $nastaveni['splatnost'],
]);
return $updated > 0;
}
/**
* @return array{maska: string, obdobi: string, splatnost: int}|null
*/
private function parseKonfigurace(string $xml): ?array
{
$xml = preg_replace('/encoding="utf-16"/i', 'encoding="utf-8"', $xml) ?? $xml;
$doc = @simplexml_load_string($xml);
if ($doc === false) {
return null;
}
$maska = $this->maskaZeSdf((string) ($doc->FAKTURA_CISLOVANI_MASKA ?? ''));
$zpusob = (int) ($doc->FAKTURA_CISLOVANI_ZPUSOB ?? 1);
$splatnost = (int) ($doc->FAKTURA_SPLATNOST ?? FakturaCislovani::SPLATNOST_VYCHOZI);
return [
'maska' => $maska,
'obdobi' => $zpusob === 2 ? 'MESIC' : 'ROK',
'splatnost' => FakturaCislovani::normalizeSplatnost($splatnost),
];
}
/**
* Stará maska používá (ROK), (MESIC), (CISLO2), (CISLO4) a (CISLO6).
*/
private function maskaZeSdf(string $maska): string
{
$maska = trim($maska);
if ($maska === '') {
return FakturaCislovani::MASKA_VYCHOZI;
}
$prevedena = preg_replace('/\((ROK|MESIC|CISLO[246])\)/', '{$1}', $maska) ?? $maska;
if (FakturaCislovani::validateMaska($prevedena) !== null) {
return FakturaCislovani::MASKA_VYCHOZI;
}
return $prevedena;
}
/** /**
* @param array<int, array<string, mixed>> $rows * @param array<int, array<string, mixed>> $rows
* @return array{map: array<int, int>, inserted: int, matched: int} * @return array{map: array<int, int>, inserted: int, matched: int}
@ -211,7 +290,14 @@ class SdfKufrikImporter
continue; continue;
} }
$hasPlatba = !empty($platbyByFaktura[$oldFakturaId]); $celkemPolozek = 0.0;
foreach ($polozkyByFaktura[$oldFakturaId] ?? [] as $polozkaSoucet) {
$celkemPolozek += (float) ($polozkaSoucet['CENA'] ?? 0);
}
$zaplaceno = 0.0;
foreach ($platbyByFaktura[$oldFakturaId] ?? [] as $platbaSoucet) {
$zaplaceno += (float) ($platbaSoucet['CASTKA'] ?? 0);
}
$dBanka = $this->clip($row['D_BANKA'] ?? null, 100); $dBanka = $this->clip($row['D_BANKA'] ?? null, 100);
$fakturaId = (int) $explorer->table('FAKTURA')->insert([ $fakturaId = (int) $explorer->table('FAKTURA')->insert([
@ -240,7 +326,7 @@ class SdfKufrikImporter
'O_IC' => $this->clip($row['O_IC'] ?? null, 8), 'O_IC' => $this->clip($row['O_IC'] ?? null, 8),
'O_DIC' => $this->clip($row['O_DIC'] ?? null, 10), 'O_DIC' => $this->clip($row['O_DIC'] ?? null, 10),
'CINNOSTI' => $row['CINNOSTI'] ?: null, 'CINNOSTI' => $row['CINNOSTI'] ?: null,
'STAV' => $hasPlatba ? 'PAID' : 'ISSUED', 'STAV' => FakturaFacade::stavZCastek($celkemPolozek, $zaplaceno),
])->ID; ])->ID;
$poradi = 1; $poradi = 1;

View File

@ -102,12 +102,13 @@ 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.', '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.',
$stats['database'], $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'
); );

View File

@ -2,9 +2,11 @@
namespace App\UI\Faktura; namespace App\UI\Faktura;
use App\Model\Faktura\FakturaPdf;
use App\UI\BasePresenter; use App\UI\BasePresenter;
use App\Model\Facade\FakturaFacade; use App\Model\Facade\FakturaFacade;
use Nette\Application\Attributes\Persistent; use Nette\Application\Attributes\Persistent;
use Nette\Application\Responses\CallbackResponse;
class FakturaPresenter extends BasePresenter class FakturaPresenter extends BasePresenter
{ {
@ -18,11 +20,33 @@ class FakturaPresenter extends BasePresenter
public string $mesic = ''; public string $mesic = '';
public function __construct( public function __construct(
private FakturaFacade $fakturaFacade private FakturaFacade $fakturaFacade,
private FakturaPdf $fakturaPdf,
) { ) {
parent::__construct(); parent::__construct();
} }
public function actionPdf(int $id, bool $tisk = false): void
{
$faktura = $this->fakturaFacade->getById($id);
if (!$faktura) {
$this->error('Faktura nebyla nalezena.', 404);
}
$pdf = $this->fakturaPdf->render($faktura, $this->fakturaFacade->getPolozky($id), $tisk);
$fileName = FakturaPdf::fileName((string) $faktura->CISLO);
$disposition = $tisk ? 'inline' : 'attachment';
$this->sendResponse(new CallbackResponse(function ($httpRequest, $httpResponse) use ($pdf, $fileName, $disposition): void {
$httpResponse->setContentType('application/pdf');
$httpResponse->setHeader(
'Content-Disposition',
$disposition . '; filename="' . $fileName . '"'
);
echo $pdf;
}));
}
protected function startup(): void protected function startup(): void
{ {
parent::startup(); parent::startup();
@ -56,21 +80,32 @@ class FakturaPresenter extends BasePresenter
$rok = $this->rok ?? (int) date('Y'); $rok = $this->rok ?? (int) date('Y');
$mesic = $this->normalizeMesic($this->mesic); $mesic = $this->normalizeMesic($this->mesic);
$this->fakturaFacade->reconcileUhrady();
$faktury = $this->fakturaFacade->getFiltered($rok, $mesic); $faktury = $this->fakturaFacade->getFiltered($rok, $mesic);
$celkemSum = 0; $celkemSum = 0;
$zbyvaSum = 0;
$neuhrazenoCount = 0; $neuhrazenoCount = 0;
foreach ($faktury as $f) { foreach ($faktury as $f) {
$celkemSum += (float) ($f->CASTKA_CELKEM ?? 0); $celkem = (float) ($f->CASTKA_CELKEM ?? 0);
if ($f->STAV !== 'PAID') { $zaplaceno = (float) ($f->ZAPLACENO ?? 0);
$zbyva = max(0, round($celkem - $zaplaceno, 2));
$f->STAV_UHRADY = FakturaFacade::stavZCastek($celkem, $zaplaceno);
$f->ZBYVA = $zbyva;
$celkemSum += $celkem;
$zbyvaSum += $zbyva;
if ($f->STAV_UHRADY !== FakturaFacade::STAV_ZAPLACENO) {
$neuhrazenoCount++; $neuhrazenoCount++;
} }
} }
$this->template->faktury = $faktury; $this->template->faktury = $faktury;
$this->template->celkemSum = $celkemSum; $this->template->celkemSum = $celkemSum;
$this->template->zbyvaSum = $zbyvaSum;
$this->template->neuhrazenoCount = $neuhrazenoCount; $this->template->neuhrazenoCount = $neuhrazenoCount;
$this->template->dnes = date('Y-m-d');
$this->template->selectedRok = $rok; $this->template->selectedRok = $rok;
$this->template->selectedMesic = $mesic; $this->template->selectedMesic = $mesic;
$this->template->roky = $this->fakturaFacade->getDostupneRoky($rok); $this->template->roky = $this->fakturaFacade->getDostupneRoky($rok);
@ -146,23 +181,6 @@ class FakturaPresenter extends BasePresenter
]; ];
} }
/**
* Přepnutí stavu zaplaceno / neuhrazeno
*/
public function handleUhradit(int $id): void
{
$novyStav = $this->fakturaFacade->toggleUhrada($id);
if ($novyStav !== '') {
$this->flashMessage(
$novyStav === 'PAID' ? 'Faktura byla označena jako zaplacená.' : 'Stav faktury byl změněn na neuhrazeno.',
'success'
);
}
$this->redirect('this');
}
/** /**
* Smazání faktury * Smazání faktury
*/ */

View File

@ -26,6 +26,12 @@
<div class="fs-4 fw-bold text-warning">{$neuhrazenoCount} ks</div> <div class="fs-4 fw-bold text-warning">{$neuhrazenoCount} 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-danger border-4">
<div class="text-muted small">Zbývá uhradit</div>
<div class="fs-4 fw-bold text-danger">{$zbyvaSum|number:2, ',', ' '} CZK</div>
</div>
</div>
</div> </div>
<!-- Filtrační lišta (Rok / Měsíc) --> <!-- Filtrační lišta (Rok / Měsíc) -->
@ -70,6 +76,7 @@
<th>Vystaveno</th> <th>Vystaveno</th>
<th>Splatnost</th> <th>Splatnost</th>
<th class="text-end">Částka</th> <th class="text-end">Částka</th>
<th class="text-end">Zaplaceno</th>
<th class="text-center">Stav</th> <th class="text-center">Stav</th>
<th class="text-end" data-orderable="false">Akce</th> <th class="text-end" data-orderable="false">Akce</th>
</tr> </tr>
@ -79,7 +86,7 @@
<tr> <tr>
<!-- Číslo dokladu --> <!-- Číslo dokladu -->
<td data-order="{$f->CISLO}"> <td data-order="{$f->CISLO}">
<a n:href="FakturaDetail:default $f->ID" class="fw-bold text-decoration-none"> <a n:href="FakturaEdit:edit $f->ID" class="fw-bold text-decoration-none">
{$f->CISLO} {$f->CISLO}
</a> </a>
</td> </td>
@ -90,38 +97,38 @@
{$f->DATUM_VYSTAVENI|date:'d.m.Y'} {$f->DATUM_VYSTAVENI|date:'d.m.Y'}
</td> </td>
<!-- Datum splatnosti --> <!-- Datum splatnosti -->
<td data-order="{$f->DATUM_SPLATNOSTI|date:'Ymd'}"> <td data-order="{$f->DATUM_SPLATNOSTI|date:'Ymd'}"
class="{if $f->STAV_UHRADY !== 'PAID' && ($f->DATUM_SPLATNOSTI|date:'Y-m-d') < $dnes}text-danger fw-bold{/if}">
{$f->DATUM_SPLATNOSTI|date:'d.m.Y'} {$f->DATUM_SPLATNOSTI|date:'d.m.Y'}
</td> </td>
<!-- Částka celkem --> <!-- Částka celkem -->
<td class="text-end fw-bold" data-order="{$f->CASTKA_CELKEM}"> <td class="text-end fw-bold" data-order="{$f->CASTKA_CELKEM}">
{$f->CASTKA_CELKEM|number:2, ',', ' '} CZK {$f->CASTKA_CELKEM|number:2, ',', ' '} CZK
</td> </td>
<!-- Zaplaceno -->
<td class="text-end {if $f->STAV_UHRADY === 'PAID'}text-success{elseif $f->ZAPLACENO > 0}text-warning{else}text-danger{/if}"
data-order="{$f->ZAPLACENO}">
{$f->ZAPLACENO|number:2, ',', ' '} CZK
</td>
<!-- Stav --> <!-- Stav -->
<td class="text-center" data-order="{$f->STAV}"> <td class="text-center" data-order="{$f->STAV_UHRADY}">
<span class="badge {if $f->STAV === 'PAID'}bg-success{else}bg-warning text-dark{/if}"> <span class="badge {if $f->STAV_UHRADY === 'PAID'}bg-success{elseif $f->STAV_UHRADY === 'PARTIAL'}bg-info text-dark{else}bg-warning text-dark{/if}">
{if $f->STAV === 'PAID'}Zaplaceno{else}Neuhrazeno{/if} {if $f->STAV_UHRADY === 'PAID'}Zaplaceno{elseif $f->STAV_UHRADY === 'PARTIAL'}Částečně{else}Neuhrazeno{/if}
</span> </span>
</td> </td>
<!-- Sloupec AKCE --> <!-- Sloupec AKCE -->
<td class="text-end text-nowrap"> <td class="text-end text-nowrap">
<!-- Přepnutí stavu úhrady --> <a href="{link FakturaEdit:edit $f->ID}#uhrady"
<a n:href="uhradit! $f->ID" class="btn btn-sm {if $f->STAV_UHRADY === 'PAID'}btn-outline-secondary{else}btn-outline-success{/if} me-1"
class="btn btn-sm {if $f->STAV === 'PAID'}btn-outline-warning{else}btn-outline-success{/if} me-1" title="Upravit úhrady">
title="{if $f->STAV === 'PAID'}Označit jako neuhrazeno{else}Označit jako zaplaceno{/if}"> <i class="bi bi-cash-coin"></i>
<i class="bi {if $f->STAV === 'PAID'}bi-x-circle{else}bi-check-circle{/if}"></i>
</a> </a>
<!-- Detail faktury --> <a n:href="pdf $f->ID" class="btn btn-sm btn-outline-secondary me-1" title="Stáhnout PDF">
<a n:href="FakturaDetail:default $f->ID" class="btn btn-sm btn-outline-info me-1" title="Zobrazit detail">
<i class="bi bi-eye"></i>
</a>
<a n:href="FakturaDetail:pdf $f->ID" class="btn btn-sm btn-outline-secondary me-1" title="Stáhnout PDF">
<i class="bi bi-file-earmark-pdf"></i> <i class="bi bi-file-earmark-pdf"></i>
</a> </a>
<a n:href="FakturaDetail:pdf $f->ID, tisk => true" target="_blank" class="btn btn-sm btn-outline-dark me-1" title="Tisk"> <a n:href="pdf $f->ID, tisk => true" target="_blank" class="btn btn-sm btn-outline-dark me-1" title="Tisk">
<i class="bi bi-printer"></i> <i class="bi bi-printer"></i>
</a> </a>

View File

@ -1,61 +0,0 @@
<?php
namespace App\UI\FakturaDetail;
use App\Model\Faktura\FakturaPdf;
use App\UI\BasePresenter;
use App\Model\Facade\FakturaFacade;
use Nette\Application\Responses\CallbackResponse;
class FakturaDetailPresenter extends BasePresenter
{
public function __construct(
private FakturaFacade $fakturaFacade,
private FakturaPdf $fakturaPdf,
) {
parent::__construct();
}
public function actionPdf(int $id, bool $tisk = false): void
{
$faktura = $this->fakturaFacade->getById($id);
if (!$faktura) {
$this->error('Faktura nebyla nalezena.', 404);
}
$pdf = $this->fakturaPdf->render($faktura, $this->fakturaFacade->getPolozky($id), $tisk);
$fileName = FakturaPdf::fileName((string) $faktura->CISLO);
$disposition = $tisk ? 'inline' : 'attachment';
$this->sendResponse(new CallbackResponse(function ($httpRequest, $httpResponse) use ($pdf, $fileName, $disposition): void {
$httpResponse->setContentType('application/pdf');
$httpResponse->setHeader(
'Content-Disposition',
$disposition . '; filename="' . $fileName . '"'
);
echo $pdf;
}));
}
public function renderDefault(int $id): void
{
$faktura = $this->fakturaFacade->getById($id);
if (!$faktura) {
$this->flashMessage('Faktura nebyla nalezena.', 'danger');
$this->redirect('Faktura:default');
}
$polozky = $this->fakturaFacade->getPolozky($id);
// Výpočet celkové částky
$celkem = 0;
foreach ($polozky as $p) {
$celkem += (float) $p->CENA;
}
$this->template->faktura = $faktura;
$this->template->polozky = $polozky;
$this->template->celkem = $celkem;
}
}

View File

@ -1,153 +0,0 @@
{block content}
<div class="container-fluid py-3">
<!-- Ovládací lišta (při tisku se schová) -->
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<div>
<a n:href="Faktura:default" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>Zpět na seznam
</a>
</div>
<div>
<a n:href="FakturaEdit:edit $faktura->ID" class="btn btn-outline-primary me-2">
<i class="bi bi-pencil me-1"></i>Upravit
</a>
<a n:href="pdf $faktura->ID" class="btn btn-outline-secondary me-2">
<i class="bi bi-file-earmark-pdf me-1"></i>PDF
</a>
<a n:href="pdf $faktura->ID, tisk => true" target="_blank" class="btn btn-success">
<i class="bi bi-printer me-1"></i>Tisk
</a>
</div>
</div>
<!-- Tělo faktury (Formát A4 náhled) -->
<div class="card shadow-sm border-0 p-4 p-md-5 bg-white mx-auto invoice-box" style="max-width: 900px;">
<!-- Hlavička faktury -->
<div class="row pb-4 mb-4 border-bottom">
<div class="col-6">
<h1 class="h3 fw-bold mb-1">FAKTURA - DAŇOVÝ DOKLAD</h1>
<div class="fs-5 text-muted">Číslo: <strong>{$faktura->CISLO}</strong></div>
</div>
<div class="col-6 text-end">
<span class="badge {if $faktura->STAV === 'PAID'}bg-success{else}bg-warning text-dark{/if} fs-6 p-2 d-print-none">
{if $faktura->STAV === 'PAID'}UHRATENO{else}NEUHRATENO{/if}
</span>
</div>
</div>
<!-- Dodavatel a Odběratel (ze snapshotů D_* a O_*) -->
<div class="row mb-4">
<!-- DODAVATEL -->
<div class="col-6">
<h6 class="text-uppercase text-muted fw-bold mb-2">Dodavatel</h6>
<div class="fw-bold fs-5">{$faktura->D_PRAJM ?: '—'}</div>
<div>{$faktura->D_ULICE}</div>
<div>{$faktura->D_PSC} {$faktura->D_MESTO}</div>
<div class="mt-2">
{if $faktura->D_IC}<strong>IČ:</strong> {$faktura->D_IC}<br>{/if}
{if $faktura->D_DIC}<strong>DIČ:</strong> {$faktura->D_DIC}<br>{/if}
</div>
{if $faktura->D_BANKA}
<div class="mt-2">
<strong>Bankovní účet:</strong><br>
<span class="fw-bold">{$faktura->D_BANKA}</span>
</div>
{/if}
</div>
<!-- ODBĚRATELE -->
<div class="col-6">
<h6 class="text-uppercase text-muted fw-bold mb-2">Odběratel</h6>
<div class="fw-bold fs-5">{$faktura->O_PRAJM ?: '—'}</div>
<div>{$faktura->O_ULICE}</div>
<div>{$faktura->O_PSC} {$faktura->O_MESTO}</div>
<div class="mt-2">
{if $faktura->O_IC}<strong>IČ:</strong> {$faktura->O_IC}<br>{/if}
{if $faktura->O_DIC}<strong>DIČ:</strong> {$faktura->O_DIC}<br>{/if}
</div>
</div>
</div>
<!-- Datuma -->
<div class="row my-3 p-3 bg-light rounded text-center">
<div class="col-4">
<div class="text-muted small">Datum vystavení:</div>
<div class="fw-bold">{$faktura->DATUM_VYSTAVENI|date:'d.m.Y'}</div>
</div>
<div class="col-4">
<div class="text-muted small">Datum DUZP:</div>
<div class="fw-bold">{if $faktura->DATUM_DUZP}{$faktura->DATUM_DUZP|date:'d.m.Y'}{else}—{/if}</div>
</div>
<div class="col-4">
<div class="text-muted small">Datum splatnosti:</div>
<div class="fw-bold text-danger">{$faktura->DATUM_SPLATNOSTI|date:'d.m.Y'}</div>
</div>
</div>
<!-- Činnosti / Popis -->
<div n:if="$faktura->CINNOSTI" class="mb-4">
<h6 class="text-uppercase text-muted fw-bold mb-1">Popis plnění / Činnosti:</h6>
<p class="mb-0 text-break" style="white-space: pre-line;">{$faktura->CINNOSTI}</p>
</div>
<!-- Tabulka položek -->
<div class="table-responsive my-4">
<table class="table table-striped align-middle">
<thead class="table-light">
<tr>
<th style="width: 40px;" class="text-center">#</th>
<th>Položka</th>
<th style="width: 90px;" class="text-end">Počet</th>
<th style="width: 70px;">Jedn.</th>
<th style="width: 120px;" class="text-end">Cena/J.</th>
<th style="width: 80px;" class="text-end">Sleva</th>
<th style="width: 130px;" class="text-end">Celkem</th>
</tr>
</thead>
<tbody>
{foreach $polozky as $p}
<tr>
<td class="text-center text-muted fw-bold">{$p->PORADI}</td>
<td>{$p->NAZEV}</td>
<td class="text-end">{$p->POCET}</td>
<td>{$p->JEDNOTKA}</td>
<td class="text-end">{$p->CENA_JEDNOTKA|number:2, ',', ' '} CZK</td>
<td class="text-end">{if $p->SLEVA > 0}{$p->SLEVA} %{else}—{/if}</td>
<td class="text-end fw-bold">{$p->CENA|number:2, ',', ' '} CZK</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
<!-- Rekapitulace a součet -->
<div class="row justify-content-end my-3">
<div class="col-md-5">
<div class="d-flex justify-content-between align-items-center p-3 bg-primary text-white rounded">
<span class="fs-5 fw-bold">Celkem k úhradě:</span>
<span class="fs-4 fw-bold">{$celkem|number:2, ',', ' '} CZK</span>
</div>
</div>
</div>
</div>
</div>
<style>
@media print {
body {
background: #fff !important;
}
.d-print-none {
display: none !important;
}
.invoice-box {
box-shadow: none !important;
padding: 0 !important;
max-width: 100% !important;
}
}
</style>

View File

@ -89,8 +89,46 @@ class FakturaEditPresenter extends BasePresenter
{ {
$this->template->isEdit = (bool) $this->id; $this->template->isEdit = (bool) $this->id;
// Pokud upravujeme, načteme stávající položky, jinak připravíme prázdné pole pro JS/Latte $polozky = $this->id ? $this->fakturaFacade->getPolozky($this->id) : [];
$this->template->polozky = $this->id ? $this->fakturaFacade->getPolozky($this->id) : []; $platby = $this->id ? $this->fakturaFacade->getPlatby($this->id) : [];
$celkem = 0.0;
foreach ($polozky as $polozka) {
$celkem += (float) $polozka->CENA;
}
$zaplaceno = 0.0;
foreach ($platby as $platba) {
$zaplaceno += (float) $platba->CASTKA;
}
$splatnostIso = date('Y-m-d');
if ($this->id) {
$faktura = $this->fakturaFacade->getById($this->id);
if ($faktura && $faktura->DATUM_SPLATNOSTI) {
$splatnostIso = $faktura->DATUM_SPLATNOSTI->format('Y-m-d');
}
}
$this->template->polozky = $polozky;
$this->template->platby = $platby;
$this->template->celkemFaktury = $celkem;
$this->template->zaplaceno = $zaplaceno;
$zbyva = max(0, round($celkem - $zaplaceno, 2));
$this->template->zbyva = $zbyva;
$this->template->zbyvaInput = $zbyva > 0 ? number_format($zbyva, 2, '.', '') : '';
$this->template->stavUhrady = FakturaFacade::stavZCastek($celkem, $zaplaceno);
$this->template->splatnostIso = $splatnostIso;
}
public function handleSmazatPlatbu(int $platbaId): void
{
if ($this->fakturaFacade->deletePlatba($platbaId) === null) {
$this->flashMessage('Platbu se nepodařilo smazat.', 'danger');
} else {
$this->flashMessage('Platba byla smazána.', 'info');
}
$this->redirect('this');
} }
protected function createComponentFakturaForm(): Form protected function createComponentFakturaForm(): Form
@ -136,11 +174,6 @@ class FakturaEditPresenter extends BasePresenter
$form->addText('DATUM_DUZP', 'Datum UZP:') $form->addText('DATUM_DUZP', 'Datum UZP:')
->setHtmlType('date'); ->setHtmlType('date');
$form->addSelect('STAV', 'Stav:', [
'ISSUED' => 'Vystaveno (Neuhrazeno)',
'PAID' => 'Zaplaceno',
])->setDefaultValue('ISSUED');
$form->addTextArea('CINNOSTI', 'Činnosti / Popis plnění:'); $form->addTextArea('CINNOSTI', 'Činnosti / Popis plnění:');
$form->addSubmit('save', 'Uložit fakturu') $form->addSubmit('save', 'Uložit fakturu')
@ -164,6 +197,7 @@ class FakturaEditPresenter extends BasePresenter
// 3. Uložení položek s automatickým výpočtem PORADI a celkové ceny // 3. Uložení položek s automatickým výpočtem PORADI a celkové ceny
$this->fakturaFacade->savePolozky($fakturaId, $polozkyRaw, $firmaId); $this->fakturaFacade->savePolozky($fakturaId, $polozkyRaw, $firmaId);
$this->fakturaFacade->syncStav($fakturaId);
$this->flashMessage( $this->flashMessage(
$this->id ? 'Faktura byla úspěšně upravena.' : 'Nová faktura byla vystavena.', $this->id ? 'Faktura byla úspěšně upravena.' : 'Nová faktura byla vystavena.',
@ -172,4 +206,98 @@ class FakturaEditPresenter extends BasePresenter
$this->redirect('Faktura:default'); $this->redirect('Faktura:default');
} }
protected function createComponentPlatbyForm(): Form
{
$form = new Form;
$platby = $form->addContainer('platby');
if ($this->id) {
foreach ($this->fakturaFacade->getPlatby($this->id) as $platba) {
$datum = $platba->DATUM instanceof \DateTimeInterface
? $platba->DATUM->format('Y-m-d')
: substr((string) $platba->DATUM, 0, 10);
$row = $platby->addContainer((string) $platba->ID);
$row->addText('castka')
->setHtmlType('number')
->setHtmlAttribute('step', '0.01')
->setHtmlAttribute('min', '0.01')
->setDefaultValue(number_format((float) $platba->CASTKA, 2, '.', ''))
->setRequired('Zadejte částku platby.')
->addRule(Form::FLOAT, 'Částka musí být číslo.')
->addRule(Form::MIN, 'Částka musí být větší než 0.', 0.01);
$row->addText('datum')
->setHtmlType('date')
->setDefaultValue($datum)
->setRequired('Zadejte datum platby.');
$row->addText('poznamka')
->setDefaultValue((string) ($platba->POZNAMKA ?? ''));
}
}
$nova = $form->addContainer('nova');
$nova->addText('castka')
->setHtmlType('number')
->setHtmlAttribute('step', '0.01')
->setHtmlAttribute('min', '0.01')
->setHtmlAttribute('id', 'edit-platba-castka')
->setHtmlAttribute('placeholder', 'Částka')
->addCondition(Form::FILLED)
->addRule(Form::FLOAT, 'Částka musí být číslo.')
->addRule(Form::MIN, 'Částka musí být větší než 0.', 0.01);
$nova->addText('datum')
->setHtmlType('date')
->setHtmlAttribute('id', 'edit-platba-datum')
->setDefaultValue(date('Y-m-d'));
$nova->addText('poznamka')
->setHtmlAttribute('id', 'edit-platba-poznamka');
$form->addSubmit('save', 'Uložit platby')
->setHtmlAttribute('class', 'btn btn-success');
$form->onSuccess[] = $this->platbyFormSucceeded(...);
return $form;
}
public function platbyFormSucceeded(Form $form, array $data): void
{
if (!$this->id) {
$form->addError('Platby lze zadat až u uložené faktury.');
return;
}
try {
foreach ($data['platby'] ?? [] as $platbaId => $platba) {
$this->fakturaFacade->updatePlatba(
(int) $platbaId,
(float) $platba['castka'],
new \DateTimeImmutable((string) $platba['datum']),
(string) ($platba['poznamka'] ?? '')
);
}
$novaCastka = trim((string) ($data['nova']['castka'] ?? ''));
if ($novaCastka !== '') {
$datumText = trim((string) ($data['nova']['datum'] ?? ''));
$datum = $datumText !== ''
? new \DateTimeImmutable($datumText)
: new \DateTimeImmutable('today');
$this->fakturaFacade->addPlatba(
$this->id,
(float) $novaCastka,
$datum,
(string) ($data['nova']['poznamka'] ?? '')
);
} else {
$this->fakturaFacade->syncStav($this->id);
}
} catch (\Throwable $e) {
$form->addError($e->getMessage());
return;
}
$this->flashMessage('Platby byly uloženy.', 'success');
$this->redirect('this');
}
} }

View File

@ -43,9 +43,14 @@
<label n:name="DATUM_SPLATNOSTI" class="form-label">Datum splatnosti</label> <label n:name="DATUM_SPLATNOSTI" class="form-label">Datum splatnosti</label>
<input n:name="DATUM_SPLATNOSTI" class="form-control"> <input n:name="DATUM_SPLATNOSTI" class="form-control">
</div> </div>
<div class="col-md-4"> <div class="col-md-4" n:if="$isEdit">
<label n:name="STAV" class="form-label">Stav úhrady</label> <label class="form-label">Stav úhrady</label>
<select n:name="STAV" class="form-select"></select> <div class="form-control bg-light">
<span class="badge {if $stavUhrady === 'PAID'}bg-success{elseif $stavUhrady === 'PARTIAL'}bg-info text-dark{else}bg-warning text-dark{/if}">
{if $stavUhrady === 'PAID'}Zaplaceno{elseif $stavUhrady === 'PARTIAL'}Částečně uhrazeno{else}Neuhrazeno{/if}
</span>
<span class="ms-2 text-muted">zbývá {$zbyva|number:2, ',', ' '} CZK</span>
</div>
</div> </div>
<hr class="my-4"> <hr class="my-4">
@ -115,8 +120,94 @@
<a n:href="Faktura:default" class="btn btn-outline-secondary">Zrušit</a> <a n:href="Faktura:default" class="btn btn-outline-secondary">Zrušit</a>
</div> </div>
</form> </form>
<div n:if="$isEdit" id="uhrady" class="mt-4 pt-3 border-top">
<h5 class="mb-3"><i class="bi bi-cash-coin me-2"></i>Úhrady</h5>
<p class="text-muted small">
Celkem {$celkemFaktury|number:2, ',', ' '} CZK,
uhrazeno {$zaplaceno|number:2, ',', ' '} CZK,
zbývá {$zbyva|number:2, ',', ' '} CZK.
</p>
<form n:name="platbyForm">
<div n:if="$presenter['platbyForm']->hasErrors()" class="alert alert-danger">
<ul class="mb-0">
<li n:foreach="$presenter['platbyForm']->errors as $error">{$error}</li>
</ul>
</div>
<div class="table-responsive mb-3">
<table class="table table-sm align-middle">
<thead class="table-light">
<tr>
<th style="width: 160px;">Datum</th>
<th style="width: 160px;" class="text-end">Částka</th>
<th>Poznámka</th>
<th style="width: 50px;"></th>
</tr>
</thead>
<tbody>
<tr n:if="count($platby) === 0">
<td colspan="4" class="text-muted">Zatím žádná platba.</td>
</tr>
<tr n:foreach="$form['platby']->getComponents() as $platbaId => $row">
<td>{input $row['datum'] class: 'form-control'}</td>
<td>{input $row['castka'] class: 'form-control text-end'}</td>
<td>{input $row['poznamka'] class: 'form-control'}</td>
<td class="text-end">
<a n:href="smazatPlatbu! platbaId => $platbaId"
class="btn btn-sm btn-outline-danger"
title="Smazat platbu"
onclick="return confirm('Opravdu smazat tuto platbu?');">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
<tr>
<td>
{input $form['nova']['datum'] class: 'form-control'}
</td>
<td>
{input $form['nova']['castka'] class: 'form-control text-end'}
</td>
<td>
{input $form['nova']['poznamka'] class: 'form-control', placeholder: 'Nová platba'}
</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="mb-3">
<button type="button" class="btn btn-sm btn-outline-secondary me-1" id="edit-platba-dnes" data-zbyva="{$zbyvaInput}">Dnes</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="edit-platba-splatnost" data-date="{$splatnostIso}" data-zbyva="{$zbyvaInput}">Den splatnosti</button>
</div>
<button n:name="save" class="btn btn-success">
<i class="bi bi-check-lg me-1"></i>Uložit platby
</button>
</form>
</div> </div>
</div> </div>
</div>
<script n:if="$isEdit">
function prefillPlatba(date, zbyva) {
if (date) {
document.getElementById('edit-platba-datum').value = date;
}
const castka = Number(zbyva);
document.getElementById('edit-platba-castka').value = castka > 0 ? castka.toFixed(2) : '';
}
document.getElementById('edit-platba-dnes').addEventListener('click', function () {
const d = new Date();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
prefillPlatba(d.getFullYear() + '-' + m + '-' + day, this.getAttribute('data-zbyva'));
});
document.getElementById('edit-platba-splatnost').addEventListener('click', function () {
prefillPlatba(this.getAttribute('data-date'), this.getAttribute('data-zbyva'));
});
</script>
{syntax off} {syntax off}
<script> <script>

View File

@ -38,6 +38,14 @@ function New-ConnectionString([string] $path, [string] $password) {
return $cs return $cs
} }
function Read-TableOptional($connection, [string] $sql) {
try {
return @(Read-Table $connection $sql)
} catch {
return @()
}
}
function Read-Table($connection, [string] $sql) { function Read-Table($connection, [string] $sql) {
$cmd = $connection.CreateCommand() $cmd = $connection.CreateCommand()
$cmd.CommandText = $sql $cmd.CommandText = $sql
@ -123,7 +131,8 @@ try {
zakaznik = @(Read-Table $connection 'SELECT * FROM ZAKAZNIK ORDER BY ID') zakaznik = @(Read-Table $connection 'SELECT * FROM ZAKAZNIK ORDER BY ID')
faktura = @(Read-Table $connection 'SELECT * FROM FAKTURA ORDER BY ID') faktura = @(Read-Table $connection 'SELECT * FROM FAKTURA ORDER BY ID')
polozka = @(Read-Table $connection 'SELECT * FROM POLOZKA ORDER BY ID') polozka = @(Read-Table $connection 'SELECT * FROM POLOZKA ORDER BY ID')
platba = @(Read-Table $connection 'SELECT * FROM PLATBA ORDER BY ID') platba = @(Read-TableOptional $connection 'SELECT * FROM PLATBA ORDER BY ID')
konfigurace = @(Read-TableOptional $connection 'SELECT ID, DATA FROM KONFIGURACE ORDER BY ID')
} }
$json = $payload | ConvertTo-Json -Depth 6 -Compress:$false $json = $payload | ConvertTo-Json -Depth 6 -Compress:$false

View File

@ -206,7 +206,7 @@ CREATE TABLE [dbo].[FAKTURA](
[O_DIC] [nvarchar](10) NULL, [O_DIC] [nvarchar](10) NULL,
[CINNOSTI] [nvarchar](max) NULL, -- Nahrazeno ntext -> nvarchar(max) [CINNOSTI] [nvarchar](max) NULL, -- Nahrazeno ntext -> nvarchar(max)
[STAV] [nvarchar](20) NOT NULL CONSTRAINT [DF_FAKTURA_STAV] DEFAULT ('ISSUED'), [STAV] [nvarchar](20) NOT NULL CONSTRAINT [DF_FAKTURA_STAV] DEFAULT ('ISSUED'), -- ISSUED / PARTIAL / PAID, odvozeno ze součtu PLATBA
CONSTRAINT [PK_FAKTURA] PRIMARY KEY CLUSTERED ([ID] ASC), CONSTRAINT [PK_FAKTURA] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_FAKTURA_FIRMA] FOREIGN KEY ([FIRMA_ID]) REFERENCES [dbo].[FIRMA] ([ID]), CONSTRAINT [FK_FAKTURA_FIRMA] FOREIGN KEY ([FIRMA_ID]) REFERENCES [dbo].[FIRMA] ([ID]),