@bash-app/bash-common 30.385.0 → 30.391.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.
- package/dist/__tests__/scenePulse.test.d.ts +2 -0
- package/dist/__tests__/scenePulse.test.d.ts.map +1 -0
- package/dist/__tests__/scenePulse.test.js +52 -0
- package/dist/__tests__/scenePulse.test.js.map +1 -0
- package/dist/definitions.d.ts +19 -13
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +13 -12
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +22 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +17 -0
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/scenePulse.d.ts +66 -0
- package/dist/scenePulse.d.ts.map +1 -0
- package/dist/scenePulse.js +54 -0
- package/dist/scenePulse.js.map +1 -0
- package/dist/utils/__tests__/paymentUtils.test.js +28 -1
- package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
- package/dist/utils/__tests__/serviceAvailabilityDisplay.test.js +56 -1
- package/dist/utils/__tests__/serviceAvailabilityDisplay.test.js.map +1 -1
- package/dist/utils/__tests__/slugUtils.test.js +5 -3
- package/dist/utils/__tests__/slugUtils.test.js.map +1 -1
- package/dist/utils/paymentUtils.d.ts +18 -1
- package/dist/utils/paymentUtils.d.ts.map +1 -1
- package/dist/utils/paymentUtils.js +29 -2
- package/dist/utils/paymentUtils.js.map +1 -1
- package/dist/utils/service/__tests__/foodAndBeverageQuote.test.d.ts +2 -0
- package/dist/utils/service/__tests__/foodAndBeverageQuote.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/foodAndBeverageQuote.test.js +353 -0
- package/dist/utils/service/__tests__/foodAndBeverageQuote.test.js.map +1 -0
- package/dist/utils/service/foodAndBeverageQuote.d.ts +171 -0
- package/dist/utils/service/foodAndBeverageQuote.d.ts.map +1 -0
- package/dist/utils/service/foodAndBeverageQuote.js +467 -0
- package/dist/utils/service/foodAndBeverageQuote.js.map +1 -0
- package/dist/utils/serviceAvailabilityDisplay.d.ts +28 -2
- package/dist/utils/serviceAvailabilityDisplay.d.ts.map +1 -1
- package/dist/utils/serviceAvailabilityDisplay.js +39 -0
- package/dist/utils/serviceAvailabilityDisplay.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +95 -4
- package/src/__tests__/scenePulse.test.ts +69 -0
- package/src/definitions.ts +18 -12
- package/src/extendedSchemas.ts +23 -0
- package/src/index.ts +6 -0
- package/src/scenePulse.ts +113 -0
- package/src/utils/__tests__/paymentUtils.test.ts +36 -0
- package/src/utils/__tests__/serviceAvailabilityDisplay.test.ts +73 -0
- package/src/utils/__tests__/slugUtils.test.ts +5 -3
- package/src/utils/paymentUtils.ts +47 -2
- package/src/utils/service/__tests__/foodAndBeverageQuote.test.ts +502 -0
- package/src/utils/service/foodAndBeverageQuote.ts +613 -0
- package/src/utils/serviceAvailabilityDisplay.ts +82 -2
package/prisma/schema.prisma
CHANGED
|
@@ -694,7 +694,11 @@ model Promoter {
|
|
|
694
694
|
competitionId String?
|
|
695
695
|
/// Cached count of qualified ticket sales for the linked competition (updated async after checkout/refund)
|
|
696
696
|
qualifiedSales Int @default(0)
|
|
697
|
+
/// Basis points of attributed ticket subtotal withheld from the host and paid to this promoter via Connect (1500 = 15%). Guest price unchanged.
|
|
698
|
+
ticketCommissionBps Int?
|
|
697
699
|
promoCodes BashEventPromoCode[]
|
|
700
|
+
ticketCommissions PromoterTicketCommission[]
|
|
701
|
+
lineupCredits BashEventLineupSlot[]
|
|
698
702
|
promoterUser User? @relation(fields: [userId], references: [id])
|
|
699
703
|
competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
|
|
700
704
|
|
|
@@ -702,6 +706,43 @@ model Promoter {
|
|
|
702
706
|
@@index([competitionId])
|
|
703
707
|
}
|
|
704
708
|
|
|
709
|
+
enum PromoterTicketCommissionStatus {
|
|
710
|
+
Accrued
|
|
711
|
+
Transferred
|
|
712
|
+
Reversed
|
|
713
|
+
Held
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/// Ledger for % of ticket sales owed to a promo-code promoter / performer.
|
|
717
|
+
/// Withheld from the host via Stripe application_fee, then transferred to the
|
|
718
|
+
/// promoter's Connect account. Guest checkout total does not change.
|
|
719
|
+
model PromoterTicketCommission {
|
|
720
|
+
id String @id @default(cuid())
|
|
721
|
+
promoterId String
|
|
722
|
+
bashEventId String
|
|
723
|
+
promoCodeId String?
|
|
724
|
+
checkoutSessionId String?
|
|
725
|
+
paymentIntentId String?
|
|
726
|
+
chargeId String?
|
|
727
|
+
transferId String?
|
|
728
|
+
reversalId String?
|
|
729
|
+
ticketCount Int
|
|
730
|
+
basisCents Int
|
|
731
|
+
commissionBps Int
|
|
732
|
+
amountCents Int
|
|
733
|
+
status PromoterTicketCommissionStatus @default(Accrued)
|
|
734
|
+
createdAt DateTime @default(now())
|
|
735
|
+
updatedAt DateTime @updatedAt
|
|
736
|
+
|
|
737
|
+
promoter Promoter @relation(fields: [promoterId], references: [id], onDelete: Cascade)
|
|
738
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
739
|
+
|
|
740
|
+
@@unique([checkoutSessionId])
|
|
741
|
+
@@index([promoterId, status])
|
|
742
|
+
@@index([bashEventId])
|
|
743
|
+
@@index([paymentIntentId])
|
|
744
|
+
}
|
|
745
|
+
|
|
705
746
|
model PromoterStats {
|
|
706
747
|
id String @id @default(cuid())
|
|
707
748
|
userId String @unique
|
|
@@ -1356,7 +1397,8 @@ model BashEvent {
|
|
|
1356
1397
|
eventGroups EventGroup[]
|
|
1357
1398
|
groupUnlockOffers GroupUnlockOffer[]
|
|
1358
1399
|
|
|
1359
|
-
|
|
1400
|
+
lineupSlots BashEventLineupSlot[]
|
|
1401
|
+
promoterTicketCommissions PromoterTicketCommission[]
|
|
1360
1402
|
vanityPaths EventVanityPath[]
|
|
1361
1403
|
flyerCampaigns FlyerCampaign[]
|
|
1362
1404
|
featuredGuests EventFeaturedGuest[]
|
|
@@ -1403,13 +1445,16 @@ model BashEvent {
|
|
|
1403
1445
|
}
|
|
1404
1446
|
|
|
1405
1447
|
/// Lineup / attribution roster for an event (optional act list for checkout tagging).
|
|
1406
|
-
model
|
|
1448
|
+
model BashEventLineupSlot {
|
|
1407
1449
|
id String @id @default(cuid())
|
|
1408
1450
|
bashEventId String
|
|
1409
1451
|
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
1410
1452
|
name String
|
|
1411
1453
|
serviceId String?
|
|
1412
1454
|
service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
|
|
1455
|
+
invitedEmail String?
|
|
1456
|
+
promoterId String?
|
|
1457
|
+
promoter Promoter? @relation(fields: [promoterId], references: [id], onDelete: SetNull)
|
|
1413
1458
|
sortOrder Int @default(0)
|
|
1414
1459
|
createdAt DateTime @default(now())
|
|
1415
1460
|
|
|
@@ -1417,6 +1462,7 @@ model BashEventArtist {
|
|
|
1417
1462
|
|
|
1418
1463
|
@@index([bashEventId])
|
|
1419
1464
|
@@index([serviceId])
|
|
1465
|
+
@@index([promoterId])
|
|
1420
1466
|
}
|
|
1421
1467
|
|
|
1422
1468
|
// --- Flyer Blast (USPS EDDM + Lob addressed mail) ---
|
|
@@ -3680,7 +3726,7 @@ model Ticket {
|
|
|
3680
3726
|
|
|
3681
3727
|
bashEvent BashEvent @relation(fields: [bashEventId], references: [id])
|
|
3682
3728
|
attributedArtistId String?
|
|
3683
|
-
attributedArtist
|
|
3729
|
+
attributedArtist BashEventLineupSlot? @relation(fields: [attributedArtistId], references: [id], onDelete: SetNull)
|
|
3684
3730
|
checkout Checkout? @relation(fields: [checkoutId], references: [id])
|
|
3685
3731
|
forUser User? @relation("TicketsISent", fields: [forUserId], references: [id])
|
|
3686
3732
|
invitation Invitation? @relation("TicketsForInvitation", fields: [invitationId], references: [id])
|
|
@@ -5485,7 +5531,7 @@ model Service {
|
|
|
5485
5531
|
latitude Float?
|
|
5486
5532
|
longitude Float?
|
|
5487
5533
|
bashFeedPosts BashFeedPost[]
|
|
5488
|
-
|
|
5534
|
+
lineupSlots BashEventLineupSlot[]
|
|
5489
5535
|
bashCreativeSubmissions BashCreativeSubmission[]
|
|
5490
5536
|
associatedServicesReferencingMe AssociatedService[]
|
|
5491
5537
|
exhibitorBookingRequests ExhibitorBookingRequest[] @relation("ExhibitorBookingService")
|
|
@@ -5690,6 +5736,10 @@ model EventService {
|
|
|
5690
5736
|
rateUnit String?
|
|
5691
5737
|
minimumChargeCents Int?
|
|
5692
5738
|
minimumQuantity Int?
|
|
5739
|
+
/// Delivery logistics for F&B listings: Pickup | Delivery | FullService.
|
|
5740
|
+
fulfillmentModes String[] @default([])
|
|
5741
|
+
/// Minimum advance-notice days required for F&B bookings (e.g. 7 = one week).
|
|
5742
|
+
leadTimeDays Int?
|
|
5693
5743
|
crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id], onDelete: Cascade)
|
|
5694
5744
|
serviceRange ServiceRange? @relation(fields: [serviceRangeId], references: [id])
|
|
5695
5745
|
service Service?
|
|
@@ -9444,6 +9494,8 @@ enum FoodAndBeverageSubType {
|
|
|
9444
9494
|
DessertBar
|
|
9445
9495
|
BeverageCart
|
|
9446
9496
|
ConcessionStand
|
|
9497
|
+
CelebrationCake
|
|
9498
|
+
GrazingTable
|
|
9447
9499
|
FoodAndBeverageOther
|
|
9448
9500
|
}
|
|
9449
9501
|
|
|
@@ -9484,6 +9536,7 @@ enum RentalEquipmentSubType {
|
|
|
9484
9536
|
BounceHouse
|
|
9485
9537
|
WaterSlide
|
|
9486
9538
|
CarnivalGames
|
|
9539
|
+
RestroomTrailer
|
|
9487
9540
|
RentalEquipmentOther
|
|
9488
9541
|
}
|
|
9489
9542
|
|
|
@@ -9571,6 +9624,7 @@ enum WellnessSubType {
|
|
|
9571
9624
|
}
|
|
9572
9625
|
|
|
9573
9626
|
enum HostingSupportSubType {
|
|
9627
|
+
Officiant
|
|
9574
9628
|
HostingSupportOther
|
|
9575
9629
|
}
|
|
9576
9630
|
|
|
@@ -9822,6 +9876,24 @@ enum DessertBarFormat {
|
|
|
9822
9876
|
MultiStationDessert
|
|
9823
9877
|
}
|
|
9824
9878
|
|
|
9879
|
+
enum CelebrationCakeFormat {
|
|
9880
|
+
WeddingCake
|
|
9881
|
+
BirthdayCake
|
|
9882
|
+
AnniversaryCake
|
|
9883
|
+
CorporateCake
|
|
9884
|
+
CustomOrder
|
|
9885
|
+
}
|
|
9886
|
+
|
|
9887
|
+
enum GrazingTableFormat {
|
|
9888
|
+
CharcuterieBoard
|
|
9889
|
+
CheeseBoard
|
|
9890
|
+
DessertBoard
|
|
9891
|
+
BreakfastBoard
|
|
9892
|
+
GrazingTableSetup
|
|
9893
|
+
CorporateSpread
|
|
9894
|
+
CustomOrder
|
|
9895
|
+
}
|
|
9896
|
+
|
|
9825
9897
|
enum BeverageCartFormat {
|
|
9826
9898
|
FullBarCart
|
|
9827
9899
|
PremiumBarCart
|
|
@@ -10105,6 +10177,25 @@ enum TablesFormat {
|
|
|
10105
10177
|
CustomTables
|
|
10106
10178
|
}
|
|
10107
10179
|
|
|
10180
|
+
enum RestroomTrailerFormat {
|
|
10181
|
+
LuxuryTrailer
|
|
10182
|
+
StandardPortable
|
|
10183
|
+
ADAAccessible
|
|
10184
|
+
MultiStall
|
|
10185
|
+
HandwashStation
|
|
10186
|
+
}
|
|
10187
|
+
|
|
10188
|
+
enum OfficiantFormat {
|
|
10189
|
+
WeddingCeremony
|
|
10190
|
+
Elopement
|
|
10191
|
+
VowRenewal
|
|
10192
|
+
CommitmentCeremony
|
|
10193
|
+
Interfaith
|
|
10194
|
+
Nondenominational
|
|
10195
|
+
Civil
|
|
10196
|
+
CustomCeremony
|
|
10197
|
+
}
|
|
10198
|
+
|
|
10108
10199
|
enum TentsFormat {
|
|
10109
10200
|
ClearSpanTents
|
|
10110
10201
|
FrameTents
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parseScenePulseScope — city default, invalid → null.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, expect, test } from "@jest/globals";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
emptyScenePulseEnergy,
|
|
8
|
+
normalizeScenePulseGeoOverride,
|
|
9
|
+
parseScenePulseScope,
|
|
10
|
+
} from "../scenePulse.js";
|
|
11
|
+
|
|
12
|
+
describe("parseScenePulseScope", () => {
|
|
13
|
+
test("defaults missing to city", () => {
|
|
14
|
+
expect(parseScenePulseScope(undefined)).toBe("city");
|
|
15
|
+
expect(parseScenePulseScope(null)).toBe("city");
|
|
16
|
+
expect(parseScenePulseScope("")).toBe("city");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("accepts city region world", () => {
|
|
20
|
+
expect(parseScenePulseScope("city")).toBe("city");
|
|
21
|
+
expect(parseScenePulseScope("Region")).toBe("region");
|
|
22
|
+
expect(parseScenePulseScope(" WORLD ")).toBe("world");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("rejects unknown values", () => {
|
|
26
|
+
expect(parseScenePulseScope("you")).toBeNull();
|
|
27
|
+
expect(parseScenePulseScope(12)).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("normalizeScenePulseGeoOverride", () => {
|
|
32
|
+
test("returns null when city and state are blank", () => {
|
|
33
|
+
expect(normalizeScenePulseGeoOverride({})).toBeNull();
|
|
34
|
+
expect(
|
|
35
|
+
normalizeScenePulseGeoOverride({ city: " ", state: "" })
|
|
36
|
+
).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("trims city and state and ignores empty sibling", () => {
|
|
40
|
+
expect(
|
|
41
|
+
normalizeScenePulseGeoOverride({ city: " Denver ", state: " " })
|
|
42
|
+
).toEqual({ city: "Denver", state: null });
|
|
43
|
+
expect(
|
|
44
|
+
normalizeScenePulseGeoOverride({ city: "", state: "CO" })
|
|
45
|
+
).toEqual({ city: null, state: "CO" });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("caps overly long values", () => {
|
|
49
|
+
const city = "A".repeat(90);
|
|
50
|
+
const parsed = normalizeScenePulseGeoOverride({ city, state: "Utah" });
|
|
51
|
+
expect(parsed?.city).toHaveLength(80);
|
|
52
|
+
expect(parsed?.state).toBe("Utah");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("emptyScenePulseEnergy", () => {
|
|
57
|
+
test("zeros every energy field", () => {
|
|
58
|
+
expect(emptyScenePulseEnergy()).toEqual({
|
|
59
|
+
liveNow: 0,
|
|
60
|
+
going: 0,
|
|
61
|
+
tonightBashes: 0,
|
|
62
|
+
tonightGoing: 0,
|
|
63
|
+
weekendBashes: 0,
|
|
64
|
+
weekendGoing: 0,
|
|
65
|
+
newThisWeek: 0,
|
|
66
|
+
namesInTheRoom: 0,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
package/src/definitions.ts
CHANGED
|
@@ -617,18 +617,19 @@ export const BASH_EVENT_WIZARD_WHY_SUBSTEP = {
|
|
|
617
617
|
export const BASH_EVENT_WIZARD_HOW_SUBSTEP = {
|
|
618
618
|
PRICE: 0,
|
|
619
619
|
APPLICATION: 1,
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
620
|
+
PROMOTER_CHALLENGE: 2,
|
|
621
|
+
PROMO_CODES: 3,
|
|
622
|
+
SPECIAL_OFFERS: 4,
|
|
623
|
+
BUDGET: 5,
|
|
624
|
+
MERCH: 6,
|
|
625
|
+
GIVEAWAYS: 7,
|
|
626
|
+
SERVICES_PARTNERS: 8,
|
|
627
|
+
TASKS: 9,
|
|
628
|
+
PAYOUT: 10,
|
|
629
|
+
WAIVERS: 11,
|
|
630
|
+
CANCELLATION_POLICIES: 12,
|
|
631
|
+
FLYER_BLAST: 13,
|
|
632
|
+
ANALYTICS: 14,
|
|
632
633
|
} as const;
|
|
633
634
|
|
|
634
635
|
export const BASH_EVENT_WIZARD_FINISH_SUBSTEP = {
|
|
@@ -830,6 +831,11 @@ export type ApiServiceBookingParams = {
|
|
|
830
831
|
bashEventId?: string;
|
|
831
832
|
/** Booking-level bundled packages (e.g. "Gold DJ Package"), independent of hourly/daily rates. */
|
|
832
833
|
packages?: ApiServicePackageParams[];
|
|
834
|
+
/**
|
|
835
|
+
* F&B per-guest bookings: host-entered guest count snapshotted at booking time.
|
|
836
|
+
* Saved to ServiceBooking.expectedAttendeesAtBooking. No promoter true-up.
|
|
837
|
+
*/
|
|
838
|
+
foodAndBeverageHeadcount?: number;
|
|
833
839
|
};
|
|
834
840
|
|
|
835
841
|
export type ApiServiceCanBookParams = {} & ApiServiceBookingParams;
|
package/src/extendedSchemas.ts
CHANGED
|
@@ -1027,6 +1027,15 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
|
|
|
1027
1027
|
serviceRatesAssociation: {
|
|
1028
1028
|
include: {
|
|
1029
1029
|
serviceGeneralRates: true,
|
|
1030
|
+
// Lean blocked/premium windows so list calendars can count open days
|
|
1031
|
+
// without the full rates association payload.
|
|
1032
|
+
serviceSpecialRates: {
|
|
1033
|
+
select: {
|
|
1034
|
+
startDate: true,
|
|
1035
|
+
endDate: true,
|
|
1036
|
+
isAvailable: true,
|
|
1037
|
+
},
|
|
1038
|
+
},
|
|
1030
1039
|
},
|
|
1031
1040
|
},
|
|
1032
1041
|
venue: {
|
|
@@ -1045,6 +1054,14 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
|
|
|
1045
1054
|
eventServiceSubType: true,
|
|
1046
1055
|
formatOptions: true,
|
|
1047
1056
|
crowdSize: true,
|
|
1057
|
+
// F&B per-guest pricing & logistics fields
|
|
1058
|
+
pricingModel: true,
|
|
1059
|
+
baseRateCents: true,
|
|
1060
|
+
minimumQuantity: true,
|
|
1061
|
+
minimumChargeCents: true,
|
|
1062
|
+
goodsOrServices: true,
|
|
1063
|
+
fulfillmentModes: true,
|
|
1064
|
+
leadTimeDays: true,
|
|
1048
1065
|
},
|
|
1049
1066
|
},
|
|
1050
1067
|
entertainmentService: {
|
|
@@ -1414,6 +1431,12 @@ export interface ServiceExt extends Service {
|
|
|
1414
1431
|
/** Attached by list endpoints (public/owner) for popularity sort and recency badges. */
|
|
1415
1432
|
bookingStats?: ServiceBookingStats;
|
|
1416
1433
|
|
|
1434
|
+
/**
|
|
1435
|
+
* Confirmed/Approved booking windows for the public availability calendar.
|
|
1436
|
+
* Attached by the public list endpoint; not a Prisma relation.
|
|
1437
|
+
*/
|
|
1438
|
+
bookedRanges?: Array<{ start: Date | string; end: Date | string }>;
|
|
1439
|
+
|
|
1417
1440
|
// bookedCheckouts: ServiceBookingCheckoutExt[]; //not necessary to include
|
|
1418
1441
|
}
|
|
1419
1442
|
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ export * from "./accessScreening.js";
|
|
|
36
36
|
export * from "./applicationFunnel.js";
|
|
37
37
|
export * from "./hostCrmSegments.js";
|
|
38
38
|
export * from "./hostCrmCrowdPulse.js";
|
|
39
|
+
export * from "./scenePulse.js";
|
|
39
40
|
export * from "./hostCrmLastBashProof.js";
|
|
40
41
|
export * from "./hostCrmInsights.js";
|
|
41
42
|
export * from "./hostCrmAutomation.js";
|
|
@@ -107,6 +108,8 @@ export {
|
|
|
107
108
|
BrandStrategistFormat,
|
|
108
109
|
CPAsFormat,
|
|
109
110
|
CateringFormat,
|
|
111
|
+
CelebrationCakeFormat,
|
|
112
|
+
GrazingTableFormat,
|
|
110
113
|
CelebrityAppearanceFormat,
|
|
111
114
|
CenterpiecesFormat,
|
|
112
115
|
ChairsFormat,
|
|
@@ -189,6 +192,7 @@ export {
|
|
|
189
192
|
NotificationPriority,
|
|
190
193
|
NotificationType,
|
|
191
194
|
Occupation,
|
|
195
|
+
OfficiantFormat,
|
|
192
196
|
OfferClaimStatus,
|
|
193
197
|
OfferDiscountType,
|
|
194
198
|
OrganizationArtistInvitationStatus,
|
|
@@ -225,6 +229,7 @@ export {
|
|
|
225
229
|
RecurringFrequency,
|
|
226
230
|
RedemptionMethod,
|
|
227
231
|
RentalEquipmentSubType,
|
|
232
|
+
RestroomTrailerFormat,
|
|
228
233
|
ReportStatus,
|
|
229
234
|
ReportingFormat,
|
|
230
235
|
RewardReportStatus,
|
|
@@ -429,6 +434,7 @@ export * from "./utils/mathUtils.js";
|
|
|
429
434
|
export * from "./utils/birthdayExperienceQuality.js";
|
|
430
435
|
export * from "./utils/service/apiServiceBookingApiUtils.js";
|
|
431
436
|
export * from "./utils/service/frontendServiceBookingUtils.js";
|
|
437
|
+
export * from "./utils/service/foodAndBeverageQuote.js";
|
|
432
438
|
export * from "./utils/service/serviceBookingStatusUtils.js";
|
|
433
439
|
export * from "./utils/service/serviceDBUtils.js";
|
|
434
440
|
export * from "./utils/service/serviceRateUtils.js";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Community Builder scene pulse — city / region / world aggregates.
|
|
3
|
+
* You (Crowd Pulse) stays host ops; these scopes are public scene energy + platform proof.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const SCENE_PULSE_SCOPES = ["city", "region", "world"] as const;
|
|
7
|
+
|
|
8
|
+
export type ScenePulseScope = (typeof SCENE_PULSE_SCOPES)[number];
|
|
9
|
+
|
|
10
|
+
export type ScenePulseGeo = {
|
|
11
|
+
city: string | null;
|
|
12
|
+
state: string | null;
|
|
13
|
+
country: string | null;
|
|
14
|
+
/** Short label for copy ("Salt Lake City", "Utah", "World"). */
|
|
15
|
+
label: string;
|
|
16
|
+
timeZone: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ScenePulseEnergy = {
|
|
20
|
+
/** Public rooms currently live. */
|
|
21
|
+
liveNow: number;
|
|
22
|
+
/** Unique Going RSVPs + ticketed people on live + tonight + this weekend. */
|
|
23
|
+
going: number;
|
|
24
|
+
tonightBashes: number;
|
|
25
|
+
tonightGoing: number;
|
|
26
|
+
weekendBashes: number;
|
|
27
|
+
weekendGoing: number;
|
|
28
|
+
/** Unique people who RSVP'd Going or paid for a ticket in the last 7 days. */
|
|
29
|
+
newThisWeek: number;
|
|
30
|
+
/**
|
|
31
|
+
* Distinct live/tonight/weekend public bashes with a lineup slot or a
|
|
32
|
+
* host-spotlighted featured guest (names in the room — not CRM).
|
|
33
|
+
*/
|
|
34
|
+
namesInTheRoom: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** World-only cumulative credibility. Null on city/region. */
|
|
38
|
+
export type ScenePulseWorldCred = {
|
|
39
|
+
totalPeople: number;
|
|
40
|
+
bashesHosted: number;
|
|
41
|
+
ticketsSold: number;
|
|
42
|
+
gmvCents: number;
|
|
43
|
+
citiesRepresented: number;
|
|
44
|
+
regionsRepresented: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ScenePulse = {
|
|
48
|
+
scope: ScenePulseScope;
|
|
49
|
+
geo: ScenePulseGeo | null;
|
|
50
|
+
/** True when city (or region) can't be resolved from profile or hosted bashes. */
|
|
51
|
+
needsLocation: boolean;
|
|
52
|
+
energy: ScenePulseEnergy;
|
|
53
|
+
world: ScenePulseWorldCred | null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Optional city/state used to pulse a scene other than the host's profile. */
|
|
57
|
+
export type ScenePulseGeoOverride = {
|
|
58
|
+
city: string | null;
|
|
59
|
+
state: string | null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const MAX_GEO_CITY_CHARS = 80;
|
|
63
|
+
const MAX_GEO_STATE_CHARS = 40;
|
|
64
|
+
|
|
65
|
+
function normalizeGeoPart(raw: unknown, maxChars: number): string | null {
|
|
66
|
+
if (typeof raw !== "string") return null;
|
|
67
|
+
const trimmed = raw.replace(/\s+/g, " ").trim();
|
|
68
|
+
if (!trimmed) return null;
|
|
69
|
+
return trimmed.slice(0, maxChars);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Blank city+state → null (use profile). Otherwise a trimmed override.
|
|
74
|
+
*/
|
|
75
|
+
export function normalizeScenePulseGeoOverride(input: {
|
|
76
|
+
city?: unknown;
|
|
77
|
+
state?: unknown;
|
|
78
|
+
}): ScenePulseGeoOverride | null {
|
|
79
|
+
const city = normalizeGeoPart(input.city, MAX_GEO_CITY_CHARS);
|
|
80
|
+
const state = normalizeGeoPart(input.state, MAX_GEO_STATE_CHARS);
|
|
81
|
+
if (!city && !state) return null;
|
|
82
|
+
return { city, state };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function emptyScenePulseEnergy(): ScenePulseEnergy {
|
|
86
|
+
return {
|
|
87
|
+
liveNow: 0,
|
|
88
|
+
going: 0,
|
|
89
|
+
tonightBashes: 0,
|
|
90
|
+
tonightGoing: 0,
|
|
91
|
+
weekendBashes: 0,
|
|
92
|
+
weekendGoing: 0,
|
|
93
|
+
newThisWeek: 0,
|
|
94
|
+
namesInTheRoom: 0,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Missing / blank → city. Invalid string → null (caller should 400).
|
|
100
|
+
*/
|
|
101
|
+
export function parseScenePulseScope(raw: unknown): ScenePulseScope | null {
|
|
102
|
+
if (raw == null || raw === "") return "city";
|
|
103
|
+
if (typeof raw !== "string") return null;
|
|
104
|
+
const normalized = raw.trim().toLowerCase();
|
|
105
|
+
if (
|
|
106
|
+
normalized === "city" ||
|
|
107
|
+
normalized === "region" ||
|
|
108
|
+
normalized === "world"
|
|
109
|
+
) {
|
|
110
|
+
return normalized;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
splitBashPlatformFeeForTicketCheckout,
|
|
22
22
|
computeTicketApplicationFeeAmountCents,
|
|
23
23
|
computeGuestTicketCheckoutBreakdown,
|
|
24
|
+
ticketCommissionCents,
|
|
25
|
+
clampTicketCommissionBps,
|
|
26
|
+
MAX_TICKET_COMMISSION_BPS,
|
|
24
27
|
guestAllInUnitPriceCents,
|
|
25
28
|
guestCheckoutHasNoAddedFees,
|
|
26
29
|
lowestGuestAllInPriceCents,
|
|
@@ -153,6 +156,7 @@ describe("PaymentUtils - Core Functions", () => {
|
|
|
153
156
|
expect(r.bashPlatformTotalCents).toBe(300);
|
|
154
157
|
expect(r.stripeProcessingCents).toBe(calculateStripeProcessingFeeCents(10000));
|
|
155
158
|
expect(r.applicationFeeAmountCents).toBe(r.stripeProcessingCents + 300);
|
|
159
|
+
expect(r.performerCommissionCents).toBe(0);
|
|
156
160
|
});
|
|
157
161
|
|
|
158
162
|
test("computeTicketApplicationFeeAmountCents — explicit isVerifiedNonprofitHost: false matches default", () => {
|
|
@@ -277,6 +281,38 @@ describe("PaymentUtils - Core Functions", () => {
|
|
|
277
281
|
expect(r.applicationFeeAmountCents).toBe(r.stripeProcessingCents);
|
|
278
282
|
});
|
|
279
283
|
|
|
284
|
+
test("ticketCommissionCents floors bps of the subtotal and caps at 50%", () => {
|
|
285
|
+
expect(ticketCommissionCents(10000, 1500)).toBe(1500);
|
|
286
|
+
expect(ticketCommissionCents(10000, 0)).toBe(0);
|
|
287
|
+
expect(ticketCommissionCents(10000, MAX_TICKET_COMMISSION_BPS + 100)).toBe(
|
|
288
|
+
5000
|
|
289
|
+
);
|
|
290
|
+
expect(clampTicketCommissionBps(99999)).toBe(MAX_TICKET_COMMISSION_BPS);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("PROMO-002: performer cut is withheld on the application fee, not the guest total", () => {
|
|
294
|
+
const base = computeTicketApplicationFeeAmountCents({
|
|
295
|
+
discountedSubtotalCents: 10000,
|
|
296
|
+
platformFeeEnabled: false,
|
|
297
|
+
platformFeeRate: 0.03,
|
|
298
|
+
feeHandling: "HostAbsorbs",
|
|
299
|
+
bashPassWaivesGuestFee: false,
|
|
300
|
+
});
|
|
301
|
+
const withCut = computeTicketApplicationFeeAmountCents({
|
|
302
|
+
discountedSubtotalCents: 10000,
|
|
303
|
+
platformFeeEnabled: false,
|
|
304
|
+
platformFeeRate: 0.03,
|
|
305
|
+
feeHandling: "HostAbsorbs",
|
|
306
|
+
bashPassWaivesGuestFee: false,
|
|
307
|
+
performerCommissionCents: 1500,
|
|
308
|
+
});
|
|
309
|
+
expect(withCut.totalChargedCents).toBe(base.totalChargedCents);
|
|
310
|
+
expect(withCut.performerCommissionCents).toBe(1500);
|
|
311
|
+
expect(withCut.applicationFeeAmountCents).toBe(
|
|
312
|
+
base.applicationFeeAmountCents + 1500
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
|
|
280
316
|
test("userHasActiveBashPass", () => {
|
|
281
317
|
expect(userHasActiveBashPass({ bashPassStripeSubscriptionId: null, bashPassCurrentPeriodEnd: null })).toBe(false);
|
|
282
318
|
expect(userHasActiveBashPass({ bashPassStripeSubscriptionId: "sub_1", bashPassCurrentPeriodEnd: null })).toBe(true);
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyAvailabilityPreset,
|
|
3
|
+
blockedRangesFromSpecialRates,
|
|
3
4
|
computeEarliestOpenDate,
|
|
5
|
+
countServicesAvailableOnDate,
|
|
4
6
|
detectAvailabilityPreset,
|
|
7
|
+
filterServicesAvailableOnDate,
|
|
5
8
|
formatAvailabilityPresetLabel,
|
|
9
|
+
isServiceAvailableOnDate,
|
|
6
10
|
parseAvailableHours,
|
|
11
|
+
type ServiceDayAvailabilityInput,
|
|
7
12
|
} from "../serviceAvailabilityDisplay.js";
|
|
8
13
|
|
|
9
14
|
describe("applyAvailabilityPreset", () => {
|
|
@@ -150,3 +155,71 @@ describe("computeEarliestOpenDate", () => {
|
|
|
150
155
|
expect(result).toBeNull();
|
|
151
156
|
});
|
|
152
157
|
});
|
|
158
|
+
|
|
159
|
+
describe("isServiceAvailableOnDate", () => {
|
|
160
|
+
const friday = new Date(2026, 8, 11); // Friday Sep 11 2026
|
|
161
|
+
const saturday = new Date(2026, 8, 12);
|
|
162
|
+
|
|
163
|
+
it("treats a listing with no hours or blocks as available", () => {
|
|
164
|
+
expect(isServiceAvailableOnDate({}, friday)).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("closes weekdays when the service only works weekends", () => {
|
|
168
|
+
const service = {
|
|
169
|
+
availableHours: applyAvailabilityPreset("weekends"),
|
|
170
|
+
};
|
|
171
|
+
expect(isServiceAvailableOnDate(service, friday)).toBe(false);
|
|
172
|
+
expect(isServiceAvailableOnDate(service, saturday)).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("treats owner-blocked special rates as unavailable", () => {
|
|
176
|
+
const service = {
|
|
177
|
+
serviceRatesAssociation: {
|
|
178
|
+
serviceSpecialRates: [
|
|
179
|
+
{
|
|
180
|
+
isAvailable: false,
|
|
181
|
+
startDate: friday,
|
|
182
|
+
endDate: friday,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
expect(isServiceAvailableOnDate(service, friday)).toBe(false);
|
|
188
|
+
expect(isServiceAvailableOnDate(service, saturday)).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("treats a confirmed booking window as occupying the day", () => {
|
|
192
|
+
const service = {
|
|
193
|
+
bookedRanges: [{ start: friday, end: friday }],
|
|
194
|
+
};
|
|
195
|
+
expect(isServiceAvailableOnDate(service, friday)).toBe(false);
|
|
196
|
+
expect(isServiceAvailableOnDate(service, saturday)).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("counts and filters a mixed set of listings", () => {
|
|
200
|
+
type ListedService = ServiceDayAvailabilityInput & { id: string };
|
|
201
|
+
const open: ListedService = { id: "open" };
|
|
202
|
+
const booked: ListedService = {
|
|
203
|
+
id: "booked",
|
|
204
|
+
bookedRanges: [{ start: friday, end: friday }],
|
|
205
|
+
};
|
|
206
|
+
const blocked: ListedService = {
|
|
207
|
+
id: "blocked",
|
|
208
|
+
serviceRatesAssociation: {
|
|
209
|
+
serviceSpecialRates: [
|
|
210
|
+
{ isAvailable: false, startDate: friday, endDate: friday },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
const all: ListedService[] = [open, booked, blocked];
|
|
215
|
+
expect(countServicesAvailableOnDate(all, friday)).toBe(1);
|
|
216
|
+
expect(filterServicesAvailableOnDate(all, friday).map((s) => s.id)).toEqual(
|
|
217
|
+
["open"]
|
|
218
|
+
);
|
|
219
|
+
expect(
|
|
220
|
+
blockedRangesFromSpecialRates(
|
|
221
|
+
blocked.serviceRatesAssociation?.serviceSpecialRates
|
|
222
|
+
)
|
|
223
|
+
).toHaveLength(1);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -117,9 +117,11 @@ describe("getBashEventWizardPayoutStepPath", () => {
|
|
|
117
117
|
test("How substep indices match host-flow order", () => {
|
|
118
118
|
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.PRICE).toBe(0);
|
|
119
119
|
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.APPLICATION).toBe(1);
|
|
120
|
-
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.
|
|
121
|
-
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.
|
|
122
|
-
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.
|
|
120
|
+
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.PROMOTER_CHALLENGE).toBe(2);
|
|
121
|
+
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.PROMO_CODES).toBe(3);
|
|
122
|
+
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.GIVEAWAYS).toBe(7);
|
|
123
|
+
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.TASKS).toBe(9);
|
|
124
|
+
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP.PAYOUT).toBe(10);
|
|
123
125
|
expect(BASH_EVENT_WIZARD_HOW_SUBSTEP_PAYOUT).toBe(
|
|
124
126
|
BASH_EVENT_WIZARD_HOW_SUBSTEP.PAYOUT
|
|
125
127
|
);
|