feat: spin the wheel, objednavani scrape menu, historie cen
CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 25s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 39s
CI / Playwright E2E tests (push) Successful in 1m52s
CI / Build and push Docker image (push) Successful in 51s
CI / Notify (push) Successful in 2s
CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 25s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 39s
CI / Playwright E2E tests (push) Successful in 1m52s
CI / Build and push Docker image (push) Successful in 51s
CI / Notify (push) Successful in 2s
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import type { StoreMenuDish } from "../../../types";
|
||||
|
||||
/** Normalizace pro porovnání: malá písmena, bez diakritiky, emoji, čísel položek („28." / „13A.") a interpunkce. */
|
||||
export function normalizeDishName(text: string): string {
|
||||
return text
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/^\s*\*?\s*\d+[a-z]?\s*[.)]\s*/, '')
|
||||
.replace(/[^a-z0-9]+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
export type MatchedPart = {
|
||||
/** Původní text části poznámky */
|
||||
text: string,
|
||||
quantity: number,
|
||||
dish?: StoreMenuDish,
|
||||
};
|
||||
|
||||
export type NoteMatch = {
|
||||
parts: MatchedPart[],
|
||||
/** Cena v Kč dle nabídky — jen pokud byly nalezeny všechny části a mají cenu */
|
||||
total?: number,
|
||||
};
|
||||
|
||||
/** Množství zapsané jako „2x burger", „2 × burger" nebo „burger 2x". */
|
||||
function parseQuantity(text: string): { quantity: number, rest: string } {
|
||||
const prefix = text.match(/^\s*(\d+)\s*[x×]\s*(.+)$/i);
|
||||
if (prefix) return { quantity: Number(prefix[1]), rest: prefix[2] };
|
||||
const suffix = text.match(/^(.+?)\s*[x×]?\s*(\d+)\s*[x×]\s*$/i);
|
||||
if (suffix) return { quantity: Number(suffix[2]), rest: suffix[1] };
|
||||
return { quantity: 1, rest: text };
|
||||
}
|
||||
|
||||
/** Najde položku nabídky k textu — přesná shoda, jinak nejbližší položka obsahující text (či naopak). */
|
||||
function findDish(text: string, dishes: { dish: StoreMenuDish, norm: string }[]): StoreMenuDish | undefined {
|
||||
const norm = normalizeDishName(text);
|
||||
if (norm.length < 3) return undefined;
|
||||
const exact = dishes.find(d => d.norm === norm);
|
||||
if (exact) return exact.dish;
|
||||
const candidates = dishes.filter(d => d.norm.length >= 3 && (d.norm.includes(norm) || norm.includes(d.norm)));
|
||||
// Nejbližší délkou = nejspecifičtější shoda („Kebab" vs. „Kebab box s hranolky")
|
||||
candidates.sort((a, b) => Math.abs(a.norm.length - norm.length) - Math.abs(b.norm.length - norm.length));
|
||||
return candidates[0]?.dish;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rozpozná v poznámce člena objednávky položky z nabídky podniku („Twister speciál + 2x Hranolky")
|
||||
* a spočítá jejich cenu. Nejprve zkusí celou poznámku (názvy jídel mohou obsahovat „+").
|
||||
*/
|
||||
export function matchNoteToMenu(note: string | undefined, menu: StoreMenuDish[]): NoteMatch | undefined {
|
||||
if (!note?.trim() || menu.length === 0) return undefined;
|
||||
const dishes = menu.map(dish => ({ dish, norm: normalizeDishName(dish.name) }));
|
||||
const whole = dishes.find(d => d.norm === normalizeDishName(note));
|
||||
const parts: MatchedPart[] = whole
|
||||
? [{ text: note.trim(), quantity: 1, dish: whole.dish }]
|
||||
: note.split('+').map(p => p.trim()).filter(Boolean).map(text => {
|
||||
const { quantity, rest } = parseQuantity(text);
|
||||
return { text, quantity, dish: findDish(rest, dishes) };
|
||||
});
|
||||
if (!parts.some(p => p.dish)) return undefined;
|
||||
const complete = parts.every(p => p.dish?.price != null);
|
||||
return {
|
||||
parts,
|
||||
total: complete ? parts.reduce((sum, p) => sum + p.dish!.price! * p.quantity, 0) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/** Počet položek v poznámce objednávky — části oddělené „+", včetně množství („2x Hranolky" = 2). */
|
||||
export function countNoteItems(note: string | undefined): number {
|
||||
if (!note?.trim()) return 0;
|
||||
return note.split('+').map(p => p.trim()).filter(Boolean)
|
||||
.reduce((sum, part) => sum + parseQuantity(part).quantity, 0);
|
||||
}
|
||||
Reference in New Issue
Block a user