fix: přesnější stav kurýra u sledování Wolt
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 24s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 40s
CI / Playwright E2E tests (push) Successful in 1m46s
CI / Build and push Docker image (push) Successful in 48s
CI / Notify (push) Successful in 3s
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 24s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 40s
CI / Playwright E2E tests (push) Successful in 1m46s
CI / Build and push Docker image (push) Successful in 48s
CI / Notify (push) Successful in 3s
Wolt hlásí stav podniku, ne rozvozu — status zůstane na "ready" i celou dobu, co kurýr objednávku veze. Vyzvednutí proto poznáme podle pickup_eta: dokud je v budoucnosti, jídlo ještě čeká v podniku, po vyzvednutí se ustálí na skutečném čase. Dřív se stav odvozoval jen z příznaků kurýra, takže objednávka zůstala viset na kroku "Vyzvedávání", i když už byla na cestě. Kurýr má nově tři odvozené stavy: assigned (ještě nevyzvedl), delivering (veze ji k nám) a delivering_other (veze ji, ale nejdřív doručuje cizí objednávku — Wolt objednávky spojuje a kurýr se pak na mapě vzdaluje). Oba stavy delivering* znamenají krok "Na cestě", tooltip u nich vysvětlí objížďku.
This commit is contained in:
@@ -52,9 +52,9 @@ const BOLT_COURIER_STATE_TO_STEP: Record<string, number> = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Známé stavy objednávky z Wolt tracking API → index kroku ve stepperu.
|
* Známé stavy objednávky z Wolt tracking API → index kroku ve stepperu.
|
||||||
* Stav "ready" znamená hotové jídlo čekající na kurýra, proto zůstává v „Přípravě"
|
* Pozor: Wolt hlásí stav podniku, ne rozvozu — "ready" (jídlo hotové) zůstává
|
||||||
* a na „Vyzvedávání" se posune, až když je kurýr přiřazený. Že objednávku kurýr
|
* i celou dobu, co kurýr objednávku veze. Kroky „Vyzvedávání" a „Na cestě" proto
|
||||||
* skutečně veze, se pozná až ze stavu kurýra 'delivering' (viz trackingProviders.ts).
|
* neurčuje status, ale odvozený stav kurýra (viz trackingProviders.ts).
|
||||||
*/
|
*/
|
||||||
const WOLT_STATE_TO_STEP: Record<string, number> = {
|
const WOLT_STATE_TO_STEP: Record<string, number> = {
|
||||||
received: 0,
|
received: 0,
|
||||||
@@ -70,6 +70,13 @@ const WOLT_STATE_TO_STEP: Record<string, number> = {
|
|||||||
delivered: 4,
|
delivered: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Popis odvozeného stavu kurýra u Woltu do tooltipu. */
|
||||||
|
const WOLT_COURIER_INFO: Record<string, string> = {
|
||||||
|
assigned: 'kurýr přiřazen',
|
||||||
|
delivering: 'kurýr veze objednávku',
|
||||||
|
delivering_other: 'kurýr veze objednávku, ale nejdřív doručuje jinou',
|
||||||
|
};
|
||||||
|
|
||||||
/** Stavy, ve kterých už jídlo čeká připravené na kurýra. */
|
/** Stavy, ve kterých už jídlo čeká připravené na kurýra. */
|
||||||
const PICKUP_READY_STATES: Record<TrackingProvider, Set<string>> = {
|
const PICKUP_READY_STATES: Record<TrackingProvider, Set<string>> = {
|
||||||
bolt: new Set(['waiting_delivery', 'ready_for_pickup', 'waiting_courier', 'waiting_pickup']),
|
bolt: new Set(['waiting_delivery', 'ready_for_pickup', 'waiting_courier', 'waiting_pickup']),
|
||||||
@@ -92,8 +99,9 @@ function stepForOrderState(provider: TrackingProvider, state: string): number |
|
|||||||
function stepForCourierState(provider: TrackingProvider, state?: string): number {
|
function stepForCourierState(provider: TrackingProvider, state?: string): number {
|
||||||
if (!state) return 0;
|
if (!state) return 0;
|
||||||
const s = state.toLowerCase();
|
const s = state.toLowerCase();
|
||||||
// Wolt má jen odvozené stavy: 'assigned' o kroku nic neříká, 'delivering' = veze naši objednávku
|
// Wolt má jen odvozené stavy: 'assigned' o kroku nic neříká, 'delivering'
|
||||||
if (provider === 'wolt') return s === 'delivering' ? 3 : 0;
|
// i 'delivering_other' znamenají, že kurýr naši objednávku už veze
|
||||||
|
if (provider === 'wolt') return s.startsWith('delivering') ? 3 : 0;
|
||||||
if (s in BOLT_COURIER_STATE_TO_STEP) return BOLT_COURIER_STATE_TO_STEP[s];
|
if (s in BOLT_COURIER_STATE_TO_STEP) return BOLT_COURIER_STATE_TO_STEP[s];
|
||||||
if (/picked|client|delivering|transport/.test(s)) return 3;
|
if (/picked|client|delivering|transport/.test(s)) return 3;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -127,7 +135,7 @@ export default function OrderProgress({ provider, state, courierState, tracking
|
|||||||
const foodReadyForPickup = PICKUP_READY_STATES[provider]?.has(state.toLowerCase());
|
const foodReadyForPickup = PICKUP_READY_STATES[provider]?.has(state.toLowerCase());
|
||||||
if (step < 3 && courierAtProvider && foodReadyForPickup) step = 2;
|
if (step < 3 && courierAtProvider && foodReadyForPickup) step = 2;
|
||||||
const courierInfo = provider === 'wolt'
|
const courierInfo = provider === 'wolt'
|
||||||
? (courierState === 'delivering' ? 'kurýr veze objednávku' : 'kurýr přiřazen')
|
? WOLT_COURIER_INFO[courierState ?? ''] ?? 'kurýr přiřazen'
|
||||||
: `kurýr: ${courierState}`;
|
: `kurýr: ${courierState}`;
|
||||||
const rawInfo = courierState ? `${state} / ${courierInfo}` : state;
|
const rawInfo = courierState ? `${state} / ${courierInfo}` : state;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -314,6 +314,10 @@ describe('checkOrderTracking (Wolt)', () => {
|
|||||||
return { data: { refresh_in_seconds: 30, timezone: 'Europe/Prague', ...details } };
|
return { data: { refresh_in_seconds: 30, timezone: 'Europe/Prague', ...details } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ISO čas N minut v minulosti/budoucnosti — pickup_eta se posuzuje vůči teď. */
|
||||||
|
const past = (minutes: number) => new Date(Date.now() - minutes * 60_000).toISOString();
|
||||||
|
const future = (minutes: number) => new Date(Date.now() + minutes * 60_000).toISOString();
|
||||||
|
|
||||||
/** 404 z Wolt API (neznámý nebo expirovaný tracking code). */
|
/** 404 z Wolt API (neznámý nebo expirovaný tracking code). */
|
||||||
function woltNotFound() {
|
function woltNotFound() {
|
||||||
(mockedAxios.isAxiosError as unknown as jest.Mock).mockImplementation((e: any) => !!e?.isAxiosError);
|
(mockedAxios.isAxiosError as unknown as jest.Mock).mockImplementation((e: any) => !!e?.isAxiosError);
|
||||||
@@ -364,10 +368,11 @@ describe('checkOrderTracking (Wolt)', () => {
|
|||||||
expect(mockedAxios.post).not.toHaveBeenCalled();
|
expect(mockedAxios.post).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('kurýr doručující cizí objednávku je jen assigned', async () => {
|
test('kurýr u podniku před vyzvednutím je jen assigned', async () => {
|
||||||
// Reálná odpověď: jídlo hotové, kurýr u podniku, ale veze ještě cizí objednávku
|
// Reálná odpověď z chvíle, kdy kurýr dorazil k podniku — pickup_eta teprve nastane
|
||||||
mockedAxios.get.mockResolvedValue(woltResponse({
|
mockedAxios.get.mockResolvedValue(woltResponse({
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
|
pickup_eta: future(10),
|
||||||
delivery_eta: '2026-08-24T09:35:33.066000+00:00',
|
delivery_eta: '2026-08-24T09:35:33.066000+00:00',
|
||||||
couriers: [{
|
couriers: [{
|
||||||
id: '31ce2f6e139bec6c',
|
id: '31ce2f6e139bec6c',
|
||||||
@@ -383,9 +388,22 @@ describe('checkOrderTracking (Wolt)', () => {
|
|||||||
expect(group.trackingCourierState).toBe('assigned');
|
expect(group.trackingCourierState).toBe('assigned');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('kurýr vezoucí naši objednávku se uloží jako delivering', async () => {
|
test('po vyzvednutí veze kurýr objednávku, i když nejdřív doručuje cizí', async () => {
|
||||||
|
// Wolt spojuje objednávky: náš oběd je v autě, kurýr se ale zatím vzdaluje k cizí adrese
|
||||||
mockedAxios.get.mockResolvedValue(woltResponse({
|
mockedAxios.get.mockResolvedValue(woltResponse({
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
|
pickup_eta: past(35),
|
||||||
|
delivery_eta: '2026-08-24T09:35:33.066000+00:00',
|
||||||
|
couriers: [{ is_delivering: true, is_delivering_other_order: true }],
|
||||||
|
}));
|
||||||
|
await checkOrderTracking();
|
||||||
|
expect((await getGroup()).trackingCourierState).toBe('delivering_other');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('kurýr mířící k nám se uloží jako delivering', async () => {
|
||||||
|
mockedAxios.get.mockResolvedValue(woltResponse({
|
||||||
|
status: 'ready',
|
||||||
|
pickup_eta: past(35),
|
||||||
delivery_eta: '2026-08-24T09:35:33.066000+00:00',
|
delivery_eta: '2026-08-24T09:35:33.066000+00:00',
|
||||||
couriers: [{ is_delivering: true, is_delivering_other_order: false }],
|
couriers: [{ is_delivering: true, is_delivering_other_order: false }],
|
||||||
}));
|
}));
|
||||||
@@ -396,6 +414,7 @@ describe('checkOrderTracking (Wolt)', () => {
|
|||||||
test('kurýr bez příznaků doručování je assigned', async () => {
|
test('kurýr bez příznaků doručování je assigned', async () => {
|
||||||
mockedAxios.get.mockResolvedValue(woltResponse({
|
mockedAxios.get.mockResolvedValue(woltResponse({
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
|
pickup_eta: past(35),
|
||||||
delivery_eta: '2026-08-24T09:35:33.066000+00:00',
|
delivery_eta: '2026-08-24T09:35:33.066000+00:00',
|
||||||
couriers: [{ coordinates: { lat: 49.72, lon: 13.34 } }],
|
couriers: [{ coordinates: { lat: 49.72, lon: 13.34 } }],
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ const boltTracker: DeliveryTracker = {
|
|||||||
interface WoltTrackingDetails {
|
interface WoltTrackingDetails {
|
||||||
status?: string;
|
status?: string;
|
||||||
delivery_eta?: string | null;
|
delivery_eta?: string | null;
|
||||||
|
pickup_eta?: string | null;
|
||||||
couriers?: WoltCourier[];
|
couriers?: WoltCourier[];
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
}
|
}
|
||||||
@@ -165,14 +166,25 @@ interface WoltCourier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wolt neposílá stav kurýra, jen jeho pozici a dva příznaky. Odvodíme z nich
|
* Wolt nemá stav „vyzvednuto" — status zůstává celou dobu doručování na hodnotě
|
||||||
* dvojí stav: 'delivering' (veze naši objednávku → krok „Na cestě") a 'assigned'
|
* ready (ta popisuje jen podnik: jídlo je hotové). Že kurýr jídlo skutečně veze,
|
||||||
* (kurýr přiřazen, ale naše jídlo ještě nemá — typicky doručuje cizí objednávku).
|
* poznáme podle pickup_eta: dokud vyzvednutí teprve nastane, je v budoucnosti;
|
||||||
|
* po vyzvednutí se ustálí na skutečném čase a zůstane v minulosti (na rozdíl od
|
||||||
|
* delivery_eta, který se dál přepočítává).
|
||||||
|
*
|
||||||
|
* Odvozené stavy kurýra:
|
||||||
|
* - 'assigned' kurýr přiřazen, jídlo ještě nevyzvedl
|
||||||
|
* - 'delivering' veze naši objednávku k nám
|
||||||
|
* - 'delivering_other' veze naši objednávku, ale nejdřív doručuje cizí (Wolt
|
||||||
|
* spojuje objednávky — kurýr se pak na mapě vzdaluje)
|
||||||
*/
|
*/
|
||||||
function woltCourierState(couriers?: WoltCourier[]): string | undefined {
|
function woltCourierState(details: WoltTrackingDetails, now: number = Date.now()): string | undefined {
|
||||||
const courier = couriers?.[0];
|
const courier = details.couriers?.[0];
|
||||||
if (!courier) return undefined;
|
if (!courier) return undefined;
|
||||||
return courier.is_delivering && !courier.is_delivering_other_order ? 'delivering' : 'assigned';
|
const pickupEta = details.pickup_eta ? new Date(details.pickup_eta).getTime() : NaN;
|
||||||
|
const pickedUp = !Number.isNaN(pickupEta) && pickupEta <= now;
|
||||||
|
if (!courier.is_delivering || !pickedUp) return 'assigned';
|
||||||
|
return courier.is_delivering_other_order ? 'delivering_other' : 'delivering';
|
||||||
}
|
}
|
||||||
|
|
||||||
const woltTracker: DeliveryTracker = {
|
const woltTracker: DeliveryTracker = {
|
||||||
@@ -214,7 +226,7 @@ const woltTracker: DeliveryTracker = {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
orderState: details.status,
|
orderState: details.status,
|
||||||
courierState: woltCourierState(details.couriers),
|
courierState: woltCourierState(details),
|
||||||
deliveryAt: details.delivery_eta ? formatEtaHHMM(details.delivery_eta, details.timezone) : undefined,
|
deliveryAt: details.delivery_eta ? formatEtaHHMM(details.delivery_eta, details.timezone) : undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -975,7 +975,7 @@ OrderGroup:
|
|||||||
description: Poslední známý stav objednávky ze sledovací služby (raw hodnota — Bolt order_state, např. waiting_preparation; Wolt status, např. production). Zůstává vyplněn i po ukončení sledování.
|
description: Poslední známý stav objednávky ze sledovací služby (raw hodnota — Bolt order_state, např. waiting_preparation; Wolt status, např. production). Zůstává vyplněn i po ukončení sledování.
|
||||||
type: string
|
type: string
|
||||||
trackingCourierState:
|
trackingCourierState:
|
||||||
description: Poslední známý stav kurýra ze sledovací služby (Bolt raw courier.state, např. arrived_to_provider; Wolt odvozené "assigned"/"delivering"). Zpřesňuje krok "Na cestě" ve stepperu.
|
description: Poslední známý stav kurýra ze sledovací služby (Bolt raw courier.state, např. arrived_to_provider; Wolt odvozené "assigned"/"delivering"/"delivering_other"). Zpřesňuje krok "Na cestě" ve stepperu.
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
# --- NEVYŘÍZENÉ QR KÓDY ---
|
# --- NEVYŘÍZENÉ QR KÓDY ---
|
||||||
|
|||||||
Reference in New Issue
Block a user