@bash-app/bash-common 30.226.0 → 30.227.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 (49) hide show
  1. package/dist/aiApproval.d.ts +25 -0
  2. package/dist/aiApproval.d.ts.map +1 -1
  3. package/dist/aiApproval.js +32 -4
  4. package/dist/aiApproval.js.map +1 -1
  5. package/dist/extendedSchemas.d.ts +43 -1
  6. package/dist/extendedSchemas.d.ts.map +1 -1
  7. package/dist/extendedSchemas.js +22 -0
  8. package/dist/extendedSchemas.js.map +1 -1
  9. package/dist/index.d.ts +4 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/utils/__tests__/flyerUtils.test.d.ts +2 -0
  14. package/dist/utils/__tests__/flyerUtils.test.d.ts.map +1 -0
  15. package/dist/utils/__tests__/flyerUtils.test.js +49 -0
  16. package/dist/utils/__tests__/flyerUtils.test.js.map +1 -0
  17. package/dist/utils/flyerUtils.d.ts +42 -0
  18. package/dist/utils/flyerUtils.d.ts.map +1 -0
  19. package/dist/utils/flyerUtils.js +104 -0
  20. package/dist/utils/flyerUtils.js.map +1 -0
  21. package/package.json +1 -1
  22. package/prisma/schema.prisma +319 -50
  23. package/src/aiApproval.ts +118 -0
  24. package/src/extendedSchemas.ts +45 -1
  25. package/src/index.ts +14 -0
  26. package/src/utils/__tests__/flyerUtils.test.ts +70 -0
  27. package/src/utils/flyerUtils.ts +137 -0
  28. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.d.ts +0 -6
  29. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.d.ts.map +0 -1
  30. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.js +0 -104
  31. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.js.map +0 -1
  32. package/dist/utils/service/serviceRateDBUtils.d.ts +0 -1
  33. package/dist/utils/service/serviceRateDBUtils.d.ts.map +0 -1
  34. package/dist/utils/service/serviceRateDBUtils.js +0 -159
  35. package/dist/utils/service/serviceRateDBUtils.js.map +0 -1
  36. package/prisma/migrations/add_bash_availability.sql +0 -53
  37. package/prisma/migrations/add_bashcash_pricing_to_ticket_tier.sql +0 -15
  38. package/prisma/migrations/add_bashpoints_purchase_tracking.sql +0 -33
  39. package/prisma/migrations/add_event_group_allow_auto_join.sql +0 -17
  40. package/prisma/migrations/add_group_member_status_not_going_maybe.sql +0 -5
  41. package/prisma/migrations/add_groups_and_momentum.sql +0 -135
  42. package/prisma/migrations/add_groups_momentum_phase0_4.sql +0 -200
  43. package/prisma/migrations/add_new_feature_tables.sql +0 -140
  44. package/prisma/migrations/add_pricing_type_enum.sql +0 -88
  45. package/prisma/migrations/add_tier_privacy_and_fee_handling.sql +0 -29
  46. package/prisma/migrations/diagnostic_bashcash_columns.sql +0 -157
  47. package/prisma/migrations/fix_bashcash_referral_code_schema_mismatch.sql +0 -81
  48. package/prisma/migrations/rename_bashcash_to_bashpoints.sql +0 -183
  49. package/prisma/migrations/rename_bashcredits_to_bashpoints.sql +0 -96
@@ -393,6 +393,7 @@ model EventTask {
393
393
  notificationsReferencingMe Notification[]
394
394
  comments TaskComment[]
395
395
  invitations TaskInvitation[]
396
+ expenses Expense[]
396
397
  potluckItem PotluckItem? @relation("PotluckItemTask")
397
398
  bashEventSuggestion BashEventSuggestion? @relation("SuggestionTask")
398
399
  }
