Dokumentace API

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í) nebo published (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...) a approved (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ědi POST /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í jen published/notified nejsou dotčené.
  • 2026-07-21 - NOVÉ: role { "role": "custom", "role_id": "...", "role_name": "..." } v roles[] u Employee - 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[].role nejsou 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": [...] } v roles[] u Employee ("Oblastní vedoucí") - spravuje vybranou skupinu prodejen, klidně napříč subjekty licence, mechanicky stejně jako role manager (stejný tvar store_ids), jen jako samostatná role. Aditivní změna - existující integrace čtoucí roles[].role nejsou dotčené.
  • 2026-07-11 - BREAKING: position v employments[] u GET/POST/PATCH /api/v1/employees už není řetězec ze starého číselníku (VP/ZVP/AP/ucen/jina), ale odkaz na vlastní pozice licence: objekt { id, name } nebo null, 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í position jako řetězec je potřeba přepnout na čtení position?.name.