Skip to content

Produkty

Produkty umožňují uživatelům nakupovat permanentky, balíčky vstupů, minutové kvóty a slevy. Každý produkt je vázán na konkrétní zóny.

Předpoklad

Produkty musí být povoleny konfigurací PRODUCTS_ENABLED = true.

Typy produktů

Denní permanentka (daily_pass)

Neomezený přístup po stanovenou dobu.

Parametry:

{
  "duration_days": 30
}

Chování:

  • Po aktivaci platí duration_days dní (do 23:59:59 posledního dne)
  • Všechny relace v přiřazených zónách jsou zdarma
  • Není třeba žádný kredit

Příklady:

Název Parametry Popis
Denní vstup {"duration_days": 1} Neomezený přístup na 1 den
Měsíční permanentka {"duration_days": 30} 30 dní neomezeného přístupu
Roční permanentka {"duration_days": 365} Celoroční přístup

Balíček vstupů (entry_bundle)

Předplacený počet vstupů. Každý vstup spotřebuje 1 vstup z balíčku.

Parametry:

{
  "entries": 10
}

Chování:

  • Při každém zahájení relace se odečte 1 vstup
  • Vstupy se odečítají až po úspěšném otevření zařízení
  • Relace je zdarma dokud jsou vstupy k dispozici
  • Po vyčerpání všech vstupů produkt vyprší

Příklady:

Název Parametry Popis
Jednorázový vstup {"entries": 1} 1 vstup zdarma
10 vstupů {"entries": 10} Balíček 10 vstupů
50 vstupů {"entries": 50} Velký balíček

Vstupní kvóta (entry_quota)

Limit počtu vstupů v klouzavém časovém okně (denně, týdně, měsíčně, ročně).

Parametry:

{
  "daily_entries": 2,
  "weekly_entries": 10,
  "monthly_entries": 0,
  "yearly_entries": 0,
  "duration_days": 365
}

Chování:

  • Počítá dokončené a aktivní relace v jednotlivých časových oknech
  • Kvóty se automaticky obnovují jak čas plyne (klouzavé okno)
  • Hodnota 0 = bez limitu pro dané období
  • Pokud je jakýkoli limit překročen, produkt relaci nepokryje
  • Platí po dobu duration_days od aktivace

Příklad: Kvóta 2 vstupy denně, 10 týdně. Uživatel může na posilovnu maximálně 2× denně a 10× týdně. Všechny tyto vstupy jsou zdarma.


Minutová kvóta (minute_quota)

Poskytuje volné minuty v klouzavém časovém okně. Pokud uživatel překročí kvótu, zaplatí poměrnou část.

Parametry:

{
  "daily_minutes": 60,
  "weekly_minutes": 300,
  "monthly_minutes": 0,
  "yearly_minutes": 0,
  "duration_days": 365
}

Chování:

  • Neomezuje zahájení relace (doba není předem známa)
  • Při ukončení relace spočítá zbytek volných minut
  • Pokud zbývající volné minuty >= doba relace → zdarma
  • Pokud zbývající minuty < doba relace → poměrná sleva
  • Hodnota 0 = bez limitu pro dané období
  • Používá se nejpřísnější nenulový limit

Příklad výpočtu:

Situace Zbývající minuty Doba relace Cena relace Výsledná cena
Plně pokryto 60 min 30 min 50 Kč 0 Kč
Částečně pokryto 30 min 60 min 100 Kč 50 Kč
Vyčerpáno 0 min 60 min 100 Kč 100 Kč

Vzorec: výsledná_cena = cena × (1 - zbývající_minuty / doba_relace)


Procentuální sleva (percentage_discount)

Fixní procentuální sleva na všechny relace.

Parametry:

{
  "discount_percent": 20,
  "duration_days": 365
}

Chování:

  • Snižuje cenu relace o daný počet procent
  • U entry-based zón se sníží potřebný kredit při vstupu
  • U time-based zón se sleva aplikuje při ukončení relace
  • Pokud duration_days chybí nebo je 0 → sleva je trvalá (nevyprší)
  • Sleva 100 % se chová jako permanentka

Příklady:

Název Parametry Popis
VIP sleva {"discount_percent": 20, "duration_days": 365} 20 % sleva na rok
Zaměstnanecká sleva {"discount_percent": 50} 50 % trvale
Promoční akce {"discount_percent": 30, "duration_days": 30} 30 % na měsíc

Kreditová kontrola

