@bash-app/bash-common 30.117.0 → 30.118.1

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 (31) hide show
  1. package/dist/extendedSchemas.d.ts +54 -0
  2. package/dist/extendedSchemas.d.ts.map +1 -1
  3. package/dist/extendedSchemas.js +10 -1
  4. package/dist/extendedSchemas.js.map +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/utils/__tests__/paymentUtils.test.d.ts +6 -0
  9. package/dist/utils/__tests__/paymentUtils.test.d.ts.map +1 -0
  10. package/dist/utils/__tests__/paymentUtils.test.js +77 -0
  11. package/dist/utils/__tests__/paymentUtils.test.js.map +1 -0
  12. package/dist/utils/discountEngine/__tests__/bestPriceResolver.test.d.ts +2 -0
  13. package/dist/utils/discountEngine/__tests__/bestPriceResolver.test.d.ts.map +1 -0
  14. package/dist/utils/discountEngine/__tests__/bestPriceResolver.test.js +457 -0
  15. package/dist/utils/discountEngine/__tests__/bestPriceResolver.test.js.map +1 -0
  16. package/dist/utils/discountEngine/__tests__/eligibilityValidator.test.d.ts +2 -0
  17. package/dist/utils/discountEngine/__tests__/eligibilityValidator.test.d.ts.map +1 -0
  18. package/dist/utils/discountEngine/__tests__/eligibilityValidator.test.js +480 -0
  19. package/dist/utils/discountEngine/__tests__/eligibilityValidator.test.js.map +1 -0
  20. package/package.json +2 -2
  21. package/prisma/COMPREHENSIVE-MIGRATION-README.md +295 -0
  22. package/prisma/MIGRATION-FILES-GUIDE.md +76 -0
  23. package/prisma/comprehensive-migration-20260120.sql +5751 -0
  24. package/prisma/delta-migration-20260120.sql +302 -0
  25. package/prisma/schema.prisma +486 -147
  26. package/prisma/verify-migration.sql +132 -0
  27. package/src/extendedSchemas.ts +10 -1
  28. package/src/index.ts +4 -0
  29. package/src/utils/__tests__/paymentUtils.test.ts +95 -0
  30. package/src/utils/discountEngine/__tests__/bestPriceResolver.test.ts +558 -0
  31. package/src/utils/discountEngine/__tests__/eligibilityValidator.test.ts +655 -0
@@ -16,6 +16,7 @@ model Club {
16
16
  events BashEvent[]
17
17
  admin ClubAdmin[]
18
18
  members ClubMember[]
19
+ budgets Budget[] @relation("ClubBudgets")
19
20
  }
20
21
 
