feat: import jen některých dnů
feat: násobení jídel/cviků feat: refaktor napojování šablon
This commit is contained in:
1 parent
c3990d80f8
commit
8dac67bdd1
37 files changed
+2314
-481
No files matched your search
+51
-12
@@ -1,7 +1,7 @@
|
||||
import ExcelJS from 'exceljs';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { ImportResult, MealEntry, MealType } from "../../types/gen/types.gen";
|
||||
import { addEntries, getExistingImportKeys, isMealType } from "./meals";
|
||||
import { ImportMode, ImportResult, MealEntry, MealType } from "../../types/gen/types.gen";
|
||||
import { addEntries, findMealsForDays, isMealType } from "./meals";
|
||||
import { BadRequestError, isValidIsoDate } from "./utils";
|
||||
|
||||
/** Označení zdroje, kterým se importované záznamy značí. */
|
||||
@@ -363,18 +363,30 @@ function toMealEntry(row: LuncherRow, mealType: MealType, importKey: string): Me
|
||||
};
|
||||
}
|
||||
|
||||
/** Vrátí true, pokud je hodnota podporovaným režimem importu. */
|
||||
const isImportMode = (value: unknown): value is ImportMode =>
|
||||
value === ImportMode.PRIDAT || value === ImportMode.NAHRADIT;
|
||||
|
||||
/**
|
||||
* Naimportuje přehled vyexportovaný z Luncheru do jídel uživatele.
|
||||
*
|
||||
* Export je typicky za celý měsíc — záznamy se rozdělí do dnů, ke kterým patří.
|
||||
* Řádky, které už z dřívějšího importu existují, se přeskočí, takže opakovaný
|
||||
* import stejného měsíce data nezduplikuje.
|
||||
* V režimu PRIDAT se řádky, které už z dřívějšího importu existují, přeskočí,
|
||||
* takže opakovaný import stejného měsíce data nezduplikuje. Režim NAHRADIT
|
||||
* dřívější import dotčených dnů odebere a založí ho znovu — hodí se, když se
|
||||
* v Luncheru něco opravilo. Ručně zadaná jídla zůstávají v obou režimech.
|
||||
*
|
||||
* Náhled vrací všechny řádky, které jde založit, a k nim jídla, která už
|
||||
* v dotčených dnech jsou. Uživatel si v něm odškrtne, co nechce, a ostrý
|
||||
* import pak dostane jen vybrané klíče.
|
||||
*
|
||||
* @param login přihlašovací jméno uživatele
|
||||
* @param fileName název souboru (určuje formát)
|
||||
* @param content obsah souboru
|
||||
* @param defaultMealType typ jídla pro importované řádky, výchozí je oběd
|
||||
* @param dryRun pokud true, výsledek se jen spočítá a nic se neuloží
|
||||
* @param mode režim importu, výchozí je PRIDAT
|
||||
* @param selectedKeys klíče řádků k založení; bez nich se založí všechny
|
||||
*/
|
||||
export async function importLuncherExport(
|
||||
login: string,
|
||||
@@ -382,13 +394,23 @@ export async function importLuncherExport(
|
||||
content: Buffer,
|
||||
defaultMealType?: unknown,
|
||||
dryRun = false,
|
||||
mode?: unknown,
|
||||
selectedKeys?: unknown,
|
||||
): Promise<ImportResult> {
|
||||
const format = detectFormat(fileName);
|
||||
if (defaultMealType != null && !isMealType(defaultMealType)) {
|
||||
throw new BadRequestError("Nebyl předán platný typ jídla");
|
||||
}
|
||||
if (mode != null && !isImportMode(mode)) {
|
||||
throw new BadRequestError("Nebyl předán platný režim importu");
|
||||
}
|
||||
if (selectedKeys != null && !Array.isArray(selectedKeys)) {
|
||||
throw new BadRequestError("Vybrané řádky musí být pole klíčů");
|
||||
}
|
||||
// Luncher řeší výběr obědů, takže bez upřesnění zakládáme importy jako oběd
|
||||
const mealType: MealType = isMealType(defaultMealType) ? defaultMealType : MealType.OBED;
|
||||
const resolvedMode: ImportMode = isImportMode(mode) ? mode : ImportMode.PRIDAT;
|
||||
const replace = resolvedMode === ImportMode.NAHRADIT;
|
||||
|
||||
let parsed: ParsedFile;
|
||||
if (format === 'csv') {
|
||||
@@ -400,10 +422,14 @@ export async function importLuncherExport(
|
||||
}
|
||||
|
||||
const dates = Array.from(new Set(parsed.rows.map(row => row.date)));
|
||||
const existingKeys = await getExistingImportKeys(login, dates);
|
||||
const existing = await findMealsForDays(login, dates);
|
||||
// Při nahrazení se dřívější import založí znovu, duplicitou tedy není
|
||||
const existingKeys = new Set(replace
|
||||
? []
|
||||
: existing.map(entry => entry.importKey).filter(key => !!key));
|
||||
|
||||
const perDayIndex = new Map<string, number>();
|
||||
const entries: MealEntry[] = [];
|
||||
const candidates: MealEntry[] = [];
|
||||
let skipped = 0;
|
||||
for (const row of parsed.rows) {
|
||||
const indexWithinDay = perDayIndex.get(row.date) ?? 0;
|
||||
@@ -413,22 +439,35 @@ export async function importLuncherExport(
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
entries.push(toMealEntry(row, mealType, importKey));
|
||||
candidates.push(toMealEntry(row, mealType, importKey));
|
||||
}
|
||||
|
||||
const selectedKeyList = selectedKeys == null ? undefined : new Set(selectedKeys.map(String));
|
||||
const selected = candidates.filter(entry => !selectedKeyList || selectedKeyList.has(entry.importKey!));
|
||||
skipped += candidates.length - selected.length;
|
||||
|
||||
// Nahrazuje se jen ve dnech, do kterých se opravdu něco zakládá
|
||||
const selectedDays = new Set(selected.map(entry => entry.date));
|
||||
const replaced = replace
|
||||
? existing.filter(entry => entry.importSource === IMPORT_SOURCE && selectedDays.has(entry.date)).length
|
||||
: 0;
|
||||
|
||||
const days = dryRun
|
||||
? Array.from(new Set(entries.map(entry => entry.date))).sort()
|
||||
: await addEntries(login, entries);
|
||||
? Array.from(selectedDays).sort()
|
||||
: await addEntries(login, selected, replace ? IMPORT_SOURCE : undefined);
|
||||
|
||||
return {
|
||||
dryRun,
|
||||
format,
|
||||
mode: resolvedMode,
|
||||
rowCount: parsed.rows.length,
|
||||
imported: entries.length,
|
||||
imported: selected.length,
|
||||
skipped,
|
||||
replaced,
|
||||
days,
|
||||
totalPrice: entries.reduce((sum, entry) => sum + (entry.price ?? 0), 0),
|
||||
entries,
|
||||
totalPrice: selected.reduce((sum, entry) => sum + (entry.price ?? 0), 0),
|
||||
entries: dryRun ? candidates : selected,
|
||||
existing,
|
||||
warnings: parsed.warnings,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user