CI / Generate TypeScript types (push) Successful in 13s
CI / Server unit tests (push) Successful in 25s
CI / Build server (push) Successful in 28s
CI / Build client (push) Successful in 41s
CI / Playwright E2E tests (push) Successful in 1m47s
CI / Build and push Docker image (push) Successful in 53s
CI / Notify (push) Successful in 2s
Vedle Bolt Food jde nově sledovat i objednávka z Woltu — stačí u objednané skupiny vložit odkaz z track.wolt.com. Logika sledování je zobecněná do registru rozvozových služeb (trackingProviders.ts): každá služba umí vytáhnout kód ze sdílecího odkazu a dotázat se svého API, scheduler i stepper jsou společné. - pole skupiny bolt* přejmenována na tracking* + nové trackingProvider - endpoint /groups/setBoltTracking → /groups/setTracking - Wolt: čas doručení z delivery_eta v časové zóně objednávky, 404 ukončí sledování, stav kurýra odvozen z is_delivering/is_delivering_other_order - DEV simulace zůstává jen pro Bolt Food
138 lines
5.0 KiB
TypeScript
138 lines
5.0 KiB
TypeScript
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 trackingProviders.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 OrderProgress. */
|
|
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/OrderProgress.tsx
|
|
* (Přijato → Příprava → Vyzvedávání → Na cestě → Doručeno).
|
|
*/
|
|
export const DEFAULT_SCENARIO: SimStep[] = [
|
|
{ order_state: 'waiting_acceptance', etaSeconds: 2100 },
|
|
{ order_state: 'accepted', etaSeconds: 1800 },
|
|
{ order_state: 'waiting_preparation', courier_state: 'accepted', etaSeconds: 1800 },
|
|
{ order_state: 'preparing', courier_state: 'heading_to_provider', 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],
|
|
};
|
|
}
|