21
22
  model ClubAdmin {
@@ -56,7 +57,8 @@ model BashComment {
56
57
  model BashFeedPost {
57
58
  id String @id @default(cuid())
58
59
  userId String
59
- bashId String
60
+ bashId String?
61
+ serviceId String?
60
62
  content String? @db.Text
61
63
  mediaIds String[] // Array of Media IDs
62
64
  mentionedUserIds String[] // Array of User IDs mentioned in post
@@ -71,7 +73,8 @@ model BashFeedPost {
71
73
 
72
74
  // Relationships
73
75
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
74
- bash BashEvent @relation(fields: [bashId], references: [id], onDelete: Cascade)
76
+ bash BashEvent? @relation(fields: [bashId], references: [id], onDelete: Cascade)
77
+ service Service? @relation(fields: [serviceId], references: [id], onDelete: Cascade)
75
78
  likes BashFeedLike[]
76
79
  comments BashFeedComment[]
77
80
  ratings BashFeedRating[]
@@ -80,6 +83,7 @@ model BashFeedPost {
80
83
  reposts BashFeedRepost[]
81
84
 
82
85
  @@index([bashId])
86
+ @@index([serviceId])
83
87
  @@index([userId])
84
88
  @@index([createdAt])
85
89
  @@index([isAnonymous])
@@ -338,6 +342,7 @@ model EventTask {
338
342
  neededCount Int? // Number of volunteers needed
339
343
  estimatedDuration Int? // Estimated duration in minutes
340
344
  createdAt DateTime? @default(now())
345
+ organizationId String? // Organization association
341
346
  assignedTo User? @relation("TasksAssignedToMe", fields: [assignedToId], references: [id], onDelete: Cascade)
342
347
  bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
343
348
  creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade)
@@ -547,89 +552,99 @@ model Invitation {
547
552
  }
548
553
 
549
554
  model BashEvent {
550
- id String @id @default(cuid())
551
- source BashEventSource @default(Bash)
552
- title String
553
- slug String? @unique
554
- creatorId String
555
- createdAt DateTime? @default(now())
556
- isApproved Boolean? @default(false)
557
- description String?
558
- eventType String @default("Other")
559
- timezone String?
560
- startDateTime DateTime?
561
- endDateTime DateTime?
562
- waiverUrl String?
563
- waiverRequired Boolean @default(false)
564
- waiverDisplayType String? @default("inline")
565
- targetAudienceId String? @unique
566
- amountOfGuestsId String? @unique
567
- vibe String?
568
- occasion String?
569
- dress String?
570
- allowed String?
571
- notAllowed String?
572
- eventFormat EventFormat? // Derived from location and videoLink: In-Person, Virtual, or Hybrid
573
- nonProfit Boolean?
574
- nonProfitId String?
575
- privacy Privacy @default(Public)
576
- capacity Int?
577
- location String?
578
- street String?
579
- city String?
580
- state String?
581
- zipCode String?
582
- country String?
583
- status BashStatus @default(Draft)
584
- tags String[]
585
- coverPhoto String?
586
- clubId String?
587
- dateTimePublished DateTime?
588
- includedItems String[]
589
- videoLink String?
590
- subtitle String?
591
- isFeatured Boolean?
592
- isTrending Boolean?
593
- venueId String?
594
- averageRating Float? @default(0)
595
- totalRatings Int? @default(0) // Total count of all ratings (Review + BashFeedRating)
596
- totalReviews Int? @default(0) // Count of Review records (anonymous quick ratings)
597
- totalFeedRatings Int? @default(0) // Count of BashFeedRating records (from posts)
598
- allowDonations Boolean? @default(true)
599
- suggestedDonationAmount Int?
600
- donationDetails String?
601
- absorbDonationFees Boolean @default(false)
602
- absorbTicketFees Boolean @default(false)
603
- showAttendees Boolean @default(true)
604
- servicePreferences Json? // New tiered status: { "Entertainment": "need", "Sponsors": "booked_closed", ... }
605
- bookedServices Json? // Booked service details: { "Entertainment": { serviceId: "...", providerId: "...", bookedAt: "..." }, ... }
606
- serviceVisibility Json? // DEPRECATED: Use servicePreferences instead. Kept for backward compatibility during migration.
607
- originalCreatorId String?
608
- transferredAt DateTime?
609
- transferredFromId String?
610
- transferCount Int @default(0)
611
- startTimeLocked Boolean @default(false)
612
- p2pPaymentMethod String?
613
- venmoUsername String?
614
- venmoQRCodeUrl String?
615
- zelleEmail String?
616
- zellePhone String?
617
- zelleQRCodeUrl String?
618
- externalTicketUrl String? // URL to external ticket purchase page (for imported events)
619
- externalPriceMin Int? // Minimum ticket price in cents (for imported events)
620
- externalPriceMax Int? // Maximum ticket price in cents (for imported events)
621
- externalPriceSummary String? // Human-readable price summary e.g., "$50 - $150" or "$75"
622
- customTermsAndConditions String?
623
- itinerary Json? // Array of { id: string, title: string, startTime: string, endTime: string, description?: string }
624
-
555
+ id String @id @default(cuid())
556
+ source BashEventSource @default(Bash)
557
+ title String
558
+ slug String? @unique
559
+ creatorId String
560
+ createdAt DateTime? @default(now())
561
+ isApproved Boolean? @default(false)
562
+ description String?
563
+ eventType String @default("Other")
564
+ timezone String?
565
+ startDateTime DateTime?
566
+ endDateTime DateTime?
567
+ waiverUrl String?
568
+ waiverRequired Boolean @default(false)
569
+ waiverDisplayType String? @default("inline")
570
+ targetAudienceId String? @unique
571
+ amountOfGuestsId String? @unique
572
+ vibe String?
573
+ occasion String?
574
+ dress String?
575
+ allowed String?
576
+ notAllowed String?
577
+ eventFormat EventFormat? // Derived from location and videoLink: In-Person, Virtual, or Hybrid
578
+ nonProfit Boolean?
579
+ nonProfitId String?
580
+ privacy Privacy @default(Public)
581
+ capacity Int?
582
+ location String?
583
+ street String?
584
+ city String?
585
+ state String?
586
+ zipCode String?
587
+ country String?
588
+ status BashStatus @default(Draft)
589
+ tags String[]
590
+ coverPhoto String?
591
+ clubId String?
592
+ dateTimePublished DateTime?
593
+ includedItems String[]
594
+ videoLink String?
595
+ subtitle String?
596
+ isFeatured Boolean?
597
+ isTrending Boolean?
598
+ venueId String?
599
+ averageRating Float? @default(0)
600
+ totalRatings Int? @default(0) // Total count of all ratings (Review + BashFeedRating)
601
+ totalReviews Int? @default(0) // Count of Review records (anonymous quick ratings)
602
+ totalFeedRatings Int? @default(0) // Count of BashFeedRating records (from posts)
603
+ allowDonations Boolean? @default(true)
604
+ suggestedDonationAmount Int?
605
+ donationDetails String?
606
+ absorbDonationFees Boolean @default(false)
607
+ absorbTicketFees Boolean @default(false)
608
+ showAttendees Boolean @default(true)
609
+ servicePreferences Json? // New tiered status: { "Entertainment": "need", "Sponsors": "booked_closed", ... }
610
+ bookedServices Json? // Booked service details: { "Entertainment": { serviceId: "...", providerId: "...", bookedAt: "..." }, ... }
611
+ serviceVisibility Json? // DEPRECATED: Use servicePreferences instead. Kept for backward compatibility during migration.
612
+ originalCreatorId String?
613
+ transferredAt DateTime?
614
+ transferredFromId String?
615
+ transferCount Int @default(0)
616
+ startTimeLocked Boolean @default(false)
617
+ p2pPaymentMethod String?
618
+ venmoUsername String?
619
+ venmoQRCodeUrl String?
620
+ zelleEmail String?
621
+ zellePhone String?
622
+ zelleQRCodeUrl String?
623
+ externalTicketUrl String? // URL to external ticket purchase page (for imported events)
624
+ externalPriceMin Int? // Minimum ticket price in cents (for imported events)
625
+ externalPriceMax Int? // Maximum ticket price in cents (for imported events)
626
+ externalPriceSummary String? // Human-readable price summary e.g., "$50 - $150" or "$75"
627
+ customTermsAndConditions String?
628
+ itinerary Json? // Array of { id: string, title: string, startTime: string, endTime: string, description?: string }
629
+
625
630
  // Idea-specific fields (only used when status = 'Idea')
626
- ideaExpiresAt DateTime?
627
- ideaInterestThreshold Int? @default(35)
628
- hideInterestCount Boolean @default(false)
629
- isAutoApprovable Boolean @default(false)
630
- autoApprovedAt DateTime?
631
- lastEngagementAt DateTime? // Track last interest/share for decay
632
-
631
+ ideaExpiresAt DateTime?
632
+ ideaInterestThreshold Int? @default(35)
633
+ hideInterestCount Boolean @default(false)
634
+ isAutoApprovable Boolean @default(false)
635
+ autoApprovedAt DateTime?
636
+ lastEngagementAt DateTime? // Track last interest/share for decay
637
+
638
+ // Organization field
639
+ organizationId String?
640
+ organization Organization? @relation("OrganizationBashEvents", fields: [organizationId], references: [id])
641
+
642
+ // Organization access controls (only apply when organizationId is set)
643
+ requiresOrgMembership Boolean @default(false) // Must be member to attend/RSVP
644
+ allowMemberGuests Boolean @default(false) // Members can bring +1s
645
+ maxGuestsPerMember Int? // Guest limit per member (null = unlimited)
646
+ restrictToOrgDepartments String[] @default([]) // Empty = all departments can see/join
647
+
633
648
  associatedBashesReferencingMe AssociatedBash[]
634
649
  comments BashComment[]
635
650
  amountOfGuests AmountOfGuests? @relation(fields: [amountOfGuestsId], references: [id], onDelete: Cascade)
@@ -682,16 +697,21 @@ model BashEvent {
682
697
  bashFeedPosts BashFeedPost[]
683
698
  bashFeedRatings BashFeedRating[]
684
699
  eventRankings EventRankings?
685
-
700
+
686
701
  // Idea Relations
687
- interests IdeaInterest[]
688
- configurations IdeaConfiguration[]
702
+ interests IdeaInterest[]
703
+ configurations IdeaConfiguration[]
689
704
 
690
705
  // Analytics Relations
691
706
  analyticsPredictions AnalyticsPrediction[]
692
707
 
708
+ // Organization Relations
709
+ budgets Budget[] @relation("EventBudgets")
710
+ rsvps EventRSVP[] @relation("EventRSVPs")
711
+
693
712
  @@index([templateId])
694
713
  @@index([parentEventId])
714
+ @@index([organizationId])
695
715
  }
696
716
 
697
717
  // ============================================
@@ -699,22 +719,22 @@ model BashEvent {
699
719
  // ============================================
700
720
 
701
721
  model IdeaInterest {
702
- id String @id @default(cuid())
722
+ id String @id @default(cuid())
703
723
  bashEventId String
704
724
  email String
705
725
  phone String?
706
- source String? // 'tiktok_bio', 'bashfeed', 'share', 'direct'
707
- ipAddress String? // For fraud prevention
726
+ source String? // 'tiktok_bio', 'bashfeed', 'share', 'direct'
727
+ ipAddress String? // For fraud prevention
708
728
  userAgent String?
709
- convertedToUserId String? // If they later create account
710
- createdAt DateTime @default(now())
711
- updatedAt DateTime @updatedAt
712
-
729
+ convertedToUserId String? // If they later create account
730
+ createdAt DateTime @default(now())
731
+ updatedAt DateTime @updatedAt
732
+
713
733
  // Relationships
714
- bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
715
- user User? @relation(fields: [convertedToUserId], references: [id])
716
- responses IdeaInterestResponse[]
717
-
734
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
735
+ user User? @relation(fields: [convertedToUserId], references: [id])
736
+ responses IdeaInterestResponse[]
737
+
718
738
  @@unique([bashEventId, email])
719
739
  @@index([bashEventId])
720
740
  @@index([email])
@@ -722,38 +742,38 @@ model IdeaInterest {
722
742
  }
723
743
 
724
744
  model IdeaInterestResponse {
725
- id String @id @default(cuid())
745
+ id String @id @default(cuid())
726
746
  interestId String
727
- questionType String // 'base_price', 'configuration_price', 'preferred_date', 'must_have'
728
- questionText String? // For custom host questions
729
- responseValue String // JSON: {min: 20, max: 50} or "Yes" or date string
730
- configurationId String? // Links to specific configuration if applicable
731
- createdAt DateTime @default(now())
732
-
747
+ questionType String // 'base_price', 'configuration_price', 'preferred_date', 'must_have'
748
+ questionText String? // For custom host questions
749
+ responseValue String // JSON: {min: 20, max: 50} or "Yes" or date string
750
+ configurationId String? // Links to specific configuration if applicable
751
+ createdAt DateTime @default(now())
752
+
733
753
  // Relationships
734
- interest IdeaInterest @relation(fields: [interestId], references: [id], onDelete: Cascade)
735
- configuration IdeaConfiguration? @relation(fields: [configurationId], references: [id], onDelete: SetNull)
736
-
754
+ interest IdeaInterest @relation(fields: [interestId], references: [id], onDelete: Cascade)
755
+ configuration IdeaConfiguration? @relation(fields: [configurationId], references: [id], onDelete: SetNull)
756
+
737
757
  @@index([interestId])
738
758
  @@index([configurationId])
739
759
  @@index([questionType])
740
760
  }
741
761
 
742
762
  model IdeaConfiguration {
743
- id String @id @default(cuid())
744
- bashEventId String
745
- name String // "Premium Open Bar", "Food Truck", "Live DJ"
746
- description String?
747
- estimatedCost Int? // In cents - helps host with pricing
748
- isActive Boolean @default(true)
749
- sortOrder Int @default(0)
750
- createdAt DateTime @default(now())
751
- updatedAt DateTime @updatedAt
752
-
763
+ id String @id @default(cuid())
764
+ bashEventId String
765
+ name String // "Premium Open Bar", "Food Truck", "Live DJ"
766
+ description String?
767
+ estimatedCost Int? // In cents - helps host with pricing
768
+ isActive Boolean @default(true)
769
+ sortOrder Int @default(0)
770
+ createdAt DateTime @default(now())
771
+ updatedAt DateTime @updatedAt
772
+
753
773
  // Relationships
754
- bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
755
- responses IdeaInterestResponse[]
756
-
774
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
775
+ responses IdeaInterestResponse[]
776
+
757
777
  @@index([bashEventId])
758
778
  @@index([isActive])
759
779
  }
@@ -925,6 +945,12 @@ model TicketTier {
925
945
  specialOffers SpecialOffer[] // NEW - Special offers for this tier
926
946
  appliedDiscounts AppliedDiscount[] // NEW - Discounts applied to this tier
927
947
 
948
+ // Organization member restrictions (for org events)
949
+ restrictedToRoles String[] @default([]) // ["Admin", "Owner"] - empty = all roles
950
+ restrictedToDepartments String[] @default([]) // [deptId1, deptId2] - empty = all departments
951
+ restrictedToMembershipTiers String[] @default([]) // ["Premium"] - empty = all membership tiers
952
+ isGuestTier Boolean @default(false) // Ticket for member +1s
953
+
928
954
  @@unique([bashEventId, title])
929
955
  }
930
956
 
@@ -1507,9 +1533,26 @@ model User {
1507
1533
  nominationsGiven Nomination[] @relation("NominatingUser")
1508
1534
  votesGiven NominationVote[]
1509
1535
  categoriesCreated NominationCategory[]
1510
-
1536
+
1511
1537
  // Idea Interest Relations
1512
1538
  ideaInterests IdeaInterest[]
1539
+
1540
+ // Organization Relations
1541
+ ownedOrganizations Organization[] @relation("OrganizationOwner")
1542
+ organizationMemberships OrganizationMember[] @relation("OrganizationMemberUser")
1543
+ organizationEventRSVPs EventRSVP[] @relation("OrganizationEventRSVP")
1544
+ submittedExpenses Expense[] @relation("ExpenseSubmitter")
1545
+ approvedExpenses Expense[] @relation("ExpenseApprover")
1546
+ approvedBudgets Budget[] @relation("BudgetApprover")
1547
+ submittedBudgets Budget[] @relation("BudgetSubmitter")
1548
+ ownedBudgets Budget[] @relation("UserBudgets")
1549
+ departmentsHeaded Department[] @relation("DepartmentHead")
1550
+ favoriteOrganizations Organization[] @relation("FavoriteOrganizations")
1551
+
1552
+ // Service Includes & Add-On Requests
1553
+ suggestedIncludes ServiceInclude[] @relation("SuggestedIncludes")
1554
+ includeVotes ServiceIncludeVote[]
1555
+ requestedAddons ServiceAddonRequest[] @relation("RequestedAddons")
1513
1556
  }
1514
1557
 
1515
1558
  model UserPreferences {
@@ -1952,6 +1995,12 @@ model Service {
1952
1995
  monthlyPrice Decimal @default(0)
1953
1996
  serviceListingStripeSubscriptionId String?
1954
1997
  acceptsBashCash Boolean @default(true) // NEW: Allow BashCash payments
1998
+ isVerified Boolean @default(false) // Service owner verified
1999
+ isLicensed Boolean @default(false) // Has required licenses
2000
+ verifiedAt DateTime? // When verification was approved
2001
+ licenseNumber String? // License/registration number
2002
+ certifications String[] @default([]) // Array of certification names
2003
+ bashFeedPosts BashFeedPost[]
1955
2004
  associatedServicesReferencingMe AssociatedService[]
1956
2005
  exhibitorBookingRequests ExhibitorBookingRequest[] @relation("ExhibitorBookingService")
1957
2006
  notification Notification[]
@@ -1984,9 +2033,13 @@ model Service {
1984
2033
  bookingCommissions BookingCommission[] @relation("CommissionServiceProfile")
1985
2034
  venueMatches VenueEntertainmentMatch[] @relation("VenueMatches")
1986
2035
  entertainerMatches VenueEntertainmentMatch[] @relation("EntertainerMatches")
2036
+ includes ServiceInclude[]
2037
+ addonRequests ServiceAddonRequest[]
1987
2038
 
1988
2039
  @@index([serviceListingStripeSubscriptionId])
1989
2040
  @@index([isFreeFirstListing])
2041
+ @@index([isVerified])
2042
+ @@index([isLicensed])
1990
2043
  }
1991
2044
 
1992
2045
  model StripeAccount {
@@ -2318,12 +2371,202 @@ model UserPromoCodeRedemption {
2318
2371
  }
2319
2372
 
2320
2373
  model Organization {
2321
- id String @id @default(cuid())
2322
- organizationType OrganizationType[]
2323
- goodsOrServices String[]
2324
- venueTypes String[]
2325
- secondaryVenueTypes String[] @default([])
2326
- service Service?
2374
+ id String @id @default(cuid())
2375
+ name String
2376
+ slug String @unique
2377
+ organizationType OrganizationType[]
2378
+ bio String?
2379
+ mission String?
2380
+ pocName String?
2381
+ pocEmail String?
2382
+ pocPhone String?
2383
+ street String?
2384
+ city String?
2385
+ state String?
2386
+ zipCode String?
2387
+ country String? @default("US")
2388
+ coverPhoto String?
2389
+ logoUrl String?
2390
+ websiteUrl String?
2391
+ socialLinks Json?
2392
+ goodsOrServices String[]
2393
+ venueTypes String[]
2394
+ secondaryVenueTypes String[] @default([])
2395
+ baseMembershipPriceCents Int?
2396
+ premiumMembershipPriceCents Int?
2397
+ allowGuestsByDefault Boolean @default(false)
2398
+ defaultGuestLimit Int @default(0)
2399
+ visibility String @default("Public")
2400
+ requiresApproval Boolean @default(true)
2401
+ memberAutoApproval Boolean @default(false)
2402
+ ownerId String
2403
+ parentOrganizationId String?
2404
+ departmentName String?
2405
+ hierarchyLevel Int @default(0)
2406
+ subscriptionTier String?
2407
+ subscriptionStatus String?
2408
+ stripeSubscriptionId String? @unique
2409
+ stripeCustomerId String?
2410
+ createdAt DateTime @default(now())
2411
+ updatedAt DateTime @updatedAt
2412
+ service Service?
2413
+ owner User @relation("OrganizationOwner", fields: [ownerId], references: [id], onDelete: Restrict)
2414
+ parentOrganization Organization? @relation("OrganizationHierarchy", fields: [parentOrganizationId], references: [id])
2415
+ childOrganizations Organization[] @relation("OrganizationHierarchy")
2416
+ members OrganizationMember[]
2417
+ budgets Budget[] @relation("OrganizationBudgets")
2418
+ departments Department[]
2419
+ bashEvents BashEvent[] @relation("OrganizationBashEvents")
2420
+ favoriteBy User[] @relation("FavoriteOrganizations")
2421
+
2422
+ @@index([ownerId])
2423
+ @@index([slug])
2424
+ @@index([parentOrganizationId])
2425
+ }
2426
+
2427
+ model OrganizationMember {
2428
+ id String @id @default(cuid())
2429
+ organizationId String
2430
+ userId String
2431
+ role String @default("Member") // Owner, Admin, Coordinator, EventOrganizer, Treasurer, Member
2432
+ membershipTier String @default("Base") // Base, Premium
2433
+ status String @default("Pending") // Pending, Active, Suspended, Removed
2434
+ joinedAt DateTime @default(now())
2435
+ approvedAt DateTime?
2436
+ approvedBy String?
2437
+ paidUntil DateTime?
2438
+ paymentStatus String? @default("Current") // Current, Overdue, Cancelled
2439
+ guestLimit Int?
2440
+ canInvite Boolean @default(true)
2441
+ canCreateEvents Boolean @default(false)
2442
+ canManageBudgets Boolean @default(false)
2443
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
2444
+ user User @relation("OrganizationMemberUser", fields: [userId], references: [id], onDelete: Cascade)
2445
+ eventRSVPs EventRSVP[]
2446
+
2447
+ @@unique([organizationId, userId])
2448
+ @@index([organizationId])
2449
+ @@index([userId])
2450
+ @@index([status])
2451
+ }
2452
+
2453
+ model Department {
2454
+ id String @id @default(cuid())
2455
+ organizationId String
2456
+ name String
2457
+ description String?
2458
+ parentDeptId String?
2459
+ headUserId String?
2460
+ budgetCents Int?
2461
+ createdAt DateTime @default(now())
2462
+ updatedAt DateTime @updatedAt
2463
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
2464
+ parentDept Department? @relation("DepartmentHierarchy", fields: [parentDeptId], references: [id])
2465
+ childDepts Department[] @relation("DepartmentHierarchy")
2466
+ head User? @relation("DepartmentHead", fields: [headUserId], references: [id])
2467
+ budgets Budget[] @relation("DepartmentBudgets")
2468
+
2469
+ @@index([organizationId])
2470
+ @@index([parentDeptId])
2471
+ }
2472
+
2473
+ model EventRSVP {
2474
+ id String @id @default(cuid())
2475
+ eventId String
2476
+ memberId String
2477
+ userId String
2478
+ status String @default("Going") // Going, Maybe, NotGoing
2479
+ guestsCount Int @default(0)
2480
+ checkedIn Boolean @default(false)
2481
+ checkedInAt DateTime?
2482
+ createdAt DateTime @default(now())
2483
+ updatedAt DateTime @updatedAt
2484
+ event BashEvent @relation("EventRSVPs", fields: [eventId], references: [id], onDelete: Cascade)
2485
+ member OrganizationMember @relation(fields: [memberId], references: [id], onDelete: Cascade)
2486
+ user User @relation("OrganizationEventRSVP", fields: [userId], references: [id])
2487
+
2488
+ @@unique([eventId, userId])
2489
+ @@index([eventId])
2490
+ @@index([userId])
2491
+ }
2492
+
2493
+ model Budget {
2494
+ id String @id @default(cuid())
2495
+ name String
2496
+
2497
+ // Flexible ownership - at least ONE should be set
2498
+ organizationId String?
2499
+ bashEventId String?
2500
+ clubId String?
2501
+ departmentId String?
2502
+ userId String? // For personal event budgets
2503
+
2504
+ // Budget details
2505
+ totalAllocatedCents Int
2506
+ totalBudgetCents Int? // Alias for backward compatibility
2507
+ spentCents Int @default(0)
2508
+ remainingCents Int? // Can be computed: totalAllocatedCents - spentCents
2509
+
2510
+ // Categorization
2511
+ category String? // "Marketing", "Catering", "Venue", "Annual", "Activities"
2512
+ fiscalYear String? // Keep as String for flexibility ("2026", "2026-Q1", "FY2026")
2513
+
2514
+ // Approval workflow
2515
+ status String? @default("Draft") // Draft, Submitted, Approved, Active, Closed, Archived
2516
+ submittedBy String?
2517
+ approverId String?
2518
+ approvedBy String? // Alias for approverId
2519
+ approvedAt DateTime?
2520
+
2521
+ // Date ranges
2522
+ startDate DateTime?
2523
+ endDate DateTime?
2524
+
2525
+ // Timestamps
2526
+ createdAt DateTime @default(now())
2527
+ updatedAt DateTime @updatedAt
2528
+
2529
+ // Relations
2530
+ organization Organization? @relation("OrganizationBudgets", fields: [organizationId], references: [id], onDelete: Cascade)
2531
+ bashEvent BashEvent? @relation("EventBudgets", fields: [bashEventId], references: [id], onDelete: Cascade)
2532
+ club Club? @relation("ClubBudgets", fields: [clubId], references: [id], onDelete: Cascade)
2533
+ department Department? @relation("DepartmentBudgets", fields: [departmentId], references: [id], onDelete: Cascade)
2534
+ user User? @relation("UserBudgets", fields: [userId], references: [id], onDelete: Cascade)
2535
+ submitter User? @relation("BudgetSubmitter", fields: [submittedBy], references: [id])
2536
+ approver User? @relation("BudgetApprover", fields: [approverId], references: [id])
2537
+ expenses Expense[]
2538
+
2539
+ @@index([organizationId])
2540
+ @@index([bashEventId])
2541
+ @@index([clubId])
2542
+ @@index([departmentId])
2543
+ @@index([userId])
2544
+ @@index([status])
2545
+ @@index([fiscalYear])
2546
+ }
2547
+
2548
+ model Expense {
2549
+ id String @id @default(cuid())
2550
+ budgetId String
2551
+ submittedBy String
2552
+ description String
2553
+ amountCents Int
2554
+ category String?
2555
+ receiptUrl String?
2556
+ expenseDate DateTime? // When the expense occurred
2557
+ status String @default("Pending") // Pending, Approved, Rejected
2558
+ approvedBy String?
2559
+ approvedAt DateTime?
2560
+ rejectionReason String?
2561
+ createdAt DateTime @default(now())
2562
+ updatedAt DateTime @updatedAt
2563
+ budget Budget @relation(fields: [budgetId], references: [id], onDelete: Cascade)
2564
+ submitter User @relation("ExpenseSubmitter", fields: [submittedBy], references: [id])
2565
+ approver User? @relation("ExpenseApprover", fields: [approvedBy], references: [id])
2566
+
2567
+ @@index([budgetId])
2568
+ @@index([submittedBy])
2569
+ @@index([status])
2327
2570
  }
2328
2571
 
2329
2572
  model ServiceRange {
@@ -2345,11 +2588,87 @@ model ServiceAddon {
2345
2588
  priceCents Int
2346
2589
  quantity Int
2347
2590
  status ServiceAddonStatus @default(Enabled)
2591
+ imageUrl String? // Add-on image URL
2592
+ imageAlt String? // Alt text for accessibility
2348
2593
  servicePackageId String?
2349
2594
  serviceRatesAssociationId String?
2350
2595
  servicePackage ServicePackage? @relation(fields: [servicePackageId], references: [id], onDelete: Cascade)
2351
2596
  serviceRatesAssociation ServiceRatesAssociation? @relation(fields: [serviceRatesAssociationId], references: [id], onDelete: Cascade)
2352
2597
  ServiceBookingAddOn ServiceBookingAddOn[]
2598
+ convertedFromInclude ServiceInclude[] @relation("IncludeConvertedToAddon")
2599
+ convertedFromRequest ServiceAddonRequest[] @relation("RequestConvertedToAddon")
2600
+
2601
+ @@index([imageUrl])
2602
+ }
2603
+
2604
+ model ServiceInclude {
2605
+ id String @id @default(cuid())
2606
+ serviceId String
2607
+ suggestedByUserId String? // Who suggested it (null if anonymous)
2608
+ name String // e.g., "Tables and Chairs"
2609
+ description String? // Optional details
2610
+ status ServiceIncludeStatus @default(Pending)
2611
+ providerResponse ServiceIncludeResponse? // How provider responded
2612
+ responseNote String? // Optional note from provider
2613
+ convertedToAddonId String? // If converted to paid add-on
2614
+ voteCount Int @default(0) // Net votes (upvotes - downvotes)
2615
+ upvoteCount Int @default(0)
2616
+ downvoteCount Int @default(0)
2617
+ isActuallyIncluded Boolean @default(false) // Provider confirmed it IS included
2618
+ createdAt DateTime @default(now())
2619
+ updatedAt DateTime @updatedAt
2620
+
2621
+ service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
2622
+ suggestedBy User? @relation("SuggestedIncludes", fields: [suggestedByUserId], references: [id], onDelete: SetNull)
2623
+ convertedToAddon ServiceAddon? @relation("IncludeConvertedToAddon", fields: [convertedToAddonId], references: [id], onDelete: SetNull)
2624
+ votes ServiceIncludeVote[]
2625
+
2626
+ @@unique([serviceId, name]) // Prevent duplicate suggestions for same service
2627
+ @@index([serviceId])
2628
+ @@index([status])
2629
+ @@index([voteCount])
2630
+ @@index([createdAt])
2631
+ }
2632
+
2633
+ model ServiceIncludeVote {
2634
+ id String @id @default(cuid())
2635
+ serviceIncludeId String
2636
+ userId String
2637
+ voteType VoteType // Upvote or Downvote
2638
+ createdAt DateTime @default(now())
2639
+ updatedAt DateTime @updatedAt
2640
+
2641
+ serviceInclude ServiceInclude @relation(fields: [serviceIncludeId], references: [id], onDelete: Cascade)
2642
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
2643
+
2644
+ @@unique([serviceIncludeId, userId]) // One vote per user per include
2645
+ @@index([serviceIncludeId])
2646
+ @@index([userId])
2647
+ }
2648
+
2649
+ model ServiceAddonRequest {
2650
+ id String @id @default(cuid())
2651
+ serviceId String
2652
+ userId String? // null if anonymous
2653
+ name String // e.g., "Specialty Lighting Package"
2654
+ description String? // Optional details
2655
+ status ServiceAddonRequestStatus @default(Pending)
2656
+ providerNote String? // Provider's response/notes
2657
+ notifyOnAvailable Boolean @default(false) // Customer wants notification
2658
+ convertedToAddonId String? // Links to actual ServiceAddon if created
2659
+ requestCount Int @default(1) // How many customers requested this
2660
+ createdAt DateTime @default(now())
2661
+ updatedAt DateTime @updatedAt
2662
+
2663
+ service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
2664
+ user User? @relation("RequestedAddons", fields: [userId], references: [id], onDelete: SetNull)
2665
+ convertedToAddon ServiceAddon? @relation("RequestConvertedToAddon", fields: [convertedToAddonId], references: [id], onDelete: SetNull)
2666
+
2667
+ @@index([serviceId])
2668
+ @@index([status])
2669
+ @@index([userId])
2670
+ @@index([requestCount])
2671
+ @@index([createdAt])
2353
2672
  }
2354
2673
 
2355
2674
  model ServicePackage {
@@ -3410,7 +3729,7 @@ enum UserIntent {
3410
3729
  }
3411
3730
 
3412
3731
  enum BashStatus {
3413
- Idea // Pre-event idea gauging interest
3732
+ Idea // Pre-event idea gauging interest
3414
3733
  Draft
3415
3734
  Pending
3416
3735
  PreSale
@@ -3729,11 +4048,6 @@ enum AudioVisualSupportSubType {
3729
4048
  ProjectorSetup
3730
4049
  }
3731
4050
 
3732
- enum HostingSupportSubType {
3733
- Emcee
3734
- Greeter
3735
- }
3736
-
3737
4051
  enum PromotionAndMarketingSubType {
3738
4052
  Influencer
3739
4053
  Promoter
@@ -3744,6 +4058,10 @@ enum StaffingSubType {
3744
4058
  CrewHand
3745
4059
  ParkingManagement
3746
4060
  GuestRegistration
4061
+ Greeter
4062
+ Ushers
4063
+ CoatCheck
4064
+ DoorAttendant
3747
4065
  Security
3748
4066
  }
3749
4067
 
@@ -4770,21 +5088,14 @@ enum OrganizationType {
4770
5088
  GreekLife
4771
5089
  SocialClub
4772
5090
  Nonprofit
4773
- Corporation
4774
- EducationalInstitution
5091
+ Other
5092
+ Professional
5093
+ RecordLabel
5094
+ School
5095
+ StudentClub
4775
5096
  Sports
4776
- Government
4777
- Cultural
4778
- Lifestyle
4779
- Political
4780
- Tourism
4781
5097
  Youth
4782
- Senior
4783
- Tradeshow
4784
- Conference
4785
- FoodAndBeverage
4786
- TechAndStartups
4787
- Other
5098
+ Cultural
4788
5099
  }
4789
5100
 
4790
5101
  enum YearsOfExperience {
@@ -4805,6 +5116,34 @@ enum ServiceAddonStatus {
4805
5116
  Disabled
4806
5117
  }
4807
5118
 
5119
+ enum ServiceIncludeStatus {
5120
+ Pending // Customer suggested, awaiting provider review
5121
+ Approved // Provider confirmed it IS included
5122
+ Declined // Provider says not possible/won't include
5123
+ Duplicate // Same as another suggestion
5124
+ }
5125
+
5126
+ enum ServiceIncludeResponse {
5127
+ NotPossible // Can't offer this
5128
+ Added // Now included in base package
5129
+ AlreadyIncluded // Was already included
5130
+ AvailableAsAddOn // Can offer but as paid add-on
5131
+ UnderConsideration // Thinking about it
5132
+ }
5133
+
5134
+ enum VoteType {
5135
+ Upvote
5136
+ Downvote
5137
+ }
5138
+
5139
+ enum ServiceAddonRequestStatus {
5140
+ Pending // New request
5141
+ Acknowledged // Provider saw it
5142
+ Added // Provider created the add-on
5143
+ Declined // Provider won't offer this
5144
+ Duplicate // Same as another request
5145
+ }
5146
+
4808
5147
  enum ServiceBookingFeeType {
4809
5148
  CleaningFee
4810
5149
  ProcessingFee