Na rozdíl od permanentky a balíčku vstupů, procentuální sleva neobchází kontrolu kreditu. Uživatel musí mít dostatek kreditu na zlevněnou cenu.


Věrnostní program na čas (playtime_loyalty)

Odměňuje uživatele volným časem na základě nahraných minut v časových zónách.

Parametry:

{
  "threshold_minutes": 600,
  "reward_minutes": 120,
  "duration_days": 365
}

Chování:

  1. Sledování: Systém sleduje minuty strávené v přiřazených zónách (nebo všech, pokud zones je prázdné)
  2. Práh: Když current_played_minutes dosáhne threshold_minutes, uživatel získá reward_minutes
  3. Převod zbytku: Minuty nad práh se převádějí do dalšího cyklu
  4. Automatická aplikace: Při ukončení relace se čekající odměny automaticky aplikují jako sleva

Stav produktu (user_products.state):

Pole Typ Popis
current_played_minutes int Nahrané minuty od posledního prahu
pending_reward_minutes int Nepoužité volné minuty k dispozici
total_rewards_given int Celkem přidaných volných minut (pro audit)

Příklady konfigurace:

Název Parametry Popis
Základní věrnostní program {"threshold_minutes": 600, "reward_minutes": 120, "duration_days": 365} Po 10 hod. získá 2 hod. zdarma
Trvalý program {"threshold_minutes": 600, "reward_minutes": 120} Nikdy nevyprší

Jak se odměny aplikují:

  1. Relace končí → systém spočítá dobu v minutách
  2. Minuty se přičtou k current_played_minutes
  3. Pokud current_played_minutes >= threshold_minutes:
  4. Spočítej cykly: cykly = current_played_minutes / threshold_minutes
  5. Přičti cykly * reward_minutes k pending_reward_minutes
  6. Aktualizuj total_rewards_given
  7. Převeď zbytek: current_played_minutes = current_played_minutes % threshold_minutes
  8. Aplikuj čekající odměny jako slevu:
  9. Spočítej sazbu za minutu: sazba = relace.celková_cena / relace.doba_minuty
  10. Aplikuj min(pending_reward_minutes, relace.doba_minuty) minut jako slevu
  11. sleva = použité_minuty * sazba
  12. výsledná_cena = relace.celková_cena - sleva

Zobrazení ve frontendu:

Aktivní playtime_loyalty produkty zobrazují: - Progress bar: current_played_minutes / threshold_minutes - Čekající odměny: Dostupné volné minuty - Celkem odměn: Celkové připsané volné minuty

Důležité

  • Čekající odměny nepropadají, dokud je produkt aktivní
  • Produkt musí být přiřazen k časovým zónám (session_mode = 'time_based')
  • Zónově specifické: sleduje pouze čas v přiřazených zónách
  • Může být aktivních více věrnostních produktů (např. jeden pro zónu A, druhý pro zónu B)

Sleva na zařízení (device_discount)

Sleva (nebo úplné zdarma použití) na konkrétní zařízení — nikdy na čas strávený v zóně, nikdy na jiná zařízení. Na rozdíl od všech ostatních typů produktů nesoutěží o "nejlepší cenu celé relace" — je to samostatná, nezávislá vrstva, která se sčítá s tím, co uplatní jiný produkt (věrnostní program, minutová kvóta, procentuální sleva, permanentka...).

Parametry:

{
  "device_ids": ["<id zařízení>", "..."],
  "discount_percent": 100,
  "duration_days": 365
}

  • device_ids — povinné, neprázdný seznam ID zařízení, na která se sleva vztahuje
  • discount_percent — nepovinné, výchozí 100 (plně zdarma), rozsah 1–100
  • duration_days — nepovinné, chybí/0 = sleva je trvalá

Chování:

  • Sleva se finálně účtuje až při ukončení relace, ale zobrazuje se živě i během probíhající relace: dashboard ukazuje skutečnou (netto) částku k úhradě za dané zařízení, ne surovou narůstající cenu — pokud je zařízení kryto na 100 %, částka k úhradě zůstává 0 po celou dobu relace, ne až na konci
  • Pokud jmenované zařízení nebylo během relace vůbec použito, produkt neuplatní žádnou slevu (zůstává aktivní pro příště)
  • Více device_discount produktů pokrývajících stejné zařízení se sčítá, ale nikdy nemůže slevu na jedno zařízení přesáhnout 100 % jeho vlastního poplatku
  • Vždy se sčítá s efektem jiného produktu (viz příklad níže) — nikdy jej nenahrazuje ani s ním nesoutěží