@@ -693,7 +694,7 @@ model BashEvent {
693
694
  servicePreferences Json? // New tiered status: { "Entertainment": "need", "Sponsors": "booked_closed", ... }
694
695
  bookedServices Json? // Booked service details: { "Entertainment": { serviceId: "...", providerId: "...", bookedAt: "..." }, ... }
695
696
  serviceVisibility Json? // DEPRECATED: Use servicePreferences instead. Kept for backward compatibility during migration.
696
- plannedServiceIds String[] @default([]) // Service IDs the host has flagged as "plan to book" in the wizard
697
+ plannedServiceIds String[] @default([]) // Service IDs the host has flagged as "plan to book" in the wizard
697
698
  originalCreatorId String?
698
699
  transferredAt DateTime?
699
700
  transferredFromId String?
@@ -749,6 +750,9 @@ model BashEvent {
749
750
  maxGuestsPerMember Int? // Guest limit per member (null = unlimited)
750
751
  restrictToOrgDepartments String[] @default([]) // Empty = all departments can see/join
751
752
 
753
+ /// When true, new event expenses default to Pending until host/org approves (see Expense.status).
754
+ requireExpenseApproval Boolean @default(false)
755
+
752
756
  associatedBashesReferencingMe AssociatedBash[]
753
757
  comments BashComment[]
754
758
  amountOfGuests AmountOfGuests? @relation(fields: [amountOfGuestsId], references: [id], onDelete: Cascade)
@@ -848,11 +852,27 @@ model BashEvent {
848
852
 
849
853
  bashEventArtists BashEventArtist[]
850
854
  vanityPaths EventVanityPath[]
855
+ flyerCampaigns FlyerCampaign[]
856
+
857
+ /// Latest AI moderator verdict (see `bashApprovalAiService` + `mapAiResultToDecision`).
858
+ /// Null = never reviewed by AI. Recomputed each time the bash transitions to Pending
859
+ /// or a super-user clicks "Re-run AI review".
860
+ aiApprovalVerdict AiApprovalVerdict?
861
+ aiApprovalConfidence Float?
862
+ /// Structured `AiApprovalReasons` (criteria scores + hostFacingMessage + adminFacingNotes).
863
+ /// Schema lives in `bash-common/src/extendedSchemas.ts`.
864
+ aiApprovalReasons Json?
865
+ aiApprovalReviewedAt DateTime?
866
+ aiApprovalModel String?
867
+ aiApprovalRunCount Int @default(0)
868
+ /// SHA-256 of the snapshot fed to the AI; lets us skip a re-run when nothing changed.
869
+ aiApprovalContentHash String?
851
870
 
852
871
  @@index([templateId])
853
872
  @@index([parentEventId])
854
873
  @@index([organizationId])
855
874
  @@index([eventSeriesId])
875
+ @@index([status, aiApprovalVerdict])
856
876
  }
857
877
 
858
878
  /// Lineup / attribution roster for an event (optional act list for checkout tagging).
@@ -872,6 +892,197 @@ model BashEventArtist {
872
892
  @@index([serviceId])
873
893
  }
874
894
 
895
+ // --- Flyer Blast (USPS EDDM neighborhood mail campaigns) ---
896
+ enum FlyerTargetingMode {
897
+ RADIUS
898
+ CUSTOM
899
+ }
900
+
901
+ enum FlyerFulfillmentType {
902
+ SELF_MAIL
903
+ DONE_FOR_YOU
904
+ }
905
+
906
+ enum FlyerCampaignStatus {
907
+ DRAFT
908
+ ARTWORK_REVIEW
909
+ PAID
910
+ PRINTING
911
+ IN_TRANSIT
912
+ DELIVERED
913
+ CANCELLED
914
+ }
915
+
916
+ enum FlyerSponsorSlot {
917
+ FEATURED
918
+ SUPPORTING_1
919
+ SUPPORTING_2
920
+ }
921
+
922
+ enum FlyerConversionType {
923
+ QR_SCAN
924
+ RSVP
925
+ TICKET_PURCHASE
926
+ SPONSOR_QR_SCAN
927
+ }
928
+
929
+ model FlyerCampaign {
930
+ id String @id @default(cuid())
931
+ bashEventId String
932
+ hostUserId String
933
+
934
+ targetingMode FlyerTargetingMode @default(RADIUS)
935
+ radiusMiles Float?
936
+ centerLat Float?
937
+ centerLng Float?
938
+ customPolygon Json?
939
+ totalHouseholds Int?
940
+
941
+ fulfillmentType FlyerFulfillmentType @default(DONE_FOR_YOU)
942
+
943
+ printPostageCostCents Int?
944
+ bashFeeCents Int?
945
+ sponsorRevenueCents Int?
946
+ hostTotalCostCents Int?
947
+ stripePaymentIntentId String?
948
+ paidAt DateTime?
949
+
950
+ campaignQrSlug String @unique
951
+ campaignPromoCode String?
952
+
953
+ ctaLabel String?
954
+ templateVariant String @default("default")
955
+
956
+ status FlyerCampaignStatus @default(DRAFT)
957
+
958
+ vendorOrderRef String?
959
+ vendorStatus String?
960
+
961
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
962
+ host User @relation(fields: [hostUserId], references: [id], onDelete: Cascade)
963
+ routes FlyerMailingRoute[]
964
+ artworkVersions FlyerArtwork[]
965
+ sponsors FlyerCampaignSponsor[]
966
+ conversions FlyerConversion[]
967
+
968
+ createdAt DateTime @default(now())
969
+ updatedAt DateTime @updatedAt
970
+
971
+ @@index([bashEventId])
972
+ @@index([hostUserId])
973
+ }
974
+
975
+ model FlyerMailingRoute {
976
+ id String @id @default(cuid())
977
+ campaignId String
978
+ campaign FlyerCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
979
+
980
+ crrtCode String
981
+ zipCode String
982
+ city String
983
+ state String @default("UT")
984
+ householdCount Int
985
+
986
+ medianIncomeBand String?
987
+ avgHouseholdSize Float?
988
+ medianAge Float?
989
+
990
+ createdAt DateTime @default(now())
991
+
992
+ @@unique([campaignId, crrtCode, zipCode])
993
+ @@index([campaignId])
994
+ }
995
+
996
+ model FlyerArtwork {
997
+ id String @id @default(cuid())
998
+ campaignId String
999
+ campaign FlyerCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
1000
+
1001
+ version Int
1002
+ s3KeyPrintPdf String?
1003
+ s3KeyPreviewPng String?
1004
+
1005
+ widthIn Float @default(6.25)
1006
+ heightIn Float @default(11)
1007
+ isEddmCompliant Boolean @default(false)
1008
+
1009
+ createdAt DateTime @default(now())
1010
+
1011
+ @@index([campaignId])
1012
+ }
1013
+
1014
+ model FlyerCampaignSponsor {
1015
+ id String @id @default(cuid())
1016
+ campaignId String
1017
+ campaign FlyerCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
1018
+
1019
+ slotType FlyerSponsorSlot
1020
+ businessName String
1021
+ logoS3Key String?
1022
+ ctaText String?
1023
+ sponsorQrSlug String? @unique
1024
+
1025
+ slotPriceCents Int @default(0)
1026
+ stripePaymentIntentId String?
1027
+ paidAt DateTime?
1028
+
1029
+ hostPayoutCents Int?
1030
+ bashFeeCents Int?
1031
+
1032
+ conversions FlyerConversion[]
1033
+
1034
+ createdAt DateTime @default(now())
1035
+
1036
+ @@unique([campaignId, slotType])
1037
+ @@index([campaignId])
1038
+ }
1039
+
1040
+ model FlyerConversion {
1041
+ id String @id @default(cuid())
1042
+ campaignId String
1043
+ campaign FlyerCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
1044
+
1045
+ type FlyerConversionType @default(QR_SCAN)
1046
+ sponsorId String?
1047
+ ticketId String?
1048
+
1049
+ crrtCode String?
1050
+ userAgent String?
1051
+
1052
+ occurredAt DateTime @default(now())
1053
+
1054
+ sponsor FlyerCampaignSponsor? @relation(fields: [sponsorId], references: [id], onDelete: SetNull)
1055
+ ticket Ticket? @relation(fields: [ticketId], references: [id], onDelete: SetNull)
1056
+
1057
+ @@index([campaignId])
1058
+ @@index([occurredAt])
1059
+ @@index([sponsorId])
1060
+ @@index([ticketId])
1061
+ }
1062
+
1063
+ /// USPS carrier-route catalog (Utah MVP; expand for national rollout).
1064
+ model PostalCarrierRoute {
1065
+ id String @id @default(cuid())
1066
+ crrtCode String
1067
+ zipCode String
1068
+ city String
1069
+ state String @default("UT")
1070
+ latitude Float
1071
+ longitude Float
1072
+ householdCount Int
1073
+ residentialOnly Boolean @default(true)
1074
+ medianIncomeBand String?
1075
+ avgHouseholdSize Float?
1076
+ medianAge Float?
1077
+
1078
+ createdAt DateTime @default(now())
1079
+ updatedAt DateTime @updatedAt
1080
+
1081
+ @@unique([zipCode, crrtCode])
1082
+ @@index([latitude, longitude])
1083
+ @@index([zipCode])
1084
+ }
1085
+
875
1086
  enum BashCreativeStatus {
876
1087
  Pending
877
1088
  InReview
@@ -1502,6 +1713,7 @@ model Ticket {
1502
1713
  transferInvite TicketTransferInvite?
1503
1714
  appliedDiscounts AppliedDiscount[] // NEW - Discounts applied to this ticket
1504
1715
  eventReferral EventReferral? // When this ticket was purchased via a referral link
1716
+ flyerConversions FlyerConversion[]
1505
1717
 
1506
1718
  @@index([bashEventId])
1507
1719
  @@index([waitlistUserId])
@@ -1931,26 +2143,26 @@ model User {
1931
2143
 
1932
2144
  givenName String?
1933
2145
  familyName String?
1934
- nameChangeCount Int @default(0)
2146
+ nameChangeCount Int @default(0)
1935
2147
  hash String?
1936
- previousPasswordHashes String[] @default([]) // Last 5 bcrypt hashes (oldest first) for password-reuse prevention
2148
+ previousPasswordHashes String[] @default([]) // Last 5 bcrypt hashes (oldest first) for password-reuse prevention
1937
2149
  emailVerified DateTime?
1938
2150
  image String?
1939
2151
  imageUpdatedAt DateTime? // When Google/OAuth image was last updated
1940
2152
  uploadedImage String?
1941
2153
  uploadedImageUpdatedAt DateTime? // When manually uploaded image was last updated
1942
2154
  dob DateTime?
1943
- showBirthdayOnProfile Boolean @default(false) // If true, show month/day on public profile
2155
+ showBirthdayOnProfile Boolean @default(false) // If true, show month/day on public profile
1944
2156
  // Part B: Birthday Rewards full product
1945
- birthdayRewardsOptIn Boolean @default(false)
2157
+ birthdayRewardsOptIn Boolean @default(false)
1946
2158
  birthdayRewardsOptInAt DateTime?
1947
2159
  birthdayLocationId String? // FK UserLocation (birthday home base)
1948
- birthdayMaxDistanceMiles Int @default(25)
1949
- birthdayNoPurchaseOnly Boolean @default(false)
2160
+ birthdayMaxDistanceMiles Int @default(25)
2161
+ birthdayNoPurchaseOnly Boolean @default(false)
1950
2162
  /// Email reminder N days before birthday (birthday rewards plan nudge)
1951
- birthdayReminderEnabled Boolean @default(true)
1952
- birthdayReminderDaysAdvance Int @default(7)
1953
- birthdayCategories String[] @default([]) // e.g. restaurant, coffee, dessert
2163
+ birthdayReminderEnabled Boolean @default(true)
2164
+ birthdayReminderDaysAdvance Int @default(7)
2165
+ birthdayCategories String[] @default([]) // e.g. restaurant, coffee, dessert
1954
2166
  /// Calendar year when we last sent the "your birthday month freebies" reminder (cron on 1st of month).
1955
2167
  birthdayMonthReminderSentYear Int?
1956
2168
  idDocumentUrl String? // Optional: secure ID upload for verification
@@ -1959,41 +2171,41 @@ model User {
1959
2171
  genderIdentity String?
1960
2172
  sex Sex?
1961
2173
  /// Public-profile visibility flags for self-reported demographics
1962
- showAgeOnProfile Boolean @default(true)
1963
- showGenderOnProfile Boolean @default(true)
2174
+ showAgeOnProfile Boolean @default(true)
2175
+ showGenderOnProfile Boolean @default(true)
1964
2176
  /// Master visibility toggle for ID/Payment/Host/Student trust badges
1965
- showVerifiedBadgesOnProfile Boolean @default(true)
2177
+ showVerifiedBadgesOnProfile Boolean @default(true)
1966
2178
  organization String?
1967
2179
  jobTitle String?
1968
- roles UserRole[] @default([User])
2180
+ roles UserRole[] @default([User])
1969
2181
  aboutMe String?
1970
2182
  levelBadge String?
1971
- temporaryBadges String[] @default([])
2183
+ temporaryBadges String[] @default([])
1972
2184
  status UserStatus
1973
2185
  hostRating Float?
1974
2186
  totalRatings Int?
1975
- totalEventsHosted Int? @default(0) // Total published/completed events hosted
1976
- totalAttendeesHosted Int? @default(0) // Total checked-in attendees across all events
1977
- totalBookingsAccepted Int? @default(0) // Service provider stat
1978
- totalBookingsCompleted Int? @default(0) // Service provider stat
2187
+ totalEventsHosted Int? @default(0) // Total published/completed events hosted
2188
+ totalAttendeesHosted Int? @default(0) // Total checked-in attendees across all events
2189
+ totalBookingsAccepted Int? @default(0) // Service provider stat
2190
+ totalBookingsCompleted Int? @default(0) // Service provider stat
1979
2191
  magicLink String?
1980
2192
  magicLinkExpiration DateTime?
1981
2193
  hostNumber Int?
1982
- foundingHostBadgeAwarded Boolean @default(false)
2194
+ foundingHostBadgeAwarded Boolean @default(false)
1983
2195
  firstBashWithAttendeesDate DateTime?
1984
- bashesCreatedCount Int @default(0)
1985
- bashesPublishedCount Int @default(0)
1986
- bashesApprovedCount Int @default(0)
1987
- bashesCompletedCount Int @default(0)
2196
+ bashesCreatedCount Int @default(0)
2197
+ bashesPublishedCount Int @default(0)
2198
+ bashesApprovedCount Int @default(0)
2199
+ bashesCompletedCount Int @default(0)
1988
2200
  magicLinkUsed DateTime?
1989
2201
  idVerified DateTime?
1990
2202
  street String?
1991
2203
  city String?
1992
2204
  state String?
1993
2205
  zipCode String?
1994
- country String? @default("US")
2206
+ country String? @default("US")
1995
2207
  phone String?
1996
- documentIDId String? @unique
2208
+ documentIDId String? @unique
1997
2209
  /// Encrypted-S3 key for the user's stored government-ID image (deleted per retention policy / on ID expiration)
1998
2210
  govIdImageS3Key String?
1999
2211
  /// When the gov-ID image becomes deletable (null = "indefinite, until I remove it").
@@ -2012,40 +2224,40 @@ model User {
2012
2224
  schoolName String?
2013
2225
  /// Whether `schoolName` came from the AI vision model or was edited by the user
2014
2226
  schoolNameSource SchoolNameSource?
2015
- accepted Boolean? @default(false)
2016
- boughtTicket Boolean? @default(false)
2017
- noPay Boolean? @default(false)
2018
- supportedEvent Boolean? @default(false)
2227
+ accepted Boolean? @default(false)
2228
+ boughtTicket Boolean? @default(false)
2229
+ noPay Boolean? @default(false)
2230
+ supportedEvent Boolean? @default(false)
2019
2231
  suspendedUntil DateTime?
2020
2232
  suspendedById String?
2021
- isLightweightAccount Boolean @default(false)
2022
- notifyForTrendingBashes Boolean @default(false)
2023
- trendingBashThreshold Int @default(10)
2024
- artistsToSee String[] @default([])
2233
+ isLightweightAccount Boolean @default(false)
2234
+ notifyForTrendingBashes Boolean @default(false)
2235
+ trendingBashThreshold Int @default(10)
2236
+ artistsToSee String[] @default([])
2025
2237
  googleCalendarSyncSettings String?
2026
- membershipTier MembershipTier @default(Basic)
2238
+ membershipTier MembershipTier @default(Basic)
2027
2239
  membershipExpiresAt DateTime?
2028
- membershipAutoRenew Boolean @default(false)
2240
+ membershipAutoRenew Boolean @default(false)
2029
2241
  membershipBillingInterval String?
2030
2242
  membershipLastBilledAt DateTime?
2031
- membershipFeaturedEventsUsed Int @default(0)
2032
- membershipStripeSubscriptionId String? @unique
2243
+ membershipFeaturedEventsUsed Int @default(0)
2244
+ membershipStripeSubscriptionId String? @unique
2033
2245
  /// Optional consumer BashPass subscription (waives guest platform fee on tickets; monthly BashPoints).
2034
- bashPassStripeSubscriptionId String? @unique
2246
+ bashPassStripeSubscriptionId String? @unique
2035
2247
  bashPassCurrentPeriodEnd DateTime?
2036
2248
  /// Stripe price tier for standalone BashPass; effective tier may also come from membership bundle.
2037
2249
  bashPassTier BashPassTier?
2038
- bashPassEventsUsedThisMonth Int @default(0)
2250
+ bashPassEventsUsedThisMonth Int @default(0)
2039
2251
  bashPassMonthlyCapResetAt DateTime?
2040
- isVerified Boolean @default(false)
2252
+ isVerified Boolean @default(false)
2041
2253
  verificationMethod String?
2042
2254
  verifiedAt DateTime?
2043
- verificationScore Int @default(0)
2255
+ verificationScore Int @default(0)
2044
2256
  /// $1 card verification (Stripe) — distinct from Stripe Identity / isVerified
2045
- paymentVerified Boolean @default(false)
2257
+ paymentVerified Boolean @default(false)
2046
2258
  paymentVerifiedAt DateTime?
2047
2259
  /// One-time BashPoints bonus after $1 verification payment succeeded
2048
- paymentVerificationBonusAwarded Boolean @default(false)
2260
+ paymentVerificationBonusAwarded Boolean @default(false)
2049
2261
  biometricFingerprint String?
2050
2262
  venmoUsername String?
2051
2263
  venmoQRCodeUrl String?
@@ -2054,17 +2266,17 @@ model User {
2054
2266
  paymentHandle String?
2055
2267
  zellePhone String?
2056
2268
  zelleQRCodeUrl String?
2057
- isAmbashador Boolean @default(false)
2269
+ isAmbashador Boolean @default(false)
2058
2270
  ambashadorAwardedAt DateTime?
2059
- isBashInsider Boolean @default(false)
2271
+ isBashInsider Boolean @default(false)
2060
2272
  insiderAwardedAt DateTime?
2061
- scoutTier ScoutTier @default(None)
2273
+ scoutTier ScoutTier @default(None)
2062
2274
  scoutAwardedAt DateTime?
2063
2275
  agentAwardedAt DateTime?
2064
- totalServiceReferrals Int @default(0)
2065
- isFirstInCity Boolean @default(false)
2276
+ totalServiceReferrals Int @default(0)
2277
+ isFirstInCity Boolean @default(false)
2066
2278
  firstInCityAt DateTime?
2067
- fcmTokens String[] @default([]) // Firebase Cloud Messaging tokens for push notifications
2279
+ fcmTokens String[] @default([]) // Firebase Cloud Messaging tokens for push notifications
2068
2280
 
2069
2281
  // BashPoints System Fields
2070
2282
  bashPointsBalance Int @default(0)
@@ -2104,6 +2316,7 @@ model User {
2104
2316
  referredTransactions BashCreditTransaction[] @relation("ReferredUser")
2105
2317
  creditTransactions BashCreditTransaction[]
2106
2318
  createdEvents BashEvent[] @relation("CreatedEvent")
2319
+ flyerCampaigns FlyerCampaign[]
2107
2320
  bashEventsTransferredFrom BashEvent[] @relation("TransferredFrom")
2108
2321
  bashEventsPayoutRecipient BashEvent[] @relation("BashEventsPayoutRecipient")
2109
2322
  bashEventMessagesSent BashEventMessage[] @relation("BashMessageSender")
@@ -2798,6 +3011,7 @@ model Service {
2798
3011
  entertainerMatches VenueEntertainmentMatch[] @relation("EntertainerMatches")
2799
3012
  includes ServiceInclude[]
2800
3013
  addonRequests ServiceAddonRequest[]
3014
+ serviceBudgets Budget[] @relation("ServiceBudgets")
2801
3015
 
2802
3016
  @@index([serviceListingStripeSubscriptionId])
2803
3017
  @@index([isFreeFirstListing])
@@ -3430,6 +3644,8 @@ model Budget {
3430
3644
  clubId String?
3431
3645
  departmentId String?
3432
3646
  userId String? // For personal event budgets
3647
+ /// Optional: budgets tied to a service profile (vendor/exhibitor/sponsor owner costs).
3648
+ serviceId String?
3433
3649
 
3434
3650
  // Budget details
3435
3651
  totalAllocatedCents Int
@@ -3462,6 +3678,7 @@ model Budget {
3462
3678
  club Club? @relation("ClubBudgets", fields: [clubId], references: [id], onDelete: Cascade)
3463
3679
  department Department? @relation("DepartmentBudgets", fields: [departmentId], references: [id], onDelete: Cascade)
3464
3680
  user User? @relation("UserBudgets", fields: [userId], references: [id], onDelete: Cascade)
3681
+ service Service? @relation("ServiceBudgets", fields: [serviceId], references: [id], onDelete: Cascade)
3465
3682
  submitter User? @relation("BudgetSubmitter", fields: [submittedBy], references: [id])
3466
3683
  approver User? @relation("BudgetApprover", fields: [approverId], references: [id])
3467
3684
  expenses Expense[]
@@ -3471,10 +3688,18 @@ model Budget {
3471
3688
  @@index([clubId])
3472
3689
  @@index([departmentId])
3473
3690
  @@index([userId])
3691
+ @@index([serviceId])
3474
3692
  @@index([status])
3475
3693
  @@index([fiscalYear])
3476
3694
  }
3477
3695
 
3696
+ enum ExpenseExtractionStatus {
3697
+ none
3698
+ pending
3699
+ complete
3700
+ failed
3701
+ }
3702
+
3478
3703
  model Expense {
3479
3704
  id String @id @default(cuid())
3480
3705
  budgetId String
@@ -3490,6 +3715,29 @@ model Expense {
3490
3715
  rejectionReason String?
3491
3716
  createdAt DateTime @default(now())
3492
3717
  updatedAt DateTime @updatedAt
3718
+
3719
+ /// Optional link to an event task (e.g. volunteer receipt for a specific task).
3720
+ eventTaskId String?
3721
+ eventTask EventTask? @relation(fields: [eventTaskId], references: [id], onDelete: SetNull)
3722
+
3723
+ /// User-confirmed or extracted metadata (Textract suggestions are confirmed before save).
3724
+ vendorName String?
3725
+ taxCents Int?
3726
+ subtotalCents Int?
3727
+ currency String? @default("USD")
3728
+
3729
+ extractionStatus ExpenseExtractionStatus @default(none)
3730
+ textractRaw Json?
3731
+
3732
+ /// Vendor invoice number (manual or extracted).
3733
+ invoiceNumber String?
3734
+ /// When payment is due (separate from expenseDate / receipt date).
3735
+ invoiceDueDate DateTime?
3736
+ /// Host marked invoice as paid outside the app — stops due-date reminders.
3737
+ invoicePaidAt DateTime?
3738
+ /// Tracks which invoice due reminders were sent: { dueSoon, dueDay, pastDue1, pastDue15 }.
3739
+ invoiceDueRemindersSent Json?
3740
+
3493
3741
  budget Budget @relation(fields: [budgetId], references: [id], onDelete: Cascade)
3494
3742
  submitter User @relation("ExpenseSubmitter", fields: [submittedBy], references: [id])
3495
3743
  approver User? @relation("ExpenseApprover", fields: [approvedBy], references: [id])
@@ -3497,6 +3745,8 @@ model Expense {
3497
3745
  @@index([budgetId])
3498
3746
  @@index([submittedBy])
3499
3747
  @@index([status])
3748
+ @@index([eventTaskId])
3749
+ @@index([invoiceDueDate])
3500
3750
  }
3501
3751
 
3502
3752
  model ServiceRange {
@@ -4418,6 +4668,12 @@ enum NotificationType {
4418
4668
  HostTopPromoter // Host: "Top promoter: Alex (6 tickets today)"
4419
4669
  HostSalesSlowing // Host: "Sales slowed today. Try sharing your event."
4420
4670
  TicketSold // Host: a ticket was purchased for your event (debounced in webhook)
4671
+ BudgetExpenseSubmitted // Host/organizer: assignee or member submitted an expense
4672
+ BudgetLineThreshold // Host/organizer: budget line crossed a spend threshold (e.g. 80% / 100%)
4673
+ BudgetInvoiceDueSoon // Host/organizer: invoice due in ~3 days
4674
+ BudgetInvoiceDueToday // Host/organizer: invoice due today
4675
+ BudgetInvoicePastDue // Host/organizer: invoice 1 day past due
4676
+ BudgetInvoicePastDueFinal // Host/organizer: invoice 15+ days past due
4421
4677
  }
4422
4678
 
4423
4679
  enum NotificationPriority {
@@ -4744,6 +5000,15 @@ enum CustomScopeStatus {
4744
5000
  Rejected
4745
5001
  }
4746
5002
 
5003
+ /// AI bash-approval reviewer verdict (see `bashApprovalAiService`).
5004
+ /// `Approve` and `Reject` are recommendations; whether the system acts on them
5005
+ /// vs. queues for a human depends on confidence + AI_BASH_APPROVAL_MODE.
5006
+ enum AiApprovalVerdict {
5007
+ Approve
5008
+ Flag
5009
+ Reject
5010
+ }
5011
+
4747
5012
  /// How a user is associated with a bash (for BashFeedPost display: "Sarah attended...", "Mike is hosting...")
4748
5013
  enum BashAssociationType {
4749
5014
  Hosting
@@ -5026,6 +5291,7 @@ enum FoodAndBeverageSubType {
5026
5291
  Bartenders
5027
5292
  DessertBar
5028
5293
  BeverageCart
5294
+ ConcessionStand
5029
5295
  }
5030
5296
 
5031
5297
  enum CleaningSubType {
@@ -5057,6 +5323,9 @@ enum RentalEquipmentSubType {
5057
5323
  Generators
5058
5324
  DanceFloor
5059
5325
  PhotoBooth
5326
+ BounceHouse
5327
+ WaterSlide
5328
+ CarnivalGames
5060
5329
  }
5061
5330
 
5062
5331
  enum MediaProductionAndCreativeSubType {
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Shared shapes for the AI bash-approval reviewer.
3
+ *
4
+ * The Zod runtime envelope lives in `api/src/utils/ai/aiApprovalEnvelope.ts` (zod is an api
5
+ * dependency, not a bash-common one). This file is the type-only source of truth that both
6
+ * the api worker and bash-app's admin UI/host-facing banners read.
7
+ *
8
+ * The persisted `BashEvent.aiApprovalReasons` JSON column is exactly an `AiApprovalReasons`.
9
+ */
10
+
11
+ import type { AiApprovalVerdict } from "@prisma/client";
12
+
13
+ /**
14
+ * Each criterion the model grades on.
15
+ *
16
+ * Keep this list short and stable — it controls the persisted JSON shape, the prompt
17
+ * the model sees, and the badges the admin UI renders. Adding a new criterion is a
18
+ * schema change (existing rows will be missing it).
19
+ */
20
+ export const AI_APPROVAL_CRITERION_KEYS = [
21
+ "completeness",
22
+ "authenticity",
23
+ "safety",
24
+ "policyFit",
25
+ ] as const;
26
+
27
+ export type AiApprovalCriterionKey = (typeof AI_APPROVAL_CRITERION_KEYS)[number];
28
+
29
+ /**
30
+ * Display labels for criteria — used in the admin queue and any host-facing breakdown.
31
+ * Keep keys exhaustive against `AiApprovalCriterionKey` (compiler will yell if drift).
32
+ */
33
+ export const AiApprovalCriterionLabel: Record<AiApprovalCriterionKey, string> = {
34
+ completeness: "Completeness",
35
+ authenticity: "Authenticity",
36
+ safety: "Safety",
37
+ policyFit: "Policy fit",
38
+ };
39
+
40
+ /**
41
+ * One criterion's grade. `score` is 0..1; `notes` is short free-text the model wrote
42
+ * about why (shown in the admin UI tooltip / host rejection reasons).
43
+ */
44
+ export interface AiApprovalCriterionResult {
45
+ score: number;
46
+ notes: string;
47
+ }
48
+
49
+ /**
50
+ * Persisted shape for `BashEvent.aiApprovalReasons` (Json column).
51
+ *
52
+ * - `criteria` keys are exhaustive over `AiApprovalCriterionKey`.
53
+ * - `hostFacingMessage` is shown to the host on rejection; written specifically to be
54
+ * actionable ("Add a start time and a location, then resubmit"), not generic.
55
+ * Null on approve.
56
+ * - `adminFacingNotes` is for super-users in the review queue; can be more candid
57
+ * ("Looks like a duplicate of bash xyz" / "Probably a service ad in disguise").
58
+ */
59
+ export interface AiApprovalReasons {
60
+ criteria: Record<AiApprovalCriterionKey, AiApprovalCriterionResult>;
61
+ hostFacingMessage: string | null;
62
+ adminFacingNotes: string;
63
+ }
64
+
65
+ /**
66
+ * Runtime values for the Prisma `AiApprovalVerdict` enum.
67
+ *
68
+ * Browser code (bash-app) cannot rely on `@prisma/client`'s runtime enum exports — the
69
+ * package's `browser` resolution doesn't reliably surface them when imported transitively
70
+ * via `@bash-app/bash-common`. So we mirror the enum here as a plain string-keyed object
71
+ * and `satisfies Record<AiApprovalVerdict, AiApprovalVerdict>` to guarantee at compile
72
+ * time that the keys/values stay in lockstep with `schema.prisma`. Prefer
73
+ * `AI_APPROVAL_VERDICTS.Approve` over `AiApprovalVerdict.Approve` in any code path that
74
+ * may be bundled for the browser.
75
+ */
76
+ export const AI_APPROVAL_VERDICTS = {
77
+ Approve: "Approve",
78
+ Flag: "Flag",
79
+ Reject: "Reject",
80
+ } as const satisfies Record<AiApprovalVerdict, AiApprovalVerdict>;
81
+
82
+ /**
83
+ * Display labels for the Prisma `AiApprovalVerdict` enum, used in admin pills and the
84
+ * host rejection banner so callers don't reach for ad-hoc switch statements.
85
+ *
86
+ * The `Record<AiApprovalVerdict, string>` annotation enforces exhaustiveness at compile
87
+ * time, so adding/renaming an enum member in `schema.prisma` will fail tsc here.
88
+ */
89
+ export const AiApprovalVerdictLabel: Record<AiApprovalVerdict, string> = {
90
+ Approve: "Approve",
91
+ Flag: "Flag for review",
92
+ Reject: "Reject",
93
+ };
94
+
95
+ /**
96
+ * Feature-flag values for `AI_BASH_APPROVAL_MODE`.
97
+ *
98
+ * - `off` - AI service is never invoked. Existing manual-only workflow.
99
+ * - `shadow` - AI runs and persists verdict + reasons, but every outcome is `flag`.
100
+ * Admins still decide everything; we use the data to tune thresholds.
101
+ * - `auto-with-flag` - High-confidence approve/reject act automatically; the rest flag for human.
102
+ * - `auto-approve-only` - High-confidence approve acts automatically; rejects always flag for human.
103
+ */
104
+ export const AI_BASH_APPROVAL_MODES = [
105
+ "off",
106
+ "shadow",
107
+ "auto-with-flag",
108
+ "auto-approve-only",
109
+ ] as const;
110
+
111
+ export type AiBashApprovalMode = (typeof AI_BASH_APPROVAL_MODES)[number];
112
+
113
+ /**
114
+ * Default thresholds. Tune in the api consumer if behavior needs to change post-rollout;
115
+ * exporting here so tests in api can import a single source of truth.
116
+ */
117
+ export const AI_APPROVAL_AUTO_APPROVE_CONFIDENCE = 0.85;
118
+ export const AI_APPROVAL_AUTO_REJECT_CONFIDENCE = 0.9;