@bash-app/bash-common 30.112.0 → 30.114.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 (40) hide show
  1. package/dist/definitions.d.ts +2 -2
  2. package/dist/definitions.d.ts.map +1 -1
  3. package/dist/definitions.js +2 -2
  4. package/dist/definitions.js.map +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/utils/__tests__/recurrenceUtils.test.d.ts +2 -0
  10. package/dist/utils/__tests__/recurrenceUtils.test.d.ts.map +1 -0
  11. package/dist/utils/__tests__/recurrenceUtils.test.js +193 -0
  12. package/dist/utils/__tests__/recurrenceUtils.test.js.map +1 -0
  13. package/dist/utils/discountEngine/bestPriceResolver.d.ts +31 -0
  14. package/dist/utils/discountEngine/bestPriceResolver.d.ts.map +1 -0
  15. package/dist/utils/discountEngine/bestPriceResolver.js +147 -0
  16. package/dist/utils/discountEngine/bestPriceResolver.js.map +1 -0
  17. package/dist/utils/discountEngine/discountCalculator.d.ts +56 -0
  18. package/dist/utils/discountEngine/discountCalculator.d.ts.map +1 -0
  19. package/dist/utils/discountEngine/discountCalculator.js +219 -0
  20. package/dist/utils/discountEngine/discountCalculator.js.map +1 -0
  21. package/dist/utils/discountEngine/eligibilityValidator.d.ts +36 -0
  22. package/dist/utils/discountEngine/eligibilityValidator.d.ts.map +1 -0
  23. package/dist/utils/discountEngine/eligibilityValidator.js +189 -0
  24. package/dist/utils/discountEngine/eligibilityValidator.js.map +1 -0
  25. package/dist/utils/discountEngine/index.d.ts +4 -0
  26. package/dist/utils/discountEngine/index.d.ts.map +1 -0
  27. package/dist/utils/discountEngine/index.js +5 -0
  28. package/dist/utils/discountEngine/index.js.map +1 -0
  29. package/dist/utils/recurrenceUtils.d.ts.map +1 -1
  30. package/dist/utils/recurrenceUtils.js +17 -4
  31. package/dist/utils/recurrenceUtils.js.map +1 -1
  32. package/package.json +1 -1
  33. package/prisma/schema.prisma +448 -118
  34. package/src/definitions.ts +2 -1
  35. package/src/index.ts +2 -0
  36. package/src/utils/discountEngine/bestPriceResolver.ts +212 -0
  37. package/src/utils/discountEngine/discountCalculator.ts +281 -0
  38. package/src/utils/discountEngine/eligibilityValidator.ts +256 -0
  39. package/src/utils/discountEngine/index.ts +5 -0
  40. package/src/utils/recurrenceUtils.ts +20 -4
