@bash-app/bash-common 30.370.0 → 30.372.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 (109) hide show
  1. package/dist/accessScreening.js +1 -0
  2. package/dist/aiApproval.js +1 -0
  3. package/dist/aiEventPlan.js +1 -0
  4. package/dist/bashFeedTypes.js +1 -0
  5. package/dist/bookingIntent.js +1 -0
  6. package/dist/causeTags.js +1 -0
  7. package/dist/definitions.d.ts +6 -0
  8. package/dist/definitions.d.ts.map +1 -1
  9. package/dist/definitions.js.map +1 -1
  10. package/dist/entertainmentNotableVenues.js +1 -0
  11. package/dist/groupTicketUtils.js +1 -0
  12. package/dist/guestPartySize.js +1 -0
  13. package/dist/hostCrmAutomation.js +1 -0
  14. package/dist/hostCrmConstants.js +1 -0
  15. package/dist/hostCrmInsights.js +1 -0
  16. package/dist/hostCrmSegments.js +1 -0
  17. package/dist/icebreakerPrompts.js +1 -0
  18. package/dist/influencerAudience.js +1 -0
  19. package/dist/legalTemplates.js +1 -0
  20. package/dist/membershipDefinitions.js +1 -0
  21. package/dist/mirroredPrismaEnums.js +1 -0
  22. package/dist/onSaleCapabilityRecommendations.js +1 -0
  23. package/dist/partnerStoreTypes.js +1 -0
  24. package/dist/productLaunch.js +1 -0
  25. package/dist/relationshipHealth.js +1 -0
  26. package/dist/requestAuthUser.js +1 -0
  27. package/dist/sideQuestIdeas.js +1 -0
  28. package/dist/storePrintArtworkUtils.js +1 -0
  29. package/dist/storeTypes.js +1 -0
  30. package/dist/stripeListingSubscriptionMessages.js +1 -0
  31. package/dist/templates/eventTemplates.js +1 -0
  32. package/dist/ticketBnplPaymentMethods.js +1 -0
  33. package/dist/userReportTypes.js +1 -0
  34. package/dist/utils/agencyBookingCheckoutCopy.js +1 -0
  35. package/dist/utils/arrayUtils.js +1 -0
  36. package/dist/utils/bashPointsPaymentUtils.js +1 -0
  37. package/dist/utils/birthdayExperienceQuality.js +1 -0
  38. package/dist/utils/contentFilterUtils.js +1 -0
  39. package/dist/utils/discountEngine/eligibilityValidator.js +1 -0
  40. package/dist/utils/displayDateUtils.js +1 -0
  41. package/dist/utils/entertainmentDrawFit.js +1 -0
  42. package/dist/utils/entityUtils.js +1 -0
  43. package/dist/utils/featuredDiscoveryGeo.js +1 -0
  44. package/dist/utils/flyerUtils.js +1 -0
  45. package/dist/utils/generalDateTimeUtils.js +1 -0
  46. package/dist/utils/lobFlyerUtils.js +1 -0
  47. package/dist/utils/localPartnerHours.js +1 -0
  48. package/dist/utils/luxonUtils.d.ts +7 -7
  49. package/dist/utils/luxonUtils.d.ts.map +1 -1
  50. package/dist/utils/luxonUtils.js +1 -0
  51. package/dist/utils/mathUtils.js +1 -0
  52. package/dist/utils/mediaClientDefaults.js +1 -0
  53. package/dist/utils/nonprofitUtils.js +1 -0
  54. package/dist/utils/objUtils.js +1 -0
  55. package/dist/utils/orgPromotionalPackageUtils.js +1 -0
  56. package/dist/utils/organizationEntitlements.d.ts +17 -14
  57. package/dist/utils/organizationEntitlements.d.ts.map +1 -1
  58. package/dist/utils/organizationEntitlements.js +12 -10
  59. package/dist/utils/organizationEntitlements.js.map +1 -1
  60. package/dist/utils/promotionBlastUtils.js +1 -0
  61. package/dist/utils/reputationUtils.js +1 -0
  62. package/dist/utils/reviewDimensionUtils.js +1 -0
  63. package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.js +66 -1
  64. package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.js.map +1 -1
  65. package/dist/utils/service/__tests__/frontendServiceBookingUtils.test.d.ts +2 -0
  66. package/dist/utils/service/__tests__/frontendServiceBookingUtils.test.d.ts.map +1 -0
  67. package/dist/utils/service/__tests__/frontendServiceBookingUtils.test.js +165 -0
  68. package/dist/utils/service/__tests__/frontendServiceBookingUtils.test.js.map +1 -0
  69. package/dist/utils/service/apiServiceBookingApiUtils.d.ts +5 -3
  70. package/dist/utils/service/apiServiceBookingApiUtils.d.ts.map +1 -1
  71. package/dist/utils/service/apiServiceBookingApiUtils.js +30 -1
  72. package/dist/utils/service/apiServiceBookingApiUtils.js.map +1 -1
  73. package/dist/utils/service/attendeeOptionUtils.js +1 -0
  74. package/dist/utils/service/billingCapUtils.js +1 -0
  75. package/dist/utils/service/eventPromoterUtils.js +1 -0
  76. package/dist/utils/service/frontendServiceBookingUtils.d.ts +10 -4
  77. package/dist/utils/service/frontendServiceBookingUtils.d.ts.map +1 -1
  78. package/dist/utils/service/frontendServiceBookingUtils.js +16 -3
  79. package/dist/utils/service/frontendServiceBookingUtils.js.map +1 -1
  80. package/dist/utils/service/offerAllowListUtils.js +1 -0
  81. package/dist/utils/service/offerComparisonUtils.js +1 -0
  82. package/dist/utils/service/regexUtils.js +1 -0
  83. package/dist/utils/service/serviceBookingExposureUtils.js +1 -0
  84. package/dist/utils/service/serviceBookingTypes.d.ts +2 -2
  85. package/dist/utils/service/serviceBookingTypes.d.ts.map +1 -1
  86. package/dist/utils/service/serviceFeaturedSortUtils.js +1 -0
  87. package/dist/utils/service/unifiedPricingResolver.js +1 -0
  88. package/dist/utils/service/venueSettlementUtils.js +1 -0
  89. package/dist/utils/service/volunteerCompensationUtils.js +1 -0
  90. package/dist/utils/serviceAvailabilityDisplay.js +1 -0
  91. package/dist/utils/sortUtils.js +1 -0
  92. package/dist/utils/stringUtils.js +1 -0
  93. package/dist/utils/ticketCheckInDisplay.js +1 -0
  94. package/dist/utils/trustScoreUtils.js +1 -0
  95. package/dist/utils/typeUtils.js +1 -0
  96. package/dist/utils/userPromoCodeUtils.js +1 -0
  97. package/dist/utils/userUtils.js +1 -0
  98. package/dist/utils/venueLoyalty/redemptionCodeUtils.js +1 -0
  99. package/dist/utils/venueTypeFormatter.js +1 -0
  100. package/dist/utmAttribution.js +1 -0
  101. package/dist/venueMatch.js +1 -0
  102. package/package.json +1 -1
  103. package/src/definitions.ts +7 -0
  104. package/src/utils/organizationEntitlements.ts +17 -14
  105. package/src/utils/service/__tests__/apiServiceBookingApiUtils.test.ts +102 -2
  106. package/src/utils/service/__tests__/frontendServiceBookingUtils.test.ts +217 -0
  107. package/src/utils/service/apiServiceBookingApiUtils.ts +53 -1
  108. package/src/utils/service/frontendServiceBookingUtils.ts +33 -2
  109. package/src/utils/service/serviceBookingTypes.ts +2 -1
