feat: Bienenpatenschaft mit Urkunde, Abo, Medaillen und 3D-Konto
Setzt Teilprojekte 1-5 der Spec 2026-10-16 um: - Urkunden-Engine (lib/certificate): ein Layout fuer PDF, Vorschau und 3D-Textur, Vorlage classic@1, eingebettete OFL-Schriften, Testmatrix - Einmal-Patenschaft ueber Traegerprodukte (Kategorie PATENSCHAFT), Paket wird im Checkout in Patenschaft (Regelsatz) und Honig aufgeteilt, Kontopflicht, rein digitale Bestellungen (FulfillmentMethod DIGITAL) - Ausstellung nach Zahlung idempotent, Wiederholung im App-Prozess (instrumentation + background-jobs), Ablage in CERTIFICATE_DIR - Verkaufsseite /patenschaft mit Konfigurator und Live-Vorschau, Warenkorb mit Schluessel je Position - Kundenkonto: 3D-Ansicht von Urkunde und Medaille, Downloads, ZIP, Uebertragung per befristetem Einmal-Link, Treuestufen bis VIP - Admin: Angebote, Patenschaften, Druck, Korrektur mit Revisionen, Abos, Erntefenster, Deckung, SEPA-Frist, Treuestufen - Abo ueber Stripe Billing: Lieferung erst nach bezahlten Monaten, SEPA-Neukunden-Frist und Rueckbuchungsschutz, Honigreservierung, Kuendigungsbutton, Monatsrechnungen, Stripe-Einrichtungsskript Startangebote werden inaktiv angelegt; Livebetrieb erst nach Freigabe. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
dcb578f062
commit
f227aeb8f9
269 files changed
+28054
-141
No files matched your search
@@ -0,0 +1,342 @@
|
||||
-- Bienenpatenschaft: Angebote, Patenschaften mit Urkunden-Revisionen,
|
||||
-- Uebertragungen, Abos (Stripe Billing) mit Zahlungen und Lieferungen,
|
||||
-- Erntefenster und Treuestufen. Rein additiv; die beiden neuen Enum-Werte
|
||||
-- (PATENSCHAFT, DIGITAL) werden in dieser Migration nicht benutzt, ALTER TYPE
|
||||
-- ADD VALUE ist deshalb auch innerhalb der Migrationstransaktion zulaessig.
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SponsorshipOfferKind" AS ENUM ('EINMALIG', 'ABO');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SponsorshipCycle" AS ENUM ('VIERTELJAEHRLICH', 'ERNTE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "MedalTier" AS ENUM ('BRONZE', 'SILBER', 'GOLD', 'ABO');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SponsorshipStatus" AS ENUM ('AUSSTEHEND', 'FEHLER', 'BEREIT', 'GEDRUCKT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SubscriptionStatus" AS ENUM ('AUSSTEHEND', 'AKTIV', 'IM_VERZUG', 'GEKUENDIGT', 'BEENDET');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DeliveryStatus" AS ENUM ('ANGELEGT', 'ZURUECKGESTELLT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LoyaltyKind" AS ENUM ('STAMMKUNDE', 'ABO');
|
||||
|
||||
-- AlterEnum
|
||||
ALTER TYPE "ProductCategory" ADD VALUE 'PATENSCHAFT';
|
||||
|
||||
-- AlterEnum
|
||||
ALTER TYPE "FulfillmentMethod" ADD VALUE 'DIGITAL';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "customers" ADD COLUMN "sepaBlockedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "stripeCustomerId" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "order_items" ADD COLUMN "personalization" JSONB,
|
||||
ADD COLUMN "sponsorshipOfferId" TEXT,
|
||||
ADD COLUMN "sponsorshipRole" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sponsorship_offers" (
|
||||
"id" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"kind" "SponsorshipOfferKind" NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameEn" TEXT,
|
||||
"description" TEXT NOT NULL,
|
||||
"descriptionEn" TEXT,
|
||||
"features" JSONB NOT NULL DEFAULT '[]',
|
||||
"featuresEn" JSONB NOT NULL DEFAULT '[]',
|
||||
"priceCents" INTEGER NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isPhysical" BOOLEAN NOT NULL DEFAULT false,
|
||||
"productId" TEXT,
|
||||
"honeyProductId" TEXT,
|
||||
"honeyJars" INTEGER NOT NULL DEFAULT 0,
|
||||
"honeyShareCentsPerJar" INTEGER NOT NULL DEFAULT 0,
|
||||
"honeyShareCentsPerMonth" INTEGER NOT NULL DEFAULT 0,
|
||||
"cycle" "SponsorshipCycle",
|
||||
"maxActiveSubscriptions" INTEGER,
|
||||
"stripeProductId" TEXT,
|
||||
"stripePriceId" TEXT,
|
||||
"stripePriceAmountCents" INTEGER,
|
||||
"medalTier" "MedalTier" NOT NULL DEFAULT 'BRONZE',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "sponsorship_offers_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sponsorships" (
|
||||
"id" TEXT NOT NULL,
|
||||
"number" TEXT NOT NULL,
|
||||
"ownerId" TEXT NOT NULL,
|
||||
"offerId" TEXT NOT NULL,
|
||||
"orderItemId" TEXT,
|
||||
"subscriptionId" TEXT,
|
||||
"locale" TEXT NOT NULL DEFAULT 'de',
|
||||
"startDate" DATE NOT NULL,
|
||||
"endDate" DATE,
|
||||
"status" "SponsorshipStatus" NOT NULL DEFAULT 'AUSSTEHEND',
|
||||
"currentRevision" INTEGER NOT NULL DEFAULT 0,
|
||||
"pdfAttempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"nextAttemptAt" TIMESTAMP(3),
|
||||
"lastError" TEXT,
|
||||
"mailSentAt" TIMESTAMP(3),
|
||||
"printedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "sponsorships_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sponsorship_revisions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sponsorshipId" TEXT NOT NULL,
|
||||
"revision" INTEGER NOT NULL,
|
||||
"personalization" JSONB NOT NULL,
|
||||
"term" JSONB NOT NULL,
|
||||
"text" JSONB NOT NULL,
|
||||
"templateVersion" TEXT NOT NULL,
|
||||
"pdfPath" TEXT NOT NULL,
|
||||
"pdfSha256" TEXT NOT NULL,
|
||||
"createdById" TEXT,
|
||||
"createdByLabel" TEXT NOT NULL,
|
||||
"reason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "sponsorship_revisions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sponsorship_counters" (
|
||||
"year" INTEGER NOT NULL,
|
||||
"lastNumber" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "sponsorship_counters_pkey" PRIMARY KEY ("year")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sponsorship_transfers" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sponsorshipId" TEXT NOT NULL,
|
||||
"tokenHash" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdById" TEXT NOT NULL,
|
||||
"claimedById" TEXT,
|
||||
"claimedAt" TIMESTAMP(3),
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "sponsorship_transfers_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sponsorship_subscriptions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"offerId" TEXT NOT NULL,
|
||||
"status" "SubscriptionStatus" NOT NULL DEFAULT 'AUSSTEHEND',
|
||||
"personalization" JSONB NOT NULL,
|
||||
"locale" TEXT NOT NULL DEFAULT 'de',
|
||||
"stripeCheckoutSessionId" TEXT,
|
||||
"stripeSubscriptionId" TEXT,
|
||||
"startedAt" TIMESTAMP(3),
|
||||
"currentPeriodEnd" TIMESTAMP(3),
|
||||
"cancelAt" TIMESTAMP(3),
|
||||
"endedAt" TIMESTAMP(3),
|
||||
"delinquentSince" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "sponsorship_subscriptions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "subscription_payments" (
|
||||
"id" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT NOT NULL,
|
||||
"stripeInvoiceId" TEXT NOT NULL,
|
||||
"stripePaymentIntentId" TEXT,
|
||||
"paymentMethodType" TEXT,
|
||||
"amountCents" INTEGER NOT NULL,
|
||||
"paidAt" TIMESTAMP(3) NOT NULL,
|
||||
"periodStart" TIMESTAMP(3) NOT NULL,
|
||||
"periodEnd" TIMESTAMP(3) NOT NULL,
|
||||
"countsFrom" TIMESTAMP(3) NOT NULL,
|
||||
"reversedAt" TIMESTAMP(3),
|
||||
"deliveryId" TEXT,
|
||||
"orderId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "subscription_payments_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "subscription_deliveries" (
|
||||
"id" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT NOT NULL,
|
||||
"dueAt" TIMESTAMP(3) NOT NULL,
|
||||
"jars" INTEGER NOT NULL,
|
||||
"status" "DeliveryStatus" NOT NULL,
|
||||
"orderId" TEXT,
|
||||
"note" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "subscription_deliveries_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "harvest_windows" (
|
||||
"id" TEXT NOT NULL,
|
||||
"offerId" TEXT NOT NULL,
|
||||
"startsOn" DATE NOT NULL,
|
||||
"endsOn" DATE NOT NULL,
|
||||
"plannedJars" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "harvest_windows_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "loyalty_levels" (
|
||||
"id" TEXT NOT NULL,
|
||||
"kind" "LoyaltyKind" NOT NULL,
|
||||
"threshold" INTEGER,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameEn" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "loyalty_levels_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorship_offers_slug_key" ON "sponsorship_offers"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorship_offers_productId_key" ON "sponsorship_offers"("productId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sponsorship_offers_kind_isActive_sortOrder_idx" ON "sponsorship_offers"("kind", "isActive", "sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sponsorship_offers_honeyProductId_idx" ON "sponsorship_offers"("honeyProductId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorships_number_key" ON "sponsorships"("number");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorships_orderItemId_key" ON "sponsorships"("orderItemId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorships_subscriptionId_key" ON "sponsorships"("subscriptionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sponsorships_ownerId_idx" ON "sponsorships"("ownerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sponsorships_status_nextAttemptAt_idx" ON "sponsorships"("status", "nextAttemptAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sponsorships_offerId_idx" ON "sponsorships"("offerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorship_revisions_sponsorshipId_revision_key" ON "sponsorship_revisions"("sponsorshipId", "revision");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorship_transfers_tokenHash_key" ON "sponsorship_transfers"("tokenHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sponsorship_transfers_sponsorshipId_idx" ON "sponsorship_transfers"("sponsorshipId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorship_subscriptions_stripeCheckoutSessionId_key" ON "sponsorship_subscriptions"("stripeCheckoutSessionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sponsorship_subscriptions_stripeSubscriptionId_key" ON "sponsorship_subscriptions"("stripeSubscriptionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sponsorship_subscriptions_customerId_idx" ON "sponsorship_subscriptions"("customerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sponsorship_subscriptions_offerId_status_idx" ON "sponsorship_subscriptions"("offerId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "subscription_payments_stripeInvoiceId_key" ON "subscription_payments"("stripeInvoiceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "subscription_payments_orderId_key" ON "subscription_payments"("orderId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_payments_subscriptionId_paidAt_idx" ON "subscription_payments"("subscriptionId", "paidAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "subscription_deliveries_orderId_key" ON "subscription_deliveries"("orderId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_deliveries_subscriptionId_dueAt_idx" ON "subscription_deliveries"("subscriptionId", "dueAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "harvest_windows_offerId_startsOn_idx" ON "harvest_windows"("offerId", "startsOn");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "loyalty_levels_kind_threshold_key" ON "loyalty_levels"("kind", "threshold");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "customers_stripeCustomerId_key" ON "customers"("stripeCustomerId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorship_offers" ADD CONSTRAINT "sponsorship_offers_productId_fkey" FOREIGN KEY ("productId") REFERENCES "products"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorship_offers" ADD CONSTRAINT "sponsorship_offers_honeyProductId_fkey" FOREIGN KEY ("honeyProductId") REFERENCES "products"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorships" ADD CONSTRAINT "sponsorships_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorships" ADD CONSTRAINT "sponsorships_offerId_fkey" FOREIGN KEY ("offerId") REFERENCES "sponsorship_offers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorships" ADD CONSTRAINT "sponsorships_orderItemId_fkey" FOREIGN KEY ("orderItemId") REFERENCES "order_items"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorships" ADD CONSTRAINT "sponsorships_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "sponsorship_subscriptions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorship_revisions" ADD CONSTRAINT "sponsorship_revisions_sponsorshipId_fkey" FOREIGN KEY ("sponsorshipId") REFERENCES "sponsorships"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorship_transfers" ADD CONSTRAINT "sponsorship_transfers_sponsorshipId_fkey" FOREIGN KEY ("sponsorshipId") REFERENCES "sponsorships"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorship_subscriptions" ADD CONSTRAINT "sponsorship_subscriptions_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sponsorship_subscriptions" ADD CONSTRAINT "sponsorship_subscriptions_offerId_fkey" FOREIGN KEY ("offerId") REFERENCES "sponsorship_offers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "subscription_payments" ADD CONSTRAINT "subscription_payments_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "sponsorship_subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "subscription_payments" ADD CONSTRAINT "subscription_payments_deliveryId_fkey" FOREIGN KEY ("deliveryId") REFERENCES "subscription_deliveries"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "subscription_payments" ADD CONSTRAINT "subscription_payments_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "orders"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "subscription_deliveries" ADD CONSTRAINT "subscription_deliveries_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "sponsorship_subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "subscription_deliveries" ADD CONSTRAINT "subscription_deliveries_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "orders"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "harvest_windows" ADD CONSTRAINT "harvest_windows_offerId_fkey" FOREIGN KEY ("offerId") REFERENCES "sponsorship_offers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
Reference in new issue
Block a user