feat: zobrazení stavu objednávky Bolt Food jako progress stepper
CI / Generate TypeScript types (push) Successful in 9s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 35s
CI / Playwright E2E tests (push) Successful in 1m22s
CI / Build and push Docker image (push) Successful in 41s
CI / Notify (push) Successful in 2s

Jak to funguje:
- OrderGroup má nové pole boltOrderState (raw order_state z Bolt API).
  Polling scheduler ho ukládá při každé změně a rozesílá přes Socket.io,
  takže stepper se posouvá živě všem uživatelům.
- Komponenta BoltOrderProgress vykresluje čtyři kroky
  (Přijato → Příprava → Na cestě → Doručeno) pod časy skupiny.
  Známé stavy se mapují explicitně, neznámé heuristikou podle
  klíčových slov, zrušená objednávka se zobrazí červeně.
  Tooltip ukazuje raw stav, aktivní krok pulzuje, dokud sledování běží.
- Po doručení (nebo zmizení objednávky z API) se token smaže,
  ale boltOrderState zůstává "delivered" — dokončený stepper je vidět
  po zbytek dne. Vynuluje se při změně/zrušení odkazu nebo návratu
  skupiny do stavu uzamčeno.
- Nastavení odkazu nově spustí okamžitý poll, stepper se tak objeví
  do vteřiny místo čekání na další tik scheduleru.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 12:28:26 +02:00
parent 9152425d2b
commit 84b95c6c70
8 changed files with 199 additions and 11 deletions
+31 -1
View File
@@ -97,10 +97,27 @@ describe('setGroupBoltTracking', () => {
expect(d.groups![0].boltTrackingToken).toBe(TOKEN);
});
test('prázdná hodnota sledování zruší', async () => {
test('prázdná hodnota sledování zruší včetně stavu', async () => {
await setGroupBoltTracking(CREATOR, groupId, SHARE_URL, TODAY);
const d = await setGroupBoltTracking(CREATOR, groupId, '', TODAY);
expect(d.groups![0].boltTrackingToken).toBeUndefined();
expect(d.groups![0].boltOrderState).toBeUndefined();
});
test('nový token vynuluje stav předchozí objednávky', async () => {
await setGroupBoltTracking(CREATOR, groupId, SHARE_URL, TODAY);
await storage.updateData<ClientData>(`2025-01-10_extra`, (current) => {
current!.groups![0].boltOrderState = 'preparing';
return current!;
});
// Stejný token stav nemění
let d = await setGroupBoltTracking(CREATOR, groupId, SHARE_URL, TODAY);
expect(d.groups![0].boltOrderState).toBe('preparing');
// Jiný token stav vynuluje
const otherUrl = `https://food.bolt.eu/sharedActiveOrder/${'b'.repeat(64)}`;
d = await setGroupBoltTracking(CREATOR, groupId, otherUrl, TODAY);
expect(d.groups![0].boltOrderState).toBeUndefined();
expect(d.groups![0].boltTrackingToken).toBe('b'.repeat(64));
});
test('odmítne neplatný odkaz', async () => {
@@ -143,6 +160,7 @@ describe('checkBoltTracking', () => {
const after = computeDeliveryHHMM(1800);
const group = await getGroup();
expect([before, after]).toContain(group.deliveryAt);
expect(group.boltOrderState).toBe('waiting_preparation');
expect(group.boltTrackingToken).toBe(TOKEN);
expect(mockEmit).toHaveBeenCalledWith('message', expect.anything());
expect(mockedAxios.post).toHaveBeenCalledWith(
@@ -167,6 +185,7 @@ describe('checkBoltTracking', () => {
await checkBoltTracking();
const group = await getGroup();
expect(group.boltTrackingToken).toBeUndefined();
expect(group.boltOrderState).toBe('delivered');
expect(group.deliveryAt).toMatch(/^\d{2}:\d{2}$/);
});
@@ -175,6 +194,17 @@ describe('checkBoltTracking', () => {
await checkBoltTracking();
const group = await getGroup();
expect(group.boltTrackingToken).toBeUndefined();
expect(group.boltOrderState).toBe('delivered');
});
test('aktualizuje boltOrderState při změně stavu beze změny času', async () => {
mockedAxios.post.mockResolvedValue(boltResponse({ order_state: 'waiting_preparation', expected_time_to_client_in_seconds: 1800 }));
await checkBoltTracking();
mockedAxios.post.mockResolvedValue(boltResponse({ order_state: 'preparing', expected_time_to_client_in_seconds: 1800 }));
await checkBoltTracking();
const group = await getGroup();
expect(group.boltOrderState).toBe('preparing');
expect(group.boltTrackingToken).toBe(TOKEN);
});
test('chybová odpověď Bolt API (code != 0) se počítá jako selhání', async () => {