@@ -1,9 +1,16 @@
1
1
  import {
2
2
  apiTransformAddOnsToAddonInputs,
3
+ apiTransformPackagesToPackageInputs,
4
+ servicePriceToBookToApiPackages,
3
5
  serviceBookingParamsToLuxon,
4
6
  } from "../apiServiceBookingApiUtils";
5
- import type { ApiServiceAddonParams, ApiServiceBookingParams } from "../../../definitions";
6
- import type { ServiceAddonExt } from "../../../extendedSchemas";
7
+ import type {
8
+ ApiServiceAddonParams,
9
+ ApiServiceBookingParams,
10
+ ApiServicePackageParams,
11
+ } from "../../../definitions";
12
+ import type { ServiceAddonExt, ServicePackageExt } from "../../../extendedSchemas";
13
+ import type { FrontendServiceGetPriceToBookResult } from "../frontendServiceBookingUtils";
7
14
 
8
15
  // ---------------------------------------------------------------------------
9
16
  // apiTransformAddOnsToAddonInputs
@@ -65,6 +72,99 @@ describe("apiTransformAddOnsToAddonInputs", () => {
65
72
  });
66
73
  });
67
74
 
75
+ // ---------------------------------------------------------------------------
76
+ // apiTransformPackagesToPackageInputs
77
+ // ---------------------------------------------------------------------------
78
+
79
+ function makePackageExt(id: string, name = "Package"): ServicePackageExt {
80
+ return {
81
+ id,
82
+ name,
83
+ description: "",
84
+ priceCents: 5000,
85
+ serviceAddons: [],
86
+ } as unknown as ServicePackageExt;
87
+ }
88
+
89
+ function makePackageParam(
90
+ packageId: string,
91
+ qty = 1
92
+ ): ApiServicePackageParams {
93
+ return { packageId, chosenQuantity: qty };
94
+ }
95
+
96
+ describe("apiTransformPackagesToPackageInputs", () => {
97
+ it("returns empty array when no packages are provided", () => {
98
+ expect(apiTransformPackagesToPackageInputs([], [])).toEqual([]);
99
+ });
100
+
101
+ it("maps package params to their matching ServicePackageExt", () => {
102
+ const servicePackages = [makePackageExt("p1"), makePackageExt("p2")];
103
+ const packagesApi = [makePackageParam("p1", 2)];
104
+
105
+ const result = apiTransformPackagesToPackageInputs(
106
+ packagesApi,
107
+ servicePackages
108
+ );
109
+
110
+ expect(result).toHaveLength(1);
111
+ expect(result[0].id).toBe("p1");
112
+ expect(result[0].chosenQuantity).toBe(2);
113
+ });
114
+
115
+ it("merges package params over the ServicePackageExt fields (params win)", () => {
116
+ const servicePackages = [makePackageExt("p1")];
117
+ const packagesApi = [
118
+ { packageId: "p1", chosenQuantity: 5, extra: "field" } as any,
119
+ ];
120
+
121
+ const result = apiTransformPackagesToPackageInputs(
122
+ packagesApi,
123
+ servicePackages
124
+ );
125
+ expect(result[0].chosenQuantity).toBe(5);
126
+ });
127
+
128
+ it("throws when a packageId does not match any ServicePackageExt", () => {
129
+ const servicePackages = [makePackageExt("p1")];
130
+ const packagesApi = [makePackageParam("ghost-id")];
131
+
132
+ expect(() =>
133
+ apiTransformPackagesToPackageInputs(packagesApi, servicePackages)
134
+ ).toThrow(/Failed to match package with id: ghost-id/);
135
+ });
136
+ });
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // servicePriceToBookToApiPackages
140
+ // ---------------------------------------------------------------------------
141
+
142
+ describe("servicePriceToBookToApiPackages", () => {
143
+ it("returns empty array when priceToBook has no packages", () => {
144
+ const priceToBook = {
145
+ packages: [],
146
+ } as unknown as FrontendServiceGetPriceToBookResult;
147
+
148
+ expect(servicePriceToBookToApiPackages(priceToBook)).toEqual([]);
149
+ });
150
+
151
+ it("maps priceToBook packages to ApiServicePackageParams", () => {
152
+ const priceToBook = {
153
+ packages: [
154
+ { packageId: "p1", chosenQuantity: 2, costPQCents: 5000 },
155
+ { packageId: "p2", chosenQuantity: 1, costPQCents: 8000 },
156
+ ],
157
+ } as unknown as FrontendServiceGetPriceToBookResult;
158
+
159
+ const result = servicePriceToBookToApiPackages(priceToBook);
160
+
161
+ expect(result).toEqual([
162
+ { packageId: "p1", chosenQuantity: 2 },
163
+ { packageId: "p2", chosenQuantity: 1 },
164
+ ]);
165
+ });
166
+ });
167
+
68
168
  // ---------------------------------------------------------------------------
