@bash-app/bash-common 30.408.0 → 30.410.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__/partnerDealflow.test.d.ts +2 -0
- package/dist/__tests__/partnerDealflow.test.d.ts.map +1 -0
- package/dist/__tests__/partnerDealflow.test.js +51 -0
- package/dist/__tests__/partnerDealflow.test.js.map +1 -0
- package/dist/__tests__/partnerRevenueShareOptIn.test.d.ts +2 -0
- package/dist/__tests__/partnerRevenueShareOptIn.test.d.ts.map +1 -0
- package/dist/__tests__/partnerRevenueShareOptIn.test.js +55 -0
- package/dist/__tests__/partnerRevenueShareOptIn.test.js.map +1 -0
- package/dist/__tests__/reservedCustomBashEventType.test.d.ts +2 -0
- package/dist/__tests__/reservedCustomBashEventType.test.d.ts.map +1 -0
- package/dist/__tests__/reservedCustomBashEventType.test.js +19 -0
- package/dist/__tests__/reservedCustomBashEventType.test.js.map +1 -0
- package/dist/__tests__/serviceListingDealPreferences.test.d.ts +2 -0
- package/dist/__tests__/serviceListingDealPreferences.test.d.ts.map +1 -0
- package/dist/__tests__/serviceListingDealPreferences.test.js +84 -0
- package/dist/__tests__/serviceListingDealPreferences.test.js.map +1 -0
- package/dist/definitions.d.ts +5 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +42 -0
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +78 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +31 -0
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/homepageSyntheticRows.d.ts +9 -0
- package/dist/homepageSyntheticRows.d.ts.map +1 -0
- package/dist/homepageSyntheticRows.js +26 -0
- package/dist/homepageSyntheticRows.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/service/partnerDealflow.d.ts +12 -0
- package/dist/utils/service/partnerDealflow.d.ts.map +1 -0
- package/dist/utils/service/partnerDealflow.js +33 -0
- package/dist/utils/service/partnerDealflow.js.map +1 -0
- package/dist/utils/service/partnerRevenueShareOptIn.d.ts +18 -0
- package/dist/utils/service/partnerRevenueShareOptIn.d.ts.map +1 -0
- package/dist/utils/service/partnerRevenueShareOptIn.js +46 -0
- package/dist/utils/service/partnerRevenueShareOptIn.js.map +1 -0
- package/dist/utils/service/serviceListingDealPreferences.d.ts +28 -0
- package/dist/utils/service/serviceListingDealPreferences.d.ts.map +1 -0
- package/dist/utils/service/serviceListingDealPreferences.js +126 -0
- package/dist/utils/service/serviceListingDealPreferences.js.map +1 -0
- package/package.json +5 -2
- package/prisma/schema.prisma +58 -0
- package/src/__tests__/partnerDealflow.test.ts +62 -0
- package/src/__tests__/partnerRevenueShareOptIn.test.ts +68 -0
- package/src/__tests__/reservedCustomBashEventType.test.ts +27 -0
- package/src/__tests__/serviceListingDealPreferences.test.ts +112 -0
- package/src/definitions.ts +51 -0
- package/src/extendedSchemas.ts +33 -0
- package/src/homepageSyntheticRows.ts +32 -0
- package/src/index.ts +4 -0
- package/src/utils/service/partnerDealflow.ts +47 -0
- package/src/utils/service/partnerRevenueShareOptIn.ts +70 -0
- package/src/utils/service/serviceListingDealPreferences.ts +184 -0
package/prisma/schema.prisma
CHANGED
|
@@ -5900,6 +5900,14 @@ model EventService {
|
|
|
5900
5900
|
fulfillmentModes String[] @default([])
|
|
5901
5901
|
/// Minimum advance-notice days required for F&B bookings (e.g. 7 = one week).
|
|
5902
5902
|
leadTimeDays Int?
|
|
5903
|
+
openToRevenueShareWithHost Boolean @default(false)
|
|
5904
|
+
revenueShareWithHostPercentage Int?
|
|
5905
|
+
revenueShareWithHostBasis String?
|
|
5906
|
+
openToInKindDeals Boolean @default(false)
|
|
5907
|
+
inKindDealOfferText String?
|
|
5908
|
+
inKindEstimatedValueCents Int?
|
|
5909
|
+
dealRequiresMinimumCash Boolean @default(false)
|
|
5910
|
+
dealMinimumCashCents Int?
|
|
5903
5911
|
crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id], onDelete: Cascade)
|
|
5904
5912
|
serviceRange ServiceRange? @relation(fields: [serviceRangeId], references: [id])
|
|
5905
5913
|
service Service?
|
|
@@ -5958,6 +5966,14 @@ model EntertainmentService {
|
|
|
5958
5966
|
minimumQuantity Int?
|
|
5959
5967
|
tierPricing Json?
|
|
5960
5968
|
pricingNotes String?
|
|
5969
|
+
openToRevenueShareWithHost Boolean @default(false)
|
|
5970
|
+
revenueShareWithHostPercentage Int?
|
|
5971
|
+
revenueShareWithHostBasis String?
|
|
5972
|
+
openToInKindDeals Boolean @default(false)
|
|
5973
|
+
inKindDealOfferText String?
|
|
5974
|
+
inKindEstimatedValueCents Int?
|
|
5975
|
+
dealRequiresMinimumCash Boolean @default(false)
|
|
5976
|
+
dealMinimumCashCents Int?
|
|
5961
5977
|
crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id], onDelete: Cascade)
|
|
5962
5978
|
serviceRange ServiceRange? @relation(fields: [serviceRangeId], references: [id])
|
|
5963
5979
|
service Service?
|
|
@@ -6072,6 +6088,18 @@ model VendorPreferences {
|
|
|
6072
6088
|
bidMinLeadDays Int?
|
|
6073
6089
|
/// small | medium | large — typical event size the vendor prefers.
|
|
6074
6090
|
bidProjectSize String?
|
|
6091
|
+
/// Partner offers a revenue share with the host instead of (or in addition to) upfront fees.
|
|
6092
|
+
openToRevenueShareWithHost Boolean @default(false)
|
|
6093
|
+
/// 0..100 — share of the partner's relevant sales paid to the host.
|
|
6094
|
+
revenueShareWithHostPercentage Int?
|
|
6095
|
+
/// ALL_SALES | REFERRAL_ATTRIBUTED — which sales count toward the share.
|
|
6096
|
+
revenueShareWithHostBasis String?
|
|
6097
|
+
openToInKindDeals Boolean @default(false)
|
|
6098
|
+
inKindDealOfferText String?
|
|
6099
|
+
inKindEstimatedValueCents Int?
|
|
6100
|
+
/// Minimum cash (cents) required alongside rev-share or in-kind deals.
|
|
6101
|
+
dealRequiresMinimumCash Boolean @default(false)
|
|
6102
|
+
dealMinimumCashCents Int?
|
|
6075
6103
|
createdAt DateTime @default(now())
|
|
6076
6104
|
updatedAt DateTime @updatedAt
|
|
6077
6105
|
vendor Vendor @relation(fields: [vendorId], references: [id], onDelete: Cascade)
|
|
@@ -6150,6 +6178,14 @@ model Exhibitor {
|
|
|
6150
6178
|
bidMinLeadDays Int?
|
|
6151
6179
|
/// small | medium | large — typical event size the exhibitor is comfortable with.
|
|
6152
6180
|
bidProjectSize String?
|
|
6181
|
+
openToRevenueShareWithHost Boolean @default(false)
|
|
6182
|
+
revenueShareWithHostPercentage Int?
|
|
6183
|
+
revenueShareWithHostBasis String?
|
|
6184
|
+
openToInKindDeals Boolean @default(false)
|
|
6185
|
+
inKindDealOfferText String?
|
|
6186
|
+
inKindEstimatedValueCents Int?
|
|
6187
|
+
dealRequiresMinimumCash Boolean @default(false)
|
|
6188
|
+
dealMinimumCashCents Int?
|
|
6153
6189
|
|
|
6154
6190
|
crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id], onDelete: Cascade)
|
|
6155
6191
|
serviceRange ServiceRange? @relation(fields: [serviceRangeId], references: [id])
|
|
@@ -6186,6 +6222,14 @@ model Sponsor {
|
|
|
6186
6222
|
bidMinLeadDays Int?
|
|
6187
6223
|
/// small | medium | large — typical event size the sponsor is comfortable with.
|
|
6188
6224
|
bidProjectSize String?
|
|
6225
|
+
openToRevenueShareWithHost Boolean @default(false)
|
|
6226
|
+
revenueShareWithHostPercentage Int?
|
|
6227
|
+
revenueShareWithHostBasis String?
|
|
6228
|
+
openToInKindDeals Boolean @default(false)
|
|
6229
|
+
inKindDealOfferText String?
|
|
6230
|
+
inKindEstimatedValueCents Int?
|
|
6231
|
+
dealRequiresMinimumCash Boolean @default(false)
|
|
6232
|
+
dealMinimumCashCents Int?
|
|
6189
6233
|
|
|
6190
6234
|
service Service?
|
|
6191
6235
|
crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id])
|
|
@@ -6277,6 +6321,11 @@ model Venue {
|
|
|
6277
6321
|
revenueShareBasis String?
|
|
6278
6322
|
/// Free-form notes shown next to the rev-share split.
|
|
6279
6323
|
revenueShareNotes String?
|
|
6324
|
+
openToInKindDeals Boolean @default(false)
|
|
6325
|
+
inKindDealOfferText String?
|
|
6326
|
+
inKindEstimatedValueCents Int?
|
|
6327
|
+
dealRequiresMinimumCash Boolean @default(false)
|
|
6328
|
+
dealMinimumCashCents Int?
|
|
6280
6329
|
|
|
6281
6330
|
fbCarryoverCredits VenueFBCarryoverCredit[]
|
|
6282
6331
|
fbMinimumOverrides VenueFBMinimumOverride[]
|
|
@@ -8075,6 +8124,15 @@ model ServiceBooking {
|
|
|
8075
8124
|
revenueShareHostPctSnapshot Int?
|
|
8076
8125
|
/// Snapshot of `Venue.revenueShareBasis`.
|
|
8077
8126
|
revenueShareBasisSnapshot String?
|
|
8127
|
+
/// Partner / venue deal prefs at confirmation (rev-share, in-kind, min cash).
|
|
8128
|
+
openToRevenueShareWithHostSnapshot Boolean?
|
|
8129
|
+
revenueShareWithHostPercentageSnapshot Int?
|
|
8130
|
+
revenueShareWithHostBasisSnapshot String?
|
|
8131
|
+
openToInKindDealsSnapshot Boolean?
|
|
8132
|
+
inKindDealOfferTextSnapshot String?
|
|
8133
|
+
inKindEstimatedValueCentsSnapshot Int?
|
|
8134
|
+
dealRequiresMinimumCashSnapshot Boolean?
|
|
8135
|
+
dealMinimumCashCentsSnapshot Int?
|
|
8078
8136
|
/// Computed venue rev-share owed (cents) at settlement.
|
|
8079
8137
|
revenueShareCalculatedCents Int?
|
|
8080
8138
|
// Settlement lifecycle state (drives dunning, retries, dispute window).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ServiceExt } from "../extendedSchemas";
|
|
2
|
+
import {
|
|
3
|
+
readPartnerDealflowFields,
|
|
4
|
+
serviceListingOpenToInKindDeal,
|
|
5
|
+
} from "../utils/service/partnerDealflow";
|
|
6
|
+
|
|
7
|
+
describe("partnerDealflow", () => {
|
|
8
|
+
it("detects in-kind listings with non-empty offer text", () => {
|
|
9
|
+
const service = {
|
|
10
|
+
sponsor: {
|
|
11
|
+
openToInKindDeals: true,
|
|
12
|
+
inKindDealOfferText: "100 free socks",
|
|
13
|
+
},
|
|
14
|
+
} as ServiceExt;
|
|
15
|
+
|
|
16
|
+
expect(readPartnerDealflowFields(service)?.openToInKindDeals).toBe(true);
|
|
17
|
+
expect(serviceListingOpenToInKindDeal(service)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("rejects in-kind when offer text is blank", () => {
|
|
21
|
+
const service = {
|
|
22
|
+
sponsor: {
|
|
23
|
+
openToInKindDeals: true,
|
|
24
|
+
inKindDealOfferText: " ",
|
|
25
|
+
},
|
|
26
|
+
} as ServiceExt;
|
|
27
|
+
|
|
28
|
+
expect(serviceListingOpenToInKindDeal(service)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("reads dealflow from vendor preferences before flat partner rows", () => {
|
|
32
|
+
const service = {
|
|
33
|
+
vendor: {
|
|
34
|
+
preferences: {
|
|
35
|
+
openToInKindDeals: true,
|
|
36
|
+
inKindDealOfferText: "Sample trays",
|
|
37
|
+
dealRequiresMinimumCash: true,
|
|
38
|
+
dealMinimumCashCents: 5000,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
sponsor: {
|
|
42
|
+
openToInKindDeals: true,
|
|
43
|
+
inKindDealOfferText: "should not win",
|
|
44
|
+
},
|
|
45
|
+
} as ServiceExt;
|
|
46
|
+
|
|
47
|
+
const row = readPartnerDealflowFields(service);
|
|
48
|
+
expect(row?.inKindDealOfferText).toBe("Sample trays");
|
|
49
|
+
expect(row?.dealMinimumCashCents).toBe(5000);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("does not treat in-kind flag alone as a listing when toggle is off", () => {
|
|
53
|
+
const service = {
|
|
54
|
+
venue: {
|
|
55
|
+
openToInKindDeals: false,
|
|
56
|
+
inKindDealOfferText: "Free room night",
|
|
57
|
+
},
|
|
58
|
+
} as ServiceExt;
|
|
59
|
+
|
|
60
|
+
expect(serviceListingOpenToInKindDeal(service)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ServiceExt } from "../extendedSchemas";
|
|
2
|
+
import {
|
|
3
|
+
readPartnerRevenueShareFields,
|
|
4
|
+
serviceListingOpenToRevenueShare,
|
|
5
|
+
} from "../utils/service/partnerRevenueShareOptIn";
|
|
6
|
+
|
|
7
|
+
describe("partnerRevenueShareOptIn", () => {
|
|
8
|
+
it("treats venue ticket/bar rev share as discoverable without partner opt-in", () => {
|
|
9
|
+
const service = {
|
|
10
|
+
venue: {
|
|
11
|
+
revenueShareEnabled: true,
|
|
12
|
+
revenueShareHostPercentage: 10,
|
|
13
|
+
},
|
|
14
|
+
} as ServiceExt;
|
|
15
|
+
|
|
16
|
+
expect(serviceListingOpenToRevenueShare(service)).toBe(true);
|
|
17
|
+
expect(readPartnerRevenueShareFields(service)).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("requires partner opt-in plus a non-null percentage", () => {
|
|
21
|
+
const openNoPct = {
|
|
22
|
+
vendor: {
|
|
23
|
+
preferences: {
|
|
24
|
+
openToRevenueShareWithHost: true,
|
|
25
|
+
revenueShareWithHostPercentage: null,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
} as ServiceExt;
|
|
29
|
+
|
|
30
|
+
expect(serviceListingOpenToRevenueShare(openNoPct)).toBe(false);
|
|
31
|
+
|
|
32
|
+
const closedWithPct = {
|
|
33
|
+
vendor: {
|
|
34
|
+
preferences: {
|
|
35
|
+
openToRevenueShareWithHost: false,
|
|
36
|
+
revenueShareWithHostPercentage: 20,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
} as ServiceExt;
|
|
40
|
+
|
|
41
|
+
expect(serviceListingOpenToRevenueShare(closedWithPct)).toBe(false);
|
|
42
|
+
|
|
43
|
+
const openZero = {
|
|
44
|
+
exhibitor: {
|
|
45
|
+
openToRevenueShareWithHost: true,
|
|
46
|
+
revenueShareWithHostPercentage: 0,
|
|
47
|
+
},
|
|
48
|
+
} as ServiceExt;
|
|
49
|
+
|
|
50
|
+
expect(serviceListingOpenToRevenueShare(openZero)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("reads rev-share fields from the first matching partner subtype", () => {
|
|
54
|
+
const service = {
|
|
55
|
+
eventService: {
|
|
56
|
+
openToRevenueShareWithHost: true,
|
|
57
|
+
revenueShareWithHostPercentage: 12,
|
|
58
|
+
revenueShareWithHostBasis: "REFERRAL_ATTRIBUTED",
|
|
59
|
+
},
|
|
60
|
+
} as ServiceExt;
|
|
61
|
+
|
|
62
|
+
expect(readPartnerRevenueShareFields(service)).toEqual({
|
|
63
|
+
openToRevenueShareWithHost: true,
|
|
64
|
+
revenueShareWithHostPercentage: 12,
|
|
65
|
+
revenueShareWithHostBasis: "REFERRAL_ATTRIBUTED",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isReservedCustomBashEventTypeDisplayName,
|
|
3
|
+
} from "../definitions.js";
|
|
4
|
+
|
|
5
|
+
describe("isReservedCustomBashEventTypeDisplayName", () => {
|
|
6
|
+
it("blocks homepage synthetic section titles", () => {
|
|
7
|
+
expect(isReservedCustomBashEventTypeDisplayName("Featured")).toBe(true);
|
|
8
|
+
expect(isReservedCustomBashEventTypeDisplayName("Popular")).toBe(true);
|
|
9
|
+
expect(isReservedCustomBashEventTypeDisplayName("Trending")).toBe(true);
|
|
10
|
+
expect(isReservedCustomBashEventTypeDisplayName("Ideas")).toBe(true);
|
|
11
|
+
expect(isReservedCustomBashEventTypeDisplayName("Pro Events")).toBe(true);
|
|
12
|
+
expect(isReservedCustomBashEventTypeDisplayName("The crew's going")).toBe(
|
|
13
|
+
true
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("blocks built-in bash type labels", () => {
|
|
18
|
+
expect(isReservedCustomBashEventTypeDisplayName("Birthday")).toBe(true);
|
|
19
|
+
expect(isReservedCustomBashEventTypeDisplayName("House Party")).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("allows novel custom labels", () => {
|
|
23
|
+
expect(isReservedCustomBashEventTypeDisplayName("Rooftop Party")).toBe(
|
|
24
|
+
false
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { ServiceExt } from "../extendedSchemas";
|
|
2
|
+
import {
|
|
3
|
+
buildPartnerDealflowBookingSnapshot,
|
|
4
|
+
getServiceListingDealBadges,
|
|
5
|
+
getServiceListingDealDetailRows,
|
|
6
|
+
} from "../utils/service/serviceListingDealPreferences";
|
|
7
|
+
|
|
8
|
+
describe("serviceListingDealPreferences", () => {
|
|
9
|
+
it("shows min cash badge only when amount is set", () => {
|
|
10
|
+
const withMin = {
|
|
11
|
+
sponsor: {
|
|
12
|
+
dealRequiresMinimumCash: true,
|
|
13
|
+
dealMinimumCashCents: 25000,
|
|
14
|
+
},
|
|
15
|
+
} as ServiceExt;
|
|
16
|
+
|
|
17
|
+
expect(getServiceListingDealBadges(withMin).some((b) => b.id === "min-cash")).toBe(
|
|
18
|
+
true
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const checkboxOnly = {
|
|
22
|
+
sponsor: {
|
|
23
|
+
dealRequiresMinimumCash: true,
|
|
24
|
+
dealMinimumCashCents: null,
|
|
25
|
+
},
|
|
26
|
+
} as ServiceExt;
|
|
27
|
+
|
|
28
|
+
expect(
|
|
29
|
+
getServiceListingDealBadges(checkboxOnly).some((b) => b.id === "min-cash")
|
|
30
|
+
).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("snapshots venue rev share instead of partner fields when venue is enabled", () => {
|
|
34
|
+
const service = {
|
|
35
|
+
venue: {
|
|
36
|
+
revenueShareEnabled: true,
|
|
37
|
+
revenueShareHostPercentage: 18,
|
|
38
|
+
revenueShareBasis: "ALL_SALES",
|
|
39
|
+
},
|
|
40
|
+
eventService: {
|
|
41
|
+
openToRevenueShareWithHost: true,
|
|
42
|
+
revenueShareWithHostPercentage: 99,
|
|
43
|
+
},
|
|
44
|
+
} as ServiceExt;
|
|
45
|
+
|
|
46
|
+
const snap = buildPartnerDealflowBookingSnapshot(service);
|
|
47
|
+
|
|
48
|
+
expect(snap.openToRevenueShareWithHostSnapshot).toBe(true);
|
|
49
|
+
expect(snap.revenueShareWithHostPercentageSnapshot).toBe(18);
|
|
50
|
+
expect(snap.revenueShareWithHostBasisSnapshot).toBe("ALL_SALES");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("snapshots hybrid min-cash flag even when cents are unset (Stripe amount is separate)", () => {
|
|
54
|
+
const service = {
|
|
55
|
+
sponsor: {
|
|
56
|
+
dealRequiresMinimumCash: true,
|
|
57
|
+
dealMinimumCashCents: null,
|
|
58
|
+
openToInKindDeals: true,
|
|
59
|
+
inKindDealOfferText: "Branded socks",
|
|
60
|
+
},
|
|
61
|
+
} as ServiceExt;
|
|
62
|
+
|
|
63
|
+
const snap = buildPartnerDealflowBookingSnapshot(service);
|
|
64
|
+
|
|
65
|
+
expect(snap.dealRequiresMinimumCashSnapshot).toBe(true);
|
|
66
|
+
expect(snap.dealMinimumCashCentsSnapshot).toBeNull();
|
|
67
|
+
expect(snap.openToInKindDealsSnapshot).toBe(true);
|
|
68
|
+
expect(snap.inKindDealOfferTextSnapshot).toBe("Branded socks");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("snapshots rev-share opt-in with null percentage when partner saved incomplete prefs", () => {
|
|
72
|
+
const service = {
|
|
73
|
+
sponsor: {
|
|
74
|
+
openToRevenueShareWithHost: true,
|
|
75
|
+
revenueShareWithHostPercentage: null,
|
|
76
|
+
},
|
|
77
|
+
} as ServiceExt;
|
|
78
|
+
|
|
79
|
+
expect(buildPartnerDealflowBookingSnapshot(service)).toEqual({
|
|
80
|
+
openToRevenueShareWithHostSnapshot: true,
|
|
81
|
+
revenueShareWithHostPercentageSnapshot: null,
|
|
82
|
+
revenueShareWithHostBasisSnapshot: null,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("includes min-cash detail copy only when cents are positive", () => {
|
|
87
|
+
const hybrid = {
|
|
88
|
+
sponsor: {
|
|
89
|
+
dealRequiresMinimumCash: true,
|
|
90
|
+
dealMinimumCashCents: 25000,
|
|
91
|
+
openToInKindDeals: true,
|
|
92
|
+
inKindDealOfferText: "100 pairs",
|
|
93
|
+
},
|
|
94
|
+
} as ServiceExt;
|
|
95
|
+
|
|
96
|
+
const rows = getServiceListingDealDetailRows(hybrid);
|
|
97
|
+
expect(rows.some((r) => r.label.includes("Minimum cash"))).toBe(true);
|
|
98
|
+
|
|
99
|
+
const flagOnly = {
|
|
100
|
+
sponsor: {
|
|
101
|
+
dealRequiresMinimumCash: true,
|
|
102
|
+
dealMinimumCashCents: 0,
|
|
103
|
+
},
|
|
104
|
+
} as ServiceExt;
|
|
105
|
+
|
|
106
|
+
expect(
|
|
107
|
+
getServiceListingDealDetailRows(flagOnly).some((r) =>
|
|
108
|
+
r.label.includes("Minimum cash")
|
|
109
|
+
)
|
|
110
|
+
).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
package/src/definitions.ts
CHANGED
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
ServiceBookingExt,
|
|
33
33
|
type UserExt,
|
|
34
34
|
} from "./extendedSchemas.js";
|
|
35
|
+
import { DISCOVERY_SCOPE_SECTION_TITLE } from "./discoveryScope.js";
|
|
36
|
+
import { HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS } from "./homepageSyntheticRows.js";
|
|
35
37
|
import { LuxonDateRange } from "./utils/luxonUtils.js";
|
|
36
38
|
import { ApiServiceCantBookReason } from "./utils/service/apiServiceBookingApiUtils.js";
|
|
37
39
|
import { ServiceAttendeeOption } from "./utils/service/attendeeOptionUtils.js";
|
|
@@ -2182,6 +2184,55 @@ export function bashSectionTitleForBashType(bashType: string): string {
|
|
|
2182
2184
|
return pluralizeBashEventSingularLabel(spaced);
|
|
2183
2185
|
}
|
|
2184
2186
|
|
|
2187
|
+
function normalizeReservedCustomBashEventTypeLabel(label: string): string {
|
|
2188
|
+
return label.trim().toLowerCase().replace(/\s+/g, " ");
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
let reservedCustomBashEventTypeLabels: Set<string> | null = null;
|
|
2192
|
+
|
|
2193
|
+
function reservedCustomBashEventTypeLabelSet(): Set<string> {
|
|
2194
|
+
if (reservedCustomBashEventTypeLabels) {
|
|
2195
|
+
return reservedCustomBashEventTypeLabels;
|
|
2196
|
+
}
|
|
2197
|
+
const set = new Set<string>();
|
|
2198
|
+
const add = (value: string) => {
|
|
2199
|
+
const normalized = normalizeReservedCustomBashEventTypeLabel(value);
|
|
2200
|
+
if (normalized) set.add(normalized);
|
|
2201
|
+
};
|
|
2202
|
+
|
|
2203
|
+
for (const key of HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS) {
|
|
2204
|
+
add(key);
|
|
2205
|
+
add(bashSectionTitleForBashType(key));
|
|
2206
|
+
}
|
|
2207
|
+
for (const title of Object.values(DISCOVERY_SCOPE_SECTION_TITLE)) {
|
|
2208
|
+
add(title);
|
|
2209
|
+
}
|
|
2210
|
+
for (const title of Object.values(SYNTHETIC_BASH_SECTION_TITLE)) {
|
|
2211
|
+
add(title);
|
|
2212
|
+
}
|
|
2213
|
+
for (const enumKey of BASH_EVENT_TYPE_ENUM_VALUES) {
|
|
2214
|
+
const k = enumKey as BashEventType;
|
|
2215
|
+
add(enumKey);
|
|
2216
|
+
add(BashEventTypeToString[k]);
|
|
2217
|
+
add(bashSectionTitleForBashType(enumKey));
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
reservedCustomBashEventTypeLabels = set;
|
|
2221
|
+
return set;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
/**
|
|
2225
|
+
* Homepage section titles and built-in bash types cannot be created as custom
|
|
2226
|
+
* types in the wizard (would collide with synthetic discovery rows).
|
|
2227
|
+
*/
|
|
2228
|
+
export function isReservedCustomBashEventTypeDisplayName(
|
|
2229
|
+
displayName: string
|
|
2230
|
+
): boolean {
|
|
2231
|
+
return reservedCustomBashEventTypeLabelSet().has(
|
|
2232
|
+
normalizeReservedCustomBashEventTypeLabel(displayName)
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2185
2236
|
export type RecordKey = string | number | symbol;
|
|
2186
2237
|
export type PartialExcept<T, K extends keyof T> = Partial<T> & {
|
|
2187
2238
|
[P in K]: T[P];
|
package/src/extendedSchemas.ts
CHANGED
|
@@ -975,6 +975,24 @@ export const SERVICE_SUBTYPE_CROWD_RANGE_INCLUDE = {
|
|
|
975
975
|
serviceRange: true,
|
|
976
976
|
} as const;
|
|
977
977
|
|
|
978
|
+
/** Rev-share, in-kind, and min-cash (all partner subtype tables + venue). */
|
|
979
|
+
export const SERVICE_PARTNER_REV_INKIND_SELECT = {
|
|
980
|
+
openToRevenueShareWithHost: true,
|
|
981
|
+
revenueShareWithHostPercentage: true,
|
|
982
|
+
revenueShareWithHostBasis: true,
|
|
983
|
+
openToInKindDeals: true,
|
|
984
|
+
inKindDealOfferText: true,
|
|
985
|
+
inKindEstimatedValueCents: true,
|
|
986
|
+
dealRequiresMinimumCash: true,
|
|
987
|
+
dealMinimumCashCents: true,
|
|
988
|
+
} as const;
|
|
989
|
+
|
|
990
|
+
/** Includes host-bid opt-in (vendor prefs, exhibitor, sponsor only). */
|
|
991
|
+
export const SERVICE_PARTNER_DEAL_PREF_SELECT = {
|
|
992
|
+
...SERVICE_PARTNER_REV_INKIND_SELECT,
|
|
993
|
+
openToHostBids: true,
|
|
994
|
+
} as const;
|
|
995
|
+
|
|
978
996
|
export const VENDOR_DATA_TO_INCLUDE = {
|
|
979
997
|
...SERVICE_SUBTYPE_CROWD_RANGE_INCLUDE,
|
|
980
998
|
spaceRequirements: true,
|
|
@@ -1069,6 +1087,14 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
|
|
|
1069
1087
|
capacity: true,
|
|
1070
1088
|
amenities: true,
|
|
1071
1089
|
features: true,
|
|
1090
|
+
revenueShareEnabled: true,
|
|
1091
|
+
revenueShareHostPercentage: true,
|
|
1092
|
+
revenueShareBasis: true,
|
|
1093
|
+
openToInKindDeals: true,
|
|
1094
|
+
inKindDealOfferText: true,
|
|
1095
|
+
inKindEstimatedValueCents: true,
|
|
1096
|
+
dealRequiresMinimumCash: true,
|
|
1097
|
+
dealMinimumCashCents: true,
|
|
1072
1098
|
},
|
|
1073
1099
|
},
|
|
1074
1100
|
eventService: {
|
|
@@ -1086,6 +1112,7 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
|
|
|
1086
1112
|
goodsOrServices: true,
|
|
1087
1113
|
fulfillmentModes: true,
|
|
1088
1114
|
leadTimeDays: true,
|
|
1115
|
+
...SERVICE_PARTNER_REV_INKIND_SELECT,
|
|
1089
1116
|
},
|
|
1090
1117
|
},
|
|
1091
1118
|
entertainmentService: {
|
|
@@ -1094,6 +1121,7 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
|
|
|
1094
1121
|
entertainmentServiceTypes: true,
|
|
1095
1122
|
genre: true,
|
|
1096
1123
|
crowdSize: true,
|
|
1124
|
+
...SERVICE_PARTNER_REV_INKIND_SELECT,
|
|
1097
1125
|
},
|
|
1098
1126
|
},
|
|
1099
1127
|
vendor: {
|
|
@@ -1108,18 +1136,23 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
|
|
|
1108
1136
|
venueTypes: true,
|
|
1109
1137
|
detailsText: true,
|
|
1110
1138
|
crowdSize: true,
|
|
1139
|
+
preferences: {
|
|
1140
|
+
select: SERVICE_PARTNER_DEAL_PREF_SELECT,
|
|
1141
|
+
},
|
|
1111
1142
|
},
|
|
1112
1143
|
},
|
|
1113
1144
|
exhibitor: {
|
|
1114
1145
|
select: {
|
|
1115
1146
|
id: true,
|
|
1116
1147
|
crowdSize: true,
|
|
1148
|
+
...SERVICE_PARTNER_DEAL_PREF_SELECT,
|
|
1117
1149
|
},
|
|
1118
1150
|
},
|
|
1119
1151
|
sponsor: {
|
|
1120
1152
|
select: {
|
|
1121
1153
|
id: true,
|
|
1122
1154
|
crowdSize: true,
|
|
1155
|
+
...SERVICE_PARTNER_DEAL_PREF_SELECT,
|
|
1123
1156
|
},
|
|
1124
1157
|
},
|
|
1125
1158
|
} satisfies Prisma.ServiceInclude;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DISCOVERY_SCOPE_ROW_KEYS } from "./discoveryScope.js";
|
|
2
|
+
|
|
3
|
+
/** Algorithm / geo / social rows — not host-picked `BashEventType` values. */
|
|
4
|
+
export const HOMEPAGE_DISCOVERY_SYNTHETIC_ROW_KEYS = [
|
|
5
|
+
"Ideas",
|
|
6
|
+
"Featured",
|
|
7
|
+
"Popular",
|
|
8
|
+
"Trending",
|
|
9
|
+
"CrewGoing",
|
|
10
|
+
...DISCOVERY_SCOPE_ROW_KEYS,
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
/** Membership-tier inventory rows (creator tier, not event type). */
|
|
14
|
+
export const MEMBERSHIP_TIER_HOMEPAGE_ROW_KEYS = [
|
|
15
|
+
"Pro Events",
|
|
16
|
+
"Elite Events",
|
|
17
|
+
"Legend Events",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
export const HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS: readonly string[] = [
|
|
21
|
+
...HOMEPAGE_DISCOVERY_SYNTHETIC_ROW_KEYS,
|
|
22
|
+
...MEMBERSHIP_TIER_HOMEPAGE_ROW_KEYS,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export function isHomepageSyntheticSectionRowKey(key: string): boolean {
|
|
26
|
+
return (HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS as readonly string[]).includes(
|
|
27
|
+
key
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @deprecated Prefer `HOMEPAGE_DISCOVERY_SYNTHETIC_ROW_KEYS` — kept for app imports. */
|
|
32
|
+
export const HOMEPAGE_DISCOVERY_ROW_KEYS = HOMEPAGE_DISCOVERY_SYNTHETIC_ROW_KEYS;
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./sideQuestTypes.js";
|
|
|
15
15
|
export * from "./icebreakerPrompts.js";
|
|
16
16
|
export * from "./causeTags.js";
|
|
17
17
|
export * from "./discoveryScope.js";
|
|
18
|
+
export * from "./homepageSyntheticRows.js";
|
|
18
19
|
export * from "./userTaste.js";
|
|
19
20
|
export * from "./crewSocialCopy.js";
|
|
20
21
|
export * from "./suggestionScore.js";
|
|
@@ -407,6 +408,9 @@ export * from "./utils/agencyBookingCheckoutCopy.js";
|
|
|
407
408
|
export * from "./utils/service/serviceUtils.js";
|
|
408
409
|
export * from "./utils/service/serviceListingMediaUtils.js";
|
|
409
410
|
export * from "./utils/service/venueSettlementUtils.js";
|
|
411
|
+
export * from "./utils/service/partnerRevenueShareOptIn.js";
|
|
412
|
+
export * from "./utils/service/partnerDealflow.js";
|
|
413
|
+
export * from "./utils/service/serviceListingDealPreferences.js";
|
|
410
414
|
export * from "./utils/service/offerAllowListUtils.js";
|
|
411
415
|
export * from "./utils/service/offerComparisonUtils.js";
|
|
412
416
|
export * from "./utils/service/unifiedPricingResolver.js";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ServiceExt } from "../../extendedSchemas";
|
|
2
|
+
|
|
3
|
+
export type PartnerDealflowFields = {
|
|
4
|
+
openToInKindDeals?: boolean | null;
|
|
5
|
+
inKindDealOfferText?: string | null;
|
|
6
|
+
inKindEstimatedValueCents?: number | null;
|
|
7
|
+
dealRequiresMinimumCash?: boolean | null;
|
|
8
|
+
dealMinimumCashCents?: number | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function rowFromPartner(
|
|
12
|
+
row: PartnerDealflowFields | null | undefined
|
|
13
|
+
): PartnerDealflowFields | null {
|
|
14
|
+
if (!row) return null;
|
|
15
|
+
return row;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Reads in-kind + hybrid cash prefs from any partner subtype payload. */
|
|
19
|
+
export function readPartnerDealflowFields(
|
|
20
|
+
service: ServiceExt
|
|
21
|
+
): PartnerDealflowFields | null {
|
|
22
|
+
if (service.vendor?.preferences) {
|
|
23
|
+
return rowFromPartner(service.vendor.preferences);
|
|
24
|
+
}
|
|
25
|
+
if (service.exhibitor) {
|
|
26
|
+
return rowFromPartner(service.exhibitor);
|
|
27
|
+
}
|
|
28
|
+
if (service.sponsor) {
|
|
29
|
+
return rowFromPartner(service.sponsor);
|
|
30
|
+
}
|
|
31
|
+
if (service.entertainmentService) {
|
|
32
|
+
return rowFromPartner(service.entertainmentService);
|
|
33
|
+
}
|
|
34
|
+
if (service.eventService) {
|
|
35
|
+
return rowFromPartner(service.eventService);
|
|
36
|
+
}
|
|
37
|
+
if (service.venue) {
|
|
38
|
+
return rowFromPartner(service.venue);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function serviceListingOpenToInKindDeal(service: ServiceExt): boolean {
|
|
44
|
+
const row = readPartnerDealflowFields(service);
|
|
45
|
+
const text = row?.inKindDealOfferText?.trim();
|
|
46
|
+
return row?.openToInKindDeals === true && Boolean(text && text.length > 0);
|
|
47
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ServiceExt } from "../../extendedSchemas";
|
|
2
|
+
|
|
3
|
+
/** Which sales count when a partner shares revenue with the host. */
|
|
4
|
+
export const REVENUE_SHARE_WITH_HOST_BASIS = {
|
|
5
|
+
ALL_SALES: "ALL_SALES",
|
|
6
|
+
REFERRAL_ATTRIBUTED: "REFERRAL_ATTRIBUTED",
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type RevenueShareWithHostBasis =
|
|
10
|
+
(typeof REVENUE_SHARE_WITH_HOST_BASIS)[keyof typeof REVENUE_SHARE_WITH_HOST_BASIS];
|
|
11
|
+
|
|
12
|
+
export const REVENUE_SHARE_WITH_HOST_BASIS_LABEL: Record<
|
|
13
|
+
RevenueShareWithHostBasis,
|
|
14
|
+
string
|
|
15
|
+
> = {
|
|
16
|
+
ALL_SALES: "All sales at the bash",
|
|
17
|
+
REFERRAL_ATTRIBUTED: "Sales from guests using my code only",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type PartnerRevenueShareFields = {
|
|
21
|
+
openToRevenueShareWithHost?: boolean | null;
|
|
22
|
+
revenueShareWithHostPercentage?: number | null;
|
|
23
|
+
revenueShareWithHostBasis?: string | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function partnerFieldsFromRecord(
|
|
27
|
+
row: PartnerRevenueShareFields | null | undefined
|
|
28
|
+
): PartnerRevenueShareFields | null {
|
|
29
|
+
if (!row) return null;
|
|
30
|
+
return row;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Reads partner rev-share opt-in from any service subtype payload. */
|
|
34
|
+
export function readPartnerRevenueShareFields(
|
|
35
|
+
service: ServiceExt
|
|
36
|
+
): PartnerRevenueShareFields | null {
|
|
37
|
+
if (service.vendor?.preferences) {
|
|
38
|
+
return partnerFieldsFromRecord(service.vendor.preferences);
|
|
39
|
+
}
|
|
40
|
+
if (service.exhibitor) {
|
|
41
|
+
return partnerFieldsFromRecord(service.exhibitor);
|
|
42
|
+
}
|
|
43
|
+
if (service.sponsor) {
|
|
44
|
+
return partnerFieldsFromRecord(service.sponsor);
|
|
45
|
+
}
|
|
46
|
+
if (service.entertainmentService) {
|
|
47
|
+
return partnerFieldsFromRecord(service.entertainmentService);
|
|
48
|
+
}
|
|
49
|
+
if (service.eventService) {
|
|
50
|
+
return partnerFieldsFromRecord(service.eventService);
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** True when listing is open to a revenue-share deal (partner or venue ticket split). */
|
|
56
|
+
export function serviceListingOpenToRevenueShare(service: ServiceExt): boolean {
|
|
57
|
+
const venue = service.venue;
|
|
58
|
+
if (
|
|
59
|
+
venue?.revenueShareEnabled === true &&
|
|
60
|
+
venue.revenueShareHostPercentage != null
|
|
61
|
+
) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const partner = readPartnerRevenueShareFields(service);
|
|
65
|
+
return (
|
|
66
|
+
partner?.openToRevenueShareWithHost === true &&
|
|
67
|
+
partner.revenueShareWithHostPercentage != null &&
|
|
68
|
+
partner.revenueShareWithHostPercentage >= 0
|
|
69
|
+
);
|
|
70
|
+
}
|