From 6a2bffa31f564dd7e81f94db0b5fc0c857fd7028 Mon Sep 17 00:00:00 2001 From: batmanisko Date: Tue, 16 Jun 2026 14:10:26 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20nov=C3=BD=20krok=20=E2=80=9EVyzved?= =?UTF-8?q?=C3=A1v=C3=A1n=C3=AD"=20ve=20stepperu=20Bolt=20Food?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Krok se aktivuje, když je kurýr u podniku a jídlo je zároveň označené jako hotové k vyzvednutí — samotný arrived_to_provider stupínek nezvedá, aby se neopakovala chyba Bolt UI, které „vyzvedávání" hlásí i během přípravy. --- client/src/components/BoltOrderProgress.tsx | 45 ++++++++++------- .../components/modals/BoltSimulationModal.tsx | 50 ++++++++++--------- server/src/boltSimulator.ts | 3 +- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/client/src/components/BoltOrderProgress.tsx b/client/src/components/BoltOrderProgress.tsx index d0435b5..86546ee 100644 --- a/client/src/components/BoltOrderProgress.tsx +++ b/client/src/components/BoltOrderProgress.tsx @@ -1,12 +1,15 @@ import { OverlayTrigger, Tooltip } from 'react-bootstrap'; import './BoltOrderProgress.scss'; -const STEPS = ['Přijato', 'Příprava', 'Na cestě', 'Doručeno']; +const STEPS = ['Přijato', 'Příprava', 'Vyzvedávání', 'Na cestě', 'Doručeno']; /** * Známé stavy objednávky z Bolt API → index kroku ve stepperu. * Pozor: waiting_delivery znamená "jídlo čeká v podniku na vyzvednutí", * nikoli "na cestě" — tu signalizuje až stav kurýra (picked_up apod.). + * Krok „Vyzvedávání" je vyhrazen pro kombinaci „kurýr u podniku + jídlo hotové" + * (viz logika níže), samotný order_state ho neudělí — jinak bychom hlásili + * vyzvedávání i když se ještě peče (chování, na které si Bolt sám občas stěžuje). */ const ORDER_STATE_TO_STEP: Record = { created: 0, @@ -18,33 +21,36 @@ const ORDER_STATE_TO_STEP: Record = { ready_for_pickup: 1, waiting_courier: 1, waiting_pickup: 1, - picked_up: 2, - in_delivery: 2, - delivering: 2, - heading_to_client: 2, - delivered: 3, - finished: 3, + picked_up: 3, + in_delivery: 3, + delivering: 3, + heading_to_client: 3, + delivered: 4, + finished: 4, }; /** Stavy kurýra z Bolt API → index kroku. Kurýr u podniku ještě neznamená "na cestě". */ const COURIER_STATE_TO_STEP: Record = { heading_to_provider: 1, arrived_to_provider: 1, - picked_up: 2, - heading_to_client: 2, - delivering: 2, - arrived_to_client: 2, - delivered: 3, + picked_up: 3, + heading_to_client: 3, + delivering: 3, + arrived_to_client: 3, + delivered: 4, }; +/** Order states ve kterých už jídlo čeká připravené na kurýra. */ +const PICKUP_READY_ORDER_STATES = new Set(['waiting_delivery', 'ready_for_pickup', 'waiting_courier', 'waiting_pickup']); + /** Neznámé stavy se mapují heuristicky podle klíčových slov. */ function stepForOrderState(state: string): number | 'cancelled' { const s = state.toLowerCase(); if (s in ORDER_STATE_TO_STEP) return ORDER_STATE_TO_STEP[s]; if (/cancel|reject|fail/.test(s)) return 'cancelled'; - if (/delivered|finished/.test(s)) return 3; + if (/delivered|finished/.test(s)) return 4; if (/^waiting|prepar|ready|cook/.test(s)) return 1; - if (/picked|delivering|heading_to_client|transport/.test(s)) return 2; + if (/picked|delivering|heading_to_client|transport/.test(s)) return 3; return 0; } @@ -52,7 +58,7 @@ function stepForCourierState(state?: string): number { if (!state) return 0; const s = state.toLowerCase(); if (s in COURIER_STATE_TO_STEP) return COURIER_STATE_TO_STEP[s]; - if (/picked|client|delivering|transport/.test(s)) return 2; + if (/picked|client|delivering|transport/.test(s)) return 3; return 0; } @@ -72,11 +78,16 @@ export default function BoltOrderProgress({ state, courierState, tracking }: Pro return Objednávka Bolt byla zrušena; } // Stav kurýra může krok jen zpřesnit dopředu (např. waiting_delivery + picked_up → Na cestě) - const step = Math.max(orderStep, stepForCourierState(courierState)); + let step = Math.max(orderStep, stepForCourierState(courierState)); + // Vyzvedávání = kurýr u podniku a jídlo skutečně hotové. Sám arrived_to_provider + // (bez toho, že by jídlo bylo hotové) nestačí — viz komentář u ORDER_STATE_TO_STEP. + const courierAtProvider = courierState?.toLowerCase() === 'arrived_to_provider'; + const foodReadyForPickup = PICKUP_READY_ORDER_STATES.has(state.toLowerCase()); + if (step < 3 && courierAtProvider && foodReadyForPickup) step = 2; const rawInfo = courierState ? `${state} / kurýr: ${courierState}` : state; return ( Stav z Bolt Food: {rawInfo}}> -
+
{STEPS.map((label, i) => (
diff --git a/client/src/components/modals/BoltSimulationModal.tsx b/client/src/components/modals/BoltSimulationModal.tsx index 3fa4c9d..fe32837 100644 --- a/client/src/components/modals/BoltSimulationModal.tsx +++ b/client/src/components/modals/BoltSimulationModal.tsx @@ -13,13 +13,14 @@ type Props = { }; /** 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' }, +const STATE_OPTIONS: { key: string; label: string; order_state: string; courier_state?: string }[] = [ + { key: 'accepted', label: 'Přijato', order_state: 'accepted' }, + { key: 'preparing', label: 'Příprava', order_state: 'preparing' }, + { key: 'waiting_delivery', label: 'Příprava (čeká na vyzvednutí)', order_state: 'waiting_delivery' }, + { key: 'pickup', label: 'Vyzvedávání', order_state: 'waiting_delivery', courier_state: 'arrived_to_provider' }, + { key: 'in_delivery', label: 'Na cestě', order_state: 'in_delivery', courier_state: 'heading_to_client' }, + { key: 'delivered', label: 'Doručeno', order_state: 'delivered' }, + { key: 'cancelled', label: 'Zrušeno', order_state: 'cancelled' }, ]; /** Modální dialog pro simulaci sledování Bolt objednávky (pouze DEV). */ @@ -27,7 +28,7 @@ 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 [manualKey, setManualKey] = useState('preparing'); const running = !!group.boltTrackingToken; @@ -58,17 +59,20 @@ export default function BoltSimulationModal({ isOpen, onClose, group }: Readonly () => 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 handleSetState = () => { + const opt = STATE_OPTIONS.find(o => o.key === manualKey); + if (!opt) return; + return run( + () => setBoltTrackingState({ + body: { + groupId: group.id, + order_state: opt.order_state, + courier_state: opt.courier_state, + }, + }), + `Stav nastaven na „${opt.label}".`, + ); + }; const handlePoll = () => run( () => pollBoltTracking(), 'Scheduler spuštěn (poll proběhl).', @@ -122,17 +126,17 @@ export default function BoltSimulationModal({ isOpen, onClose, group }: Readonly <>

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

Konkrétní stav setManualState(e.target.value)} + value={manualKey} + onChange={e => setManualKey(e.target.value)} > {STATE_OPTIONS.map(o => ( - + ))}
diff --git a/server/src/boltSimulator.ts b/server/src/boltSimulator.ts index f2e9e1f..5a7cf38 100644 --- a/server/src/boltSimulator.ts +++ b/server/src/boltSimulator.ts @@ -40,7 +40,8 @@ interface Simulation { /** * 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). + * client/src/components/BoltOrderProgress.tsx + * (Přijato → Příprava → Vyzvedávání → Na cestě → Doručeno). */ export const DEFAULT_SCENARIO: SimStep[] = [ { order_state: 'accepted', etaSeconds: 1800 },