/** * Ersteinrichtung der Bienenpatenschaft in einer bestehenden Installation. * * Wiederholbar: legt nur an, was fehlt, und aendert nie ein Angebot, das der * Inhaber schon bearbeitet hat (Felder werden nur gesetzt, wenn sie leer sind). * * - verknuepft die physischen Einmal-Pakete mit einem Honigprodukt, * - legt die beiden Abo-Angebote INAKTIV an (Testpreise, im Admin pruefen), * - legt fuer jedes Abo-Angebot Stripe-Produkt, Monatspreis und * Traegerprodukt an (syncOfferStripePrice). * * Aufruf (Werkzeug-Image, Umgebung wie die App): * tsx scripts/sponsorship-bootstrap.ts --honey= (Probelauf) * tsx scripts/sponsorship-bootstrap.ts --honey= --apply (ausfuehren) */ import { prisma } from "@/lib/prisma"; import { syncOfferStripePrice } from "@/lib/sponsorship-subscription/stripe-prices"; interface AboTemplate { slug: string; name: string; nameEn: string; description: string; descriptionEn: string; features: string[]; featuresEn: string[]; priceCents: number; honeyJars: number; honeyShareCentsPerMonth: number; cycle: "VIERTELJAEHRLICH" | "ERNTE"; maxActiveSubscriptions: number; sortOrder: number; } // Kurze Namen: die Verkaufsseite zeigt sie unter "Im Abo", Stripe und die // Rechnung als "Bienenpatenschaft-Abo ". // Testpreise — vor der Aktivierung kaufmaennisch pruefen (Spec, "Geschaeftliche // Konfiguration"). Honiganteil je Monat = Glaeser je Lieferung × 7 € ÷ Monate // je Lieferung, zum Honig-Steuersatz. const ABO_TEMPLATES: AboTemplate[] = [ { slug: "abo-vierteljaehrlich", name: "Vierteljährlich", nameEn: "Quarterly", description: "Monatlich zahlen, alle drei bezahlten Monate zwei Gläser Wabenhain-Honig. Versand inklusive, monatlich kündbar.", descriptionEn: "Pay monthly, two jars of Wabenhain honey after every three paid months. Shipping included, cancel monthly.", features: ["Symbolische Namensgebung mit Urkunde", "2 × 500 g Honig nach jeweils 3 bezahlten Monaten", "Versand inklusive", "Monatlich kündbar"], featuresEn: ["Symbolic naming with certificate", "2 × 500 g honey after every 3 paid months", "Shipping included", "Cancel monthly"], priceCents: 1290, honeyJars: 2, honeyShareCentsPerMonth: 467, cycle: "VIERTELJAEHRLICH", maxActiveSubscriptions: 20, sortOrder: 10, }, { slug: "abo-ernte", name: "Nach der Ernte", nameEn: "After the harvest", description: "Monatlich zahlen, Honig frisch nach der Ernte: drei Gläser, sobald seit der letzten Lieferung sechs Monate bezahlt sind. Versand inklusive, monatlich kündbar.", descriptionEn: "Pay monthly, honey fresh after the harvest: three jars once six months have been paid since the last delivery. Shipping included, cancel monthly.", features: ["Symbolische Namensgebung mit Urkunde", "3 × 500 g Honig im Erntefenster", "Versand inklusive", "Monatlich kündbar"], featuresEn: ["Symbolic naming with certificate", "3 × 500 g honey in the harvest window", "Shipping included", "Cancel monthly"], priceCents: 990, honeyJars: 3, honeyShareCentsPerMonth: 350, cycle: "ERNTE", maxActiveSubscriptions: 10, sortOrder: 11, }, ]; function arg(name: string): string | undefined { const hit = process.argv.find((value) => value.startsWith(`--${name}=`)); return hit ? hit.slice(name.length + 3) : undefined; } async function main(): Promise { const apply = process.argv.includes("--apply"); const honeySlug = arg("honey"); if (!honeySlug) throw new Error("--honey= fehlt."); const honey = await prisma.product.findUnique({ where: { slug: honeySlug }, select: { id: true, name: true, category: true, isActive: true } }); if (!honey || honey.category !== "HONIG" || !honey.isActive) { throw new Error(`Honigprodukt "${honeySlug}" nicht gefunden, nicht HONIG oder nicht aktiv.`); } console.log(`${apply ? "AUSFUEHREN" : "PROBELAUF"} — Honig: ${honey.name}`); const physical = await prisma.sponsorshipOffer.findMany({ where: { kind: "EINMALIG", isPhysical: true, honeyProductId: null }, select: { id: true, slug: true }, }); for (const offer of physical) { console.log(` Paket ${offer.slug}: Honig verknuepfen`); if (apply) await prisma.sponsorshipOffer.update({ where: { id: offer.id }, data: { honeyProductId: honey.id } }); } for (const template of ABO_TEMPLATES) { const existing = await prisma.sponsorshipOffer.findUnique({ where: { slug: template.slug }, select: { id: true, stripePriceId: true } }); if (existing) { console.log(` Abo ${template.slug}: vorhanden${existing.stripePriceId ? "" : ", Stripe-Preis fehlt"}`); if (apply && !existing.stripePriceId) await syncOfferStripePrice(existing.id); continue; } console.log(` Abo ${template.slug}: anlegen (inaktiv, ${(template.priceCents / 100).toFixed(2)} €/Monat) + Stripe-Preis`); if (!apply) continue; const created = await prisma.sponsorshipOffer.create({ data: { ...template, kind: "ABO", isActive: false, isPhysical: true, honeyProductId: honey.id, medalTier: "ABO", }, }); await syncOfferStripePrice(created.id); } const offers = await prisma.sponsorshipOffer.findMany({ orderBy: { sortOrder: "asc" }, select: { slug: true, kind: true, isActive: true, priceCents: true, honeyProductId: true, stripePriceId: true, productId: true }, }); console.table(offers); } main() .catch((error: unknown) => { console.error(error); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); });