import { eq, and } from "drizzle-orm"; import type { Tx } from "../../lib/audit.js"; import * as schema from "../schema/index.js"; import type { MerkmalSeed } from "./data/merkmale.js"; import type { VehicleTemplateSeed } from "./data/vehicle-templates.js"; import type { EquipmentCategorySeed } from "./data/equipment-categories.js"; /** * Idempotente Upserts für Workstream-9-Seeds (Querschnittsstandard 7: * ausschließlich `onConflictDoUpdate` auf Natural Keys, mehrfaches Ausführen * ändert keine Counts). * * Natural Keys: * - merkmale.slug * - merkmal_optionen(merkmalId, wert) * - vehicle_templates.code * - vehicle_template_merkmale(templateId, merkmalId) [PK] * - vehicle_template_aliasse(templateId, alias) * - equipment_categories.name * - equipment_category_merkmale(categoryId, merkmalId) [PK] * * Alle Funktionen nehmen die laufende Transaktion `tx`, damit der gesamte Seed * atomar bleibt. */ /** Upsert eines Merkmals (+ Enum-Optionen). Liefert die Merkmal-UUID. */ export async function upsertMerkmal(tx: Tx, m: MerkmalSeed): Promise { const [row] = await tx .insert(schema.merkmale) .values({ slug: m.slug, name: m.name, typ: m.typ, einheit: m.einheit ?? null, geltungsbereich: m.geltungsbereich, // Katalog-Merkmale gelten unmittelbar als aktiv (kein Vorschlag). status: "active", }) .onConflictDoUpdate({ target: schema.merkmale.slug, set: { name: m.name, typ: m.typ, einheit: m.einheit ?? null, geltungsbereich: m.geltungsbereich, status: "active", }, }) .returning({ id: schema.merkmale.id }); if (!row) throw new Error(`Merkmal-Upsert ohne Rückgabe: ${m.slug}`); for (const opt of m.optionen ?? []) { await tx .insert(schema.merkmalOptionen) .values({ merkmalId: row.id, wert: opt.wert, label: opt.label, reihenfolge: opt.reihenfolge, }) .onConflictDoUpdate({ target: [schema.merkmalOptionen.merkmalId, schema.merkmalOptionen.wert], set: { label: opt.label, reihenfolge: opt.reihenfolge }, }); } return row.id; } /** Upsert einer Vorlage (ohne Merkmale/Aliasse). Liefert die Template-UUID. */ export async function upsertVehicleTemplate( tx: Tx, t: VehicleTemplateSeed, reihenfolge: number, ): Promise { const [row] = await tx .insert(schema.vehicleTemplates) .values({ code: t.code, name: t.name, beschreibung: t.beschreibung ?? null, reihenfolge, }) .onConflictDoUpdate({ target: schema.vehicleTemplates.code, set: { name: t.name, beschreibung: t.beschreibung ?? null, reihenfolge }, }) .returning({ id: schema.vehicleTemplates.id }); if (!row) throw new Error(`Vorlagen-Upsert ohne Rückgabe: ${t.code}`); return row.id; } /** Upsert eines Vorlagen-Pflichtmerkmals (PK templateId+merkmalId). */ export async function upsertTemplateMerkmal( tx: Tx, templateId: string, merkmalId: string, m: VehicleTemplateSeed["merkmale"][number], reihenfolge: number, ): Promise { await tx .insert(schema.vehicleTemplateMerkmale) .values({ templateId, merkmalId, vorgabewertNum: m.vorgabewertNum ?? null, vorgabewertText: m.vorgabewertText ?? null, vorgabewertBool: m.vorgabewertBool ?? null, pflicht: m.pflicht ?? false, reihenfolge, }) .onConflictDoUpdate({ target: [ schema.vehicleTemplateMerkmale.templateId, schema.vehicleTemplateMerkmale.merkmalId, ], set: { vorgabewertNum: m.vorgabewertNum ?? null, vorgabewertText: m.vorgabewertText ?? null, vorgabewertBool: m.vorgabewertBool ?? null, pflicht: m.pflicht ?? false, reihenfolge, }, }); } /** Upsert eines Alias (Natural Key templateId+alias). */ export async function upsertTemplateAlias( tx: Tx, templateId: string, alias: string, bestaetigt: boolean, ): Promise { await tx .insert(schema.vehicleTemplateAliasse) .values({ templateId, alias, bestaetigt }) .onConflictDoUpdate({ target: [ schema.vehicleTemplateAliasse.templateId, schema.vehicleTemplateAliasse.alias, ], set: { bestaetigt }, }); } /** Upsert einer Geräte-Kategorie (Natural Key name). Liefert die UUID. */ export async function upsertEquipmentCategory( tx: Tx, c: EquipmentCategorySeed, ): Promise { const [row] = await tx .insert(schema.equipmentCategories) .values({ name: c.name, reihenfolge: c.reihenfolge }) .onConflictDoUpdate({ target: schema.equipmentCategories.name, set: { reihenfolge: c.reihenfolge }, }) .returning({ id: schema.equipmentCategories.id }); if (!row) throw new Error(`Kategorie-Upsert ohne Rückgabe: ${c.name}`); return row.id; } /** * Upsert der Kategorie-Merkmal-Verknüpfung (PK categoryId+merkmalId). * Idempotent über `onConflictDoNothing` (keine zusätzlichen Felder zu * aktualisieren außer reihenfolge). */ export async function upsertCategoryMerkmal( tx: Tx, categoryId: string, merkmalId: string, reihenfolge: number, ): Promise { await tx .insert(schema.equipmentCategoryMerkmale) .values({ categoryId, merkmalId, reihenfolge }) .onConflictDoUpdate({ target: [ schema.equipmentCategoryMerkmale.categoryId, schema.equipmentCategoryMerkmale.merkmalId, ], set: { reihenfolge }, }); } /** * Entfernt Aliasse zu einer Vorlage, die NICHT mehr im Seed stehen * (z. B. nachdem ein Alias aus dem Katalog gestrichen wurde). Hält den * Aliasse-Bestand exakt deckungsgleich mit dem Seed und damit idempotent, * ohne verwaiste Aliasse zu hinterlassen. */ export async function pruneTemplateAliasse( tx: Tx, templateId: string, keepAliasse: readonly string[], ): Promise { const existing = await tx .select({ alias: schema.vehicleTemplateAliasse.alias }) .from(schema.vehicleTemplateAliasse) .where(eq(schema.vehicleTemplateAliasse.templateId, templateId)); const keep = new Set(keepAliasse); for (const e of existing) { if (!keep.has(e.alias)) { await tx .delete(schema.vehicleTemplateAliasse) .where( and( eq(schema.vehicleTemplateAliasse.templateId, templateId), eq(schema.vehicleTemplateAliasse.alias, e.alias), ), ); } } }