Koncepty
Směna vs. pípnutí vs. pracovní den
Tři samostatné entity, které spolu souvisí, ale nejsou totéž:
- Směna (shift) - PLÁN. Kdo má kdy podle rozvrhu pracovat. Má stav
draft(rozpracovaná, vidí ji jen vedoucí) nebopublished(zveřejněná, vidí a dostane o ní e-mail zaměstnanec). - Pípnutí (punch) - SKUTEČNOST. Okamžik, kdy někdo fyzicky přišel nebo odešel (QR sken, ruční zápis, nebo cizí terminál přes v1 API). NEMĚNITELNÝ záznam - oprava vzniká NOVÝM pípnutím, ne úpravou starého.
- Pracovní den (timesheet) - ODVOZENÝ souhrn. Ze všech pípnutí daného dne (a naplánovaných směn) systém spočítá odpracované minuty, noční/víkendovou/sváteční část a přestávku. Stavy:
open(den ještě neskončil),ok,flagged(chybí odchod, ruční úprava...) aapproved(vedoucí den zkontroloval a schválil).
Časové zóny
Všechny prodejny jsou v ČR - interně systém pracuje s LOKÁLNÍM datem a minutami od půlnoci (business_date + start_min/end_min), NE UTC instanty - směna 14:00-22:00 tak zůstane 14:00-22:00 i v týdnu, kdy se mění letní/zimní čas. Veřejné v1 API navíc vrací i odvozené UTC instanty (start/end u směny) pro pohodlí integrací, které potřebují absolutní čas - výpočet korektně zohledňuje DST přechod (viz start_min 20:00 v den přechodu na letní čas dá jiný UTC offset než 20:00 den předtím). end_min nad 1440 znamená "přes půlnoc" (např. 1500 = 01:00 následujícího dne).
Licence a izolace dat
onDuty je multi-tenant - každá franšíza (licence) má vlastní, plně izolovaný prostor dat. Subjekty, prodejny, lidé, směny, pípnutí, webhooky i API klíče patří vždy JEDNÉ licenci. Klíč jedné franšízy nikdy nevidí ani nemutuje data jiné, a to ani nepřímo (notifikace, seznamy, reference v rolích) - cizí ID (i platné, jen patřící jiné licenci) se odpovědí chová stejně jako neexistující (404), aby se existence cizích záznamů nijak neprozradila. Výjimkou je role Vývojář (interní, nepřidělitelná přes API/portál) - ta stojí mimo licence a slouží jen k technické podpoře.
Uzávěrka měsíce
Centrála nebo franšízant může měsíc SUBJEKTU zamknout (POST /api/v1/month-locks). Zamčený měsíc je jen ke čtení - mutace docházky (pípnutí, schválení dne) i absencí do zamčeného měsíce vrátí 409 s { error: "month-locked" }. Mzdový podklad (GET /api/v1/payroll-exports) jde stáhnout kdykoli - hlavička odpovědi X-OnDuty-Month-Locked říká, jestli je podklad definitivní (true) nebo se ještě může měnit. Odemčení je čistě interní admin akce - v1 API ho neumožňuje.
Změny API (changelog)
- 2026-08-15 - NOVÉ:
skipped_invited({ count, names }) v odpovědiPOST /api/v1/shifts/publish- drafty přiřazené lidem, kteří ještě nepřijali pozvánku, se nepublikují; pole říká kolik a koho. Aditivní změna, existující integrace čtoucí jenpublished/notifiednejsou dotčené. - 2026-07-21 - NOVÉ: role
{ "role": "custom", "role_id": "...", "role_name": "..." }vroles[]uEmployee- licence si v portálu (sekce Oprávnění) může založit vlastní pojmenované role s vlastní maticí práv a rozsahem. Aditivní změna - existující integrace čtoucíroles[].rolenejsou dotčené. Matice práv i vlastní role ovlivňují jen portál/appku - v1 API klíč se pořád autorizuje jako centrála (viz Autentizace / Scopes). - 2026-07-15 - NOVÉ: role
{ "role": "area_manager", "store_ids": [...] }vroles[]uEmployee("Oblastní vedoucí") - spravuje vybranou skupinu prodejen, klidně napříč subjekty licence, mechanicky stejně jako rolemanager(stejný tvarstore_ids), jen jako samostatná role. Aditivní změna - existující integrace čtoucíroles[].rolenejsou dotčené. - 2026-07-11 - BREAKING:
positionvemployments[]uGET/POST/PATCH /api/v1/employeesuž není řetězec ze starého číselníku (VP/ZVP/AP/ucen/jina), ale odkaz na vlastní pozice licence: objekt{ id, name }nebonull, pokud úvazek žádnou pozici nemá. Důvod: pozice jsou teď entita, kterou si franšíza sama pojmenuje (centrála v portálu, sekce Pozice), ne uzavřený seznam - integrace čtoucípositionjako řetězec je potřeba přepnout na čteníposition?.name.