@bash-app/bash-common 30.383.0 → 30.386.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/__tests__/accessScreening.test.js +39 -1
  2. package/dist/__tests__/accessScreening.test.js.map +1 -1
  3. package/dist/__tests__/applicationFunnel.test.d.ts +2 -0
  4. package/dist/__tests__/applicationFunnel.test.d.ts.map +1 -0
  5. package/dist/__tests__/applicationFunnel.test.js +126 -0
  6. package/dist/__tests__/applicationFunnel.test.js.map +1 -0
  7. package/dist/__tests__/definitionsHelpers.test.js +20 -1
  8. package/dist/__tests__/definitionsHelpers.test.js.map +1 -1
  9. package/dist/__tests__/fundingMeter.test.d.ts +2 -0
  10. package/dist/__tests__/fundingMeter.test.d.ts.map +1 -0
  11. package/dist/__tests__/fundingMeter.test.js +129 -0
  12. package/dist/__tests__/fundingMeter.test.js.map +1 -0
  13. package/dist/__tests__/giveaway.test.d.ts +2 -0
  14. package/dist/__tests__/giveaway.test.d.ts.map +1 -0
  15. package/dist/__tests__/giveaway.test.js +65 -0
  16. package/dist/__tests__/giveaway.test.js.map +1 -0
  17. package/dist/__tests__/promoterCompetitionUtils.test.d.ts +2 -0
  18. package/dist/__tests__/promoterCompetitionUtils.test.d.ts.map +1 -0
  19. package/dist/__tests__/promoterCompetitionUtils.test.js +262 -0
  20. package/dist/__tests__/promoterCompetitionUtils.test.js.map +1 -0
  21. package/dist/accessScreening.d.ts +18 -3
  22. package/dist/accessScreening.d.ts.map +1 -1
  23. package/dist/accessScreening.js +26 -0
  24. package/dist/accessScreening.js.map +1 -1
  25. package/dist/applicationFunnel.d.ts +58 -0
  26. package/dist/applicationFunnel.d.ts.map +1 -0
  27. package/dist/applicationFunnel.js +147 -0
  28. package/dist/applicationFunnel.js.map +1 -0
  29. package/dist/definitions.d.ts +146 -5
  30. package/dist/definitions.d.ts.map +1 -1
  31. package/dist/definitions.js +138 -5
  32. package/dist/definitions.js.map +1 -1
  33. package/dist/extendedSchemas.d.ts +174 -0
  34. package/dist/extendedSchemas.d.ts.map +1 -1
  35. package/dist/extendedSchemas.js +15 -0
  36. package/dist/extendedSchemas.js.map +1 -1
  37. package/dist/fundingMeter.d.ts +66 -0
  38. package/dist/fundingMeter.d.ts.map +1 -0
  39. package/dist/fundingMeter.js +99 -0
  40. package/dist/fundingMeter.js.map +1 -0
  41. package/dist/giveaway.d.ts +71 -0
  42. package/dist/giveaway.d.ts.map +1 -0
  43. package/dist/giveaway.js +117 -0
  44. package/dist/giveaway.js.map +1 -0
  45. package/dist/index.d.ts +5 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +6 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/utils/__tests__/bashHostIdentity.test.d.ts +2 -0
  50. package/dist/utils/__tests__/bashHostIdentity.test.d.ts.map +1 -0
  51. package/dist/utils/__tests__/bashHostIdentity.test.js +39 -0
  52. package/dist/utils/__tests__/bashHostIdentity.test.js.map +1 -0
  53. package/dist/utils/__tests__/paymentUtils.test.js +28 -1
  54. package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
  55. package/dist/utils/__tests__/promoterCompetitionUtils.test.d.ts +2 -0
  56. package/dist/utils/__tests__/promoterCompetitionUtils.test.d.ts.map +1 -0
  57. package/dist/utils/__tests__/promoterCompetitionUtils.test.js +104 -0
  58. package/dist/utils/__tests__/promoterCompetitionUtils.test.js.map +1 -0
  59. package/dist/utils/__tests__/serviceAvailabilityDisplay.test.js +56 -1
  60. package/dist/utils/__tests__/serviceAvailabilityDisplay.test.js.map +1 -1
  61. package/dist/utils/__tests__/slugUtils.test.js +31 -2
  62. package/dist/utils/__tests__/slugUtils.test.js.map +1 -1
  63. package/dist/utils/bashHostIdentity.d.ts +18 -0
  64. package/dist/utils/bashHostIdentity.d.ts.map +1 -0
  65. package/dist/utils/bashHostIdentity.js +17 -0
  66. package/dist/utils/bashHostIdentity.js.map +1 -0
  67. package/dist/utils/paymentUtils.d.ts +18 -1
  68. package/dist/utils/paymentUtils.d.ts.map +1 -1
  69. package/dist/utils/paymentUtils.js +29 -2
  70. package/dist/utils/paymentUtils.js.map +1 -1
  71. package/dist/utils/promoterCompetitionUtils.d.ts +89 -0
  72. package/dist/utils/promoterCompetitionUtils.d.ts.map +1 -0
  73. package/dist/utils/promoterCompetitionUtils.js +158 -0
  74. package/dist/utils/promoterCompetitionUtils.js.map +1 -0
  75. package/dist/utils/serviceAvailabilityDisplay.d.ts +28 -2
  76. package/dist/utils/serviceAvailabilityDisplay.d.ts.map +1 -1
  77. package/dist/utils/serviceAvailabilityDisplay.js +39 -0
  78. package/dist/utils/serviceAvailabilityDisplay.js.map +1 -1
  79. package/dist/utils/slugUtils.d.ts +2 -0
  80. package/dist/utils/slugUtils.d.ts.map +1 -1
  81. package/dist/utils/slugUtils.js +5 -1
  82. package/dist/utils/slugUtils.js.map +1 -1
  83. package/package.json +1 -1
  84. package/prisma/schema.prisma +465 -29
  85. package/src/__tests__/accessScreening.test.ts +69 -1
  86. package/src/__tests__/applicationFunnel.test.ts +197 -0
  87. package/src/__tests__/definitionsHelpers.test.ts +24 -0
  88. package/src/__tests__/fundingMeter.test.ts +143 -0
  89. package/src/__tests__/giveaway.test.ts +111 -0
  90. package/src/__tests__/promoterCompetitionUtils.test.ts +334 -0
  91. package/src/accessScreening.ts +49 -0
  92. package/src/applicationFunnel.ts +225 -0
  93. package/src/definitions.ts +182 -5
  94. package/src/extendedSchemas.ts +131 -0
  95. package/src/fundingMeter.ts +188 -0
  96. package/src/giveaway.ts +182 -0
  97. package/src/index.ts +14 -0
  98. package/src/utils/__tests__/bashHostIdentity.test.ts +57 -0
  99. package/src/utils/__tests__/paymentUtils.test.ts +36 -0
  100. package/src/utils/__tests__/promoterCompetitionUtils.test.ts +128 -0
  101. package/src/utils/__tests__/serviceAvailabilityDisplay.test.ts +73 -0
  102. package/src/utils/__tests__/slugUtils.test.ts +46 -0
  103. package/src/utils/bashHostIdentity.ts +30 -0
  104. package/src/utils/paymentUtils.ts +47 -2
  105. package/src/utils/promoterCompetitionUtils.ts +235 -0
  106. package/src/utils/serviceAvailabilityDisplay.ts +82 -2
  107. package/src/utils/slugUtils.ts +11 -0
