Položky předvyplněné při kopii (ještě neuložená faktura). */ private array $kopiePolozky = []; private ?int $ensureZakaznikId = null; public function __construct( private FakturaFacade $fakturaFacade, private ZakaznikFacade $zakaznikFacade ) { parent::__construct(); } public function actionNew(): void { $this->id = null; $this->setView('edit'); $firmaId = (int) $this->getUser()->getIdentity()->firma_id; $dalsi = $this->fakturaFacade->getNextCislo($firmaId); $this['fakturaForm']->setDefaults([ 'CISLO' => $dalsi['cislo'], 'CISLO_ABS' => $dalsi['cisloAbs'], 'FORMA_UHRADY' => 1, 'DATUM_SPLATNOSTI' => (new \DateTimeImmutable('today')) ->modify('+' . $dalsi['splatnostDny'] . ' days') ->format('Y-m-d'), ] + $this->fakturaFacade->getDodavatelDefaults($firmaId)); } /** * Kopie existující faktury jako nová (všechna pole editovatelná, uloží se až po uložení). */ public function actionCopy(int $id): void { $this->id = null; $this->setView('edit'); $zdroj = $this->fakturaFacade->getById($id); if (!$zdroj) { $this->flashMessage('Faktura nebyla nalezena.', 'danger'); $this->redirect('Faktura:default'); } $firmaId = (int) $this->getUser()->getIdentity()->firma_id; $dnes = new \DateTimeImmutable('today'); $dalsi = $this->fakturaFacade->getNextCislo($firmaId, $dnes); $datumVystaveni = $dnes->format('Y-m-d'); if ($zdroj->ZAKAZNIK !== null) { $this->ensureZakaznikId = (int) $zdroj->ZAKAZNIK; } $this['fakturaForm']->setDefaults([ 'ZAKAZNIK_ID' => $this->ensureZakaznikId, 'CISLO' => $dalsi['cislo'], 'CISLO_ABS' => $dalsi['cisloAbs'], 'FORMA_UHRADY' => (int) ($zdroj->FORMA_UHRADY ?? 1), 'DATUM_VYSTAVENI' => $datumVystaveni, 'DATUM_SPLATNOSTI' => $dnes ->modify('+' . $dalsi['splatnostDny'] . ' days') ->format('Y-m-d'), 'DATUM_DUZP' => $datumVystaveni, 'CINNOSTI' => $zdroj->CINNOSTI, 'O_PRAJM' => $zdroj->O_PRAJM, 'O_ULICE' => $zdroj->O_ULICE, 'O_MESTO' => $zdroj->O_MESTO, 'O_PSC' => $zdroj->O_PSC, 'O_IC' => $zdroj->O_IC, 'O_DIC' => $zdroj->O_DIC, ] + $this->fakturaFacade->getDodavatelDefaults($firmaId)); $this->kopiePolozky = $this->fakturaFacade->getPolozky($id); } public function actionEdit(int $id): void { $this->id = $id; $faktura = $this->fakturaFacade->getById($id); if (!$faktura) { $this->flashMessage('Faktura nebyla nalezena.', 'danger'); $this->redirect('Faktura:default'); } $defaults = $faktura->toArray(); // Explicitně přiřadíme ID zákazníka if ($faktura->ZAKAZNIK !== null) { $defaults['ZAKAZNIK_ID'] = (int) $faktura->ZAKAZNIK; $this->ensureZakaznikId = (int) $faktura->ZAKAZNIK; } $defaults['FORMA_UHRADY'] = (int) ($faktura->FORMA_UHRADY ?? 1); if ($faktura->DATUM_VYSTAVENI) { $defaults['DATUM_VYSTAVENI'] = $faktura->DATUM_VYSTAVENI->format('Y-m-d'); } if ($faktura->DATUM_SPLATNOSTI) { $defaults['DATUM_SPLATNOSTI'] = $faktura->DATUM_SPLATNOSTI->format('Y-m-d'); } if ($faktura->DATUM_DUZP) { $defaults['DATUM_DUZP'] = $faktura->DATUM_DUZP->format('Y-m-d'); } // 1. Nastavení výchozích hodnot $this['fakturaForm']->setDefaults($defaults); // Společný CSS styl pro zablokované / zešedlé prvky (odpovídá Bootstrap disabled vzhledu) $disabledStyle = 'pointer-events: none; background-color: #e9ecef; opacity: 1;'; // 2. Zamknutí prvků při editaci (Odběratel, Číslo faktury, Datum vystavení) $this['fakturaForm']['ZAKAZNIK_ID'] ->setHtmlAttribute('style', $disabledStyle) ->setHtmlAttribute('tabindex', '-1'); $this['fakturaForm']['CISLO'] ->setHtmlAttribute('readonly', true) ->setHtmlAttribute('style', $disabledStyle) ->setHtmlAttribute('tabindex', '-1'); $this['fakturaForm']['DATUM_VYSTAVENI'] ->setHtmlAttribute('readonly', true) ->setHtmlAttribute('style', $disabledStyle) ->setHtmlAttribute('tabindex', '-1'); } public function renderNew(): void { $this->prepareEditTemplate(); } public function renderCopy(): void { $this->prepareEditTemplate(); } public function renderEdit(?int $id = null): void { $this->prepareEditTemplate(); } private function prepareEditTemplate(): void { $this->template->isEdit = (bool) $this->id; $polozky = $this->id ? $this->fakturaFacade->getPolozky($this->id) : $this->kopiePolozky; $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'); } } $jsonFlags = JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR; $this->template->katalogPolozekJson = json_encode( $this->fakturaFacade->getKatalogPolozek(), $jsonFlags ); $this->template->zakazniciDetailJson = json_encode( $this->zakazniciDetailProJs(), $jsonFlags ); $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 { $form = new Form; $zakaznici = $this->zakaznikFacade->getFiltered('', 'aktivni'); $zakaznikId = $this->ensureZakaznikId ?? 0; if ($zakaznikId) { $uzJeVSeznamu = false; foreach ($zakaznici as $z) { if ((int) $z->ID === $zakaznikId) { $uzJeVSeznamu = true; break; } } if (!$uzJeVSeznamu) { $stavajici = $this->zakaznikFacade->getById($zakaznikId); if ($stavajici) { $zakaznici[] = $stavajici; } } } $zakazniciPairs = []; foreach ($zakaznici as $z) { $zakazniciPairs[(int) $z->ID] = $this->nazevZakaznika($z); } $form->addSelect('ZAKAZNIK_ID', 'Odběratel:', $zakazniciPairs) ->setPrompt('-- Vyberte zákazníka --') ->setRequired('Vyberte odběratele.'); $form->addText('CISLO', 'Číslo faktury:') ->setDefaultValue(date('Y') . '0001'); $form->addHidden('CISLO_ABS'); $form->addText('DATUM_VYSTAVENI', 'Datum vystavení:') ->setHtmlType('date') ->setDefaultValue(date('Y-m-d')); $splatnostDny = 15; $firmaId = $this->getUser()->getIdentity()->firma_id ?? null; if ($firmaId) { $splatnostDny = $this->fakturaFacade->getNastaveni((int) $firmaId)['splatnostDny']; } $form->addText('DATUM_SPLATNOSTI', 'Datum splatnosti:') ->setHtmlType('date') ->setDefaultValue((new \DateTimeImmutable('today'))->modify('+' . $splatnostDny . ' days')->format('Y-m-d')) ->setRequired('Zadejte datum splatnosti.'); $form->addSelect('FORMA_UHRADY', 'Forma úhrady:', [ 1 => 'převodem', 2 => 'hotově', ])->setDefaultValue(1); $form->addText('DATUM_DUZP', 'Datum UZP:') ->setHtmlType('date'); // Detailní informace — dodavatel $form->addText('D_PRAJM', 'Dodavatel:')->setMaxLength(511); $form->addText('D_ULICE', 'Ulice:')->setMaxLength(50); $form->addText('D_PSC', 'PSČ:')->setMaxLength(50); $form->addText('D_MESTO', 'Město:')->setMaxLength(50); $form->addText('D_IC', 'IČ:')->setMaxLength(8); $form->addText('D_DIC', 'DIČ:')->setMaxLength(12); $form->addText('D_TELEFON', 'Telefon:')->setMaxLength(20); $form->addText('D_EMAIL', 'E-mail:')->setMaxLength(100); $form->addText('D_BANKA', 'Bankovní účet:')->setMaxLength(100); $form->addText('D_WEB', 'Web:')->setMaxLength(100); // Detailní informace — odběratel $form->addText('O_PRAJM', 'Odběratel:')->setMaxLength(511); $form->addText('O_ULICE', 'Ulice:')->setMaxLength(50); $form->addText('O_PSC', 'PSČ:')->setMaxLength(5); $form->addText('O_MESTO', 'Město:')->setMaxLength(50); $form->addText('O_IC', 'IČ:')->setMaxLength(8); $form->addText('O_DIC', 'DIČ:')->setMaxLength(10); $form->addTextArea('CINNOSTI', 'Činnosti / Popis plnění:'); $form->addSubmit('save', 'Uložit fakturu') ->setHtmlAttribute('class', 'btn btn-primary'); $form->onSuccess[] = [$this, 'fakturaFormSucceeded']; return $form; } /** * @return array */ private function zakazniciDetailProJs(): array { $map = []; foreach ($this->zakaznikFacade->getFiltered('', 'aktivni') as $z) { $map[(string) $z->ID] = [ 'nazev' => trim(($z->PRIJMENI ?? '') . ' ' . ($z->JMENO ?? '')), 'ulice' => $z->ULICE, 'psc' => $z->PSC, 'mesto' => $z->MESTO, 'ic' => $z->IC, 'dic' => $z->DIC, ]; } $zakaznikId = $this->ensureZakaznikId ?? 0; if ($zakaznikId && !isset($map[(string) $zakaznikId])) { $stavajici = $this->zakaznikFacade->getById($zakaznikId); if ($stavajici) { $map[(string) $zakaznikId] = [ 'nazev' => trim(($stavajici->PRIJMENI ?? '') . ' ' . ($stavajici->JMENO ?? '')), 'ulice' => $stavajici->ULICE, 'psc' => $stavajici->PSC, 'mesto' => $stavajici->MESTO, 'ic' => $stavajici->IC, 'dic' => $stavajici->DIC, ]; } } return $map; } private function nazevZakaznika(object $zakaznik): string { $nazev = trim(($zakaznik->PRIJMENI ?? '') . ' ' . ($zakaznik->JMENO ?? '')); $doplnek = []; if (!empty($zakaznik->MESTO)) { $doplnek[] = (string) $zakaznik->MESTO; } if (!empty($zakaznik->IC)) { $doplnek[] = 'IČ: ' . $zakaznik->IC; } if ($doplnek) { $nazev .= ' (' . implode(', ', $doplnek) . ')'; } return $nazev; } public function fakturaFormSucceeded(Form $form, \stdClass $data): void { $firmaId = $this->getUser()->getIdentity()->firma_id; $httpRequest = $this->getHttpRequest(); // 1. Uložení hlavičky faktury $fakturaId = $this->fakturaFacade->saveFaktura((array) $data, $this->id, $firmaId); // 2. Získání položek z POSTu (z dynamického formuláře z Latte/JS) $polozkyRaw = $httpRequest->getPost('polozky', []); // 3. Uložení položek s automatickým výpočtem PORADI a celkové ceny $this->fakturaFacade->savePolozky($fakturaId, $polozkyRaw, $firmaId); $this->fakturaFacade->syncStav($fakturaId); $this->flashMessage( $this->id ? 'Faktura byla úspěšně upravena.' : 'Nová faktura byla vystavena.', 'success' ); $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'); } }