69
169
  // serviceBookingParamsToLuxon
70
170
  // ---------------------------------------------------------------------------
@@ -0,0 +1,217 @@
1
+ import { DateTime } from "luxon";
2
+
3
+ import {
4
+ frontendServiceGetPriceToBook,
5
+ frontendServiceGetPriceToBookFees,
6
+ ServicePackageInput,
7
+ } from "../frontendServiceBookingUtils";
8
+ import type { ServiceAttendeeOption } from "../attendeeOptionUtils";
9
+ import type { ServiceRatesAssociationExt } from "../../../extendedSchemas";
10
+ import type { LuxonDateRange } from "../../luxonUtils";
11
+
12
+ function makeRange(startISO: string, endISO: string): LuxonDateRange {
13
+ return {
14
+ start: DateTime.fromISO(startISO, { zone: "utc" }),
15
+ end: DateTime.fromISO(endISO, { zone: "utc" }),
16
+ };
17
+ }
18
+
19
+ function makeFlatRateAssociation(
20
+ flatRateCents = 20000
21
+ ): ServiceRatesAssociationExt {
22
+ return {
23
+ serviceId: "svc-1",
24
+ serviceGeneralRates: {
25
+ flatRateCents,
26
+ hourlyRateCents: null,
27
+ dailyRateCents: null,
28
+ minimumTimeBlockHours: null,
29
+ cleaningFeePerBookingCents: 0,
30
+ },
31
+ serviceSpecialRates: [],
32
+ serviceDailyRates: [],
33
+ } as unknown as ServiceRatesAssociationExt;
34
+ }
35
+
36
+ function makeTimeBasedAssociation(
37
+ hourlyRateCents = 5000
38
+ ): ServiceRatesAssociationExt {
39
+ return {
40
+ serviceId: "svc-1",
41
+ serviceGeneralRates: {
42
+ flatRateCents: null,
43
+ hourlyRateCents,
44
+ dailyRateCents: null,
45
+ minimumTimeBlockHours: null,
46
+ cleaningFeePerBookingCents: 0,
47
+ },
48
+ serviceSpecialRates: [],
49
+ serviceDailyRates: [],
50
+ } as unknown as ServiceRatesAssociationExt;
51
+ }
52
+
53
+ function makePackageInput(
54
+ overrides: Partial<ServicePackageInput> = {}
55
+ ): ServicePackageInput {
56
+ return {
57
+ id: "pkg-1",
58
+ name: "Gold DJ Package",
59
+ description: "",
60
+ priceCents: 10000,
61
+ serviceAddons: [],
62
+ chosenQuantity: 1,
63
+ ...overrides,
64
+ } as unknown as ServicePackageInput;
65
+ }
66
+
67
+ const ATTENDEE_OPTION = null as unknown as ServiceAttendeeOption;
68
+ const RANGE_2H = makeRange("2025-07-01T09:00:00Z", "2025-07-01T11:00:00Z");
69
+
70
+ describe("frontendServiceGetPriceToBookFees — packages", () => {
71
+ it("returns an empty packages array and adds nothing to the subtotal when no packages are given", () => {
72
+ const result = frontendServiceGetPriceToBookFees(
73
+ makeFlatRateAssociation(20000),
74
+ {
75
+ daysToBookParams: [{ dateTimeRange: RANGE_2H, fees: [], addOns: [] }],
76
+ selectedAttendeeOption: ATTENDEE_OPTION,
77
+ additionalFees: [],
78
+ addOns: [],
79
+ timezone: "UTC",
80
+ }
81
+ );
82
+
83
+ expect(result.packages).toEqual([]);
84
+ expect(result.subtotalBeforeTaxesCents).toBe(20000);
85
+ });
86
+
87
+ it("folds a single package's cost into the subtotal", () => {
88
+ const result = frontendServiceGetPriceToBookFees(
89
+ makeFlatRateAssociation(20000),
90
+ {
91
+ daysToBookParams: [{ dateTimeRange: RANGE_2H, fees: [], addOns: [] }],
92
+ selectedAttendeeOption: ATTENDEE_OPTION,
93
+ additionalFees: [],
94
+ addOns: [],
95
+ packages: [makePackageInput({ priceCents: 15000, chosenQuantity: 1 })],
96
+ timezone: "UTC",
97
+ }
98
+ );
99
+
100
+ expect(result.packages).toHaveLength(1);
101
+ expect(result.packages[0]).toMatchObject({
102
+ packageId: "pkg-1",
103
+ chosenQuantity: 1,
104
+ costPQCents: 15000,
105
+ });
106
+ expect(result.subtotalBeforeTaxesCents).toBe(20000 + 15000);
107
+ });
108
+
109
+ it("multiplies a package's cost by chosenQuantity", () => {
110
+ const result = frontendServiceGetPriceToBookFees(
111
+ makeFlatRateAssociation(20000),
112
+ {
113
+ daysToBookParams: [{ dateTimeRange: RANGE_2H, fees: [], addOns: [] }],
114
+ selectedAttendeeOption: ATTENDEE_OPTION,
115
+ additionalFees: [],
116
+ addOns: [],
117
+ packages: [makePackageInput({ priceCents: 15000, chosenQuantity: 3 })],
118
+ timezone: "UTC",
119
+ }
120
+ );
121
+
122
+ expect(result.packages[0].chosenQuantity).toBe(3);
123
+ expect(result.subtotalBeforeTaxesCents).toBe(20000 + 15000 * 3);
124
+ });
125
+
126
+ it("sums multiple distinct packages into the subtotal", () => {
127
+ const result = frontendServiceGetPriceToBookFees(
128
+ makeFlatRateAssociation(20000),
129
+ {
130
+ daysToBookParams: [{ dateTimeRange: RANGE_2H, fees: [], addOns: [] }],
131
+ selectedAttendeeOption: ATTENDEE_OPTION,
132
+ additionalFees: [],
133
+ addOns: [],
134
+ packages: [
135
+ makePackageInput({
136
+ id: "pkg-1",
137
+ priceCents: 15000,
138
+ chosenQuantity: 1,
139
+ }),
140
+ makePackageInput({
141
+ id: "pkg-2",
142
+ priceCents: 8000,
143
+ chosenQuantity: 2,
144
+ }),
145
+ ],
146
+ timezone: "UTC",
147
+ }
148
+ );
149
+
150
+ expect(result.packages).toHaveLength(2);
151
+ expect(result.subtotalBeforeTaxesCents).toBe(20000 + 15000 + 8000 * 2);
152
+ });
153
+
154
+ it("defaults chosenQuantity to 0 when not provided", () => {
155
+ const result = frontendServiceGetPriceToBookFees(
156
+ makeFlatRateAssociation(20000),
157
+ {
158
+ daysToBookParams: [{ dateTimeRange: RANGE_2H, fees: [], addOns: [] }],
159
+ selectedAttendeeOption: ATTENDEE_OPTION,
160
+ additionalFees: [],
161
+ addOns: [],
162
+ packages: [
163
+ makePackageInput({ priceCents: 15000, chosenQuantity: undefined }),
164
+ ],
165
+ timezone: "UTC",
166
+ }
167
+ );
168
+
169
+ expect(result.packages[0].chosenQuantity).toBe(0);
170
+ expect(result.subtotalBeforeTaxesCents).toBe(20000);
171
+ });
172
+
173
+ it("applies package pricing on top of time-based (hourly) bookings too", () => {
174
+ const result = frontendServiceGetPriceToBookFees(
175
+ makeTimeBasedAssociation(5000),
176
+ {
177
+ daysToBookParams: [{ dateTimeRange: RANGE_2H, fees: [], addOns: [] }],
178
+ selectedAttendeeOption: ATTENDEE_OPTION,
179
+ additionalFees: [],
180
+ addOns: [],
181
+ packages: [makePackageInput({ priceCents: 12000, chosenQuantity: 1 })],
182
+ timezone: "UTC",
183
+ }
184
+ );
185
+
186
+ // 2 hours @ $50/hr = $100 in day totals, plus the $120 package.
187
+ expect(result.daysTotalBeforeTaxesCents).toBe(10000);
188
+ expect(result.subtotalBeforeTaxesCents).toBe(10000 + 12000);
189
+ });
190
+ });
191
+
192
+ describe("frontendServiceGetPriceToBook — packages", () => {
193
+ it("passes packages through to frontendServiceGetPriceToBookFees", () => {
194
+ const result = frontendServiceGetPriceToBook(makeFlatRateAssociation(20000), {
195
+ addOns: [],
196
+ packages: [makePackageInput({ priceCents: 5000, chosenQuantity: 2 })],
197
+ selectedAttendeeOption: ATTENDEE_OPTION,
198
+ bookingDateTimeRanges: [RANGE_2H],
199
+ timezone: "UTC",
200
+ });
201
+
202
+ expect(result.packages).toHaveLength(1);
203
+ expect(result.subtotalBeforeTaxesCents).toBe(20000 + 5000 * 2);
204
+ });
205
+
206
+ it("defaults to an empty packages array when packages is omitted", () => {
207
+ const result = frontendServiceGetPriceToBook(makeFlatRateAssociation(20000), {
208
+ addOns: [],
209
+ selectedAttendeeOption: ATTENDEE_OPTION,
210
+ bookingDateTimeRanges: [RANGE_2H],
211
+ timezone: "UTC",
212
+ });
213
+
214
+ expect(result.packages).toEqual([]);
215
+ expect(result.subtotalBeforeTaxesCents).toBe(20000);
216
+ });
217
+ });
@@ -5,6 +5,7 @@ import {
5
5
  ApiServiceBookedDayParams,
6
6
  ApiServiceBookedDayParamsLuxon,
7
7
  ApiServiceBookingParams,
8
+ ApiServicePackageParams,
8
9
  ApiServicePriceToBookResult,
9
10
  } from "../../definitions.js";
