Fix early-bird price blending and mislabeling; add contact-only events
- Early-bird pricing: RegistrationOption now tracks each purchase as a separate price tranche instead of overwriting a single price/quantity on repeat purchases, so buying more tickets after a tier expires no longer re-prices tickets already bought at the old price. Stock-limit checks, total-due calculation, and the Finance report's revenue-by- option are all tranche-aware; pages that showed one blended price per line now render/total each tranche. Viewing a pending/partially-paid registration (dashboard, detail page, or an event's registration list) now refreshes stale pricing on the spot instead of only at payment time. - Fixed the "(early bird)" dashboard label incorrectly firing on any line priced below the base option price (e.g. a plain cheaper variant) — it now checks the real applied-tier flag. - Added contact-only events (e.g. baptism): no registration/payment flow, shown on the public site with a "Contact us" popup instead of a Register button. Configurable via the admin event wizard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `feeAmount` on the `EventCashupLine` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `feeAmount` on the `Payment` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `feeChannel` on the `Payment` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `feeRate` on the `Payment` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "EventCashupLine" DROP COLUMN "feeAmount";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Payment" DROP COLUMN "feeAmount",
|
||||
DROP COLUMN "feeChannel",
|
||||
DROP COLUMN "feeRate";
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Event" ADD COLUMN "contactEmail" TEXT,
|
||||
ADD COLUMN "contactName" TEXT,
|
||||
ADD COLUMN "contactPhone" TEXT,
|
||||
ADD COLUMN "requiresRegistration" BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RegistrationOptionTranche" (
|
||||
"id" TEXT NOT NULL,
|
||||
"registrationOptionId" TEXT NOT NULL,
|
||||
"quantity" INTEGER NOT NULL,
|
||||
"priceSnapshot" DOUBLE PRECISION NOT NULL,
|
||||
"appliedTierId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "RegistrationOptionTranche_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RegistrationOptionTranche_registrationOptionId_idx" ON "RegistrationOptionTranche"("registrationOptionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RegistrationOptionTranche_appliedTierId_idx" ON "RegistrationOptionTranche"("appliedTierId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RegistrationOptionTranche" ADD CONSTRAINT "RegistrationOptionTranche_registrationOptionId_fkey" FOREIGN KEY ("registrationOptionId") REFERENCES "RegistrationOption"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RegistrationOptionTranche" ADD CONSTRAINT "RegistrationOptionTranche_appliedTierId_fkey" FOREIGN KEY ("appliedTierId") REFERENCES "EarlyBirdTier"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -101,6 +101,10 @@ model Event {
|
||||
isActive Boolean @default(true)
|
||||
isHidden Boolean @default(false)
|
||||
requiresAuth Boolean @default(true)
|
||||
requiresRegistration Boolean @default(true)
|
||||
contactName String?
|
||||
contactPhone String?
|
||||
contactEmail String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdById String?
|
||||
@@ -163,6 +167,7 @@ model EarlyBirdTier {
|
||||
eventOption EventOption @relation(fields: [eventOptionId], references: [id], onDelete: Cascade)
|
||||
variant OptionVariant? @relation(fields: [variantId], references: [id], onDelete: Cascade)
|
||||
registrationOptions RegistrationOption[]
|
||||
tranches RegistrationOptionTranche[]
|
||||
|
||||
@@index([eventOptionId, deadline])
|
||||
@@index([variantId])
|
||||
@@ -217,6 +222,7 @@ model RegistrationOption {
|
||||
variant OptionVariant? @relation(fields: [variantId], references: [id], onDelete: SetNull)
|
||||
appliedTier EarlyBirdTier? @relation(fields: [appliedTierId], references: [id], onDelete: SetNull)
|
||||
tickets Ticket[]
|
||||
tranches RegistrationOptionTranche[]
|
||||
|
||||
@@index([registrationId])
|
||||
@@index([eventOptionId])
|
||||
@@ -224,6 +230,27 @@ model RegistrationOption {
|
||||
@@index([appliedTierId])
|
||||
}
|
||||
|
||||
// One row per purchase-at-a-price for a RegistrationOption. Never mutated after creation
|
||||
// (mirrors the Payment model's append-only pattern) — this is what lets a single ticket
|
||||
// type be bought in multiple batches at different early-bird prices without either batch's
|
||||
// price bleeding into the other. RegistrationOption.quantity/priceSnapshot/appliedTierId
|
||||
// stay in sync as an aggregate (quantity = sum of tranche quantities; priceSnapshot/appliedTierId
|
||||
// mirror the most recently added tranche) for the many call sites that only need "how many"
|
||||
// or a single display price.
|
||||
model RegistrationOptionTranche {
|
||||
id String @id @default(uuid())
|
||||
registrationOptionId String
|
||||
quantity Int
|
||||
priceSnapshot Float
|
||||
appliedTierId String?
|
||||
createdAt DateTime @default(now())
|
||||
registrationOption RegistrationOption @relation(fields: [registrationOptionId], references: [id], onDelete: Cascade)
|
||||
appliedTier EarlyBirdTier? @relation(fields: [appliedTierId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([registrationOptionId])
|
||||
@@index([appliedTierId])
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(uuid())
|
||||
amount Float
|
||||
|
||||
Reference in New Issue
Block a user