| Server IP : 193.86.120.172 / Your IP : 216.73.216.215 Web Server : Apache/2.4.63 (Unix) System : Linux JServices 3.10.108 #86003 SMP Wed Oct 22 13:20:46 CST 2025 x86_64 User : kubec ( 1026) PHP Version : 8.2.28 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /volume1/web/hotel_jservices_cz/api/ |
Upload File : |
<?php
// ============================================================
// Hotel DEMO — api/dostupnost.php
// Výpočet volných typů pokojů a dostupnosti kapacitních doplňků
// Veřejný endpoint — nevyžaduje přihlášení
// ============================================================
$action = $_GET['action'] ?? 'pokoje';
match ($action) {
'pokoje' => dos_volne_pokoje(),
'doplnky' => dos_dostupne_doplnky(),
'kalendar' => dos_kalendar(),
'kapacity_tyden' => dos_kapacity_tyden(),
default => api_response(false, null, 'Neznámá akce', 400),
};
// ------------------------------------------------------------
// VOLNÉ TYPY POKOJŮ pro daný termín (veřejný formulář)
// Zákazník vybírá TYP, systém přiřadí konkrétní pokoj automaticky.
// GET ?action=pokoje&datum_od=2025-07-14&datum_do=2025-07-17
// &pocet_hostu=2
// &admin=1 (admin vidí konkrétní pokoje pro interní použití)
// ------------------------------------------------------------
function dos_volne_pokoje(): never {
$datum_od = $_GET['datum_od'] ?? '';
$datum_do = $_GET['datum_do'] ?? '';
$pocet_hostu = (int)($_GET['pocet_hostu'] ?? 1);
$je_admin = !empty($_GET['admin']) && _dos_overit_admina();
if (!$datum_od || !$datum_do) {
api_response(false, null, 'Zadejte datum příjezdu a odjezdu.', 400);
}
if (!_dos_validuj_datum($datum_od) || !_dos_validuj_datum($datum_do)) {
api_response(false, null, 'Neplatný formát data.', 400);
}
if ($datum_od >= $datum_do) {
api_response(false, null, 'Datum odjezdu musí být po datu příjezdu.', 400);
}
$min_predem = (int)nastaveni('rezervace_min_predem_dni', '1');
if (!$je_admin && $datum_od < date('Y-m-d', strtotime("+{$min_predem} days"))) {
api_response(false, null, "Rezervaci lze vytvořit nejdříve {$min_predem} den(dní) předem.", 400);
}
$pocet_noci = _dos_pocet_noci($datum_od, $datum_do);
$vyjmout_rezervaci = (int)($_GET['vyjmout_rezervaci'] ?? 0);
// Obsazené pokoje v daném termínu (vyjma rezervace, kterou právě potvrzujeme)
$obsazene = array_column(db_rows(
"SELECT DISTINCT pokoj_id FROM rezervace
WHERE stav != 'zrusena'
AND id != ?
AND datum_prijezdu < ? AND datum_odjezdu > ?",
[$vyjmout_rezervaci, $datum_do, $datum_od]
), 'pokoj_id');
// Blokované termíny
$blokovane = array_column(db_rows(
"SELECT DISTINCT pokoj_id FROM blokovane_terminy
WHERE datum_od < ? AND datum_do > ?",
[$datum_do, $datum_od]
), 'pokoj_id');
$nedostupne_pokoje = array_unique(array_merge($obsazene, $blokovane));
// Nastavení automatické slevy
$sleva_zapnuta = nastaveni('sleva_jednoluzko_zapnuta', '1') === '1';
$sleva_procent = (int)nastaveni('sleva_jednoluzko_procent', '20');
if ($je_admin) {
// Admin vidí konkrétní pokoje (pro Gantt a interní výběr)
$pokoje = db_rows(
"SELECT p.id, p.cislo, p.nazev, p.aktivni, p.typ_id,
t.nazev AS typ_nazev, t.kapacita, t.cena_za_noc,
t.min_pobyt_noci, t.snidane_dostupne, t.popis
FROM pokoje p
JOIN typy_pokoju t ON t.id = p.typ_id
WHERE p.aktivni = 1 AND t.aktivni = 1
ORDER BY t.poradi ASC, p.poradi ASC, p.cislo ASC"
);
foreach ($pokoje as &$p) {
$p['dostupny'] = !in_array($p['id'], $nedostupne_pokoje);
$p['pocet_noci'] = $pocet_noci;
$p['cena_pobyt'] = round($p['cena_za_noc'] * $pocet_noci, 2);
$p['min_splnen'] = $pocet_noci >= $p['min_pobyt_noci'];
}
unset($p);
api_response(true, [
'pokoje' => $pokoje, 'datum_od' => $datum_od,
'datum_do' => $datum_do, 'pocet_noci' => $pocet_noci,
'pocet_hostu' => $pocet_hostu, 'rezim' => 'admin',
]);
}
// Veřejný formulář — vrací TYPY pokojů
$typy = db_rows(
"SELECT t.id, t.nazev, t.nazev_en, t.nazev_de, t.kapacita, t.cena_za_noc,
t.min_pobyt_noci, t.snidane_dostupne, t.popis, t.popis_en, t.popis_de, t.poradi,
COUNT(p.id) AS pocet_pokoju_celkem
FROM typy_pokoju t
JOIN pokoje p ON p.typ_id = t.id AND p.aktivni = 1
WHERE t.aktivni = 1
GROUP BY t.id
ORDER BY t.poradi ASC, t.nazev ASC"
);
$max_kapacita = 0;
foreach ($typy as $t) {
if ((int)$t['kapacita'] > $max_kapacita) $max_kapacita = (int)$t['kapacita'];
}
foreach ($typy as &$typ) {
$pokoje_typu = array_column(db_rows(
'SELECT id FROM pokoje WHERE typ_id = ? AND aktivni = 1',
[$typ['id']]
), 'id');
$volne = array_filter($pokoje_typu, fn($id) => !in_array($id, $nedostupne_pokoje));
$typ['volnych_pokoju'] = count($volne);
$typ['dostupny'] = count($volne) > 0
&& $pocet_noci >= $typ['min_pobyt_noci']
&& $typ['kapacita'] >= $pocet_hostu;
$typ['min_splnen'] = $pocet_noci >= $typ['min_pobyt_noci'];
$typ['kapacita_splnena'] = (int)$typ['kapacita'] >= $pocet_hostu;
$typ['pocet_noci'] = $pocet_noci;
$cena_zaklad = round($typ['cena_za_noc'] * $pocet_noci, 2);
$typ['sleva_jednoluzko'] = false;
$typ['sleva_jednoluzko_procent'] = 0;
$typ['cena_po_sleve'] = $cena_zaklad;
$typ['cena_pobyt'] = $cena_zaklad;
if ($sleva_zapnuta && $pocet_hostu === 1 && (int)$typ['kapacita'] > 1) {
$sleva_castka = round($cena_zaklad * $sleva_procent / 100, 2);
$typ['sleva_jednoluzko'] = true;
$typ['sleva_jednoluzko_procent'] = $sleva_procent;
$typ['cena_po_sleve'] = round($cena_zaklad - $sleva_castka, 2);
$typ['cena_pobyt'] = $typ['cena_po_sleve'];
$typ['sleva_castka_pobyt'] = $sleva_castka;
}
}
unset($typ);
api_response(true, [
'typy' => $typy,
'datum_od' => $datum_od,
'datum_do' => $datum_do,
'pocet_noci' => $pocet_noci,
'pocet_hostu' => $pocet_hostu,
'max_kapacita' => $max_kapacita,
'rezim' => 'verejny',
]);
}
// ------------------------------------------------------------
// DOSTUPNÉ DOPLŇKY pro daný termín
// ------------------------------------------------------------
function dos_dostupne_doplnky(): never {
$datum_od = $_GET['datum_od'] ?? '';
$datum_do = $_GET['datum_do'] ?? '';
$typ_id = (int)($_GET['typ_id'] ?? 0);
$pocet_hostu = (int)($_GET['pocet_hostu'] ?? 1);
if (!$datum_od || !$datum_do) {
api_response(false, null, 'Zadejte termín.', 400);
}
$pocet_noci = _dos_pocet_noci($datum_od, $datum_do);
// Zjistit zda typ pokoje nabízí snídaně
$snidane_dostupne = true;
if ($typ_id) {
$snidane_dostupne = (bool)db_val(
'SELECT snidane_dostupne FROM typy_pokoju WHERE id = ?',
[$typ_id]
);
}
$doplnky = db_rows('SELECT * FROM doplnky WHERE aktivni = 1 ORDER BY poradi ASC');
$dobijeni_vybrano = false;
$dobijeni_id = null;
$parkovani_id = null;
// Nejdřív zjistíme ID doplňků dobíjení a parkování
foreach ($doplnky as $d) {
$n = mb_strtolower($d['nazev']);
if (str_contains($n, 'dob') || str_contains($n, 'elektro') || str_contains($n, 'nabíj')) {
$dobijeni_id = $d['id'];
}
if (str_contains($n, 'parkov')) {
$parkovani_id = $d['id'];
}
}
foreach ($doplnky as &$d) {
$d['dostupny'] = true;
$noci_pro_vypocet = in_array($d['typ_uctovani'], ['za_noc', 'za_osobu_noc'], true) ? $pocet_noci : 1;
$hostu_pro_vypocet = $d['typ_uctovani'] === 'za_osobu_noc' ? $pocet_hostu : 1;
$d['cena_celkem'] = round($d['cena'] * $noci_pro_vypocet * $hostu_pro_vypocet, 2);
$n = mb_strtolower($d['nazev']);
// Snídaně — filtr dle typu pokoje
if (str_contains($n, 'snídaně') || str_contains($n, 'snidane')) {
if (!$snidane_dostupne) {
$d['dostupny'] = false;
$d['duvod_nedostupnosti'] = 'Pro tento typ pokoje není snídaně k dispozici.';
continue;
}
}
// Kapacitní doplňky — ověřit obsazenost
if ($d['kapacita'] !== null) {
$obsazeno = (int)db_val(
"SELECT COUNT(*) FROM rezervace_doplnky rd
JOIN rezervace r ON r.id = rd.rezervace_id
WHERE rd.doplnek_id = ?
AND r.stav != 'zrusena'
AND r.datum_prijezdu < ? AND r.datum_odjezdu > ?",
[$d['id'], $datum_do, $datum_od]
);
$zbyva = (int)$d['kapacita'] - $obsazeno;
$d['kapacita_obsazeno'] = $obsazeno;
$d['kapacita_zbyva'] = max(0, $zbyva);
if ($zbyva <= 0) {
$d['dostupny'] = false;
$d['duvod_nedostupnosti'] = 'Kapacita je pro tento termín vyčerpána.';
}
}
}
unset($d);
api_response(true, [
'doplnky' => $doplnky,
'pocet_noci' => $pocet_noci,
'dobijeni_id' => $dobijeni_id,
'parkovani_id' => $parkovani_id,
]);
}
// ------------------------------------------------------------
// KALENDÁŘ OBSAZENOSTI (pro admin Gantt)
// ------------------------------------------------------------
function dos_kapacity_tyden(): never {
$user = auth_check();
$datum_od = $_GET['datum_od'] ?? date('Y-m-d');
$pocet_dni = max(1, min(31, (int)($_GET['pocet_dni'] ?? 7)));
$datum_do_excl = date('Y-m-d', strtotime($datum_od . " +{$pocet_dni} days"));
$parkovani_kap = (int)nastaveni('parkovani_kapacita', '5');
$dobijeni_kap = (int)nastaveni('dobijeni_kapacita', '2');
// Rezervace zasahující do rozsahu
$rezervace = db_rows(
"SELECT r.id, r.jmeno, r.prijmeni, r.datum_prijezdu, r.datum_odjezdu, r.pocet_hostu
FROM rezervace r
WHERE r.stav != 'zrusena'
AND r.datum_prijezdu < ?
AND r.datum_odjezdu > ?",
[$datum_do_excl, $datum_od]
);
$rez_ids = array_column($rezervace, 'id');
$rez_doplnky_map = [];
if ($rez_ids) {
$placeholders = implode(',', array_fill(0, count($rez_ids), '?'));
$rez_doplnky = db_rows(
"SELECT rd.rezervace_id, rd.nazev
FROM rezervace_doplnky rd
WHERE rd.rezervace_id IN ($placeholders)",
$rez_ids
);
foreach ($rez_doplnky as $rd) {
$rez_doplnky_map[$rd['rezervace_id']][] = strtolower($rd['nazev']);
}
}
$rozpoznat_typy = function(array $nazvy): array {
$typy = [];
foreach ($nazvy as $n) {
if (str_contains($n, 'park')) $typy[] = 'parking';
elseif (str_contains($n, 'dobíj') || str_contains($n, 'dobij') || str_contains($n, 'elektro')) $typy[] = 'charging';
elseif (str_contains($n, 'snídaně') || str_contains($n, 'snidane') || str_contains($n, 'snídaň')) $typy[] = 'breakfast';
}
return array_unique($typy);
};
$dny = [];
$d = $datum_od;
for ($i = 0; $i < $pocet_dni; $i++) {
$parkObsaz = 0; $dobiObsaz = 0; $snidanePorci = 0;
$parkHoste = []; $dobiHoste = []; $snidaneHoste = [];
foreach ($rezervace as $rez) {
if ($rez['datum_prijezdu'] > $d || $rez['datum_odjezdu'] <= $d) continue;
$jmeno = $rez['jmeno'] . ' ' . $rez['prijmeni'];
$typy = $rozpoznat_typy($rez_doplnky_map[$rez['id']] ?? []);
if (in_array('parking', $typy, true)) { $parkObsaz++; $parkHoste[] = $jmeno; }
if (in_array('charging', $typy, true)) { $dobiObsaz++; $dobiHoste[] = $jmeno; }
if (in_array('breakfast', $typy, true)) { $snidanePorci += (int)$rez['pocet_hostu']; $snidaneHoste[] = ['jmeno' => $jmeno, 'pocet' => (int)$rez['pocet_hostu']]; }
}
$dny[] = [
'datum' => $d,
'parkovani' => ['obsazeno' => $parkObsaz, 'kapacita' => $parkovani_kap, 'hoste' => $parkHoste],
'dobijeni' => ['obsazeno' => $dobiObsaz, 'kapacita' => $dobijeni_kap, 'hoste' => $dobiHoste],
'snidane' => ['porci' => $snidanePorci, 'hoste' => $snidaneHoste],
];
$d = date('Y-m-d', strtotime($d . ' +1 day'));
}
api_response(true, ['dny' => $dny]);
}
function dos_kalendar(): never {
$user = auth_check();
$datum_od = $_GET['datum_od'] ?? date('Y-m-01');
$datum_do = $_GET['datum_do'] ?? date('Y-m-t');
$pokoje = db_rows(
'SELECT p.id, p.cislo, p.nazev, p.aktivni, t.nazev AS typ_nazev
FROM pokoje p
JOIN typy_pokoju t ON t.id = p.typ_id
ORDER BY p.poradi ASC, p.cislo ASC'
);
$rezervace = db_rows(
"SELECT r.id, r.pokoj_id, r.datum_prijezdu, r.datum_odjezdu,
r.jmeno, r.prijmeni, r.stav, r.pocet_noci,
r.checkin_cas, r.checkout_cas,
r.sleva_stav, r.cena_zaklad, r.cena_upravena,
r.pokoj_zadost_stav, r.cas_zadost_stav
FROM rezervace r
WHERE r.stav != 'zrusena'
AND r.datum_prijezdu < ?
AND r.datum_odjezdu > ?
ORDER BY r.datum_prijezdu ASC",
[$datum_do, $datum_od]
);
// Načíst doplňky pro každou rezervaci (ikonky v kalendáři)
$rez_ids = array_column($rezervace, 'id');
$rez_doplnky_map = [];
if ($rez_ids) {
$placeholders = implode(',', array_fill(0, count($rez_ids), '?'));
$rez_doplnky = db_rows(
"SELECT rd.rezervace_id, rd.nazev
FROM rezervace_doplnky rd
WHERE rd.rezervace_id IN ($placeholders)",
$rez_ids
);
foreach ($rez_doplnky as $rd) {
$rez_doplnky_map[$rd['rezervace_id']][] = strtolower($rd['nazev']);
}
}
// Rozpoznání typu doplňku podle názvu
foreach ($rezervace as &$r) {
$nazvy = $rez_doplnky_map[$r['id']] ?? [];
$typy = [];
foreach ($nazvy as $n) {
if (str_contains($n, 'park')) $typy[] = 'parking';
elseif (str_contains($n, 'dobíj') || str_contains($n, 'dobij') || str_contains($n, 'elektro')) $typy[] = 'charging';
elseif (str_contains($n, 'snídaně') || str_contains($n, 'snidane') || str_contains($n, 'snídaň')) $typy[] = 'breakfast';
elseif (str_contains($n, 'check-out') || str_contains($n, 'checkout') || str_contains($n, 'pozdní') || str_contains($n, 'pozdn')) $typy[] = 'late_checkout';
else $typy[] = 'other';
}
$r['doplnky_typy'] = array_values(array_unique($typy));
}
unset($r);
$blokovane = db_rows(
'SELECT * FROM blokovane_terminy
WHERE datum_od < ? AND datum_do > ?
ORDER BY datum_od ASC',
[$datum_do, $datum_od]
);
api_response(true, [
'pokoje' => $pokoje,
'rezervace' => $rezervace,
'blokovane' => $blokovane,
'datum_od' => $datum_od,
'datum_do' => $datum_do,
]);
}
// ------------------------------------------------------------
// INTERNÍ HELPERY
// ------------------------------------------------------------
function _dos_pocet_noci(string $od, string $do): int {
return max(1, (int)(new DateTime($od))->diff(new DateTime($do))->days);
}
function _dos_validuj_datum(string $datum): bool {
$d = DateTime::createFromFormat('Y-m-d', $datum);
return $d && $d->format('Y-m-d') === $datum;
}
function _dos_overit_admina(): bool {
$token = $_COOKIE['session_token'] ?? $_SERVER['HTTP_X_SESSION_TOKEN'] ?? '';
if (!$token) return false;
$row = db_row(
"SELECT u.role FROM uzivatel_session s
JOIN uzivatele u ON u.id = s.uzivatel_id
WHERE s.token = ? AND s.platnost_do > NOW() AND u.aktivni = 1",
[$token]
);
return $row && in_array($row['role'], ['admin', 'manazer', 'zamestnanec'], true);
}