@@ -268,6 +268,19 @@ model Competition {
268
268
  /// How 1st/2nd/3rd place icons appear on leaderboards and profiles
269
269
  placementAwardStyle PlacementAwardStyle @default(Trophy)
270
270
 
271
+ /// PROMOTER competitions only — purpose distinguishes consumer contests from ticket-sales races
272
+ competitionPurpose CompetitionPurpose @default(CONSUMER)
273
+ /// PROMOTER: total ticket-sale goal for the competition
274
+ promoterSalesGoal Int?
275
+ /// PROMOTER: maximum organizer cash reward exposure in cents
276
+ promoterRewardBudgetCents Int?
277
+ /// PROMOTER: how milestone rewards are calculated
278
+ promoterRewardMode PromoterRewardMode @default(TOTAL_AT_MILESTONE)
279
+ /// PROMOTER: who can enroll as a promoter
280
+ promoterRecruitMode PromoterRecruitMode @default(InviteOnly)
281
+ /// PROMOTER: attribution window in days (purchases within N days of referral click count)
282
+ referralWindowDays Int? @default(30)
283
+
271
284
  /// Optional Adventures rewards when someone becomes an ACCEPTED participant
272
285
  participationBashPoints Int?
273
286
  participationEventBadgeId String?
@@ -292,6 +305,11 @@ model Competition {
292
305
  judgeScores CompetitionJudgeScore[]
293
306
  notifications Notification[]
294
307
  sideQuests SideQuest[]
308
+ giveaways Giveaway[]
309
+ promoterMilestones PromoterMilestone[]
310
+ promoterRewards PromoterReward[]
311
+ promoters Promoter[]
312
+ promoterEventPerformances PromoterEventPerformance[]
295
313
  participationEventBadge EventBadge? @relation("CompetitionParticipationBadge", fields: [participationEventBadgeId], references: [id], onDelete: SetNull)
296
314
  finalistEventBadge EventBadge? @relation("CompetitionFinalistBadge", fields: [finalistEventBadgeId], references: [id], onDelete: SetNull)
297
315
  winEventBadge EventBadge? @relation("CompetitionWinBadge", fields: [winEventBadgeId], references: [id], onDelete: SetNull)
@@ -305,6 +323,67 @@ model Competition {
305
323
  @@index([winEventBadgeId])
306
324
  }
307
325
 
326
+ /// Threshold-based reward step in a PROMOTER competition.
327
+ /// Each row defines what a promoter earns when they hit `threshold` qualified ticket sales,
328
+ /// or `rank` (e.g. rank=1 for first-place prize).
329
+ model PromoterMilestone {
330
+ id String @id @default(cuid())
331
+ competitionId String
332
+ /// null = rank-based prize (use `rank`); non-null = threshold-based
333
+ threshold Int?
334
+ /// null = threshold-based (use `threshold`); 1 = first-place prize, etc.
335
+ rank Int?
336
+ rewardType PromoterRewardType
337
+ /// Cash reward in cents (used when rewardType = CASH)
338
+ rewardAmountCents Int?
339
+ /// Human-readable label shown to promoters (e.g. "Free VIP upgrade")
340
+ rewardDescription String?
341
+ /// Ticket tier to award (for TICKET / TICKET_UPGRADE rewards)
342
+ ticketTierId String?
343
+ /// Linked sponsor prize (for SPONSOR_PRIZE rewards)
344
+ sponsorPrizeId String?
345
+ /// Controls display order in UIs
346
+ displayOrder Int @default(0)
347
+ createdAt DateTime @default(now())
348
+ updatedAt DateTime @updatedAt
349
+
350
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
351
+ rewards PromoterReward[]
352
+
353
+ @@index([competitionId])
354
+ @@index([threshold])
355
+ @@index([rank])
356
+ }
357
+
358
+ /// Tracks a specific reward earned (or pending/revoked) by a promoter participant.
359
+ /// Created when a promoter's qualified sales cross a milestone threshold or they win a rank prize.
360
+ model PromoterReward {
361
+ id String @id @default(cuid())
362
+ competitionId String
363
+ /// FK to Promoter.id (the per-bash promoter row, not User)
364
+ promoterId String
365
+ /// null for rank-based rewards (use `rank`)
366
+ milestoneId String?
367
+ /// For rank prizes (1 = first place, etc.)
368
+ rank Int?
369
+ /// Snapshot of qualified sales at the time this reward status was last updated
370
+ qualifiedSalesSnapshot Int @default(0)
371
+ status PromoterRewardStatus @default(PENDING)
372
+ earnedAt DateTime?
373
+ fulfilledAt DateTime?
374
+ revokedAt DateTime?
375
+ revokeReason String?
376
+ createdAt DateTime @default(now())
377
+ updatedAt DateTime @updatedAt
378
+
379
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
380
+ milestone PromoterMilestone? @relation(fields: [milestoneId], references: [id], onDelete: SetNull)
381
+
382
+ @@index([competitionId, promoterId])
383
+ @@index([promoterId])
384
+ @@index([status])
385
+ }
386
+
308
387
  model CompetitionJudgeAssignment {
309
388
  id String @id @default(cuid())
310
389
  competitionId String
@@ -611,10 +690,57 @@ model Promoter {
611
690
  updatedAt DateTime @updatedAt
612
691
  userId String? // null until invited promoter signs up (do not use host id as placeholder)
613
692
  invitedEmail String? // normalized invitee email for reconciliation when userId is null
693
+ /// When set, this promoter is enrolled in a specific PROMOTER competition
694
+ competitionId String?
695
+ /// Cached count of qualified ticket sales for the linked competition (updated async after checkout/refund)
696
+ qualifiedSales Int @default(0)
697
+ /// Basis points of attributed ticket subtotal withheld from the host and paid to this promoter via Connect (1500 = 15%). Guest price unchanged.
698
+ ticketCommissionBps Int?
614
699
  promoCodes BashEventPromoCode[]
700
+ ticketCommissions PromoterTicketCommission[]
701
+ lineupCredits BashEventLineupSlot[]
615
702
  promoterUser User? @relation(fields: [userId], references: [id])
703
+ competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
616
704
 
617
705
  @@unique([promoterId, promotedForBash])
706
+ @@index([competitionId])
707
+ }
708
+
709
+ enum PromoterTicketCommissionStatus {
710
+ Accrued
711
+ Transferred
712
+ Reversed
713
+ Held
714
+ }
715
+
716
+ /// Ledger for % of ticket sales owed to a promo-code promoter / performer.
717
+ /// Withheld from the host via Stripe application_fee, then transferred to the
718
+ /// promoter's Connect account. Guest checkout total does not change.
719
+ model PromoterTicketCommission {
720
+ id String @id @default(cuid())
721
+ promoterId String
722
+ bashEventId String
723
+ promoCodeId String?
724
+ checkoutSessionId String?
725
+ paymentIntentId String?
726
+ chargeId String?
727
+ transferId String?
728
+ reversalId String?
729
+ ticketCount Int
730
+ basisCents Int
731
+ commissionBps Int
732
+ amountCents Int
733
+ status PromoterTicketCommissionStatus @default(Accrued)
734
+ createdAt DateTime @default(now())
735
+ updatedAt DateTime @updatedAt
736
+
737
+ promoter Promoter @relation(fields: [promoterId], references: [id], onDelete: Cascade)
738
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
739
+
740
+ @@unique([checkoutSessionId])
741
+ @@index([promoterId, status])
742
+ @@index([bashEventId])
743
+ @@index([paymentIntentId])
618
744
  }
619
745
 
620
746
  model PromoterStats {
@@ -632,11 +758,85 @@ model PromoterStats {
632
758
  pendingCredits Int @default(0)
633
759
  availableCredits Int @default(0)
634
760
  promoterInvitations Int @default(0)
761
+ // Cross-competition lifetime aggregates (added in promoter network layer migration)
762
+ totalCompetitionsJoined Int @default(0)
763
+ totalCompetitionsWithSale Int @default(0)
764
+ totalMilestonesReached Int @default(0)
765
+ totalCompetitionsWon Int @default(0) // rank=1 achieved
766
+ totalEventsPromoted Int @default(0)
767
+ repeatOrganizerCount Int @default(0) // distinct organizers worked with 2+ times
768
+ lifetimeQualifiedTickets Int @default(0)
769
+ lifetimeRevenueCents Int @default(0)
635
770
  createdAt DateTime @default(now())
636
771
  updatedAt DateTime @updatedAt
637
772
  user User @relation(fields: [userId], references: [id])
638
773
  }
639
774
 
775
+ /// Persisted per-promoter per-event performance record.
776
+ /// Created when a promoter joins a PROMOTER competition; updated async after each qualified sale/refund.
777
+ /// Survives competition end and event archival.
778
+ model PromoterEventPerformance {
779
+ id String @id @default(cuid())
780
+ promoterUserId String
781
+ bashEventId String
782
+ competitionId String
783
+
784
+ clicks Int @default(0)
785
+ checkoutStarts Int @default(0)
786
+ qualifiedOrders Int @default(0)
787
+ qualifiedTickets Int @default(0)
788
+ revenueGeneratedCents Int @default(0)
789
+
790
+ highestMilestoneThreshold Int?
791
+ finalRank Int?
792
+
793
+ eventCategory String?
794
+ eventCity String?
795
+ eventRegion String?
796
+
797
+ joinedAt DateTime @default(now())
798
+ lastSaleAt DateTime?
799
+ completedAt DateTime?
800
+ createdAt DateTime @default(now())
801
+ updatedAt DateTime @updatedAt
802
+
803
+ promoterUser User @relation("PromoterEventPerformances", fields: [promoterUserId], references: [id], onDelete: Cascade)
804
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
805
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
806
+
807
+ @@unique([promoterUserId, competitionId])
808
+ @@index([promoterUserId])
809
+ @@index([bashEventId])
810
+ @@index([eventCategory, promoterUserId])
811
+ @@index([eventCity, promoterUserId])
812
+ }
813
+
814
+ /// Cross-event relationship between a host (organizer) and a promoter.
815
+ /// Updated each time they work together. Enables "Invite Previous Promoters" and relationship depth metrics.
816
+ model OrganizerPromoterRelationship {
817
+ id String @id @default(cuid())
818
+ organizerUserId String
819
+ promoterUserId String
820
+
821
+ timesInvited Int @default(1)
822
+ timesJoined Int @default(0)
823
+ eventsWithSale Int @default(0)
824
+ totalTicketsGenerated Int @default(0)
825
+ totalRevenueGeneratedCents Int @default(0)
826
+ lastWorkedTogetherAt DateTime?
827
+ lastInvitedAt DateTime @default(now())
828
+
829
+ createdAt DateTime @default(now())
830
+ updatedAt DateTime @updatedAt
831
+
832
+ organizer User @relation("OrganizerPromoterRelationshipsAsOrganizer", fields: [organizerUserId], references: [id], onDelete: Cascade)
833
+ promoter User @relation("OrganizerPromoterRelationshipsAsPromoter", fields: [promoterUserId], references: [id], onDelete: Cascade)
834
+
835
+ @@unique([organizerUserId, promoterUserId])
836
+ @@index([organizerUserId])
837
+ @@index([promoterUserId])
838
+ }
839
+
640
840
  model PromoterClick {
641
841
  id String @id @default(cuid())
642
842
  promoterId String
@@ -936,6 +1136,12 @@ model BashEvent {
936
1136
  /// When true, only accounts with a published service profile (Vendor/Venue/Entertainment/Exhibitor/Sponsor/Event
937
1137
  /// Service) can view budget lines revealed via `Budget.showOnBashDetailPage`. Default off = anyone can click to view.
938
1138
  budgetRestrictedToServiceProviders Boolean @default(false)
1139
+ /// Public funding meter: value coming in toward included budget lines (not host spend).
1140
+ showFundingMeter Boolean @default(false)
1141
+ /// When false, public can Pay now only — Commit / pledge POST is rejected.
1142
+ fundingAllowCommitments Boolean @default(true)
1143
+ /// When true, open pledges fill the lighter meter segment. Default off: bar is collected only.
1144
+ fundingShowPledgesOnMeter Boolean @default(false)
939
1145
  /// When true on Rsvp-mode events, affirmative RSVPs land in a host approval queue first.
940
1146
  rsvpRequiresApproval Boolean @default(false)
941
1147
  /// Hide when guests RSVP'd on host-facing attendee views.
@@ -943,6 +1149,8 @@ model BashEvent {
943
1149
  /// Only allow inviting people who share a mutual bash with the host.
944
1150
  invitesLimitedToMutuals Boolean @default(false)
945
1151
  showOrganizerTitles Boolean @default(false)
1152
+ /// When false, public/guest surfaces hide host identity (cards + Meet the host).
1153
+ showHostOnBashPage Boolean @default(true)
946
1154
  /// When true, low-stock scarcity badges show "X% left" instead of "Only N left".
947
1155
  showPercentRemaining Boolean @default(false)
948
1156
  servicePreferences Json? // New tiered status: { "Entertainment": "need", "Sponsors": "booked_closed", ... }
@@ -1002,6 +1210,14 @@ model BashEvent {
1002
1210
  guestsCanInviteOthers Boolean @default(false)
1003
1211
  /// Private-event screening: route access requests to a host approval queue
1004
1212
  accessScreeningEnabled Boolean @default(false)
1213
+ /// Optional host application (markup + questions) before RSVP or ticket checkout.
1214
+ applicationEnabled Boolean @default(false)
1215
+ /// Rich-text intro shown at the top of the guest application.
1216
+ applicationIntroHtml String? @db.Text
1217
+ /// After this time, guests cannot submit a new application. Hosts can still review.
1218
+ applicationClosesAt DateTime?
1219
+ /// When true, auto-approve applicants this host has approved on a prior bash.
1220
+ autoApproveRegulars Boolean @default(false)
1005
1221
  /// Collaborative post-event photo/video album contributions
1006
1222
  albumEnabled Boolean @default(false)
1007
1223
  /// When true, attendee uploads require host approval before appearing in the public album.
@@ -1038,6 +1254,10 @@ model BashEvent {
1038
1254
  organizationId String?
1039
1255
  organization Organization? @relation("OrganizationBashEvents", fields: [organizationId], references: [id])
1040
1256
 
1257
+ /// Host-as-service identity. XOR with organizationId (enforced in upsert + DB check).
1258
+ hostedByServiceId String?
1259
+ hostedByService Service? @relation("BashEventHostedByService", fields: [hostedByServiceId], references: [id], onDelete: SetNull)
1260
+
1041
1261
  // Organization access controls (only apply when organizationId is set)
1042
1262
  requiresOrgMembership Boolean @default(false) // Must be member to attend/RSVP
1043
1263
  allowMemberGuests Boolean @default(false) // Members can bring +1s
@@ -1149,7 +1369,10 @@ model BashEvent {
1149
1369
  eventReferralRewards EventReferralReward[]
1150
1370
  eventReferrals EventReferral[]
1151
1371
  discussionComments EventDiscussionComment[]
1152
- bashEventDonations BashEventDonation[]
1372
+ bashEventDonations BashEventDonation[]
1373
+ budgetContributions BudgetContribution[]
1374
+ giveaways Giveaway[]
1375
+ promoterEventPerformances PromoterEventPerformance[]
1153
1376
 
1154
1377
  // Adventures / Side Quests
1155
1378
  sideQuests SideQuest[]
@@ -1174,7 +1397,8 @@ model BashEvent {
1174
1397
  eventGroups EventGroup[]
1175
1398
  groupUnlockOffers GroupUnlockOffer[]
1176
1399
 
1177
- bashEventArtists BashEventArtist[]
1400
+ lineupSlots BashEventLineupSlot[]
1401
+ promoterTicketCommissions PromoterTicketCommission[]
1178
1402
  vanityPaths EventVanityPath[]
1179
1403
  flyerCampaigns FlyerCampaign[]
1180
1404
  featuredGuests EventFeaturedGuest[]
@@ -1215,18 +1439,22 @@ model BashEvent {
1215
1439
  @@index([templateId])
1216
1440
  @@index([parentEventId])
1217
1441
  @@index([organizationId])
1442
+ @@index([hostedByServiceId])
1218
1443
  @@index([eventSeriesId])
1219
1444
  @@index([status, aiApprovalVerdict])
1220
1445
  }
1221
1446
 
1222
1447
  /// Lineup / attribution roster for an event (optional act list for checkout tagging).
1223
- model BashEventArtist {
1448
+ model BashEventLineupSlot {
1224
1449
  id String @id @default(cuid())
1225
1450
  bashEventId String
1226
1451
  bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
1227
1452
  name String
1228
1453
  serviceId String?
1229
1454
  service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
1455
+ invitedEmail String?
1456
+ promoterId String?
1457
+ promoter Promoter? @relation(fields: [promoterId], references: [id], onDelete: SetNull)
1230
1458
  sortOrder Int @default(0)
1231
1459
  createdAt DateTime @default(now())
1232
1460
 
@@ -1234,6 +1462,7 @@ model BashEventArtist {
1234
1462
 
1235
1463
  @@index([bashEventId])
1236
1464
  @@index([serviceId])
1465
+ @@index([promoterId])
1237
1466
  }
1238
1467
 
1239
1468
  // --- Flyer Blast (USPS EDDM + Lob addressed mail) ---
@@ -2025,11 +2254,16 @@ model PublicBashRsvp {
2025
2254
  plusOneNames String[] @default([])
2026
2255
  /// Set when the guest keeps or declines after a postponement; cleared on each new Postpone.
2027
2256
  postponeRespondedAt DateTime?
2257
+ /// Optional guest photo submitted with an RSVP application.
2258
+ applicationPhotoUrl String?
2259
+ /// Optional host sentence included when an RSVP application is denied.
2260
+ hostNote String?
2028
2261
  createdAt DateTime @default(now())
2029
2262
  updatedAt DateTime @updatedAt
2030
2263
 
2031
- bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
2032
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
2264
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
2265
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
2266
+ applicationAnswers BashEventQuestionAnswer[]
2033
2267
 
2034
2268
  @@unique([bashEventId, userId])
2035
2269
  @@index([bashEventId])
@@ -2190,6 +2424,7 @@ model SideQuest {
2190
2424
  eventBadge EventBadge? @relation(fields: [eventBadgeId], references: [id], onDelete: SetNull)
2191
2425
  competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
2192
2426
  completions SideQuestCompletion[]
2427
+ giveaways Giveaway[]
2193
2428
  milestonesUnlocking SideQuestMilestone[] @relation("MilestoneUnlocksQuest")
2194
2429
  checkpoints SideQuestCheckpoint[]
2195
2430
  progressHits SideQuestProgressHit[]
@@ -2464,34 +2699,57 @@ enum BashEventQuestionType {
2464
2699
  YesNo
2465
2700
  }
2466
2701
 
2467
- /// Host-defined custom RSVP / checkout question for a bash.
2702
+ /// What happens when a choice answer is not in acceptableOptions.
2703
+ enum QuestionMismatchAction {
2704
+ None
2705
+ FlagForReview
2706
+ AutoDeny
2707
+ }
2708
+
2709
+ /// Host-defined custom RSVP / checkout / application question for a bash.
2468
2710
  model BashEventCustomQuestion {
2469
- id String @id @default(cuid())
2470
- bashEventId String
2471
- prompt String
2472
- type BashEventQuestionType @default(ShortText)
2473
- options String[] @default([])
2474
- required Boolean @default(false)
2475
- sortOrder Int @default(0)
2476
- createdAt DateTime @default(now())
2477
- updatedAt DateTime @updatedAt
2711
+ id String @id @default(cuid())
2712
+ bashEventId String
2713
+ prompt String
2714
+ type BashEventQuestionType @default(ShortText)
2715
+ options String[] @default([])
2716
+ required Boolean @default(false)
2717
+ sortOrder Int @default(0)
2718
+ /// null = every applying surface (all application tiers + RSVP).
2719
+ ticketTierId String?
2720
+ mismatchAction QuestionMismatchAction @default(None)
2721
+ acceptableOptions String[] @default([])
2722
+ /// When set, only show this question if the parent answer matches showWhenAnswer.
2723
+ parentQuestionId String?
2724
+ showWhenAnswer String?
2725
+ createdAt DateTime @default(now())
2726
+ updatedAt DateTime @updatedAt
2478
2727
 
2479
2728
  @@index([bashEventId])
2729
+ @@index([ticketTierId])
2730
+ @@index([parentQuestionId])
2480
2731
  }
2481
2732
 
2482
- /// An attendee's answer to a custom question (captured at RSVP / checkout).
2733
+ /// An attendee's answer to a custom question (captured at RSVP / checkout / application).
2483
2734
  model BashEventQuestionAnswer {
2484
- id String @id @default(cuid())
2485
- questionId String
2486
- bashEventId String
2487
- userId String?
2488
- email String?
2489
- ticketId String?
2490
- answer String
2491
- createdAt DateTime @default(now())
2735
+ id String @id @default(cuid())
2736
+ questionId String
2737
+ bashEventId String
2738
+ userId String?
2739
+ email String?
2740
+ ticketId String?
2741
+ accessRequestId String?
2742
+ publicRsvpId String?
2743
+ answer String
2744
+ createdAt DateTime @default(now())
2745
+
2746
+ accessRequest BashEventAccessRequest? @relation(fields: [accessRequestId], references: [id], onDelete: SetNull)
2747
+ publicRsvp PublicBashRsvp? @relation(fields: [publicRsvpId], references: [id], onDelete: SetNull)
2492
2748
 
2493
2749
  @@index([questionId])
2494
2750
  @@index([bashEventId])
2751
+ @@index([accessRequestId])
2752
+ @@index([publicRsvpId])
2495
2753
  }
2496
2754
 
2497
2755
  enum BashEventAccessRequestStatus {
@@ -2510,10 +2768,17 @@ model BashEventAccessRequest {
2510
2768
  status BashEventAccessRequestStatus @default(Pending)
2511
2769
  reviewedByUserId String?
2512
2770
  reviewedAt DateTime?
2513
- createdAt DateTime @default(now())
2514
- updatedAt DateTime @updatedAt
2771
+ /// Optional host sentence included when the request is denied.
2772
+ hostNote String?
2773
+ ticketTierId String?
2774
+ applicationPhotoUrl String?
2775
+ createdAt DateTime @default(now())
2776
+ updatedAt DateTime @updatedAt
2777
+
2778
+ answers BashEventQuestionAnswer[]
2515
2779
 
2516
2780
  @@index([bashEventId, status])
2781
+ @@index([ticketTierId])
2517
2782
  }
2518
2783
 
2519
2784
  /// A collaborative album contribution (photo/video URL) for a bash.
@@ -3324,6 +3589,8 @@ model TicketTier {
3324
3589
 
3325
3590
  /// Host opt-in: buyers of this tier may choose mailed or venue will-call physical delivery (see TicketPrintOrder).
3326
3591
  allowsPhysicalDelivery Boolean @default(false)
3592
+ /// Guests must complete the event application and be approved before buying this tier.
3593
+ requiresApplication Boolean @default(false)
3327
3594
 
3328
3595
  @@unique([bashEventId, title])
3329
3596
  @@index([availableAt])
@@ -3459,7 +3726,7 @@ model Ticket {
3459
3726
 
3460
3727
  bashEvent BashEvent @relation(fields: [bashEventId], references: [id])
3461
3728
  attributedArtistId String?
3462
- attributedArtist BashEventArtist? @relation(fields: [attributedArtistId], references: [id], onDelete: SetNull)
3729
+ attributedArtist BashEventLineupSlot? @relation(fields: [attributedArtistId], references: [id], onDelete: SetNull)
3463
3730
  checkout Checkout? @relation(fields: [checkoutId], references: [id])
3464
3731
  forUser User? @relation("TicketsISent", fields: [forUserId], references: [id])
3465
3732
  invitation Invitation? @relation("TicketsForInvitation", fields: [invitationId], references: [id])
@@ -3474,6 +3741,7 @@ model Ticket {
3474
3741
  flyerConversions FlyerConversion[]
3475
3742
  coatCheckEntitlements CoatCheckEntitlement[]
3476
3743
  printOrder TicketPrintOrder?
3744
+ giveawayRedemptions GiveawayRedemption[]
3477
3745
 
3478
3746
  @@index([bashEventId])
3479
3747
  @@index([bashEventId, seatId])
@@ -4451,6 +4719,7 @@ model User {
4451
4719
  eventFeaturedGuestSpots EventFeaturedGuest[] @relation("FeaturedGuestOnEvent")
4452
4720
  eventAttendeeVisibilities EventAttendeeVisibility[] @relation("AttendeeVisibilityOnEvent")
4453
4721
  bashEventDonations BashEventDonation[]
4722
+ budgetContributions BudgetContribution[]
4454
4723
  bashCreativeSubmissions BashCreativeSubmission[]
4455
4724
  ticketsOnWaitlist Ticket[] @relation("TicketsOnWaitlist")
4456
4725
  transfersFrom TicketTransfer[] @relation("TransfersFrom")
@@ -4557,6 +4826,9 @@ model User {
4557
4826
  coatCheckEntitlementsOwned CoatCheckEntitlement[] @relation("CoatCheckOwner")
4558
4827
  coatCheckEntitlementsCheckedIn CoatCheckEntitlement[] @relation("CoatCheckCheckedInBy")
4559
4828
  coatCheckEntitlementsClaimed CoatCheckEntitlement[] @relation("CoatCheckClaimedBy")
4829
+ giveawayRedemptions GiveawayRedemption[] @relation("GiveawayRedemptionUser")
4830
+ giveawayRedemptionsReceived GiveawayRedemption[] @relation("GiveawayReceivedBy")
4831
+ giveawayRedemptionsVoided GiveawayRedemption[] @relation("GiveawayVoidedBy")
4560
4832
  bashBusWaitlistSignup BashBusWaitlistSignup?
4561
4833
  collectibleRedemptions CollectibleRedemption[]
4562
4834
  bashFeedSaves BashFeedSave[]
@@ -4636,6 +4908,11 @@ model User {
4636
4908
 
4637
4909
  phoneVerificationCodes PhoneVerificationCode[]
4638
4910
  smsSendLogs SmsSendLog[]
4911
+
4912
+ // Promoter Network Layer relations
4913
+ promoterEventPerformances PromoterEventPerformance[] @relation("PromoterEventPerformances")
4914
+ organizerPromoterRelationshipsAsOrganizer OrganizerPromoterRelationship[] @relation("OrganizerPromoterRelationshipsAsOrganizer")
4915
+ organizerPromoterRelationshipsAsPromoter OrganizerPromoterRelationship[] @relation("OrganizerPromoterRelationshipsAsPromoter")
4639
4916
  }
4640
4917
 
4641
4918
  model PhoneVerificationCode {
@@ -5254,7 +5531,7 @@ model Service {
5254
5531
  latitude Float?
5255
5532
  longitude Float?
5256
5533
  bashFeedPosts BashFeedPost[]
5257
- bashEventArtists BashEventArtist[]
5534
+ lineupSlots BashEventLineupSlot[]
5258
5535
  bashCreativeSubmissions BashCreativeSubmission[]
5259
5536
  associatedServicesReferencingMe AssociatedService[]
5260
5537
  exhibitorBookingRequests ExhibitorBookingRequest[] @relation("ExhibitorBookingService")
@@ -5285,6 +5562,7 @@ model Service {
5285
5562
  serviceCategoryRequests ServiceCategoryRequest[] // Services created from approved category requests
5286
5563
  associatedBashesReferencingMe AssociatedBash[] @relation("AssociatedBashToService")
5287
5564
  bashEvent BashEvent[] @relation("BashEventToService")
5565
+ hostedBashEvents BashEvent[] @relation("BashEventHostedByService")
5288
5566
  media Media[] @relation("MediaToService")
5289
5567
  analytics ServiceAnalytics[] // Track service analytics
5290
5568
  scoutReferrals ScoutReferral[] @relation("ScoutServiceProfile")
@@ -5308,6 +5586,7 @@ model Service {
5308
5586
  serviceReviews ServiceReview[]
5309
5587
  providerHostReviews ProviderHostReview[]
5310
5588
  sideQuestCheckpoints SideQuestCheckpoint[]
5589
+ giveaways Giveaway[]
5311
5590
  serviceRankings ServiceRankings?
5312
5591
 
5313
5592
  @@index([serviceListingStripeSubscriptionId])
@@ -6993,6 +7272,8 @@ model Budget {
6993
7272
  remainingCents Int? // Can be computed: totalAllocatedCents - spentCents
6994
7273
  /// Host opt-in: reveal this line's category + amount to providers on the public bash detail page.
6995
7274
  showOnBashDetailPage Boolean @default(false)
7275
+ /// When true (default), this line's allocation is part of the public funding goal.
7276
+ includeOnFundingMeter Boolean @default(true)
6996
7277
 
6997
7278
  // Categorization
6998
7279
  category String? // "Marketing", "Catering", "Venue", "Annual", "Activities"
@@ -7022,7 +7303,8 @@ model Budget {
7022
7303
  service Service? @relation("ServiceBudgets", fields: [serviceId], references: [id], onDelete: Cascade)
7023
7304
  submitter User? @relation("BudgetSubmitter", fields: [submittedBy], references: [id])
7024
7305
  approver User? @relation("BudgetApprover", fields: [approverId], references: [id])
7025
- expenses Expense[]
7306
+ expenses Expense[]
7307
+ contributions BudgetContribution[]
7026
7308
 
7027
7309
  @@index([organizationId])
7028
7310
  @@index([bashEventId])
@@ -7034,6 +7316,116 @@ model Budget {
7034
7316
  @@index([fiscalYear])
7035
7317
  }
7036
7318
 
7319
+ enum BudgetContributionKind {
7320
+ Cash
7321
+ InKind
7322
+ }
7323
+
7324
+ enum BudgetContributionStatus {
7325
+ Pledged
7326
+ Paid
7327
+ Fulfilled
7328
+ Declined
7329
+ Cancelled
7330
+ }
7331
+
7332
+ enum BudgetContributionSource {
7333
+ Stripe
7334
+ HostEntered
7335
+ PublicPledge
7336
+ }
7337
+
7338
+ /// Value coming in toward a bash budget line (host-entered, public pledge, or Stripe).
7339
+ /// `budgetId` is required on create for HostEntered / PublicPledge / Stripe; null is unallocated only.
7340
+ model BudgetContribution {
7341
+ id String @id @default(cuid())
7342
+ bashEventId String
7343
+ budgetId String?
7344
+ contributorUserId String?
7345
+ displayName String
7346
+ kind BudgetContributionKind
7347
+ status BudgetContributionStatus
7348
+ source BudgetContributionSource
7349
+ amountCents Int
7350
+ note String?
7351
+ stripeCheckoutSessionId String? @unique
7352
+ stripePaymentIntentId String? @unique
7353
+ createdAt DateTime @default(now())
7354
+ updatedAt DateTime @updatedAt
7355
+
7356
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
7357
+ budget Budget? @relation(fields: [budgetId], references: [id], onDelete: SetNull)
7358
+ contributor User? @relation(fields: [contributorUserId], references: [id], onDelete: SetNull)
7359
+
7360
+ @@index([bashEventId])
7361
+ @@index([budgetId])
7362
+ @@index([contributorUserId])
7363
+ @@index([status])
7364
+ }
7365
+
7366
+ enum GiveawayEligibility {
7367
+ AnyoneGoing
7368
+ TicketHolders
7369
+ CheckedIn
7370
+ CompetitionParticipants
7371
+ SideQuestCompleters
7372
+ }
7373
+
7374
+ /// Host/sponsor swag handed out at the bash. Not merch (for sale) and not a competition prize.
7375
+ model Giveaway {
7376
+ id String @id @default(cuid())
7377
+ bashEventId String
7378
+ name String
7379
+ description String?
7380
+ imageUrl String?
7381
+ quantityTotal Int
7382
+ quantityRemaining Int
7383
+ displayOrder Int @default(0)
7384
+ sponsorLabel String?
7385
+ sponsorServiceId String?
7386
+ eligibility GiveawayEligibility
7387
+ competitionId String?
7388
+ sideQuestId String?
7389
+ pickupNote String?
7390
+ createdAt DateTime @default(now())
7391
+ updatedAt DateTime @updatedAt
7392
+
7393
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
7394
+ competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
7395
+ sideQuest SideQuest? @relation(fields: [sideQuestId], references: [id], onDelete: SetNull)
7396
+ sponsorService Service? @relation(fields: [sponsorServiceId], references: [id], onDelete: SetNull)
7397
+ redemptions GiveawayRedemption[]
7398
+
7399
+ @@index([bashEventId])
7400
+ @@index([bashEventId, displayOrder])
7401
+ @@index([competitionId])
7402
+ @@index([sideQuestId])
7403
+ }
7404
+
7405
+ /// Event-team confirmed handout. Partial unique (giveawayId, attendeeId) WHERE voidedAt IS NULL — migration SQL only.
7406
+ model GiveawayRedemption {
7407
+ id String @id @default(cuid())
7408
+ giveawayId String
7409
+ attendeeId String
7410
+ userId String?
7411
+ ticketId String?
7412
+ receivedAt DateTime @default(now())
7413
+ receivedByUserId String
7414
+ voidedAt DateTime?
7415
+ voidedByUserId String?
7416
+
7417
+ giveaway Giveaway @relation(fields: [giveawayId], references: [id], onDelete: Cascade)
7418
+ user User? @relation("GiveawayRedemptionUser", fields: [userId], references: [id], onDelete: SetNull)
7419
+ ticket Ticket? @relation(fields: [ticketId], references: [id], onDelete: SetNull)
7420
+ receivedBy User @relation("GiveawayReceivedBy", fields: [receivedByUserId], references: [id], onDelete: Restrict)
7421
+ voidedBy User? @relation("GiveawayVoidedBy", fields: [voidedByUserId], references: [id], onDelete: SetNull)
7422
+
7423
+ @@index([giveawayId])
7424
+ @@index([attendeeId])
7425
+ @@index([ticketId])
7426
+ @@index([userId])
7427
+ }
7428
+
7037
7429
  enum ExpenseExtractionStatus {
7038
7430
  none
7039
7431
  pending
@@ -8176,6 +8568,13 @@ enum NotificationType {
8176
8568
  CompetitionPrizeOfferReceived // Host: someone offered a prize contribution
8177
8569
  CompetitionPrizeOfferAccepted // Offeror: host accepted your prize offer
8178
8570
  CompetitionPrizeOfferDeclined // Offeror: host declined your prize offer
8571
+ PromoterCompetitionJoined // Organizer: a promoter joined the challenge
8572
+ PromoterCompetitionMilestoneReached // Promoter: you hit a milestone and earned a reward
8573
+ PromoterCompetitionLeaderboardChange // Promoter: your leaderboard rank changed
8574
+ PromoterCompetitionEnding // Promoter: competition ending in 24h (with earned progress)
8575
+ PromoterCompetitionCompleted // Promoter + organizer: competition finished
8576
+ PromoterCompetitionRewardFulfilled // Promoter: organizer marked your reward as fulfilled
8577
+ PreviousPromoterInvited // Promoter: organizer you've worked with is inviting you back
8179
8578
  BashRating
8180
8579
  BashRatingMilestone
8181
8580
  BashFeedPost
@@ -8872,6 +9271,41 @@ enum CompetitionType {
8872
9271
  AUDIENCE_VOTING // Audience Voting-Based
8873
9272
  }
8874
9273
 
9274
+ enum CompetitionPurpose {
9275
+ CONSUMER // existing behavior — vote/bracket/raffle/contest
9276
+ PROMOTER // ticket sales contest — rewards promoters per milestone
9277
+ }
9278
+
9279
+ enum PromoterRewardMode {
9280
+ TOTAL_AT_MILESTONE // "10 sales = $40 total earned" (default)
9281
+ ADDITIVE // milestone rewards stack on each other
9282
+ }
9283
+
9284
+ enum PromoterRecruitMode {
9285
+ Public // anyone can join from bash page
9286
+ InviteOnly // organizer sends invites (default)
9287
+ Private // no enrollment shown publicly
9288
+ }
9289
+
9290
+ enum PromoterRewardType {
9291
+ CASH
9292
+ TICKET
9293
+ TICKET_UPGRADE
9294
+ TABLE
9295
+ ACCESS
9296
+ MERCH
9297
+ SPONSOR_PRIZE
9298
+ CREDIT
9299
+ CUSTOM
9300
+ }
9301
+
9302
+ enum PromoterRewardStatus {
9303
+ PENDING // milestone not yet reached
9304
+ EARNED // threshold crossed; awaiting fulfillment
9305
+ FULFILLED // organizer marked delivered
9306
+ REVOKED // refunds pushed promoter below threshold; FULFILLED rewards are never auto-revoked
9307
+ }
9308
+
8875
9309
  enum PlacementAwardStyle {
8876
9310
  Trophy // Gold/silver/bronze trophies for 1st/2nd/3rd
8877
9311
  Medal // Gold/silver/bronze medals for 1st/2nd/3rd
@@ -12212,6 +12646,8 @@ model AppliedDiscount {
12212
12646
  attributionCampaignId String?
12213
12647
  commissionRate Float?
12214
12648
  commissionAmount Int? // In cents
12649
+ /// Set when this discount was applied under a PROMOTER competition — used for attribution recalculation
12650
+ competitionId String?
12215
12651
 
12216
12652
  // Display
12217
12653
  displayReason String? // "Early Bird Discount" for receipt