Relace a vyúčtování (REST API)¶
Tato stránka popisuje části REST API, ze kterých se skládá panel aktivní relace: náhled vyúčtování, pole active_session na dashboardu a stav zařízení. Všechny endpointy vyžadují přihlášeného uživatele (PocketBase auth token).
Jedno vyúčtování pro náhled i skutečný konec¶
Cena, kterou vidíte během relace, a částka, kterou systém při ukončení skutečně účtuje, jsou počítány jednou a toutéž funkcí (session.ComputeSettlement). Náhled ji spustí nad ještě běžící relací „jako by skončila právě teď“ a výsledek zahodí; EndSession ji spustí a výsledek zapíše. Proto náhled zahrnuje všechno, co skutečné ukončení:
- ceník zóny (sazby, přechody mezi sazbami, volné minuty) a ceny zařízení — i zařízení, které je v tu chvíli ještě zapnuté (účtuje se do okamžiku ukončení),
- slevy a pokrytí produkty (procentní sleva, permanentka, minutová kvóta, věrnostní program, balíček vstupů, sleva na zařízení),
- administrátora (vše zdarma),
- vstupní zóny: vstupné zaplacené při zahájení (
prepaid) a případně doplatek za zařízení,allow_debt, - rozdělení na úhradu z kreditu a dluh.
Náhled je čistě čtecí: nic nezapisuje (relace, položky vyúčtování, stav produktů, kredit, action logy zůstanou beze změny), neposílá příkazy zařízením a nespouští vedlejší efekty produktů (např. načítání odehraných minut věrnostního programu — ty proběhnou právě jednou, až při skutečném ukončení). Lze ho volat opakovaně.
Částka se počítá k okamžiku volání; mezi náhledem a potvrzením ukončení uplyne několik sekund, o které se výsledek může lišit.
GET /api/v1/sessions/{id}/settlement-preview¶
Vrátí vyúčtování, které by teď nastalo při ukončení relace. Pouze vlastník relace a pouze aktivní relace.
| Stav | Kód chyby | Význam |
|---|---|---|
| 401 | — | Nepřihlášen |
| 403 | forbidden |
Relace patří jinému uživateli |
| 404 | session_not_found |
Relace neexistuje |
| 400 | session_not_active |
Relace už skončila |
Odpověď (200):
{
"items": [
{ "kind": "charge", "label": "Rate 10:00-10:30 (30 min)", "label_key": "zone_rate_range",
"label_params": { "from": "10:00", "to": "10:30", "minutes": 30 }, "amount": 60 },
{ "kind": "discount", "label": "Discount (20% Sleva)", "label_key": "discount_product_named",
"label_params": { "product": "20% Sleva" }, "amount": 12 },
{ "kind": "payment", "label": "Credit", "label_key": "credit_payment", "label_params": {}, "amount": 48 }
],
"total_price": 60,
"discount": 12,
"rounding": 0,
"final_price": 48,
"prepaid": 0,
"prepaid_price": 0,
"amount_due": 48,
"from_credit": 48,
"to_debt": 0,
"coverage": "none",
"coverage_name": null
}
| Pole | Význam |
|---|---|
items |
Řádky vyúčtování, které by EndSession přidal, v pořadí zápisu. amount je vždy kladné, směr určuje kind (charge zvyšuje dlužnou částku, discount a payment ji snižují). label je anglický záložní text; UI překládá label_key + label_params (jména produktů a zařízení v label_params se nepřekládají). U vstupní zóny, jejíž vstupné bylo zaplaceno při zahájení, se jeho řádky znovu neuvádějí — viz prepaid. |
total_price |
Hrubé poplatky celkem. |
discount |
Jen skutečné snížení ceny (volné minuty, produkty, administrátor) před použitím kreditu — nezahrnuje zaokrouhlení. |
rounding |
± rozdíl mezi přesnou cenou a final_price zaokrouhlenou na PRICE_DECIMAL_PLACES (např. -0.3 pro 820,30 → 820). Při 2 desetinných místech vždy 0. |
final_price |
Cena relace po slevách a zaokrouhlení, před kreditem (total_price − discount + rounding). U vstupní zóny včetně již zaplaceného vstupného. |
prepaid |
Co už bylo zaplaceno při zahájení (vstupné z kreditu). Jinak 0. |
prepaid_price |
Přesná cena (na 2 desetinná místa) již zaplaceného vstupného — první řádek účtenky ve vyúčtování. Platí prepaid_price + Σ items (charge − discount) + rounding = final_price. prepaid je naproti tomu skutečně zaplacená (zaokrouhlená) částka. Jinak 0. |
amount_due |
Co ukončení právě teď ještě vypořádá (≥ 0). U zaplaceného vstupu jen doplatek za zařízení. amount_due = from_credit + to_debt. |
from_credit |
Část amount_due pokrytá aktuálním kreditem uživatele. |
to_debt |
Zbytek, který se stane dluhem. |
coverage |
Proč relace stojí, kolik stojí: none (platí se běžně), admin, product, prepaid (vstup zaplacen při zahájení), free (nic k placení). |
coverage_name |
Název produktu při coverage: "product" (pokud je znám), jinak null. |
Cena a dlužná částka u uložené relace¶
U uložených (ukončených) relací mají peněžní pole tento význam:
| Pole | Význam |
|---|---|
final_price |
Cena po slevách a zaokrouhlení. Úhrady (kredit, bankovní převod, ruční úhrada) ji nesnižují — zaplacená relace má dál např. 818. |
discount |
Jen skutečná snížení (slevy, produkty, permanentky, ruční slevy, historické úpravy z migrace). Zaokrouhlení v ní už není. |
price_rounding |
± zaokrouhlení na PRICE_DECIMAL_PLACES (výchozí 0 = celé koruny, půlka od nuly). Při PRICE_DECIMAL_PLACES = 2 vždy 0. |
amount_due |
Kolik zbývá doplatit (≥ 0, u zaplacené relace 0). |
paid |
Relace má poplatek a amount_due ≤ 0. |
Platí identity: total_price − discount + price_rounding = final_price,
součet řádků session_price_items (bez úhrad) + price_rounding = final_price
a final_price − součet úhrad = amount_due.
Klíč rounding obsahují odpovědi vyúčtování (settlement-preview, end-session,
terminálový odchod); relace vrácené jako záznamy (statistiky, recent_sessions
na dashboardu, admin endpointy relací, PocketBase kolekce sessions) používají
název pole price_rounding.
amount_due vrací odpovědi end-session (POST /api/v1/sessions/{id}/end),
GET /api/v1/sessions/{id}/qr (částka QR/SPD), dashboard (recent_sessions),
statistiky, terminálový odchod a admin endpointy relací vedle final_price.
Tento amount_due je stav po ukončení; amount_due v
settlement-preview výše je naproti tomu částka, kterou by ukončení teprve
vypořádalo.
Souhrny
total_unpaid (session-summary, statistiky) sčítá amount_due;
total_paid a tržby sčítají final_price zaplacených relací.
GET /api/v1/me/dashboard — active_session¶
Při aktivní relaci obsahuje active_session (jinak je active_session null):
| Pole | Popis |
|---|---|
settlement |
Stejný objekt jako výše (z téže funkce); null, pokud se vyúčtování nepodařilo spočítat (chyba se zaloguje a do errors[] dashboardu se přidá failed to compute session settlement; dashboard neselže). Frontend v tom případě nezobrazí žádnou cenu (ani „Zdarma“), jen hlášku, že cenu nelze načíst. |
estimated_price |
settlement.final_price (zpětná kompatibilita). |
estimated_discount |
settlement.discount. |
discount_items |
Slevové položky z settlement.items (kind: "discount"). |
zone_discount_percent |
Pevné procento slevy na čas v zóně (procentní sleva, permanentka), jinak null. |
zone_discount_stepped |
true, pokud sleva nemá pevné procento (minutová kvóta, věrnostní program) — UI ji nesmí extrapolovat. |
zone_fully_covered |
true, pokud stupňovitá sleva právě teď pokrývá celý poplatek za čas v zóně (sazba je skutečně 0). |
device_discount_percents |
Sleva na zařízení v % podle ID zařízení (nezávisle na použití). |
device_charges |
Poplatky za zařízení: device_id, device_name, amount, discount_amount, net_amount, use_count, duration_sec. |
device_states |
Skutečný stav zařízení zóny (níže). |
device_states¶
"device_states": {
"<device_id>": {
"state": "on",
"confirmed": true,
"last_command": "on",
"last_command_at": "2026-10-07 18:02:11.000Z"
}
}
| Pole | Popis |
|---|---|
state |
on | off | open | close | unknown |
confirmed |
true právě tehdy, když adaptér vrátil jiný stav než unknown bez chyby. Jen potvrzený stav smí UI zvýraznit. |
last_command |
Poslední příkaz měnící stav (on, off, open, close, short_open) napříč všemi relacemi — blink se ignoruje; null, pokud žádný není. |
last_command_at |
Čas toho příkazu v UTC, formát PocketBase (2026-10-07 18:02:11.000Z), nebo null. |
Klíče jsou všechna zařízení zóny aktivní relace. Dotazy na stav běží souběžně, každý s limitem 2 s; výsledek se na 3 s cachuje po zařízení a po úspěšném příkazu (POST /api/v1/sessions/{id}/devices/{deviceId}/control) se cache daného zařízení zahodí, takže následné obnovení dashboardu vrací čerstvý stav. Chyba nebo timeout zařízení nikdy neshodí dashboard — zařízení se pouze ohlásí jako unknown / confirmed: false.
GET /api/v1/sessions/{id}/timeline¶
Události zařízení relace seřazené podle času (vlastník relace nebo admin):
{
"events": [
{
"timestamp": "2026-10-08T08:34:00Z",
"device_name": "Světla",
"device_type": "light",
"action": "off",
"triggered_by": "user",
"status": "failed",
"failure_reason": "timeout",
"error_detail": "http request failed: context deadline exceeded"
}
]
}
| Pole | Popis |
|---|---|
status |
success | failed | not_sent. Prázdná hodnota v databázi (starší záznam) se vrací jako success. |
failure_reason |
U failed: timeout | unreachable | device_rejected | config_error | unknown; jinak prázdný řetězec. |
error_detail |
Technický detail (oříznutý na 1024 znaků, bez přihlašovacích údajů). Jen pro admina — pro ostatní klíč v odpovědi vůbec není. |
failed záznamy se do ceny nepočítají (jediný filtr: actionlog.Billable v pricing enginu); not_sent ano. GET /api/v1/me/dashboard vrací v active_session.action_logs[] navíc pole status (stejná normalizace).