Příklady:

Název Parametry Popis
Osvětlení zdarma {"device_ids": ["<id světla>"], "discount_percent": 100} Trvale zdarma použití daného zařízení
Sleva na skříňku {"device_ids": ["<id skříňky>"], "discount_percent": 50, "duration_days": 30} 50 % sleva na měsíc

Kombinace produktů: fotbalový kroužek

Člen fotbalového kroužku má dva samostatné produkty zakoupené nezávisle na sobě:

  1. minute_quota — 60 volných minut hřiště denně
  2. device_discount — osvětlení zdarma (device_ids = ID reflektorů)

Při ukončení relace se uplatní oba současně: minutová kvóta pokryje čas na hřišti, sleva na zařízení pokryje osvětlení — nezávisle na tom, který produkt "vyhraje" soutěž o nejlepší celkovou cenu (tu soutěží jen produkty typu minute_quota/percentage_discount/daily_pass/atd., device_discount se jí nikdy neúčastní).


Vytvoření produktu v administraci

V Administrace → Produkty → Přidat definici vyberte Typ produktu. Místo ručního psaní JSON se zobrazí formulář s poli přesně podle zvoleného typu (např. Počet vstupů u balíčku, Zařízení u slevy na zařízení). Povinná pole jsou označena *, prázdné pole u limitů znamená „bez limitu“. Při změně typu se pole nastaví na výchozí hodnoty nového typu.

  • Začít z předvolby (jen při vytváření): vyplní název, cenu, typ a parametry hotovou šablonou — např. měsíční/roční/denní permanentka, balíček 10 nebo 50 vstupů, týdenní limit vstupů, měsíční limit minut, sleva 20 % na rok, věrnostní program 600/120, přístup údržby nebo sleva na zařízení. Cena je jen příklad v Kč, upravte ji. U slevy na zařízení nejsou předvybrána žádná zařízení — vyberte skutečná. Seznam předvoleb viz Předvolby a kontrola konfigurace.
  • Pokročilé (JSON): přepínač zobrazí surový JSON parametrů. Je to totéž, co formulář upravuje — změny se promítnou oběma směry.
  • Živá kontrola: pod formulářem se průběžně zobrazují problémy s cestou k poli (např. discount_percent — must be <= 100). Chyby uložení zablokují, upozornění ne.

Pravidla (které klíče jsou povoleny, zda je pole povinné, rozsahy) určuje backend a stejně se kontrolují při každém uložení, i přes API. Nové klíče se nepovolují — překlep jako {"minutes": 60} místo {"daily_minutes": 60} se odmítne, protože by jinak vznikl produkt bez limitů. Beze změny platí: {} u vstupní/minutové kvóty = bez limitu, percentage_discount bez discount_percent je povolená, balíček vstupů vyžaduje entries > 0, sleva na zařízení vyžaduje neprázdný seznam unikátních device_ids, procenta musí být v rozmezí (0, 100].

Starší produkty

Produkt uložený před zavedením kontroly, jehož parametry dnešním pravidlům neodpovídají, lze dál upravovat (přejmenovat, změnit cenu, deaktivovat) — kontrola se spustí, až když změníte typ nebo parametry. Takové záznamy vypíše GET /api/v1/admin/config/health.

Nákup produktů

Produkty lze nakoupit dvěma způsoby:

1. Bankovním převodem

  1. Vyberte produkt v katalogu
  2. Systém vygeneruje QR kód pro platbu
  3. Zaplaťte přes bankovní aplikaci
  4. Po připsání na účet se produkt automaticky aktivuje

2. Kreditem

  1. Vyberte produkt v katalogu
  2. Zvolte platbu kreditem
  3. Částka se strhne z kreditu
  4. Produkt se ihned aktivuje

Přiřazení k zónám

Každý produkt je vázán na konkrétní zóny. Produkt pokrývá relace pouze v přiřazených zónách. Pokud uživatel zahájí relaci v jiné zóně, produkt se neuplatní.

Životní cyklus produktu

Nákup → pending_payment → Platba přijata → active → Expirace → expired
                                                   ↘ Zrušení → cancelled
Stav Popis
pending_payment Čeká na zaplacení
active Aktivní, uplatňuje se na relace
expired Vypršel (čas nebo vstupy vyčerpány)
cancelled Zrušen administrátorem