feat: podpora simulace objednávek z Bolt Food ve vývoji

This commit is contained in:
mates committed 2026-06-12 19:44:20 +02:00
1 parent bcec015c37
commit 34f6ba520e
13 files changed
+662 -4

No files matched your search

+134
View File
@@ -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<string, Simulation>();
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],
};
}