From 34f6ba520e03e4f7d22196eab36435b44325bb89 Mon Sep 17 00:00:00 2001 From: Martin Berka Date: Fri, 12 Jun 2026 19:44:20 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20podpora=20simulace=20objedn=C3=A1vek=20?= =?UTF-8?q?z=20Bolt=20Food=20ve=20v=C3=BDvoji?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/modals/BoltSimulationModal.tsx | 170 ++++++++++++++++++ client/src/pages/OrderGroupsPage.tsx | 18 ++ server/.env.template | 6 +- server/src/boltSimulator.ts | 134 ++++++++++++++ server/src/boltTracking.ts | 17 +- server/src/routes/devRoutes.ts | 80 +++++++++ server/src/tests/boltTracking.test.ts | 58 ++++++ types/api.yml | 8 + types/paths/dev/boltAdvance.yml | 20 +++ types/paths/dev/boltPoll.yml | 15 ++ types/paths/dev/boltSimulate.yml | 40 +++++ types/paths/dev/boltState.yml | 20 +++ types/schemas/_index.yml | 80 +++++++++ 13 files changed, 662 insertions(+), 4 deletions(-) create mode 100644 client/src/components/modals/BoltSimulationModal.tsx create mode 100644 server/src/boltSimulator.ts create mode 100644 types/paths/dev/boltAdvance.yml create mode 100644 types/paths/dev/boltPoll.yml create mode 100644 types/paths/dev/boltSimulate.yml create mode 100644 types/paths/dev/boltState.yml diff --git a/client/src/components/modals/BoltSimulationModal.tsx b/client/src/components/modals/BoltSimulationModal.tsx new file mode 100644 index 0000000..3fa4c9d --- /dev/null +++ b/client/src/components/modals/BoltSimulationModal.tsx @@ -0,0 +1,170 @@ +import { useState } from "react"; +import { Modal, Button, Form, Alert, Badge } from "react-bootstrap"; +import { + OrderGroup, + simulateBoltTracking, advanceBoltTracking, setBoltTrackingState, + pollBoltTracking, stopBoltTrackingSimulation, +} from "../../../../types"; + +type Props = { + isOpen: boolean; + onClose: () => void; + group: OrderGroup; +}; + +/** Nabídka stavů pro ruční nastavení (odpovídá mapování v BoltOrderProgress). */ +const STATE_OPTIONS: { value: string; label: string }[] = [ + { value: 'accepted', label: 'Přijato' }, + { value: 'preparing', label: 'Příprava' }, + { value: 'waiting_delivery', label: 'Příprava (čeká na vyzvednutí)' }, + { value: 'in_delivery', label: 'Na cestě' }, + { value: 'delivered', label: 'Doručeno' }, + { value: 'cancelled', label: 'Zrušeno' }, +]; + +/** Modální dialog pro simulaci sledování Bolt objednávky (pouze DEV). */ +export default function BoltSimulationModal({ isOpen, onClose, group }: Readonly) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [info, setInfo] = useState(null); + const [manualState, setManualState] = useState('preparing'); + + const running = !!group.boltTrackingToken; + + /** Obecný runner — spustí akci, ošetří chybu a krátce zobrazí výsledek. */ + const run = async (action: () => Promise<{ error?: unknown }>, okMsg: string) => { + setError(null); + setInfo(null); + setLoading(true); + try { + const response = await action(); + if (response.error) { + setError((response.error as any)?.error || 'Akce simulace selhala'); + } else { + setInfo(okMsg); + } + } catch (e: any) { + setError(e?.message || 'Akce simulace selhala'); + } finally { + setLoading(false); + } + }; + + const handleStart = () => run( + () => simulateBoltTracking({ body: { groupId: group.id } }), + 'Simulace spuštěna — objednávka přijata.', + ); + const handleAdvance = () => run( + () => advanceBoltTracking({ body: { groupId: group.id } }), + 'Posunuto na další krok.', + ); + const handleSetState = () => run( + () => setBoltTrackingState({ + body: { + groupId: group.id, + order_state: manualState, + // "Na cestě" zpřesníme i stavem kurýra + courier_state: manualState === 'in_delivery' ? 'heading_to_client' : undefined, + }, + }), + `Stav nastaven na „${STATE_OPTIONS.find(o => o.value === manualState)?.label}".`, + ); + const handlePoll = () => run( + () => pollBoltTracking(), + 'Scheduler spuštěn (poll proběhl).', + ); + const handleStop = () => run( + () => stopBoltTrackingSimulation({ body: { groupId: group.id } }), + 'Simulace ukončena.', + ); + + const handleClose = () => { + setError(null); + setInfo(null); + onClose(); + }; + + return ( + + +

Simulace sledování Bolt

+
+ + + DEV režim – simuluje sledování objednávky Bolt pro skupinu{' '} + {group.name} bez reálné objednávky. + + + {error && ( + setError(null)} dismissible> + {error} + + )} + {info && ( + setInfo(null)} dismissible> + {info} + + )} + +

+ Stav simulace:{' '} + {running + ? běží{group.boltOrderState ? ` — ${group.boltOrderState}` : ''} + : neběží} +

+ + {!running ? ( +

+ Spuštěním se skupina přepne do stavu „Objednáno", přiřadí se simulovaný + sledovací token a provede se první poll (stav „Přijato"). +

+ ) : ( + <> +

+ Krokuj objednávku tlačítkem Další krok (Přijato → Příprava → + Na cestě → Doručeno) nebo nastav konkrétní stav ručně: +

+ +
+ Konkrétní stav + setManualState(e.target.value)} + > + {STATE_OPTIONS.map(o => ( + + ))} + +
+ +
+ + )} +
+ + {!running ? ( + + ) : ( + <> + + + + + )} + + +
+ ); +} diff --git a/client/src/pages/OrderGroupsPage.tsx b/client/src/pages/OrderGroupsPage.tsx index f589b37..0c04219 100644 --- a/client/src/pages/OrderGroupsPage.tsx +++ b/client/src/pages/OrderGroupsPage.tsx @@ -22,6 +22,7 @@ import Loader from '../components/Loader'; import StoreAdminModal from '../components/modals/StoreAdminModal'; import PayForGroupModal from '../components/modals/PayForGroupModal'; import EditGroupFeesModal from '../components/modals/EditGroupFeesModal'; +import BoltSimulationModal from '../components/modals/BoltSimulationModal'; import PendingPayments from '../components/PendingPayments'; import BoltOrderProgress from '../components/BoltOrderProgress'; @@ -29,6 +30,7 @@ const SLOT = MealSlot.EXTRA; const TIME_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/; const BOLT_SHARE_URL_PREFIX = 'https://food.bolt.eu/sharedActiveOrder/'; const BOLT_TOKEN_REGEX = /^[0-9a-f]{64}$/i; +const IS_DEV = process.env.NODE_ENV === 'development'; /** Vytáhne sledovací token ze sdílecí URL Bolt Food, nebo přijme samotný token. Null = neplatný vstup. */ function extractBoltToken(input: string): string | null { @@ -100,6 +102,7 @@ export default function OrderGroupsPage() { const [editTimes, setEditTimes] = useState>({}); const [payModal, setPayModal] = useState(null); const [feesModal, setFeesModal] = useState(null); + const [boltSimModal, setBoltSimModal] = useState(null); const [confirmOrderGroup, setConfirmOrderGroup] = useState(null); const [pageError, setPageError] = useState(null); @@ -803,6 +806,13 @@ export default function OrderGroupsPage() { )} + {IS_DEV && ( +
+ +
+ )} )} @@ -874,6 +884,14 @@ export default function OrderGroupsPage() { }} /> )} + {IS_DEV && boltSimModal && ( + setBoltSimModal(null)} + // živá skupina z dat (aktualizuje se přes websocket), fallback na snapshot + group={groups.find(g => g.id === boltSimModal.id) ?? boltSimModal} + /> + )} ); } diff --git a/server/.env.template b/server/.env.template index da52252..108594f 100644 --- a/server/.env.template +++ b/server/.env.template @@ -51,4 +51,8 @@ # Admin heslo pro správu seznamu obchodů na stránce /objednani. # Bez hesla nelze přidávat ani odebírat obchody ze seznamu (POST/DELETE na /api/stores vrátí 403). -# ADMIN_PASSWORD= \ No newline at end of file +# ADMIN_PASSWORD= + +# Interval (ms) scheduleru sledování objednávek Bolt Food. Výchozí 60000 (60 s). +# Pro vývoj se simulací lze zkrátit (min. 1000), aby se změny stavu projevily rychleji. +# BOLT_POLL_INTERVAL_MS=3000 diff --git a/server/src/boltSimulator.ts b/server/src/boltSimulator.ts new file mode 100644 index 0000000..f2e9e1f --- /dev/null +++ b/server/src/boltSimulator.ts @@ -0,0 +1,134 @@ +import crypto from 'crypto'; + +/** + * Vývojový simulátor sledování objednávek Bolt Food. + * + * Drží in-memory registr „simulovaných" objednávek klíčovaný tokenem. Funkce + * pollBoltOrder v boltTracking.ts se na začátku podívá, zda je token simulovaný, + * a pokud ano, vrátí vyfabrikovaný stav místo dotazu na reálné Bolt API. + * + * Registr se plní výhradně přes dev endpointy (gated requireDevMode), takže + * v produkci zůstává prázdný a chování pollBoltOrder se nijak nemění. + * + * Postup stavů je řízen ručně (krokováním), bez časové osy — viz advance/setState. + */ + +/** Jeden krok simulace — odpovídá tomu, co vrací Bolt API a co čte BoltOrderProgress. */ +export interface SimStep { + order_state: string; + courier_state?: string; + etaSeconds?: number; +} + +/** Tvar objednávky, který vrací pollBoltOrder (shodný s interface BoltOrder). */ +export interface SimulatedBoltOrder { + order_id: number; + order_state: string; + expected_time_to_client_in_seconds?: number; + courier?: { state?: string } | null; +} + +interface Simulation { + groupId: string; + token: string; + orderId: number; + steps: SimStep[]; + index: number; + /** Jednorázové ruční přepsání aktuálního stavu (má přednost před steps[index]). */ + override?: SimStep; +} + +/** + * Výchozí scénář „happy path". Stavy a stavy kurýra odpovídají mapování ve + * client/src/components/BoltOrderProgress.tsx (Přijato → Příprava → Na cestě → Doručeno). + */ +export const DEFAULT_SCENARIO: SimStep[] = [ + { order_state: 'accepted', etaSeconds: 1800 }, + { order_state: 'preparing', etaSeconds: 1500 }, + { order_state: 'waiting_delivery', courier_state: 'arrived_to_provider', etaSeconds: 900 }, + { order_state: 'in_delivery', courier_state: 'heading_to_client', etaSeconds: 300 }, + { order_state: 'delivered', courier_state: 'delivered', etaSeconds: 0 }, +]; + +const byToken = new Map(); + +function findByGroup(groupId: string): Simulation | undefined { + for (const sim of byToken.values()) { + if (sim.groupId === groupId) return sim; + } + return undefined; +} + +/** Sestaví Bolt objednávku z aktuálního kroku simulace. */ +function buildOrder(sim: Simulation): SimulatedBoltOrder { + const step = sim.override ?? sim.steps[sim.index]; + return { + order_id: sim.orderId, + order_state: step.order_state, + expected_time_to_client_in_seconds: step.etaSeconds, + courier: step.courier_state ? { state: step.courier_state } : null, + }; +} + +/** Je token registrovaný v simulátoru? */ +export function isBoltSimulated(token: string): boolean { + return byToken.has(token); +} + +/** Vrátí simulovaný stav objednávky pro daný token (nebo null, pokud není simulovaný). */ +export function getSimulatedBoltOrder(token: string): SimulatedBoltOrder | null { + const sim = byToken.get(token); + return sim ? buildOrder(sim) : null; +} + +/** + * Spustí novou simulaci pro skupinu. Pokud už pro skupinu simulace běží, nahradí ji. + * Vrátí vygenerovaný 64-hex token (validní pro extractBoltToken). + */ +export function startBoltSimulation(groupId: string, steps: SimStep[] = DEFAULT_SCENARIO): string { + stopBoltSimulationByGroup(groupId); + const token = crypto.randomBytes(32).toString('hex'); + byToken.set(token, { + groupId, + token, + orderId: crypto.randomInt(100000, 999999), + steps: steps.length ? steps : DEFAULT_SCENARIO, + index: 0, + }); + return token; +} + +/** Posune simulaci skupiny na další krok (a zruší případný ruční override). Vrátí aktuální krok. */ +export function advanceBoltSimulation(groupId: string): SimStep { + const sim = findByGroup(groupId); + if (!sim) throw new Error('Pro skupinu neběží žádná simulace Bolt'); + sim.override = undefined; + sim.index = Math.min(sim.index + 1, sim.steps.length - 1); + return sim.steps[sim.index]; +} + +/** Nastaví konkrétní stav simulace skupiny (ruční override — užitečné pro edge-case stavy). */ +export function setBoltSimulationStep(groupId: string, step: SimStep): SimStep { + const sim = findByGroup(groupId); + if (!sim) throw new Error('Pro skupinu neběží žádná simulace Bolt'); + sim.override = step; + return step; +} + +/** Ukončí simulaci pro skupinu. */ +export function stopBoltSimulationByGroup(groupId: string): void { + const sim = findByGroup(groupId); + if (sim) byToken.delete(sim.token); +} + +/** Vrátí přehled simulace skupiny (pro stavové zobrazení v UI). */ +export function getBoltSimulation(groupId: string): { token: string; index: number; total: number; current: SimStep } | null { + const sim = findByGroup(groupId); + if (!sim) return null; + return { + token: sim.token, + index: sim.index, + total: sim.steps.length, + current: sim.override ?? sim.steps[sim.index], + }; +} diff --git a/server/src/boltTracking.ts b/server/src/boltTracking.ts index 222ada1..ede2e98 100644 --- a/server/src/boltTracking.ts +++ b/server/src/boltTracking.ts @@ -6,6 +6,7 @@ import { getToday } from './service'; import { formatDate } from './utils'; import { getWebsocket } from './websocket'; import { ClientData, GroupState } from '../../types/gen/types.gen'; +import { isBoltSimulated, getSimulatedBoltOrder } from './boltSimulator'; const storage = getStorage(); const lease = createLeaderLease('luncher:bolt:leader'); @@ -58,6 +59,11 @@ interface BoltOrder { /** Dotáže se veřejného Bolt API na stav sdílené objednávky. Vrátí null, pokud objednávka už neexistuje. */ export async function pollBoltOrder(token: string): Promise { + // DEV simulace: simulované tokeny obsluhuje boltSimulator místo reálného Bolt API. + // V produkci je registr vždy prázdný, takže se sem nikdy nedostane. + if (isBoltSimulated(token)) { + return getSimulatedBoltOrder(token); + } const res = await axios.post(BOLT_POLLING_URL, { token }, { params: { version: 'FW.1.111', @@ -158,10 +164,15 @@ export async function checkBoltTracking(): Promise { } } -/** Spustí scheduler pro sledování Bolt objednávek (každou minutu). */ +/** + * Spustí scheduler pro sledování Bolt objednávek. Interval je 60 s, lze ho ale + * zkrátit přes env BOLT_POLL_INTERVAL_MS (užitečné při vývoji se simulací). + */ export function startBoltTrackingScheduler(): void { - boltInterval = setInterval(checkBoltTracking, 60_000); - console.log('Bolt tracking: scheduler spuštěn'); + const parsed = Number(process.env.BOLT_POLL_INTERVAL_MS); + const intervalMs = Number.isFinite(parsed) && parsed >= 1000 ? parsed : 60_000; + boltInterval = setInterval(checkBoltTracking, intervalMs); + console.log(`Bolt tracking: scheduler spuštěn (interval ${intervalMs} ms)`); } /** Stopne scheduler sledování. Volá se při graceful shutdown. */ diff --git a/server/src/routes/devRoutes.ts b/server/src/routes/devRoutes.ts index 05787a7..792a784 100644 --- a/server/src/routes/devRoutes.ts +++ b/server/src/routes/devRoutes.ts @@ -6,6 +6,12 @@ import { getWebsocket } from "../websocket"; import { getLogin } from "../auth"; import { parseToken } from "../utils"; import webpush from 'web-push'; +import { ClientData, GroupState } from "../../../types/gen/types.gen"; +import { + startBoltSimulation, advanceBoltSimulation, setBoltSimulationStep, + stopBoltSimulationByGroup, getBoltSimulation, +} from "../boltSimulator"; +import { checkBoltTracking } from "../boltTracking"; const router = express.Router(); const storage = getStorage(); @@ -195,4 +201,78 @@ router.post("/testPush", async (req, res, next) => { } catch (e: any) { next(e) } }); +// --- DEV simulace sledování Bolt Food --- + +/** Nastaví/zruší sledovací token skupiny a zajistí stav ORDERED. Vrátí aktualizovaná data. */ +async function applyBoltToken(groupId: string, token: string | undefined): Promise { + const key = `${formatDate(getToday())}_extra`; + return storage.updateData(key, current => { + const d = current; + const group = d?.groups?.find(g => g.id === groupId); + if (!group) throw new Error('Skupina nebyla nalezena'); + group.boltTrackingToken = token; + if (token) { + group.state = GroupState.ORDERED; + } else { + group.boltOrderState = undefined; + group.boltCourierState = undefined; + } + return d!; + }); +} + +/** Spustí simulaci sledování Bolt pro skupinu a provede první poll. */ +router.post("/bolt/simulate", async (req: Request<{}, any, any>, res, next) => { + try { + const groupId = req.body?.groupId; + if (!groupId) return res.status(400).json({ error: 'Chybí groupId' }); + const token = startBoltSimulation(groupId); + await applyBoltToken(groupId, token); + await checkBoltTracking(); // okamžitý první poll → stav "accepted" + websocket + res.status(200).json({ success: true, token, simulation: getBoltSimulation(groupId) }); + } catch (e: any) { next(e); } +}); + +/** Posune simulaci na další krok a přepošle aktualizovaný stav klientům. */ +router.post("/bolt/advance", async (req: Request<{}, any, any>, res, next) => { + try { + const groupId = req.body?.groupId; + if (!groupId) return res.status(400).json({ error: 'Chybí groupId' }); + advanceBoltSimulation(groupId); + await checkBoltTracking(); + res.status(200).json({ success: true, simulation: getBoltSimulation(groupId) }); + } catch (e: any) { next(e); } +}); + +/** Nastaví konkrétní stav simulace (ruční override, např. pro stav "cancelled"). */ +router.post("/bolt/state", async (req: Request<{}, any, any>, res, next) => { + try { + const { groupId, order_state, courier_state, etaSeconds } = req.body ?? {}; + if (!groupId || !order_state) return res.status(400).json({ error: 'Chybí groupId nebo order_state' }); + setBoltSimulationStep(groupId, { order_state, courier_state, etaSeconds }); + await checkBoltTracking(); + res.status(200).json({ success: true, simulation: getBoltSimulation(groupId) }); + } catch (e: any) { next(e); } +}); + +/** Spustí jeden tik scheduleru okamžitě (bez čekání na interval). */ +router.post("/bolt/poll", async (_req, res, next) => { + try { + await checkBoltTracking(); + res.status(200).json({ success: true }); + } catch (e: any) { next(e); } +}); + +/** Ukončí simulaci skupiny a odebere sledovací token. */ +router.delete("/bolt/simulate", async (req: Request<{}, any, any>, res, next) => { + try { + const groupId = req.body?.groupId; + if (!groupId) return res.status(400).json({ error: 'Chybí groupId' }); + stopBoltSimulationByGroup(groupId); + const updated = await applyBoltToken(groupId, undefined); + getWebsocket()?.emit('message', updated); + res.status(200).json({ success: true }); + } catch (e: any) { next(e); } +}); + export default router; diff --git a/server/src/tests/boltTracking.test.ts b/server/src/tests/boltTracking.test.ts index 560102d..97ff677 100644 --- a/server/src/tests/boltTracking.test.ts +++ b/server/src/tests/boltTracking.test.ts @@ -4,6 +4,7 @@ import getStorage from '../storage'; import { addStore } from '../stores'; import { createGroup, setGroupState, setGroupBoltTracking } from '../groups'; import { extractBoltToken, computeDeliveryHHMM, checkBoltTracking } from '../boltTracking'; +import { startBoltSimulation, advanceBoltSimulation, setBoltSimulationStep, stopBoltSimulationByGroup } from '../boltSimulator'; import { ClientData, GroupState } from '../../../types/gen/types.gen'; import { formatDate } from '../utils'; @@ -263,3 +264,60 @@ describe('checkBoltTracking', () => { expect(mockedAxios.post).not.toHaveBeenCalled(); }); }); + +describe('DEV simulace (boltSimulator + checkBoltTracking)', () => { + const extraKey = () => `${formatDate(new Date())}_extra`; + let groupId: string; + + beforeEach(async () => { + const d = await createGroup(CREATOR, STORE); + groupId = d.groups![0].id; + await setGroupState(CREATOR, groupId, GroupState.LOCKED); + await setGroupState(CREATOR, groupId, GroupState.ORDERED); + // Simulátor vygeneruje validní 64-hex token a přiřadíme ho skupině jako reálný dev endpoint + const token = startBoltSimulation(groupId); + await setGroupBoltTracking(CREATOR, groupId, `https://food.bolt.eu/sharedActiveOrder/${token}`); + }); + + afterEach(() => stopBoltSimulationByGroup(groupId)); + + async function getGroup() { + const data = await storage.getData(extraKey()); + return data!.groups!.find(g => g.id === groupId)!; + } + + test('simulovaný token nevolá reálné Bolt API', async () => { + await checkBoltTracking(); + expect(mockedAxios.post).not.toHaveBeenCalled(); + }); + + test('první poll nastaví stav accepted a ETA', async () => { + await checkBoltTracking(); + const g = await getGroup(); + expect(g.boltOrderState).toBe('accepted'); + expect(g.deliveryAt).toBe(computeDeliveryHHMM(1800)); + }); + + test('advance posune sekvenci na preparing', async () => { + await checkBoltTracking(); + advanceBoltSimulation(groupId); + await checkBoltTracking(); + expect((await getGroup()).boltOrderState).toBe('preparing'); + }); + + test('ruční nastavení stavu (override) se projeví při pollu', async () => { + setBoltSimulationStep(groupId, { order_state: 'in_delivery', courier_state: 'heading_to_client', etaSeconds: 300 }); + await checkBoltTracking(); + const g = await getGroup(); + expect(g.boltOrderState).toBe('in_delivery'); + expect(g.boltCourierState).toBe('heading_to_client'); + }); + + test('terminální stav delivered ukončí sledování (smaže token)', async () => { + setBoltSimulationStep(groupId, { order_state: 'delivered' }); + await checkBoltTracking(); + const g = await getGroup(); + expect(g.boltOrderState).toBe('delivered'); + expect(g.boltTrackingToken).toBeUndefined(); + }); +}); diff --git a/types/api.yml b/types/api.yml index 0c62ed8..3d85cc2 100644 --- a/types/api.yml +++ b/types/api.yml @@ -118,6 +118,14 @@ paths: $ref: "./paths/dev/generate.yml" /dev/clear: $ref: "./paths/dev/clear.yml" + /dev/bolt/simulate: + $ref: "./paths/dev/boltSimulate.yml" + /dev/bolt/advance: + $ref: "./paths/dev/boltAdvance.yml" + /dev/bolt/state: + $ref: "./paths/dev/boltState.yml" + /dev/bolt/poll: + $ref: "./paths/dev/boltPoll.yml" components: schemas: diff --git a/types/paths/dev/boltAdvance.yml b/types/paths/dev/boltAdvance.yml new file mode 100644 index 0000000..17f8df2 --- /dev/null +++ b/types/paths/dev/boltAdvance.yml @@ -0,0 +1,20 @@ +post: + operationId: advanceBoltTracking + summary: Posun simulace sledování Bolt na další krok (pouze DEV režim) + requestBody: + required: true + content: + application/json: + schema: + $ref: "../../schemas/_index.yml#/BoltSimGroupRequest" + responses: + "200": + description: Simulace byla posunuta + content: + application/json: + schema: + $ref: "../../schemas/_index.yml#/BoltSimResponse" + "400": + description: Chybný požadavek + "403": + description: Endpoint není dostupný v tomto režimu diff --git a/types/paths/dev/boltPoll.yml b/types/paths/dev/boltPoll.yml new file mode 100644 index 0000000..540f257 --- /dev/null +++ b/types/paths/dev/boltPoll.yml @@ -0,0 +1,15 @@ +post: + operationId: pollBoltTracking + summary: Okamžité spuštění jednoho tiku scheduleru sledování Bolt (pouze DEV režim) + responses: + "200": + description: Poll proběhl + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + "403": + description: Endpoint není dostupný v tomto režimu diff --git a/types/paths/dev/boltSimulate.yml b/types/paths/dev/boltSimulate.yml new file mode 100644 index 0000000..f5cb908 --- /dev/null +++ b/types/paths/dev/boltSimulate.yml @@ -0,0 +1,40 @@ +post: + operationId: simulateBoltTracking + summary: Spuštění simulace sledování Bolt pro skupinu (pouze DEV režim) + requestBody: + required: true + content: + application/json: + schema: + $ref: "../../schemas/_index.yml#/BoltSimGroupRequest" + responses: + "200": + description: Simulace byla spuštěna + content: + application/json: + schema: + $ref: "../../schemas/_index.yml#/BoltSimResponse" + "400": + description: Chybný požadavek + "403": + description: Endpoint není dostupný v tomto režimu +delete: + operationId: stopBoltTrackingSimulation + summary: Ukončení simulace sledování Bolt pro skupinu (pouze DEV režim) + requestBody: + required: true + content: + application/json: + schema: + $ref: "../../schemas/_index.yml#/BoltSimGroupRequest" + responses: + "200": + description: Simulace byla ukončena + content: + application/json: + schema: + $ref: "../../schemas/_index.yml#/BoltSimResponse" + "400": + description: Chybný požadavek + "403": + description: Endpoint není dostupný v tomto režimu diff --git a/types/paths/dev/boltState.yml b/types/paths/dev/boltState.yml new file mode 100644 index 0000000..1220c9d --- /dev/null +++ b/types/paths/dev/boltState.yml @@ -0,0 +1,20 @@ +post: + operationId: setBoltTrackingState + summary: Ruční nastavení konkrétního stavu simulace Bolt (pouze DEV režim) + requestBody: + required: true + content: + application/json: + schema: + $ref: "../../schemas/_index.yml#/BoltSimStateRequest" + responses: + "200": + description: Stav byl nastaven + content: + application/json: + schema: + $ref: "../../schemas/_index.yml#/BoltSimResponse" + "400": + description: Chybný požadavek + "403": + description: Endpoint není dostupný v tomto režimu diff --git a/types/schemas/_index.yml b/types/schemas/_index.yml index 7d3383b..0421636 100644 --- a/types/schemas/_index.yml +++ b/types/schemas/_index.yml @@ -707,6 +707,86 @@ ClearMockDataRequest: description: Index dne v týdnu (0 = pondělí, 4 = pátek). Pokud není zadán, použije se aktuální den. $ref: "#/DayIndex" +# --- DEV SIMULACE BOLT --- +BoltSimGroupRequest: + description: Request s identifikátorem skupiny pro DEV simulaci Bolt (pouze DEV režim) + type: object + additionalProperties: false + required: + - groupId + properties: + groupId: + description: ID skupiny, jejíž sledování Bolt se má simulovat + type: string +BoltSimStateRequest: + description: Request pro ruční nastavení konkrétního stavu simulace Bolt (pouze DEV režim) + type: object + additionalProperties: false + required: + - groupId + - order_state + properties: + groupId: + description: ID skupiny + type: string + order_state: + description: Stav objednávky (např. accepted, preparing, waiting_delivery, in_delivery, delivered, cancelled) + type: string + courier_state: + description: Stav kurýra (např. arrived_to_provider, heading_to_client) + type: string + etaSeconds: + description: Očekávaný čas do doručení v sekundách + type: integer + minimum: 0 +BoltSimStep: + description: Aktuální krok simulace Bolt + type: object + additionalProperties: false + required: + - order_state + properties: + order_state: + type: string + courier_state: + type: string + etaSeconds: + type: integer +BoltSimStatus: + description: Stav simulace sledování Bolt pro skupinu + type: object + additionalProperties: false + required: + - token + - index + - total + - current + properties: + token: + description: Vygenerovaný sledovací token + type: string + index: + description: Index aktuálního kroku v sekvenci + type: integer + total: + description: Celkový počet kroků v sekvenci + type: integer + current: + $ref: "#/BoltSimStep" +BoltSimResponse: + description: Odpověď DEV endpointů simulace Bolt + type: object + additionalProperties: false + required: + - success + properties: + success: + type: boolean + token: + type: string + simulation: + $ref: "#/BoltSimStatus" + # --- SKUPINOVÉ OBJEDNÁVKY --- Store: description: Povolený obchod/restaurace pro extra objednávky.