@@ -54,15 +54,20 @@ model BashComment {
54
54
 
55
55
  // BashFeed Models - Event-Centric Social Discovery Feed
56
56
  model BashFeedPost {
57
- id String @id @default(cuid())
58
- userId String
59
- bashId String
60
- content String? @db.Text
61
- mediaIds String[] // Array of Media IDs
62
- isAnonymous Boolean @default(false) // User can choose to post anonymously
63
- createdAt DateTime @default(now())
64
- updatedAt DateTime @updatedAt
65
- editedAt DateTime?
57
+ id String @id @default(cuid())
58
+ userId String
59
+ bashId String
60
+ content String? @db.Text
61
+ mediaIds String[] // Array of Media IDs
62
+ mentionedUserIds String[] // Array of User IDs mentioned in post
63
+ isAnonymous Boolean @default(false) // User can choose to post anonymously
64
+ viewCount Int @default(0) // Analytics only, not shown publicly
65
+ isFeatured Boolean @default(false) // Post is promoted/featured
66
+ featuredAt DateTime? // When post was featured
67
+ featuredBoost Float @default(0) // Current boost score (time-decayed)
68
+ createdAt DateTime @default(now())
69
+ updatedAt DateTime @updatedAt
70
+ editedAt DateTime?
66
71
 
67
72
  // Relationships
68
73
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -78,6 +83,7 @@ model BashFeedPost {
78
83
  @@index([userId])
79
84
  @@index([createdAt])
80
85
  @@index([isAnonymous])
86
+ @@index([isFeatured, featuredBoost])
81
87
  }
82
88
 
83
89
  model BashFeedLike {
@@ -94,13 +100,14 @@ model BashFeedLike {
94
100
  }
95
101
 
96
102
  model BashFeedComment {
97
- id String @id @default(cuid())
98
- userId String
99
- postId String
100
- content String @db.Text
101
- parentId String? // For nested replies
102
- createdAt DateTime @default(now())
103
- updatedAt DateTime @updatedAt
103
+ id String @id @default(cuid())
104
+ userId String
105
+ postId String
106
+ content String @db.Text
107
+ mentionedUserIds String[] // Array of User IDs mentioned in comment
108
+ parentId String? // For nested replies
109
+ createdAt DateTime @default(now())
110
+ updatedAt DateTime @updatedAt
104
111
 
105
112
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
106
113
  post BashFeedPost @relation(fields: [postId], references: [id], onDelete: Cascade)
@@ -194,29 +201,36 @@ model BashFeedRepost {
194
201
  }
195
202
 
196
203
  model Competition {
197
- id String @id @default(cuid())
204
+ id String @id @default(cuid())
198
205
  name String
199
- description String @db.Text
206
+ description String @db.Text
200
207
  userId String
201
208
  bashEventId String
202
209
  numberOfPrizes Int
203
210
  competitionType CompetitionType? // Main category
204
211
  subtype String? // Subcategory
205
- rules String? @db.Text // Detailed rules/disclaimers
212
+ rules String? @db.Text // Detailed rules/disclaimers
206
213
  judgingType JudgingType? // How winner is determined
207
214
  startTime DateTime? // Competition start time (for voting)
208
215
  endTime DateTime? // Competition end time (for voting)
209
- allowSelfRegistration Boolean @default(false)
216
+ allowSelfRegistration Boolean @default(false)
210
217
  maxParticipants Int? // Optional cap on participants
211
- createdAt DateTime @default(now())
212
- updatedAt DateTime @updatedAt
213
- bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
214
- owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
215
- sponsor CompetitionSponsor[]
216
- prizes Prize[]
217
- participants CompetitionParticipant[]
218
- votes CompetitionVote[]
219
- notifications Notification[]
218
+
219
+ // Tournament bracket fields
220
+ bracketType BracketType? @default(NONE)
221
+ bracketData Json? // Store bracket structure/metadata
222
+ bracketVisible Boolean @default(true)
223
+
224
+ createdAt DateTime @default(now())
225
+ updatedAt DateTime @updatedAt
226
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
227
+ owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
228
+ sponsor CompetitionSponsor[]
229
+ prizes Prize[]
230
+ participants CompetitionParticipant[]
231
+ votes CompetitionVote[]
232
+ matches CompetitionMatch[]
233
+ notifications Notification[]
220
234
 
221
235
  @@index([bashEventId])
222
236
  @@index([competitionType])
@@ -243,17 +257,53 @@ model CompetitionParticipant {
243
257
  imageUrl String? // Photo/avatar
244
258
  order Int @default(0) // Display order
245
259
  voteCount Int @default(0) // Denormalized for performance
260
+ isEliminated Boolean @default(false) // For tournament brackets
261
+ placement Int? // Final placement (1st, 2nd, 3rd, etc.)
246
262
  addedBy String @default("host") // 'host' or 'self'
247
263
  createdAt DateTime @default(now())
248
264
  competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
249
265
  user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
250
266
  votes CompetitionVote[]
251
267
 
268
+ // Tournament bracket relations
269
+ matchesAsParticipant1 CompetitionMatch[] @relation("MatchParticipant1")
270
+ matchesAsParticipant2 CompetitionMatch[] @relation("MatchParticipant2")
271
+ matchesWon CompetitionMatch[] @relation("MatchWinner")
272
+
252
273
  @@index([competitionId])
253
274
  @@index([userId])
254
275
  @@index([voteCount])
255
276
  }
256
277
 
278
+ // Tournament bracket matches
279
+ model CompetitionMatch {
280
+ id String @id @default(cuid())
281
+ competitionId String
282
+ round Int // 1 = first round, 2 = semifinals, etc.
283
+ matchNumber Int // Position in round (1, 2, 3...)
284
+
285
+ participant1Id String?
286
+ participant2Id String?
287
+ winnerId String?
288
+
289
+ scheduledTime DateTime?
290
+ completedAt DateTime?
291
+ score Json? // {participant1: 10, participant2: 8}
292
+ notes String? // Match notes
293
+
294
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
295
+ participant1 CompetitionParticipant? @relation("MatchParticipant1", fields: [participant1Id], references: [id])
296
+ participant2 CompetitionParticipant? @relation("MatchParticipant2", fields: [participant2Id], references: [id])
297
+ winner CompetitionParticipant? @relation("MatchWinner", fields: [winnerId], references: [id])
298
+
299
+ createdAt DateTime @default(now())
300
+ updatedAt DateTime @updatedAt
301
+
302
+ @@unique([competitionId, round, matchNumber])
303
+ @@index([competitionId])
304
+ @@index([round])
305
+ }
306
+
257
307
  model CompetitionVote {
258
308
  id String @id @default(cuid())
259
309
  competitionId String
@@ -331,6 +381,28 @@ model Reminder {
331
381
  remindWho User? @relation("RemindersAssignedToMe", fields: [remindWhoId], references: [id], onDelete: Cascade)
332
382
  }
333
383
 
384
+ model SentReminder {
385
+ id String @id @default(cuid())
386
+ userId String
387
+ bashEventId String?
388
+ serviceBookingId String?
389
+ invitationId String?
390
+ reminderType String // 'HOST_IMPROVEMENT_7D', 'HOST_IMPROVEMENT_3D', 'BOOKING_7D', 'BOOKING_24H', 'BOOKING_3H', 'RSVP_3D', 'RSVP_1D', 'PENDING_BOOKING_2D', 'PENDING_BOOKING_5D'
391
+ sentAt DateTime @default(now())
392
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
393
+ bashEvent BashEvent? @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
394
+ serviceBooking ServiceBooking? @relation(fields: [serviceBookingId], references: [id], onDelete: Cascade)
395
+ invitation Invitation? @relation(fields: [invitationId], references: [id], onDelete: Cascade)
396
+
397
+ @@unique([userId, bashEventId, serviceBookingId, invitationId, reminderType])
398
+ @@index([userId])
399
+ @@index([bashEventId])
400
+ @@index([serviceBookingId])
401
+ @@index([invitationId])
402
+ @@index([reminderType])
403
+ @@index([sentAt])
404
+ }
405
+
334
406
  model BashEventPromoCode {
335
407
  id String @id @default(cuid())
336
408
  code String
@@ -462,28 +534,29 @@ model Invitation {
462
534
  sentTo User? @relation("InvitationsSentToMe", fields: [sentToId], references: [id], onDelete: Cascade)
463
535
  notifications Notification[]
464
536
  tickets Ticket[] @relation("TicketsForInvitation")
537
+ sentReminders SentReminder[]
465
538
 
466
539
  @@index([email])
467
540
  }
468
541
 
469
542
  model BashEvent {
470
- id String @id @default(cuid())
471
- source BashEventSource @default(Bash)
543
+ id String @id @default(cuid())
544
+ source BashEventSource @default(Bash)
472
545
  title String
473
- slug String? @unique
546
+ slug String? @unique
474
547
  creatorId String
475
- createdAt DateTime? @default(now())
476
- isApproved Boolean? @default(false)
548
+ createdAt DateTime? @default(now())
549
+ isApproved Boolean? @default(false)
477
550
  description String?
478
- eventType String @default("Other")
551
+ eventType String @default("Other")
479
552
  timezone String?
480
553
  startDateTime DateTime?
481
554
  endDateTime DateTime?
482
555
  waiverUrl String?
483
- waiverRequired Boolean @default(false)
484
- waiverDisplayType String? @default("inline")
485
- targetAudienceId String? @unique
486
- amountOfGuestsId String? @unique
556
+ waiverRequired Boolean @default(false)
557
+ waiverDisplayType String? @default("inline")
558
+ targetAudienceId String? @unique
559
+ amountOfGuestsId String? @unique
487
560
  vibe String?
488
561
  occasion String?
489
562
  dress String?
@@ -492,7 +565,7 @@ model BashEvent {
492
565
  eventFormat EventFormat? // Derived from location and videoLink: In-Person, Virtual, or Hybrid
493
566
  nonProfit Boolean?
494
567
  nonProfitId String?
495
- privacy Privacy @default(Public)
568
+ privacy Privacy @default(Public)
496
569
  capacity Int?
497
570
  location String?
498
571
  street String?
@@ -500,7 +573,7 @@ model BashEvent {
500
573
  state String?
501
574
  zipCode String?
502
575
  country String?
503
- status BashStatus @default(Draft)
576
+ status BashStatus @default(Draft)
504
577
  tags String[]
505
578
  coverPhoto String?
506
579
  clubId String?
@@ -511,24 +584,24 @@ model BashEvent {
511
584
  isFeatured Boolean?
512
585
  isTrending Boolean?
513
586
  venueId String?
514
- averageRating Float? @default(0)
515
- totalRatings Int? @default(0) // Total count of all ratings (Review + BashFeedRating)
516
- totalReviews Int? @default(0) // Count of Review records (anonymous quick ratings)
517
- totalFeedRatings Int? @default(0) // Count of BashFeedRating records (from posts)
518
- allowDonations Boolean? @default(true)
587
+ averageRating Float? @default(0)
588
+ totalRatings Int? @default(0) // Total count of all ratings (Review + BashFeedRating)
589
+ totalReviews Int? @default(0) // Count of Review records (anonymous quick ratings)
590
+ totalFeedRatings Int? @default(0) // Count of BashFeedRating records (from posts)
591
+ allowDonations Boolean? @default(true)
519
592
  suggestedDonationAmount Int?
520
593
  donationDetails String?
521
- absorbDonationFees Boolean @default(false)
522
- absorbTicketFees Boolean @default(false)
523
- showAttendees Boolean @default(true)
594
+ absorbDonationFees Boolean @default(false)
595
+ absorbTicketFees Boolean @default(false)
596
+ showAttendees Boolean @default(true)
524
597
  servicePreferences Json? // New tiered status: { "Entertainment": "need", "Sponsors": "booked_closed", ... }
525
598
  bookedServices Json? // Booked service details: { "Entertainment": { serviceId: "...", providerId: "...", bookedAt: "..." }, ... }
526
599
  serviceVisibility Json? // DEPRECATED: Use servicePreferences instead. Kept for backward compatibility during migration.
527
600
  originalCreatorId String?
528
601
  transferredAt DateTime?
529
602
  transferredFromId String?
530
- transferCount Int @default(0)
531
- startTimeLocked Boolean @default(false)
603
+ transferCount Int @default(0)
604
+ startTimeLocked Boolean @default(false)
532
605
  p2pPaymentMethod String?
533
606
  venmoUsername String?
534
607
  venmoQRCodeUrl String?
@@ -543,48 +616,50 @@ model BashEvent {
543
616
  itinerary Json? // Array of { id: string, title: string, startTime: string, endTime: string, description?: string }
544
617
  associatedBashesReferencingMe AssociatedBash[]
545
618
  comments BashComment[]
546
- amountOfGuests AmountOfGuests? @relation(fields: [amountOfGuestsId], references: [id], onDelete: Cascade)
547
- club Club? @relation(fields: [clubId], references: [id])
548
- creator User @relation("CreatedEvent", fields: [creatorId], references: [id], onDelete: Cascade)
549
- targetAudience TargetAudience? @relation(fields: [targetAudienceId], references: [id], onDelete: Cascade)
550
- transferredFrom User? @relation("TransferredFrom", fields: [transferredFromId], references: [id])
551
- venue Venue? @relation(fields: [venueId], references: [id])
619
+ amountOfGuests AmountOfGuests? @relation(fields: [amountOfGuestsId], references: [id], onDelete: Cascade)
620
+ club Club? @relation(fields: [clubId], references: [id])
621
+ creator User @relation("CreatedEvent", fields: [creatorId], references: [id], onDelete: Cascade)
622
+ targetAudience TargetAudience? @relation(fields: [targetAudienceId], references: [id], onDelete: Cascade)
623
+ transferredFrom User? @relation("TransferredFrom", fields: [transferredFromId], references: [id])
624
+ venue Venue? @relation(fields: [venueId], references: [id])
552
625
  messages BashEventMessage[]
553
626
  transferRequests BashEventTransferRequest[]
627
+ updateNotificationQueue EventUpdateNotificationQueue[]
554
628
  slugHistory BashSlugHistory[]
555
629
  checkouts Checkout[]
556
630
  competitions Competition[]
557
631
  coordinates Coordinates[]
558
632
  links EventLink[]
559
633
  eventTasks EventTask[]
560
- exhibitorBookingRequests ExhibitorBookingRequest[] @relation("ExhibitorBookingEvent")
634
+ exhibitorBookingRequests ExhibitorBookingRequest[] @relation("ExhibitorBookingEvent")
561
635
  investments Investment[]
562
636
  invitations Invitation[]
563
637
  notificationsReferencingMe Notification[]
638
+ sentReminders SentReminder[]
564
639
  recurrence Recurrence?
565
640
  reviews Review[]
566
641
  serviceBookings ServiceBooking[]
567
- sponsorBookingRequests SponsorBookingRequest[] @relation("SponsorBookingEvent")
642
+ sponsorBookingRequests SponsorBookingRequest[] @relation("SponsorBookingEvent")
568
643
  sponsorships SponsoredEvent[]
569
644
  tickets Ticket[]
570
645
  ticketTiers TicketTier[]
571
646
  favoritedBy UserFavorite[]
572
647
  userPromoCodeRedemption UserPromoCodeRedemption[]
573
648
  userReport UserReport[]
574
- vendorBookingRequests VendorBookingRequest[] @relation("VendorBookingEvent")
575
- media Media[] @relation("BashEventToMedia")
576
- associatedServicesReferencingMe Service[] @relation("BashEventToService")
649
+ vendorBookingRequests VendorBookingRequest[] @relation("VendorBookingEvent")
650
+ media Media[] @relation("BashEventToMedia")
651
+ associatedServicesReferencingMe Service[] @relation("BashEventToService")
577
652
  userConnections UserConnection[]
578
653
  aiRecommendationLogs AIRecommendationLog[]
579
654
  aiRecommendationCaches AIRecommendationCache[]
580
655
  // Template tracking
581
656
  templateId String?
582
- template BashEventTemplate? @relation("CreatedFromTemplate", fields: [templateId], references: [id])
657
+ template BashEventTemplate? @relation("CreatedFromTemplate", fields: [templateId], references: [id])
583
658
  // Recurring event fields (for child occurrences)
584
- isRecurringChild Boolean @default(false)
659
+ isRecurringChild Boolean @default(false)
585
660
  parentEventId String?
586
- parentEvent BashEvent? @relation("RecurringEventChildren", fields: [parentEventId], references: [id], onDelete: SetNull)
587
- childEvents BashEvent[] @relation("RecurringEventChildren")
661
+ parentEvent BashEvent? @relation("RecurringEventChildren", fields: [parentEventId], references: [id], onDelete: SetNull)
662
+ childEvents BashEvent[] @relation("RecurringEventChildren")
588
663
  occurrenceIndex Int? // 1, 2, 3... for child events
589
664
 
590
665
  // BashFeed Relations
@@ -592,6 +667,9 @@ model BashEvent {
592
667
  bashFeedRatings BashFeedRating[]
593
668
  eventRankings EventRankings?
594
669
 
670
+ // Analytics Relations
671
+ analyticsPredictions AnalyticsPrediction[]
672
+
595
673
  @@index([templateId])
596
674
  @@index([parentEventId])
597
675
  }
@@ -605,14 +683,15 @@ model Coordinates {
605
683
  }
606
684
 
607
685
  model Checkout {
608
- id String @id @default(cuid())
686
+ id String @id @default(cuid())
609
687
  ownerId String
610
688
  bashEventId String
611
- checkoutDateTime DateTime? @default(now())
612
- stripeCheckoutSessionId String? @unique
613
- bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
614
- owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
689
+ checkoutDateTime DateTime? @default(now())
690
+ stripeCheckoutSessionId String? @unique
691
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
692
+ owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
615
693
  tickets Ticket[]
694
+ appliedDiscounts AppliedDiscount[] // NEW - Discounts applied to this checkout
616
695
 
617
696
  @@index([bashEventId])
618
697
  }
@@ -759,12 +838,14 @@ model TicketTier {
759
838
  tickets Ticket[]
760
839
  bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
761
840
  waitList User[] @relation("TicketTierToUser")
841
+ specialOffers SpecialOffer[] // NEW - Special offers for this tier
842
+ appliedDiscounts AppliedDiscount[] // NEW - Discounts applied to this tier
762
843
 
763
844
  @@unique([bashEventId, title])
764
845
  }
765
846
 
766
847
  model Ticket {
767
- id String @id @default(cuid())
848
+ id String @id @default(cuid())
768
849
  ownerId String
769
850
  bashEventId String
770
851
  ticketTierId String?
@@ -774,35 +855,36 @@ model Ticket {
774
855
  email String?
775
856
  paidOn DateTime?
776
857
  allowPromiseToPay Boolean?
777
- status TicketStatus @default(Pending)
858
+ status TicketStatus @default(Pending)
778
859
  isFreeGuest Boolean?
779
860
  geoFenceCheckInUnnecessary Boolean?
780
861
  checkedInAt DateTime?
781
862
  checkedOutAt DateTime?
782
863
  invitationId String?
783
864
  checkoutId String?
784
- checkedInMethod String? @default("manual")
865
+ checkedInMethod String? @default("manual")
785
866
  waitlistPosition Int?
786
867
  waitlistJoinedAt DateTime?
787
868
  waitlistNotifiedAt DateTime?
788
869
  waitlistExpiresAt DateTime?
789
870
  checkInLocation String?
790
871
  checkOutLocation String?
791
- checkOutMethod String? @default("manual")
872
+ checkOutMethod String? @default("manual")
792
873
  lastLocationUpdate DateTime?
793
- validationAttempts Int @default(0)
874
+ validationAttempts Int @default(0)
794
875
  lastValidationAttempt DateTime?
795
876
  lastValidatedBy String?
796
877
  waitlistUserId String?
797
- bashEvent BashEvent @relation(fields: [bashEventId], references: [id])
798
- checkout Checkout? @relation(fields: [checkoutId], references: [id])
799
- forUser User? @relation("TicketsISent", fields: [forUserId], references: [id])
800
- invitation Invitation? @relation("TicketsForInvitation", fields: [invitationId], references: [id])
801
- owner User @relation("TicketsIOwn", fields: [ownerId], references: [id])
802
- ticketTier TicketTier? @relation(fields: [ticketTierId], references: [id])
803
- waitlistUser User? @relation("TicketsOnWaitlist", fields: [waitlistUserId], references: [id])
878
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id])
879
+ checkout Checkout? @relation(fields: [checkoutId], references: [id])
880
+ forUser User? @relation("TicketsISent", fields: [forUserId], references: [id])
881
+ invitation Invitation? @relation("TicketsForInvitation", fields: [invitationId], references: [id])
882
+ owner User @relation("TicketsIOwn", fields: [ownerId], references: [id])
883
+ ticketTier TicketTier? @relation(fields: [ticketTierId], references: [id])
884
+ waitlistUser User? @relation("TicketsOnWaitlist", fields: [waitlistUserId], references: [id])
804
885
  metadata TicketMetadata[]
805
886
  transfers TicketTransfer[]
887
+ appliedDiscounts AppliedDiscount[] // NEW - Discounts applied to this ticket
806
888
 
807
889
  @@index([bashEventId])
808
890
  @@index([waitlistUserId])
@@ -847,10 +929,14 @@ model Recurrence {
847
929
  interval Int @default(1)
848
930
  bashEventId String @unique
849
931
  frequency RecurringFrequency @default(Never)
850
- ends DateTime @default(now())
932
+ ends DateTime? // Nullable - used when endsType = ON_DATE
933
+ repeatCount Int? // Used when endsType = AFTER_COUNT
934
+ endsType RecurrenceEndsType @default(ON_DATE)
851
935
  repeatOnDays DayOfWeek[]
852
936
  repeatOnDayOfMonth Int?
853
937
  repeatYearlyDate DateTime?
938
+ createdAt DateTime @default(now())
939
+ updatedAt DateTime @updatedAt
854
940
  bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
855
941
  }
856
942
 
@@ -1253,6 +1339,7 @@ model User {
1253
1339
  promoterStats PromoterStats?
1254
1340
  remindersCreatedByMe Reminder[] @relation("RemindersCreatedByMe")
1255
1341
  remindersAssignedToMe Reminder[] @relation("RemindersAssignedToMe")
1342
+ sentReminders SentReminder[]
1256
1343
  reviews Review[]
1257
1344
  firstFreeListingId String?
1258
1345
  createdServices Service[] @relation("CreatedService")
@@ -1313,6 +1400,9 @@ model User {
1313
1400
  reviewedCategoryRequests ServiceCategoryRequest[] @relation("ReviewedCategoryRequests")
1314
1401
  bookingCommissionsEarned BookingCommission[] @relation("CommissionReferrer")
1315
1402
 
1403
+ // Special Offers - Discount Attribution
1404
+ discountAttributions AppliedDiscount[] @relation("DiscountAttribution")
1405
+
1316
1406
  // BashCash Referral Relations
1317
1407
  referredBy User? @relation("Referrals", fields: [referredById], references: [id])
1318
1408
  referrals User[] @relation("Referrals")
@@ -1397,40 +1487,46 @@ model UserPreferences {
1397
1487
  preferredCurrency String @default("USD")
1398
1488
  savePaymentInfo Boolean @default(false)
1399
1489
  showEventPrices Boolean @default(true)
1400
- createdAt DateTime @default(now())
1401
- updatedAt DateTime @updatedAt
1402
- smsNotifications Boolean @default(false)
1403
- friendRequestNotify Boolean @default(true)
1404
- allowTagging Boolean @default(true)
1405
- showActivityStatus Boolean @default(true)
1406
- hiddenBashIds String[] @default([])
1407
- hideActivitySection Boolean @default(false)
1408
- allowLocationSharing Boolean @default(false)
1409
- defaultLandingPage String @default("dashboard")
1410
- contentDensity String @default("COMFORTABLE")
1411
- fontScale String @default("MEDIUM")
1412
- animationsEnabled Boolean @default(true)
1413
- useHighContrastMode Boolean @default(false)
1414
- contentFilters String[] @default([])
1415
- topicInterests String[] @default([])
1416
- hideSeenContent Boolean @default(false)
1417
- autoplayVideos Boolean @default(true)
1418
- contentSortPreference String @default("RECENT")
1419
- contentLanguages String[] @default(["en"])
1420
- showSensitiveContent Boolean @default(false)
1421
- defaultEventReminder Int @default(60)
1422
- communicationFrequency String @default("NORMAL")
1423
- preferredContactMethod String @default("EMAIL")
1424
- googleCalendarSyncEnabled Boolean @default(false)
1425
- googleCalendarEventsSync Boolean @default(false)
1426
- googleCalendarServicesSync Boolean @default(false)
1427
- localEventsToCalendarSync Boolean @default(false)
1428
- localEventsCity String @default("")
1429
- localEventsState String @default("")
1430
- showRankings Boolean @default(true) // Show rankings on profile
1431
- showAttendeeRank Boolean @default(true) // Show attendee rank publicly
1432
- showHostRank Boolean @default(true) // Show host rank publicly
1433
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1490
+
1491
+ // BashFeed Preferences
1492
+ bashFeedDefaultTab String @default("all") // "all", "upcoming", "live", "past", "saved", "my-posts"
1493
+ bashFeedDefaultSort String @default("chronological") // "for_you", "chronological", "trending", etc.
1494
+ bashFeedFilters Json? // Saved filter preferences as JSON
1495
+
1496
+ createdAt DateTime @default(now())
1497
+ updatedAt DateTime @updatedAt
1498
+ smsNotifications Boolean @default(false)
1499
+ friendRequestNotify Boolean @default(true)
1500
+ allowTagging Boolean @default(true)
1501
+ showActivityStatus Boolean @default(true)
1502
+ hiddenBashIds String[] @default([])
1503
+ hideActivitySection Boolean @default(false)
1504
+ allowLocationSharing Boolean @default(false)
1505
+ defaultLandingPage String @default("dashboard")
1506
+ contentDensity String @default("COMFORTABLE")
1507
+ fontScale String @default("MEDIUM")
1508
+ animationsEnabled Boolean @default(true)
1509
+ useHighContrastMode Boolean @default(false)
1510
+ contentFilters String[] @default([])
1511
+ topicInterests String[] @default([])
1512
+ hideSeenContent Boolean @default(false)
1513
+ autoplayVideos Boolean @default(true)
1514
+ contentSortPreference String @default("RECENT")
1515
+ contentLanguages String[] @default(["en"])
1516
+ showSensitiveContent Boolean @default(false)
1517
+ defaultEventReminder Int @default(60)
1518
+ communicationFrequency String @default("NORMAL")
1519
+ preferredContactMethod String @default("EMAIL")
1520
+ googleCalendarSyncEnabled Boolean @default(false)
1521
+ googleCalendarEventsSync Boolean @default(false)
1522
+ googleCalendarServicesSync Boolean @default(false)
1523
+ localEventsToCalendarSync Boolean @default(false)
1524
+ localEventsCity String @default("")
1525
+ localEventsState String @default("")
1526
+ showRankings Boolean @default(true) // Show rankings on profile
1527
+ showAttendeeRank Boolean @default(true) // Show attendee rank publicly
1528
+ showHostRank Boolean @default(true) // Show host rank publicly
1529
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1434
1530
  }
1435
1531
 
1436
1532
  model UserFollowing {
@@ -2359,6 +2455,7 @@ model ServiceBooking {
2359
2455
  messages ServiceBookingMessage[]
2360
2456
  packages ServiceBookingPackage[]
2361
2457
  commissions BookingCommission[] @relation("BookingCommissions")
2458
+ sentReminders SentReminder[]
2362
2459
 
2363
2460
  @@index([status])
2364
2461
  @@index([creatorId])
@@ -2717,6 +2814,24 @@ model BashEventTransferRequest {
2717
2814
  @@index([fromUserId])
2718
2815
  }
2719
2816
 
2817
+ model EventUpdateNotificationQueue {
2818
+ id String @id @default(cuid())
2819
+ bashEventId String
2820
+ scheduledFor DateTime // When to send the notification (5 minutes after last change)
2821
+ changes Json // Array of change objects: [{ field, oldValue, newValue, description }]
2822
+ oldEventSnapshot Json // Complete snapshot of event before any changes
2823
+ createdAt DateTime @default(now())
2824
+ updatedAt DateTime @updatedAt
2825
+ status String @default("pending") // pending, sent, cancelled
2826
+ sentAt DateTime?
2827
+ errorMessage String?
2828
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
2829
+
2830
+ @@index([bashEventId])
2831
+ @@index([scheduledFor, status])
2832
+ @@index([status])
2833
+ }
2834
+
2720
2835
  model BlogPost {
2721
2836
  id String @id @default(cuid())
2722
2837
  title String
@@ -3321,6 +3436,21 @@ enum JudgingType {
3321
3436
  HOST_CHOOSES // Host/organizer picks winner
3322
3437
  FIRST_COME // First X people win
3323
3438
  HIGHEST_BID // Auction-style
3439
+ BRACKET_TOURNAMENT // Winner determined through tournament bracket progression
3440
+ }
3441
+
3442
+ enum BracketType {
3443
+ NONE // No bracket (voting/judging only)
3444
+ SINGLE_ELIMINATION // Lose once, you're out
3445
+ DOUBLE_ELIMINATION // Lose twice, you're out
3446
+ ROUND_ROBIN // Everyone plays everyone
3447
+ SWISS // Pairing based on performance
3448
+ }
3449
+
3450
+ enum RecurrenceEndsType {
3451
+ NEVER // Never ends
3452
+ ON_DATE // Ends on specific date
3453
+ AFTER_COUNT // Ends after X occurrences
3324
3454
  }
3325
3455
 
3326
3456
  enum Sex {
@@ -5229,3 +5359,203 @@ enum EventFormat {
5229
5359
  Virtual
5230
5360
  Hybrid
5231
5361
  }
5362
+
5363
+ // ============================================
5364
+ // SPECIAL OFFERS & UNIFIED DISCOUNT SYSTEM
5365
+ // ============================================
5366
+
5367
+ enum DiscountSourceType {
5368
+ PROMO_CODE
5369
+ SPECIAL_OFFER
5370
+ PLATFORM_CREDIT // For future BashCash integration
5371
+ MANUAL_OVERRIDE // Admin adjustments
5372
+ }
5373
+
5374
+ enum DiscountScope {
5375
+ ORDER // Applied to entire order
5376
+ LINE_ITEM // Applied to specific line item
5377
+ TICKET_TIER // Applied to specific ticket tier
5378
+ }
5379
+
5380
+ enum SpecialOfferType {
5381
+ BOGO // Buy One Get One
5382
+ GROUP_DISCOUNT // Group rates
5383
+ EARLY_BIRD // Early bird pricing
5384
+ FLASH_SALE // Time-limited offer
5385
+ BUNDLE // Package deal
5386
+ REFERRAL // Friend referral (needs attribution)
5387
+ VOLUME_DISCOUNT // Buy more save more
5388
+ FAMILY_PACK // Family package
5389
+ LOYALTY // Repeat customer
5390
+ COUNTDOWN // Urgency timer
5391
+ }
5392
+
5393
+ enum OfferDiscountType {
5394
+ PERCENTAGE // X% off
5395
+ FIXED_AMOUNT // $X off
5396
+ FREE_ITEMS // Buy X get Y free
5397
+ UPGRADED_TIER // Free upgrade to VIP
5398
+ }
5399
+
5400
+ // Unified discount tracking - captures ALL discounts at checkout
5401
+ model AppliedDiscount {
5402
+ id String @id @default(cuid())
5403
+ createdAt DateTime @default(now())
5404
+
5405
+ // What discount was applied
5406
+ sourceType DiscountSourceType
5407
+ sourceId String // ID of PromoCode or SpecialOffer
5408
+ sourceName String // "SUMMER20" or "Early Bird Special"
5409
+
5410
+ // Scope and target
5411
+ scope DiscountScope
5412
+ checkoutId String?
5413
+ ticketId String?
5414
+ ticketTierId String?
5415
+
5416
+ // Discount details
5417
+ discountType String // "PERCENTAGE" or "FIXED_AMOUNT"
5418
+ discountValue Int // Percentage or cents
5419
+ amountDiscounted Int // Actual discount applied in cents
5420
+ originalPrice Int // Price before discount
5421
+ finalPrice Int // Price after discount
5422
+
5423
+ // Attribution (optional - for PromoCodes and Referral offers)
5424
+ hasAttribution Boolean @default(false)
5425
+ attributionUserId String? // Promoter/Influencer/Referrer
5426
+ attributionCampaignId String?
5427
+ commissionRate Float?
5428
+ commissionAmount Int? // In cents
5429
+
5430
+ // Display
5431
+ displayReason String? // "Early Bird Discount" for receipt
5432
+ internalNote String? // For support/debugging
5433
+
5434
+ // Relations
5435
+ checkout Checkout? @relation(fields: [checkoutId], references: [id])
5436
+ ticket Ticket? @relation(fields: [ticketId], references: [id])
5437
+ ticketTier TicketTier? @relation(fields: [ticketTierId], references: [id])
5438
+ attributionUser User? @relation("DiscountAttribution", fields: [attributionUserId], references: [id])
5439
+
5440
+ @@index([checkoutId])
5441
+ @@index([sourceType, sourceId])
5442
+ @@index([attributionUserId])
5443
+ @@index([createdAt])
5444
+ }
5445
+
5446
+ // Special offers - automatic, visible, conversion-oriented deals
5447
+ model SpecialOffer {
5448
+ id String @id @default(cuid())
5449
+ createdAt DateTime @default(now())
5450
+ updatedAt DateTime @updatedAt
5451
+
5452
+ // Basic Info
5453
+ ticketTierId String
5454
+ offerType SpecialOfferType
5455
+ title String // "Early Bird Special", "BOGO Deal"
5456
+ description String? @db.Text
5457
+ isActive Boolean @default(true)
5458
+ displayBadge Boolean @default(true)
5459
+ badgeText String? // "50% OFF", "BOGO"
5460
+
5461
+ // Discount Configuration
5462
+ discountType OfferDiscountType
5463
+ discountValue Int // Percentage or cents
5464
+
5465
+ // BOGO Configuration
5466
+ buyQuantity Int? // Buy X tickets
5467
+ getQuantity Int? // Get Y tickets
5468
+ getDiscountPercent Int? // Get Y at Z% off (0-100)
5469
+
5470
+ // Group Discount Configuration
5471
+ minGroupSize Int?
5472
+ maxGroupSize Int?
5473
+ tieredDiscounts Json? // [{qty: 5, discount: 10}, {qty: 10, discount: 20}]
5474
+
5475
+ // Time-Based Configuration
5476
+ startDate DateTime?
5477
+ endDate DateTime?
5478
+ availableUntil DateTime? // Early bird cutoff
5479
+
5480
+ // Quantity Limitations
5481
+ maxRedemptions Int?
5482
+ maxPerUser Int?
5483
+ limitedQuantity Int?
5484
+ currentRedemptions Int @default(0)
5485
+
5486
+ // Social Proof Tracking
5487
+ viewCount Int @default(0) // How many times this offer was viewed
5488
+ clickCount Int @default(0) // How many times users clicked "Apply"
5489
+ lastRedemptionAt DateTime? // When was the last redemption
5490
+
5491
+ // Price Increase (Countdown)
5492
+ priceIncreasesAt DateTime?
5493
+ newPriceAfterIncrease Int?
5494
+ countdownEnabled Boolean @default(false)
5495
+
5496
+ // Bundle Configuration
5497
+ bundledItems Json?
5498
+ bundleSavings Int?
5499
+
5500
+ // Attribution (ONLY for Referral type offers)
5501
+ requiresAttribution Boolean @default(false)
5502
+ referralDiscountForBuyer Int?
5503
+ referralDiscountForFriend Int?
5504
+ commissionRate Float? // For referrer
5505
+
5506
+ // Loyalty Configuration
5507
+ requiresEventAttendance Int?
5508
+ hostSpecificLoyalty Boolean @default(false)
5509
+
5510
+ // Stacking Rules
5511
+ canStackWithPromoCodes Boolean @default(true) // Host decides
5512
+ canStackWithOtherOffers Boolean @default(false)
5513
+ priority Int @default(0) // Higher = applied first
5514
+
5515
+ // Display & Marketing
5516
+ urgencyMessage String? @db.Text
5517
+ showSocialProof Boolean @default(false)
5518
+ highlightColor String?
5519
+ sortOrder Int @default(0)
5520
+
5521
+ // Guardrails
5522
+ minPriceFloor Int? // Never discount below this
5523
+ maxDiscountPercent Int? // Never discount more than X%
5524
+
5525
+ // Relations
5526
+ ticketTier TicketTier @relation(fields: [ticketTierId], references: [id], onDelete: Cascade)
5527
+
5528
+ @@index([ticketTierId])
5529
+ @@index([offerType])
5530
+ @@index([isActive])
5531
+ @@index([endDate])
5532
+ }
5533
+
5534
+ // Analytics Prediction Cache Model
5535
+ model AnalyticsPrediction {
5536
+ id String @id @default(cuid())
5537
+ bashEventId String
5538
+ type PredictionType
5539
+ source PredictionSource
5540
+ prediction Json
5541
+ confidence Float
5542
+ generatedAt DateTime @default(now())
5543
+ validUntil DateTime
5544
+
5545
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
5546
+
5547
+ @@index([bashEventId, type])
5548
+ @@index([validUntil])
5549
+ }
5550
+
5551
+ enum PredictionType {
5552
+ REVENUE_FORECAST
5553
+ PRICING_OPTIMIZATION
5554
+ SELLOUT_PROBABILITY
5555
+ ACTION_RECOMMENDATION
5556
+ }
5557
+
5558
+ enum PredictionSource {
5559
+ RULE_BASED
5560
+ AI_ENHANCED
5561
+ }