10
11
  import {
@@ -13,8 +14,10 @@ import {
13
14
  ServiceBookingDayExt,
14
15
  ServiceBookingExt,
15
16
  ServiceBookingFeeExt,
17
+ ServiceBookingPackageExt,
16
18
  ServiceBookingPriceBreakdownExt,
17
19
  ServiceExt,
20
+ ServicePackageExt,
18
21
  } from "../../extendedSchemas.js";
19
22
  import { dateTimeFromString, dateTimeRangeFromDates } from "../luxonUtils.js";
20
23
  import { serviceAttendeeOptions } from "./attendeeOptionUtils.js";
@@ -22,6 +25,7 @@ import {
22
25
  FrontendServiceBookingDayInfo,
23
26
  FrontendServiceGetPriceToBookResult,
24
27
  ServiceAddonInput,
28
+ ServicePackageInput,
25
29
  } from "./frontendServiceBookingUtils.js";
26
30
  import {
27
31
  ServiceBookingPriceBreakdownBaseT,
@@ -79,6 +83,31 @@ export function apiTransformAddOnsToAddonInputs<
79
83
  });
80
84
  }
81
85
 
86
+ export function apiTransformPackagesToPackageInputs<
87
+ PackageParams_t extends ApiServicePackageParams
88
+ >(
89
+ packagesApi: PackageParams_t[],
90
+ servicePackages: ServicePackageExt[]
91
+ ): ServicePackageInput[] {
92
+ // Create a Map for faster lookup by packageId
93
+ const packageMap = new Map<string, ServicePackageExt>(
94
+ servicePackages.map((pkg) => [pkg.id, pkg])
95
+ );
96
+
97
+ return packagesApi.map((packageApi) => {
98
+ const matchedPackage = packageMap.get(packageApi.packageId);
99
+ if (!matchedPackage) {
100
+ throw new Error(
101
+ `Failed to match package with id: ${packageApi.packageId}`
102
+ );
103
+ }
104
+ return {
105
+ ...matchedPackage,
106
+ ...packageApi,
107
+ } as ServicePackageInput;
108
+ });
109
+ }
110
+
82
111
  export function apiTransformBookedDays(
83
112
  priceToBook: ApiServicePriceToBookResult
84
113
  ): Partial<ServiceBookingDayExt>[] {
@@ -108,6 +137,8 @@ export function apiTransformBookedDays(
108
137
  startDate: day.dateTimeRange.start.toJSDate(),
109
138
  endDate: day.dateTimeRange.end.toJSDate(),
110
139
  addOns: addOns,
140
+ // Packages are booking-level bundles (see servicePriceToBookToApiBookingBase),
141
+ // not tied to a specific day, so this stays empty here.
111
142
  packages: [],
112
143
  fees: day.fees as ServiceBookingFeeExt[],
113
144
  priceBreakdown: priceBreakdown,
@@ -164,6 +195,7 @@ export function serviceGetPriceToBookFromBooking(
164
195
  flatRateCents: booking.flatRateCents,
165
196
  additionalFees: booking.additionalFees,
166
197
  addOns: booking.addOns,
198
+ packages: booking.packages,
167
199
  daysTotalBeforeTaxesCents: booking.daysTotalATBCents,
168
200
  subtotalBeforeTaxesCents: booking.subtotalATBCents,
169
201
  totalBeforeTaxesCents: booking.totalATBCents,
@@ -234,6 +266,18 @@ export function servicePriceToBookToApiDays(
234
266
 
235
267
  return bookedDays;
236
268
  }
269
+
270
+ export function servicePriceToBookToApiPackages(
271
+ priceToBook: FrontendServiceGetPriceToBookResult
272
+ ): ApiServicePackageParams[] {
273
+ return priceToBook.packages.map(
274
+ (pkg): ApiServicePackageParams => ({
275
+ packageId: pkg.packageId,
276
+ chosenQuantity: pkg.chosenQuantity,
277
+ })
278
+ );
279
+ }
280
+
237
281
  export interface ServicePriceToBookToApiBookingBaseArgs {
238
282
  bookingType: ServiceBookingType;
239
283
  status: ServiceBookingStatus;
@@ -260,6 +304,14 @@ export function servicePriceToBookToApiBookingBase({
260
304
  costPQCents: addOn.costPQCents,
261
305
  })
262
306
  );
307
+ const packages = priceToBook.packages.map(
308
+ (pkg): Partial<ServiceBookingPackageExt> => ({
309
+ packageId: pkg.packageId,
310
+ package: pkg.package,
311
+ chosenQuantity: pkg.chosenQuantity,
312
+ costPQCents: pkg.costPQCents,
313
+ })
314
+ );
263
315
 
264
316
  return {
265
317
  bookingType: bookingType,
@@ -273,7 +325,7 @@ export function servicePriceToBookToApiBookingBase({
273
325
  subtotalATBCents: priceToBook.subtotalBeforeTaxesCents,
274
326
  totalATBCents: priceToBook.totalBeforeTaxesCents,
275
327
  addOns: addOns as ServiceBookingAddOnExt[],
276
- packages: [],
328
+ packages: packages as ServiceBookingPackageExt[],
277
329
  allowPromiseToPay: allowPromiseToPay,
278
330
  isFreeGuest: isFreeGuest,
279
331
  // Phase 3 — providers opt in per-booking when they want BP-paid tickets
@@ -10,6 +10,7 @@ import {
10
10
  ServiceBookingFeeExt,
11
11
  ServiceBookingPriceBreakdownExt,
12
12
  ServiceDailyRatesExt,
13
+ ServicePackageExt,
13
14
  ServiceRatesAssociationExt,
14
15
  ServiceSpecialRatesExt,
15
16
  } from "../../extendedSchemas.js";
@@ -31,16 +32,20 @@ import {
31
32
  ServiceBookingFeeBase,
32
33
  ServiceBookingPriceBreakdownBaseT,
33
34
  ServiceBookingAddOnBase,
35
+ ServiceBookingPackageBase,
34
36
  } from "./serviceBookingTypes.js";
35
37
  import {
36
38
  calculateStripeProcessingFeeCents,
37
39
  convertDollarsToCents,
38
40
  } from "../paymentUtils.js";
39
-
40
41
  export interface ServiceAddonInput extends ServiceAddon {
41
42
  chosenQuantity?: number;
42
43
  }
43
44
 
45
+ export interface ServicePackageInput extends ServicePackageExt {
46
+ chosenQuantity?: number;
47
+ }
48
+
44
49
  export interface FrontendServiceBookingDayInfoParams {
45
50
  dateTimeRange: LuxonDateRange;
46
51
  fees: ServiceBookingFeeBase[];
@@ -70,6 +75,7 @@ export interface FrontendServiceGetPriceToBookResult {
70
75
  flatRateCents?: number; //only valid on flatRate
71
76
 
72
77
  addOns: ServiceBookingAddOnBase[]; //for bookings that do not bill by the time. e.g. flat rate bookings. For time based bookings, addOns are part of the day
78
+ packages: ServiceBookingPackageBase[]; //booking-level bundled packages, independent of hourly/daily/flat rate option
73
79
 
74
80
  daysTotalBeforeTaxesCents: number;
75
81
  subtotalBeforeTaxesCents: number; //sum of all daysTotalBeforeTaxesCents, plus addOns/packages
@@ -197,6 +203,7 @@ export interface FrontendServiceGetPriceToBookFeesParams {
197
203
 
198
204
  additionalFees: ServiceBookingFeeBase[];
199
205
  addOns: ServiceAddonInput[]; //used for bookings that do not bill by the time. e.g. flat rate bookings
206
+ packages?: ServicePackageInput[]; //booking-level bundled packages, additive regardless of rate option
200
207
 
201
208
  // overlappingBusinessHours: LuxonDateRange[] | null;
202
209
  timezone: string | null | undefined;
@@ -208,6 +215,7 @@ export function frontendServiceGetPriceToBookFees(
208
215
  daysToBookParams,
209
216
  selectedAttendeeOption,
210
217
  addOns,
218
+ packages = [],
211
219
  additionalFees,
212
220
  // overlappingBusinessHours,
213
221
  timezone,
@@ -240,6 +248,19 @@ export function frontendServiceGetPriceToBookFees(
240
248
  })
241
249
  : [];
242
250
 
251
+ // Packages are booking-level bundled line items (e.g. "Gold DJ Package") and apply
252
+ // on top of the booking regardless of hourly/daily/flat rate option.
253
+ const usedPackages: ServiceBookingPackageBase[] = packages.map(
254
+ (pkg): ServiceBookingPackageBase => {
255
+ return {
256
+ package: pkg,
257
+ packageId: pkg.id,
258
+ chosenQuantity: pkg.chosenQuantity ?? 0,
259
+ costPQCents: pkg.priceCents,
260
+ };
261
+ }
262
+ );
263
+
243
264
  const bookingDayResults: FrontendServiceBookingDayInfo[] =
244
265
  frontendServiceGetBookingDayInfo({
245
266
  rateOption: rateOption,
@@ -286,7 +307,13 @@ export function frontendServiceGetPriceToBookFees(
286
307
  0
287
308
  );
288
309
 
289
- const subtotalBeforeTaxesCents = daysTotalBeforeTaxesCents + addOnsTotalCents;
310
+ const packagesTotalCents = usedPackages.reduce(
311
+ (sofar, pkg) => sofar + pkg.costPQCents * pkg.chosenQuantity,
312
+ 0
313
+ );
314
+
315
+ const subtotalBeforeTaxesCents =
316
+ daysTotalBeforeTaxesCents + addOnsTotalCents + packagesTotalCents;
290
317
 
291
318
  if (!hasCustomProcessingFee) {
292
319
  // Same pass-through formula as bash ticket/donation checkout (STRIPE_PROCESSING_FEE).
@@ -319,6 +346,7 @@ export function frontendServiceGetPriceToBookFees(
319
346
  totalWithFees: totalBeforeTaxesCents, // Same as totalBeforeTaxesCents
320
347
  additionalFees: allAdditionalFees,
321
348
  addOns: usedAddons,
349
+ packages: usedPackages,
322
350
  minimumTimeBlockHours: minimumTimeBlockHours,
323
351
  rateOption: rateOption,
324
352
  flatRateCents:
@@ -330,6 +358,7 @@ export function frontendServiceGetPriceToBookFees(
330
358
 
331
359
  export interface FrontendServiceGetPriceToBookParams {
332
360
  addOns: ServiceAddonInput[];
361
+ packages?: ServicePackageInput[];
333
362
 
334
363
  selectedAttendeeOption: ServiceAttendeeOption;
335
364
 
@@ -343,6 +372,7 @@ export function frontendServiceGetPriceToBook(
343
372
  serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
344
373
  {
345
374
  addOns,
375
+ packages = [],
346
376
  selectedAttendeeOption,
347
377
  bookingDateTimeRanges, //list of dateTime ranges to book
348
378
  // overlappingBusinessHours,
@@ -380,6 +410,7 @@ export function frontendServiceGetPriceToBook(
380
410
  selectedAttendeeOption: selectedAttendeeOption,
381
411
  additionalFees: [],
382
412
  addOns: addOns,
413
+ packages: packages,
383
414
  timezone: timezone,
384
415
  });
385
416
  }
@@ -2,6 +2,7 @@ import { ServiceBookingType, ServiceBookingStatus } from "@prisma/client";
2
2
  import {
3
3
  ServiceBookingAddOnExt,
4
4
  ServiceBookingFeeExt,
5
+ ServiceBookingPackageExt,
5
6
  ServiceBookingPriceBreakdownExt,
6
7
  ServiceBookingDayExt,
7
8
  ServiceDailyRatesExt,
@@ -21,7 +22,7 @@ export type ServiceBookingAddOnBase = Omit<
21
22
  | "serviceBooking"
22
23
  >;
23
24
  export type ServiceBookingPackageBase = Omit<
24
- ServiceBookingAddOnExt,
25
+ ServiceBookingPackageExt,
25
26
  | "id"
26
27
  | "serviceBookingDayId"
27
28
  | "serviceBookingDay"