import crypto from "crypto"; import getStorage from "./storage"; import { getClientData, getToday, initIfNeeded } from "./service"; import { getStores } from "./stores"; import { removePendingQrsByGroupId } from "./pizza"; import { ClientData, GroupState, MealSlot, OrderGroup, OrderGroupMember } from "../../types/gen/types.gen"; import { formatDate } from "./utils"; const storage = getStorage(); async function getExtraData(date?: Date): Promise { await initIfNeeded(date, MealSlot.EXTRA); const data = await getClientData(date, MealSlot.EXTRA); data.stores = await getStores(); return data; } function getExtraKey(date?: Date): string { return `${formatDate(date ?? getToday())}_extra`; } async function saveExtraData(data: ClientData, date?: Date): Promise { await storage.setData(getExtraKey(date), data); return data; } function findGroup(data: ClientData, id: string): OrderGroup | undefined { return data.groups?.find(g => g.id === id); } /** * Vrátí seznam ISO dat (YYYY-MM-DD), pro která existuje alespoň jedna objednávková skupina. * Slouží ke zvýraznění dnů v date pickeru na stránce objednávání. */ export async function getOrderDates(): Promise { const EXTRA_SUFFIX = '_extra'; const keys = await storage.listKeys(EXTRA_SUFFIX); const dates: string[] = []; for (const key of keys) { if (!key.endsWith(EXTRA_SUFFIX)) continue; const data = await storage.getData(key); if (data?.groups && data.groups.length > 0) { dates.push(key.slice(0, -EXTRA_SUFFIX.length)); } } return dates.sort(); } export async function createGroup(creatorLogin: string, name: string, date?: Date): Promise { const stores = await getStores(); if (!stores.some(s => s.toLowerCase() === name.trim().toLowerCase())) { throw new Error('Obchod není v seznamu povolených obchodů'); } const data = await getExtraData(date); const canonical = stores.find(s => s.toLowerCase() === name.trim().toLowerCase())!; const group: OrderGroup = { id: crypto.randomUUID(), name: canonical, creatorLogin, state: GroupState.OPEN, members: { [creatorLogin]: {} }, }; data.groups = [...(data.groups ?? []), group]; return saveExtraData(data, date); } export async function deleteGroup(login: string, groupId: string, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group) throw new Error('Skupina nebyla nalezena'); if (group.creatorLogin !== login) throw new Error('Skupinu může smazat pouze zakladatel'); data.groups = (data.groups ?? []).filter(g => g.id !== groupId); return saveExtraData(data, date); } export async function addGroupMember(login: string, groupId: string, targetLogin: string, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group) throw new Error('Skupina nebyla nalezena'); if (group.state === GroupState.ORDERED) throw new Error('Skupinu ve stavu "objednáno" nelze upravovat'); if (login !== group.creatorLogin && login !== targetLogin) { throw new Error('Přidat jiného uživatele může pouze zakladatel'); } if (group.state === GroupState.LOCKED && login !== group.creatorLogin) { throw new Error('Skupinu ve stavu "uzamčeno" může upravovat pouze zakladatel'); } if (group.members[targetLogin]) throw new Error('Uživatel je již ve skupině'); group.members[targetLogin] = {}; return saveExtraData(data, date); } export async function removeGroupMember(login: string, groupId: string, targetLogin: string, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group) throw new Error('Skupina nebyla nalezena'); if (group.state === GroupState.ORDERED) throw new Error('Skupinu ve stavu "objednáno" nelze upravovat'); if (login !== group.creatorLogin && login !== targetLogin) { throw new Error('Odebrat jiného uživatele může pouze zakladatel'); } if (group.state === GroupState.LOCKED && login !== group.creatorLogin) { throw new Error('Skupinu ve stavu "uzamčeno" může upravovat pouze zakladatel'); } if (targetLogin === group.creatorLogin) throw new Error('Zakladatel skupiny nemůže být odebrán'); if (!group.members[targetLogin]) throw new Error('Uživatel není ve skupině'); delete group.members[targetLogin]; return saveExtraData(data, date); } export async function updateGroupMember(login: string, groupId: string, targetLogin: string, patch: Partial, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group) throw new Error('Skupina nebyla nalezena'); if (group.state === GroupState.ORDERED) throw new Error('Skupinu ve stavu "objednáno" nelze upravovat'); const isSelf = login === targetLogin; const isCreator = login === group.creatorLogin; if (!isSelf && !isCreator) throw new Error('Upravit jiného uživatele může pouze zakladatel'); if (!isCreator && group.state === GroupState.LOCKED) { throw new Error('Skupinu ve stavu "uzamčeno" může upravovat pouze zakladatel'); } if (!group.members[targetLogin]) throw new Error('Uživatel není ve skupině'); group.members[targetLogin] = { ...group.members[targetLogin], ...patch }; return saveExtraData(data, date); } const VALID_TRANSITIONS: Record = { [GroupState.OPEN]: [GroupState.LOCKED], [GroupState.LOCKED]: [GroupState.OPEN, GroupState.ORDERED], [GroupState.ORDERED]: [GroupState.LOCKED], }; function getCurrentHHMM(): string { const now = new Date(); return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; } export async function setGroupState(login: string, groupId: string, newState: GroupState, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group) throw new Error('Skupina nebyla nalezena'); if (group.creatorLogin !== login) throw new Error('Stav může měnit pouze zakladatel'); if (!VALID_TRANSITIONS[group.state].includes(newState)) { throw new Error(`Nelze přejít ze stavu "${group.state}" do stavu "${newState}"`); } if (newState === GroupState.ORDERED) { group.orderedAt = getCurrentHHMM(); } if (group.state === GroupState.ORDERED && newState === GroupState.LOCKED) { const memberLogins = Object.keys(group.members); await removePendingQrsByGroupId(memberLogins, groupId); group.orderedAt = undefined; group.deliveryAt = undefined; group.qrGenerated = undefined; for (const ml of memberLogins) { group.members[ml] = { ...group.members[ml], paid: undefined }; } } group.state = newState; return saveExtraData(data, date); } export async function markGroupQrGenerated(login: string, groupId: string, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group) throw new Error('Skupina nebyla nalezena'); if (group.creatorLogin !== login) throw new Error('QR kódy může generovat pouze zakladatel'); if (group.state !== GroupState.ORDERED) throw new Error('QR kódy lze generovat pouze ve stavu "objednáno"'); group.qrGenerated = true; await saveExtraData(data, date); } export async function markGroupMemberPaid(login: string, groupId: string, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group || !group.members[login]) return null; group.members[login] = { ...group.members[login], paid: true }; return saveExtraData(data, date); } export async function updateGroupFees(login: string, groupId: string, fees?: number, shipping?: number, tip?: number, discountType?: string, discountValue?: number, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group) throw new Error('Skupina nebyla nalezena'); if (group.creatorLogin !== login) throw new Error('Poplatky může měnit pouze zakladatel'); if (group.state === GroupState.ORDERED) throw new Error('Skupinu ve stavu "objednáno" nelze upravovat'); if (fees !== undefined) group.fees = fees > 0 ? fees : undefined; if (shipping !== undefined) group.shipping = shipping > 0 ? shipping : undefined; if (tip !== undefined) group.tip = tip > 0 ? tip : undefined; if (discountType !== undefined) group.discountType = (discountType as any) || undefined; if (discountValue !== undefined) group.discountValue = discountValue > 0 ? discountValue : undefined; return saveExtraData(data, date); } export async function updateGroupTimes(login: string, groupId: string, orderedAt?: string, deliveryAt?: string, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); if (!group) throw new Error('Skupina nebyla nalezena'); if (group.creatorLogin !== login) throw new Error('Časy může měnit pouze zakladatel'); if (orderedAt !== undefined) group.orderedAt = orderedAt || undefined; if (deliveryAt !== undefined) group.deliveryAt = deliveryAt || undefined; return saveExtraData(data, date); }