Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m18s
Docker Image bauen und veroeffentlichen / test (push) Successful in 5m32s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 3m41s
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 20m38s
Pakete, Abo-Reservierung und Abo-Lieferungen nehmen immer den aktiven, nicht cremigen Honig der neuesten Ernte mit genug freiem Bestand, bei gleichem Jahrgang den mit dem meisten Bestand. Feste Honig-Zuordnung im Admin entfaellt (Spalte bleibt, wird nicht mehr genutzt). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
123 lines
5.1 KiB
TypeScript
123 lines
5.1 KiB
TypeScript
/**
|
||
* 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).
|
||
*
|
||
* - legt die beiden Abo-Angebote INAKTIV an (Testpreise, im Admin pruefen),
|
||
* - legt fuer jedes Abo-Angebot Stripe-Produkt, Monatspreis und
|
||
* Traegerprodukt an (syncOfferStripePrice).
|
||
*
|
||
* Einen Honig verknuepft das Skript nicht mehr (frueher --honey=<slug>):
|
||
* Pakete und Abos bekommen ihren Honig automatisch — nie cremig, neueste
|
||
* Ernte, genug Bestand (lib/sponsorship/honey-selection.ts). Die Spalte
|
||
* honeyProductId bleibt ungenutzt im Schema; der Probelauf zeigt zur
|
||
* Kontrolle, welchen Honig die Regel gerade waehlen wuerde.
|
||
*
|
||
* Aufruf (Werkzeug-Image, Umgebung wie die App):
|
||
* tsx scripts/sponsorship-bootstrap.ts (Probelauf)
|
||
* tsx scripts/sponsorship-bootstrap.ts --apply (ausfuehren)
|
||
*/
|
||
import { prisma } from "@/lib/prisma";
|
||
import { syncOfferStripePrice } from "@/lib/sponsorship-subscription/stripe-prices";
|
||
import { loadHoneyCandidates } from "@/lib/sponsorship/honey-candidates";
|
||
import { honeyLabel, pickHoneyProduct } from "@/lib/sponsorship/honey-selection";
|
||
|
||
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 <Name>".
|
||
// 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,
|
||
},
|
||
];
|
||
|
||
async function main(): Promise<void> {
|
||
const apply = process.argv.includes("--apply");
|
||
const honey = pickHoneyProduct(await loadHoneyCandidates(), 1);
|
||
console.log(
|
||
`${apply ? "AUSFUEHREN" : "PROBELAUF"} — Honig automatisch, derzeit: ${honey ? honeyLabel(honey) : "kein nicht-cremiger Honig mit freiem Bestand"}`
|
||
);
|
||
|
||
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,
|
||
medalTier: "ABO",
|
||
},
|
||
});
|
||
await syncOfferStripePrice(created.id);
|
||
}
|
||
|
||
const offers = await prisma.sponsorshipOffer.findMany({
|
||
orderBy: { sortOrder: "asc" },
|
||
select: { slug: true, kind: true, isActive: true, priceCents: true, honeyJars: true, stripePriceId: true, productId: true },
|
||
});
|
||
console.table(offers);
|
||
}
|
||
|
||
main()
|
||
.catch((error: unknown) => {
|
||
console.error(error);
|
||
process.exitCode = 1;
|
||
})
|
||
.finally(async () => {
|
||
await prisma.$disconnect();
|
||
});
|