Gerichtnamen, Essenszeiten und Allergen-/Zusatzstoff-Bezeichnungen sind jetzt genauso dynamisch mehrsprachig wie die Bedienoberfläche aus v1.10.0, statt fest auf Englisch/Polnisch begrenzt zu sein. Neue Übersetzungstabellen ersetzen die alten name_en/name_pl-Spalten, DeepL-Auto-Übersetzung und CSV-Import/-Export unterstützen jetzt beliebig viele aktive Sprachen.
115 lines
4.0 KiB
TypeScript
115 lines
4.0 KiB
TypeScript
import "server-only";
|
||
import { query, queryOne } from "@/lib/db";
|
||
import {
|
||
loadTranslations,
|
||
loadTranslationsFor,
|
||
replaceTranslations,
|
||
type TranslationTable,
|
||
} from "./translations";
|
||
import type { CodeItem } from "@/lib/types";
|
||
|
||
// "allergens"/"additives" sind identisch aufgebaute Tabellen (Kürzel +
|
||
// Bezeichnung in Deutsch, Übersetzungen in der jeweiligen
|
||
// *_translations-Tabelle). Tabellenname kommt nie aus Nutzereingaben,
|
||
// sondern immer aus dem literal getypten Parameter unten – sicher für
|
||
// String-Interpolation in der Query.
|
||
export type CodeTable = "allergens" | "additives";
|
||
|
||
const TRANSLATION_TABLE: Record<CodeTable, TranslationTable> = {
|
||
allergens: "allergen_translations",
|
||
additives: "additive_translations",
|
||
};
|
||
|
||
interface CodeRow {
|
||
code: string;
|
||
name: string;
|
||
sort: number;
|
||
}
|
||
|
||
export async function listCodes(table: CodeTable): Promise<CodeItem[]> {
|
||
const rows = await query<CodeRow>(`select code, name, sort from ${table} order by sort`);
|
||
const translations = await loadTranslations(TRANSLATION_TABLE[table]);
|
||
return rows.map((r) => ({ ...r, translations: translations.get(r.code) ?? {} }));
|
||
}
|
||
|
||
export async function createCode(
|
||
table: CodeTable,
|
||
input: { code: string; name: string; translations?: Record<string, string | null> }
|
||
): Promise<CodeItem> {
|
||
const maxRow = await queryOne<{ sort: number }>(
|
||
`select sort from ${table} order by sort desc limit 1`
|
||
);
|
||
const row = (await queryOne<CodeRow>(
|
||
`insert into ${table} (code, name, sort) values ($1, $2, $3) returning code, name, sort`,
|
||
[input.code, input.name, (maxRow?.sort ?? 0) + 1]
|
||
))!;
|
||
if (input.translations) {
|
||
await replaceTranslations(TRANSLATION_TABLE[table], row.code, input.translations);
|
||
}
|
||
const translations = await loadTranslationsFor(TRANSLATION_TABLE[table], [row.code]);
|
||
return { ...row, translations: translations.get(row.code) ?? {} };
|
||
}
|
||
|
||
export async function updateCode(
|
||
table: CodeTable,
|
||
originalCode: string,
|
||
input: { code: string; name: string; translations?: Record<string, string | null> }
|
||
): Promise<CodeItem | null> {
|
||
const row = await queryOne<CodeRow>(
|
||
`update ${table} set code = $1, name = $2 where code = $3 returning code, name, sort`,
|
||
[input.code, input.name, originalCode]
|
||
);
|
||
if (!row) return null;
|
||
if (input.translations) {
|
||
await replaceTranslations(TRANSLATION_TABLE[table], row.code, input.translations);
|
||
}
|
||
const translations = await loadTranslationsFor(TRANSLATION_TABLE[table], [row.code]);
|
||
return { ...row, translations: translations.get(row.code) ?? {} };
|
||
}
|
||
|
||
export async function deleteCode(table: CodeTable, code: string): Promise<void> {
|
||
await query(`delete from ${table} where code = $1`, [code]);
|
||
}
|
||
|
||
export interface CodeImportRow {
|
||
code: string;
|
||
name: string;
|
||
translations: Record<string, string>;
|
||
}
|
||
|
||
/** CSV-Import: legt neue Kürzel an, bestehende (gleicher Code) werden
|
||
* aktualisiert – ein doppelter Code innerhalb derselben Datei
|
||
* aktualisiert also einfach zweimal statt einen Fehler zu werfen. */
|
||
export async function importCodes(
|
||
table: CodeTable,
|
||
rows: CodeImportRow[]
|
||
): Promise<{ created: number; updated: number }> {
|
||
const existing = await query<{ code: string }>(`select code from ${table}`);
|
||
const known = new Set(existing.map((r) => r.code));
|
||
const maxRow = await queryOne<{ sort: number }>(
|
||
`select coalesce(max(sort), 0) as sort from ${table}`
|
||
);
|
||
let nextSort = (maxRow?.sort ?? 0) + 1;
|
||
let created = 0;
|
||
let updated = 0;
|
||
|
||
for (const row of rows) {
|
||
if (known.has(row.code)) {
|
||
await query(`update ${table} set name = $1 where code = $2`, [row.name, row.code]);
|
||
updated++;
|
||
} else {
|
||
await query(`insert into ${table} (code, name, sort) values ($1, $2, $3)`, [
|
||
row.code,
|
||
row.name,
|
||
nextSort++,
|
||
]);
|
||
known.add(row.code);
|
||
created++;
|
||
}
|
||
if (Object.keys(row.translations).length > 0) {
|
||
await replaceTranslations(TRANSLATION_TABLE[table], row.code, row.translations);
|
||
}
|
||
}
|
||
return { created, updated };
|
||
}
|