@bash-app/bash-common 30.235.0 → 30.237.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__/statusEnums.test.js +88 -7
- package/dist/__tests__/statusEnums.test.js.map +1 -1
- package/dist/definitions.d.ts +45 -14
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +38 -17
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +8 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/mirroredPrismaEnums.d.ts +145 -0
- package/dist/mirroredPrismaEnums.d.ts.map +1 -0
- package/dist/mirroredPrismaEnums.js +136 -0
- package/dist/mirroredPrismaEnums.js.map +1 -0
- package/dist/utils/__tests__/nonprofitUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/nonprofitUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/nonprofitUtils.test.js +84 -0
- package/dist/utils/__tests__/nonprofitUtils.test.js.map +1 -0
- package/dist/utils/__tests__/paymentUtils.test.js +87 -0
- package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
- package/dist/utils/nonprofitUtils.d.ts +21 -0
- package/dist/utils/nonprofitUtils.d.ts.map +1 -0
- package/dist/utils/nonprofitUtils.js +29 -0
- package/dist/utils/nonprofitUtils.js.map +1 -0
- package/dist/utils/paymentUtils.d.ts +23 -0
- package/dist/utils/paymentUtils.d.ts.map +1 -1
- package/dist/utils/paymentUtils.js +24 -7
- package/dist/utils/paymentUtils.js.map +1 -1
- package/dist/utils/service/__tests__/billingCapUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.js +120 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js +1 -1
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js.map +1 -1
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.d.ts +2 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.js +243 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.js.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.js +333 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.js +52 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.js +111 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.js +155 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.d.ts +2 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.js +77 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.js.map +1 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.js +157 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.js +175 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.js +118 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.js.map +1 -0
- package/dist/utils/service/apiServiceBookingApiUtils.d.ts.map +1 -1
- package/dist/utils/service/apiServiceBookingApiUtils.js +4 -0
- package/dist/utils/service/apiServiceBookingApiUtils.js.map +1 -1
- package/dist/utils/service/billingCapUtils.d.ts +86 -0
- package/dist/utils/service/billingCapUtils.d.ts.map +1 -0
- package/dist/utils/service/billingCapUtils.js +96 -0
- package/dist/utils/service/billingCapUtils.js.map +1 -0
- package/dist/utils/service/cancellationPolicyRefundResolver.d.ts +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.d.ts.map +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.js +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.js.map +1 -1
- package/dist/utils/service/eventPromoterUtils.d.ts +319 -0
- package/dist/utils/service/eventPromoterUtils.d.ts.map +1 -0
- package/dist/utils/service/eventPromoterUtils.js +372 -0
- package/dist/utils/service/eventPromoterUtils.js.map +1 -0
- package/dist/utils/service/offerAllowListUtils.d.ts +42 -0
- package/dist/utils/service/offerAllowListUtils.d.ts.map +1 -0
- package/dist/utils/service/offerAllowListUtils.js +49 -0
- package/dist/utils/service/offerAllowListUtils.js.map +1 -0
- package/dist/utils/service/offerComparisonUtils.d.ts +69 -0
- package/dist/utils/service/offerComparisonUtils.d.ts.map +1 -0
- package/dist/utils/service/offerComparisonUtils.js +110 -0
- package/dist/utils/service/offerComparisonUtils.js.map +1 -0
- package/dist/utils/service/serviceBookingExposureUtils.d.ts +111 -0
- package/dist/utils/service/serviceBookingExposureUtils.d.ts.map +1 -0
- package/dist/utils/service/serviceBookingExposureUtils.js +133 -0
- package/dist/utils/service/serviceBookingExposureUtils.js.map +1 -0
- package/dist/utils/service/serviceBookingTypes.d.ts +1 -1
- package/dist/utils/service/serviceBookingTypes.d.ts.map +1 -1
- package/dist/utils/service/serviceUtils.d.ts +2 -1
- package/dist/utils/service/serviceUtils.d.ts.map +1 -1
- package/dist/utils/service/serviceUtils.js +17 -16
- package/dist/utils/service/serviceUtils.js.map +1 -1
- package/dist/utils/service/unifiedPricingResolver.d.ts +67 -0
- package/dist/utils/service/unifiedPricingResolver.d.ts.map +1 -0
- package/dist/utils/service/unifiedPricingResolver.js +75 -0
- package/dist/utils/service/unifiedPricingResolver.js.map +1 -0
- package/dist/utils/service/venueFBSettlementUtils.d.ts +122 -0
- package/dist/utils/service/venueFBSettlementUtils.d.ts.map +1 -0
- package/dist/utils/service/venueFBSettlementUtils.js +106 -0
- package/dist/utils/service/venueFBSettlementUtils.js.map +1 -0
- package/dist/utils/service/venueSettlementUtils.d.ts +107 -0
- package/dist/utils/service/venueSettlementUtils.d.ts.map +1 -0
- package/dist/utils/service/venueSettlementUtils.js +117 -0
- package/dist/utils/service/venueSettlementUtils.js.map +1 -0
- package/dist/utils/service/volunteerCompensationUtils.d.ts +75 -0
- package/dist/utils/service/volunteerCompensationUtils.d.ts.map +1 -0
- package/dist/utils/service/volunteerCompensationUtils.js +88 -0
- package/dist/utils/service/volunteerCompensationUtils.js.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.d.ts +2 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.js +67 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.js.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.d.ts +2 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.js +71 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.js.map +1 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.d.ts +33 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.js +49 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.js.map +1 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.d.ts +35 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.js +74 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.js.map +1 -0
- package/dist/venueLoyaltyRedemption.d.ts +146 -1
- package/dist/venueLoyaltyRedemption.d.ts.map +1 -1
- package/dist/venueLoyaltyRedemption.js +18 -2
- package/dist/venueLoyaltyRedemption.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +638 -214
- package/src/__tests__/statusEnums.test.ts +119 -11
- package/src/definitions.ts +65 -22
- package/src/extendedSchemas.ts +8 -0
- package/src/index.ts +16 -6
- package/src/mirroredPrismaEnums.ts +181 -0
- package/src/utils/__tests__/nonprofitUtils.test.ts +117 -0
- package/src/utils/__tests__/paymentUtils.test.ts +93 -0
- package/src/utils/nonprofitUtils.ts +44 -0
- package/src/utils/paymentUtils.ts +33 -7
- package/src/utils/service/__tests__/billingCapUtils.test.ts +175 -0
- package/src/utils/service/__tests__/cancellationPolicyRefundResolver.test.ts +1 -1
- package/src/utils/service/__tests__/eventPromoterPhase3.test.ts +296 -0
- package/src/utils/service/__tests__/eventPromoterUtils.test.ts +396 -0
- package/src/utils/service/__tests__/offerAllowListUtils.test.ts +76 -0
- package/src/utils/service/__tests__/offerComparisonUtils.test.ts +124 -0
- package/src/utils/service/__tests__/serviceBookingExposureUtils.test.ts +171 -0
- package/src/utils/service/__tests__/unifiedPricingResolver.test.ts +83 -0
- package/src/utils/service/__tests__/venueSettlementUtils.test.ts +219 -0
- package/src/utils/service/__tests__/volunteerCompensationUtils.test.ts +172 -0
- package/src/utils/service/apiServiceBookingApiUtils.ts +4 -0
- package/src/utils/service/billingCapUtils.ts +145 -0
- package/src/utils/service/cancellationPolicyRefundResolver.ts +1 -1
- package/src/utils/service/eventPromoterUtils.ts +644 -0
- package/src/utils/service/offerAllowListUtils.ts +81 -0
- package/src/utils/service/offerComparisonUtils.ts +197 -0
- package/src/utils/service/serviceBookingExposureUtils.ts +218 -0
- package/src/utils/service/serviceBookingTypes.ts +4 -0
- package/src/utils/service/serviceUtils.ts +18 -21
- package/src/utils/service/unifiedPricingResolver.ts +149 -0
- package/src/utils/service/venueSettlementUtils.ts +198 -0
- package/src/utils/service/volunteerCompensationUtils.ts +145 -0
- package/src/utils/venueLoyalty/__tests__/eligibilityUtils.test.ts +103 -0
- package/src/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.ts +92 -0
- package/src/utils/venueLoyalty/eligibilityUtils.ts +84 -0
- package/src/utils/venueLoyalty/redemptionCodeUtils.ts +94 -0
- package/src/venueLoyaltyRedemption.ts +177 -2
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Promoter is a productized hire-a-promoter service profile under
|
|
3
|
+
* `EventService` → `PromotionAndMarketing` → `Promoter`. Its pricing model
|
|
4
|
+
* is intentionally locked to PER_ATTENDEE so:
|
|
5
|
+
*
|
|
6
|
+
* 1. Bookings can compute a deposit at booking time from
|
|
7
|
+
* `bashEvent.expectedAttendees * baseRateCents`.
|
|
8
|
+
* 2. The bash-close settlement job can run a delta charge/refund based on
|
|
9
|
+
* final checked-in attendee count (see
|
|
10
|
+
* `apiEventPromoterSettlementMath` below).
|
|
11
|
+
*
|
|
12
|
+
* Keep the disambiguation: this is the **Event Promoter** service, not the
|
|
13
|
+
* **Promo-code Promoter** referral program (`model Promoter` +
|
|
14
|
+
* `BashEventPromoCode`), which earns BashPoints per ticket purchased.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Returns true when the given `PromotionAndMarketing` subtype is locked to a
|
|
18
|
+
* specific `PricingModel`. Today only `Promoter` is locked (PER_ATTENDEE).
|
|
19
|
+
*
|
|
20
|
+
* Compared with the **string literal** rather than the Prisma enum object
|
|
21
|
+
* because the browser bundle (`index-browser`) does not always re-export
|
|
22
|
+
* Prisma enums as runtime values — see `bash-common/src/mirroredPrismaEnums.ts`
|
|
23
|
+
* for the same pattern.
|
|
24
|
+
*/
|
|
25
|
+
export function isPerAttendeeLockedPromotionSubType(subType) {
|
|
26
|
+
return subType === "Promoter";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the URL the host clicks from an Event Promoter booking thread to
|
|
30
|
+
* issue/edit a `BashEventPromoCode` with the promoter pre-selected. The
|
|
31
|
+
* landing page reads `promoterUserId` from the query string and opens the
|
|
32
|
+
* promo-code create modal already focused on that user.
|
|
33
|
+
*
|
|
34
|
+
* Keeping this in `bash-common` so api emails + bash-app threads + push
|
|
35
|
+
* notifications all generate the same deep link.
|
|
36
|
+
*/
|
|
37
|
+
export function buildIssuePromoCodeDeepLink(args) {
|
|
38
|
+
const params = new URLSearchParams({
|
|
39
|
+
promoterUserId: args.promoterUserId,
|
|
40
|
+
source: "event-promoter-booking",
|
|
41
|
+
});
|
|
42
|
+
if (args.ticketTierId) {
|
|
43
|
+
params.set("ticketTierId", args.ticketTierId);
|
|
44
|
+
}
|
|
45
|
+
return `/bashes/${args.bashEventId}/edit?step=how&open=promo-codes&${params.toString()}`;
|
|
46
|
+
}
|
|
47
|
+
/** Hours-before-bash threshold under which a host cancellation is "late". */
|
|
48
|
+
export const EVENT_PROMOTER_LATE_CANCEL_HOURS = 48;
|
|
49
|
+
/**
|
|
50
|
+
* Late-cancellation penalty as a fraction of the deposit (0..1). 0.25 means
|
|
51
|
+
* the host forfeits 25% of the deposit when they cancel inside the
|
|
52
|
+
* `EVENT_PROMOTER_LATE_CANCEL_HOURS` window — covers the promoter's
|
|
53
|
+
* sunk planning time.
|
|
54
|
+
*/
|
|
55
|
+
export const EVENT_PROMOTER_LATE_CANCEL_PENALTY_FRACTION = 0.25;
|
|
56
|
+
/**
|
|
57
|
+
* Pure decision function that returns what should happen when someone tries
|
|
58
|
+
* to cancel an Event Promoter booking. Mirrors the matrix called out in the
|
|
59
|
+
* plan:
|
|
60
|
+
*
|
|
61
|
+
* - Host, bash hasn't started, > 48h out → FullRefund.
|
|
62
|
+
* - Host, bash hasn't started, ≤ 48h out → PartialRefund (25% penalty).
|
|
63
|
+
* - Host, bash already running/ended → NoRefund (settlement takes over).
|
|
64
|
+
* - Promoter, bash not started → FullRefund + promoter notified
|
|
65
|
+
* (Bash never holds a promoter to a job they can't do).
|
|
66
|
+
* - Promoter, bash already running/ended → Blocked (use the booking thread
|
|
67
|
+
* to coordinate; nothing automatic).
|
|
68
|
+
* - Admin always allowed; refund amount decided by caller.
|
|
69
|
+
* - Already settled → Blocked (no automatic cancel).
|
|
70
|
+
*
|
|
71
|
+
* The actual Stripe refund + DB mutation lives in the api-side counterpart;
|
|
72
|
+
* this function only encodes policy so the same matrix surfaces in tests,
|
|
73
|
+
* the booking thread copy, and the audit log.
|
|
74
|
+
*/
|
|
75
|
+
export function decideEventPromoterCancellation(input) {
|
|
76
|
+
if (input.alreadySettled) {
|
|
77
|
+
return {
|
|
78
|
+
allow: false,
|
|
79
|
+
outcome: "Blocked",
|
|
80
|
+
refundCents: 0,
|
|
81
|
+
penaltyFraction: 0,
|
|
82
|
+
reason: "Booking already settled — cancellation no longer applies.",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const bashHasStarted = input.bashStartMs != null && input.nowMs >= input.bashStartMs;
|
|
86
|
+
const bashHasEnded = input.bashEndMs != null && input.nowMs >= input.bashEndMs;
|
|
87
|
+
const bashInProgressOrPast = bashHasStarted || bashHasEnded;
|
|
88
|
+
const hoursUntilStart = input.bashStartMs != null
|
|
89
|
+
? (input.bashStartMs - input.nowMs) / (60 * 60 * 1000)
|
|
90
|
+
: Number.POSITIVE_INFINITY;
|
|
91
|
+
if (input.who === "admin") {
|
|
92
|
+
if (bashInProgressOrPast) {
|
|
93
|
+
return {
|
|
94
|
+
allow: true,
|
|
95
|
+
outcome: "NoRefund",
|
|
96
|
+
refundCents: 0,
|
|
97
|
+
penaltyFraction: 0,
|
|
98
|
+
reason: "Admin cancel after bash started — settlement handles money.",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
allow: true,
|
|
103
|
+
outcome: "FullRefund",
|
|
104
|
+
refundCents: input.depositCents,
|
|
105
|
+
penaltyFraction: 0,
|
|
106
|
+
reason: "Admin cancel — full refund of deposit.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (input.who === "promoter") {
|
|
110
|
+
if (bashInProgressOrPast) {
|
|
111
|
+
return {
|
|
112
|
+
allow: false,
|
|
113
|
+
outcome: "Blocked",
|
|
114
|
+
refundCents: 0,
|
|
115
|
+
penaltyFraction: 0,
|
|
116
|
+
reason: "Bash already started — promoter must coordinate with host in the thread, not auto-cancel.",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
allow: true,
|
|
121
|
+
outcome: "FullRefund",
|
|
122
|
+
refundCents: input.depositCents,
|
|
123
|
+
penaltyFraction: 0,
|
|
124
|
+
reason: "Promoter cancelled — full refund of host deposit. Bash notifies the host and re-opens the slot.",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// who === "host"
|
|
128
|
+
if (bashInProgressOrPast) {
|
|
129
|
+
return {
|
|
130
|
+
allow: false,
|
|
131
|
+
outcome: "NoRefund",
|
|
132
|
+
refundCents: 0,
|
|
133
|
+
penaltyFraction: 0,
|
|
134
|
+
reason: "Bash already in progress or over — the post-event settlement will calculate any refund automatically.",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (hoursUntilStart >= EVENT_PROMOTER_LATE_CANCEL_HOURS) {
|
|
138
|
+
return {
|
|
139
|
+
allow: true,
|
|
140
|
+
outcome: "FullRefund",
|
|
141
|
+
refundCents: input.depositCents,
|
|
142
|
+
penaltyFraction: 0,
|
|
143
|
+
reason: `Host cancelled with more than ${EVENT_PROMOTER_LATE_CANCEL_HOURS}h notice — full refund of deposit.`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const penaltyFraction = EVENT_PROMOTER_LATE_CANCEL_PENALTY_FRACTION;
|
|
147
|
+
const refundCents = Math.max(0, Math.round(input.depositCents * (1 - penaltyFraction)));
|
|
148
|
+
return {
|
|
149
|
+
allow: true,
|
|
150
|
+
outcome: "PartialRefund",
|
|
151
|
+
refundCents,
|
|
152
|
+
penaltyFraction,
|
|
153
|
+
reason: `Host cancelled inside the ${EVENT_PROMOTER_LATE_CANCEL_HOURS}h window — promoter keeps ${Math.round(penaltyFraction * 100)}% of the deposit to cover planning time.`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The single allowed pricing model for the locked promotion subtype. Exposed
|
|
158
|
+
* as a constant so wizard + api validators share the same source of truth.
|
|
159
|
+
*
|
|
160
|
+
* Retained for backwards compatibility; new call sites should prefer
|
|
161
|
+
* {@link EVENT_PROMOTER_LOCKED_PRICING_MODELS} to allow Phase 3's
|
|
162
|
+
* `PER_PAID_ATTENDEE` and `PER_BASHPOINTS_ATTENDEE`.
|
|
163
|
+
*/
|
|
164
|
+
export const EVENT_PROMOTER_LOCKED_PRICING_MODEL = "PER_ATTENDEE";
|
|
165
|
+
/**
|
|
166
|
+
* Phase 3 — set of pricing models the locked Event Promoter subtype may use.
|
|
167
|
+
* Any of these guarantees a per-head settlement at bash close, which is the
|
|
168
|
+
* actual constraint the worker cares about. Wizard offers them as a choice;
|
|
169
|
+
* api validator accepts any.
|
|
170
|
+
*/
|
|
171
|
+
export const EVENT_PROMOTER_LOCKED_PRICING_MODELS = [
|
|
172
|
+
"PER_ATTENDEE",
|
|
173
|
+
"PER_PAID_ATTENDEE",
|
|
174
|
+
"PER_BASHPOINTS_ATTENDEE",
|
|
175
|
+
];
|
|
176
|
+
/**
|
|
177
|
+
* Validates that an `EventService` save with `subType === "Promoter"` uses
|
|
178
|
+
* one of the {@link EVENT_PROMOTER_LOCKED_PRICING_MODELS}. Intended for both:
|
|
179
|
+
*
|
|
180
|
+
* - the Event Services wizard (block save with a friendly message), and
|
|
181
|
+
* - the api save path (reject the request even if the wizard is bypassed).
|
|
182
|
+
*
|
|
183
|
+
* Returns `{ ok: true }` for any non-locked subtype.
|
|
184
|
+
*/
|
|
185
|
+
export function validateEventPromoterPricing(args) {
|
|
186
|
+
if (!isPerAttendeeLockedPromotionSubType(args.eventServiceSubType)) {
|
|
187
|
+
return { ok: true };
|
|
188
|
+
}
|
|
189
|
+
if (args.pricingModel != null &&
|
|
190
|
+
EVENT_PROMOTER_LOCKED_PRICING_MODELS.includes(args.pricingModel)) {
|
|
191
|
+
return { ok: true };
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
expected: EVENT_PROMOTER_LOCKED_PRICING_MODELS,
|
|
196
|
+
reason: `Event Promoter services must use one of: ${EVENT_PROMOTER_LOCKED_PRICING_MODELS.join(", ")}. Got: ${args.pricingModel ?? "null"}.`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Computes the deposit cents to charge a host at Event Promoter booking time.
|
|
201
|
+
*
|
|
202
|
+
* Logic:
|
|
203
|
+
* - Baseline = max(`bashExpectedAttendees`, `minimumQuantity ?? 0`).
|
|
204
|
+
* - Deposit = baseline * `baseRateCents`.
|
|
205
|
+
*
|
|
206
|
+
* Returns `null` if `baseRateCents` is not set (caller should surface a
|
|
207
|
+
* validation error rather than silently charge $0).
|
|
208
|
+
*/
|
|
209
|
+
export function computeEventPromoterDepositCents(args) {
|
|
210
|
+
if (args.baseRateCents == null || args.baseRateCents <= 0) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const baseline = Math.max(Math.max(0, args.bashExpectedAttendees), Math.max(0, args.minimumQuantity ?? 0));
|
|
214
|
+
return baseline * args.baseRateCents;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Pure function: compute the per-attendee settlement delta for an Event
|
|
218
|
+
* Promoter `ServiceBooking`. Caller is responsible for translating the result
|
|
219
|
+
* into Stripe API calls (charge if positive delta, refund if negative).
|
|
220
|
+
*
|
|
221
|
+
* Worked example (host hires two promoters for the same bash @ $5/attendee):
|
|
222
|
+
*
|
|
223
|
+
* Promoter A: expected=200, ceiling=250, checked-in=220
|
|
224
|
+
* → finalCount=220, delta = (220-200) * 500 = +$100
|
|
225
|
+
*
|
|
226
|
+
* Promoter B: expected=100, ceiling=150, same 220 checked-in
|
|
227
|
+
* → finalCount=min(220,150)=150, delta = (150-100) * 500 = +$250
|
|
228
|
+
*
|
|
229
|
+
* @see plan: docs/design/CONTENT-PATTERNS.md, .cursor/plans/ambashadors_page_tiers_ux_*.plan.md
|
|
230
|
+
*/
|
|
231
|
+
export function computeEventPromoterSettlement(input) {
|
|
232
|
+
const checkedIn = Math.max(0, input.totalCheckedInAttendees);
|
|
233
|
+
const ceiling = input.maxAttendeesAttributable != null && input.maxAttendeesAttributable >= 0
|
|
234
|
+
? input.maxAttendeesAttributable
|
|
235
|
+
: Number.POSITIVE_INFINITY;
|
|
236
|
+
const minQty = Math.max(0, input.minimumQuantity ?? 0);
|
|
237
|
+
// Apply ceiling first, then enforce minimum-quantity floor for the
|
|
238
|
+
// billing-floor case (host committed to paying for at least `minQty`
|
|
239
|
+
// attendees regardless of turnout).
|
|
240
|
+
const cappedByCeiling = Math.min(checkedIn, ceiling);
|
|
241
|
+
const finalCount = Math.max(cappedByCeiling, minQty);
|
|
242
|
+
const ceilingHit = checkedIn > ceiling;
|
|
243
|
+
const totalCents = finalCount * input.baseRateCents;
|
|
244
|
+
const expectedCents = input.expectedAttendeesAtBooking * input.baseRateCents;
|
|
245
|
+
const settlementDeltaCents = totalCents - expectedCents;
|
|
246
|
+
return {
|
|
247
|
+
finalCount,
|
|
248
|
+
ceilingHit,
|
|
249
|
+
settlementDeltaCents,
|
|
250
|
+
totalCents,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Choose the correct counting basis given the pricing model and the
|
|
255
|
+
* per-booking `countsBashPointsTickets` knob. Pure function so wizard +
|
|
256
|
+
* worker share one mapping.
|
|
257
|
+
*/
|
|
258
|
+
export function eventPromoterCountingBasisFor(args) {
|
|
259
|
+
switch (args.pricingModel) {
|
|
260
|
+
case "PER_PAID_ATTENDEE":
|
|
261
|
+
return args.countsBashPointsTickets ? "PaidAny" : "PaidUsdOnly";
|
|
262
|
+
case "PER_BASHPOINTS_ATTENDEE":
|
|
263
|
+
return "PaidBpOnly";
|
|
264
|
+
case "PER_ATTENDEE":
|
|
265
|
+
default:
|
|
266
|
+
return "AllCheckedIn";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
export function pickCheckedInForBasis(basis, s) {
|
|
270
|
+
switch (basis) {
|
|
271
|
+
case "AllCheckedIn":
|
|
272
|
+
return Math.max(0, s.totalCheckedInAttendees);
|
|
273
|
+
case "PaidUsdOnly":
|
|
274
|
+
return Math.max(0, s.paidAttendeesAtSettle ?? 0);
|
|
275
|
+
case "PaidBpOnly":
|
|
276
|
+
return Math.max(0, s.paidInBashPointsAttendeesAtSettle ?? 0);
|
|
277
|
+
case "PaidAny":
|
|
278
|
+
return (Math.max(0, s.paidAttendeesAtSettle ?? 0) +
|
|
279
|
+
Math.max(0, s.paidInBashPointsAttendeesAtSettle ?? 0));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Pure function for mixed PER_PAID_ATTENDEE / PER_BASHPOINTS_ATTENDEE
|
|
284
|
+
* settlement. Either side can be unset (the worker only computes the side
|
|
285
|
+
* that applies). Caller is responsible for the actual Stripe + BP ledger
|
|
286
|
+
* postings.
|
|
287
|
+
*/
|
|
288
|
+
export function computeEventPromoterMixedSettlement(input) {
|
|
289
|
+
// --- USD side -----------------------------------------------------------
|
|
290
|
+
const usdRate = Math.max(0, input.baseRateCents ?? 0);
|
|
291
|
+
let usdCount = pickCheckedInForBasis("PaidUsdOnly", input.snapshots);
|
|
292
|
+
const usdCeiling = input.maxUsdAttendeesAttributable != null &&
|
|
293
|
+
input.maxUsdAttendeesAttributable >= 0
|
|
294
|
+
? input.maxUsdAttendeesAttributable
|
|
295
|
+
: Number.POSITIVE_INFINITY;
|
|
296
|
+
usdCount = Math.min(usdCount, usdCeiling);
|
|
297
|
+
let usdTotalCents = usdRate * usdCount;
|
|
298
|
+
let cappedUsd = false;
|
|
299
|
+
if (input.maxBillableCents != null &&
|
|
300
|
+
input.maxBillableCents >= 0 &&
|
|
301
|
+
usdTotalCents > input.maxBillableCents) {
|
|
302
|
+
usdTotalCents = input.maxBillableCents;
|
|
303
|
+
cappedUsd = true;
|
|
304
|
+
}
|
|
305
|
+
const usdExpected = Math.max(0, input.expectedPaidAttendeesAtBooking ?? 0) * usdRate;
|
|
306
|
+
const usdDeltaCents = usdTotalCents - usdExpected;
|
|
307
|
+
// --- BP side ------------------------------------------------------------
|
|
308
|
+
const bpRate = Math.max(0, input.baseRateBashPoints ?? 0);
|
|
309
|
+
let bpCount = pickCheckedInForBasis("PaidBpOnly", input.snapshots);
|
|
310
|
+
const bpCeiling = input.maxBpAttendeesAttributable != null &&
|
|
311
|
+
input.maxBpAttendeesAttributable >= 0
|
|
312
|
+
? input.maxBpAttendeesAttributable
|
|
313
|
+
: Number.POSITIVE_INFINITY;
|
|
314
|
+
bpCount = Math.min(bpCount, bpCeiling);
|
|
315
|
+
let bpTotal = bpRate * bpCount;
|
|
316
|
+
let cappedBp = false;
|
|
317
|
+
if (input.maxBillableBashPoints != null &&
|
|
318
|
+
input.maxBillableBashPoints >= 0 &&
|
|
319
|
+
bpTotal > input.maxBillableBashPoints) {
|
|
320
|
+
bpTotal = input.maxBillableBashPoints;
|
|
321
|
+
cappedBp = true;
|
|
322
|
+
}
|
|
323
|
+
const bpExpected = Math.max(0, input.expectedBpAttendeesAtBooking ?? 0) * bpRate;
|
|
324
|
+
const bpDelta = bpTotal - bpExpected;
|
|
325
|
+
return {
|
|
326
|
+
usdTotalCents,
|
|
327
|
+
usdDeltaCents,
|
|
328
|
+
bpTotal,
|
|
329
|
+
bpDelta,
|
|
330
|
+
cappedUsd,
|
|
331
|
+
cappedBp,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Pure function that decides which BashPoints buckets to drain to satisfy a
|
|
336
|
+
* provider payout. Mirrors the worker's behavior:
|
|
337
|
+
*
|
|
338
|
+
* - `HostWallet`: take everything from the host's wallet; any shortfall is
|
|
339
|
+
* a payment failure (`SettlementPaymentFailed`).
|
|
340
|
+
* - `BashBpPool`: drain the pool first; pay the remainder from the host's
|
|
341
|
+
* wallet; if still short, mark `SettlementPaymentFailed`.
|
|
342
|
+
*
|
|
343
|
+
* Negative inputs are clamped to zero so the resolver never returns a
|
|
344
|
+
* negative bucket.
|
|
345
|
+
*/
|
|
346
|
+
export function resolveBashPointsPayoutSources(input) {
|
|
347
|
+
const need = Math.max(0, Math.floor(input.payoutBp));
|
|
348
|
+
const wallet = Math.max(0, Math.floor(input.hostWalletBp));
|
|
349
|
+
const pool = Math.max(0, Math.floor(input.bashPoolBp ?? 0));
|
|
350
|
+
if (input.source === "HostWallet") {
|
|
351
|
+
const fromWalletBp = Math.min(need, wallet);
|
|
352
|
+
const shortfallBp = need - fromWalletBp;
|
|
353
|
+
return {
|
|
354
|
+
fromPoolBp: 0,
|
|
355
|
+
fromWalletBp,
|
|
356
|
+
shortfallBp,
|
|
357
|
+
walletDebited: fromWalletBp > 0,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
// BashBpPool
|
|
361
|
+
const fromPoolBp = Math.min(need, pool);
|
|
362
|
+
const remainAfterPool = need - fromPoolBp;
|
|
363
|
+
const fromWalletBp = Math.min(remainAfterPool, wallet);
|
|
364
|
+
const shortfallBp = remainAfterPool - fromWalletBp;
|
|
365
|
+
return {
|
|
366
|
+
fromPoolBp,
|
|
367
|
+
fromWalletBp,
|
|
368
|
+
shortfallBp,
|
|
369
|
+
walletDebited: fromWalletBp > 0,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=eventPromoterUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventPromoterUtils.js","sourceRoot":"","sources":["../../../src/utils/service/eventPromoterUtils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 4 of the unified-pricing roadmap — host-side pricing-model allow-list
|
|
3
|
+
* validation.
|
|
4
|
+
*
|
|
5
|
+
* Each vertical request entity (`VendorBookingRequest`,
|
|
6
|
+
* `ExhibitorBookingRequest`, `SponsorBookingRequest`) carries an optional
|
|
7
|
+
* `pricingModelAllowList String[]`. When empty, the host hasn't restricted
|
|
8
|
+
* anything and providers may bid in any of their supported models. When
|
|
9
|
+
* non-empty, providers can only bid in models that appear in the list.
|
|
10
|
+
*
|
|
11
|
+
* Pure function so the wizard, api validators, and offer-suggestion AI all
|
|
12
|
+
* share one rule.
|
|
13
|
+
*/
|
|
14
|
+
import type { PricingModel } from "@prisma/client";
|
|
15
|
+
export type AllowListValidation = {
|
|
16
|
+
ok: true;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
reason: string;
|
|
20
|
+
offered: PricingModel | string;
|
|
21
|
+
allowList: ReadonlyArray<string>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Returns `{ ok: true }` if either:
|
|
25
|
+
* - the allow-list is empty / null (host has no restriction), or
|
|
26
|
+
* - the offered model appears in the allow-list.
|
|
27
|
+
*
|
|
28
|
+
* Otherwise returns `{ ok: false }` with a friendly reason suitable for
|
|
29
|
+
* surfacing in the SendOfferSheet error region.
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateOfferAgainstAllowList(args: {
|
|
32
|
+
offered: PricingModel | string | null | undefined;
|
|
33
|
+
allowList: ReadonlyArray<string> | null | undefined;
|
|
34
|
+
}): AllowListValidation;
|
|
35
|
+
/**
|
|
36
|
+
* Per-vertical sensible defaults for what a wizard should pre-check in the
|
|
37
|
+
* "I'll consider bids priced as…" group when the host hasn't customized.
|
|
38
|
+
* Used by Phase 4 wizard sections; safe to share with the AI service so its
|
|
39
|
+
* suggestions match the wizard's defaults.
|
|
40
|
+
*/
|
|
41
|
+
export declare const DEFAULT_PRICING_MODEL_ALLOW_LIST_BY_SERVICE_TYPE: Record<string, ReadonlyArray<PricingModel>>;
|
|
42
|
+
//# sourceMappingURL=offerAllowListUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offerAllowListUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/service/offerAllowListUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,GAAG,MAAM,CAAC;IAC/B,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAClC,CAAC;AAEN;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;CACrD,GAAG,mBAAmB,CAwBtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,gDAAgD,EAAE,MAAM,CACnE,MAAM,EACN,aAAa,CAAC,YAAY,CAAC,CAW5B,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `{ ok: true }` if either:
|
|
3
|
+
* - the allow-list is empty / null (host has no restriction), or
|
|
4
|
+
* - the offered model appears in the allow-list.
|
|
5
|
+
*
|
|
6
|
+
* Otherwise returns `{ ok: false }` with a friendly reason suitable for
|
|
7
|
+
* surfacing in the SendOfferSheet error region.
|
|
8
|
+
*/
|
|
9
|
+
export function validateOfferAgainstAllowList(args) {
|
|
10
|
+
if (!args.offered) {
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
reason: "Offer is missing a pricing model.",
|
|
14
|
+
offered: "",
|
|
15
|
+
allowList: args.allowList ?? [],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const list = args.allowList ?? [];
|
|
19
|
+
if (list.length === 0) {
|
|
20
|
+
return { ok: true };
|
|
21
|
+
}
|
|
22
|
+
if (list.includes(args.offered)) {
|
|
23
|
+
return { ok: true };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
reason: `Host accepts bids priced as ${list.join(", ")}. ${args.offered} is not on the list.`,
|
|
28
|
+
offered: args.offered,
|
|
29
|
+
allowList: list,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Per-vertical sensible defaults for what a wizard should pre-check in the
|
|
34
|
+
* "I'll consider bids priced as…" group when the host hasn't customized.
|
|
35
|
+
* Used by Phase 4 wizard sections; safe to share with the AI service so its
|
|
36
|
+
* suggestions match the wizard's defaults.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_PRICING_MODEL_ALLOW_LIST_BY_SERVICE_TYPE = {
|
|
39
|
+
// Strings match the Prisma `ServiceTypes` enum values (see schema.prisma).
|
|
40
|
+
Vendors: ["FLAT_FEE", "PRODUCT_SALES_PERCENTAGE"],
|
|
41
|
+
Exhibitors: ["FLAT_FEE"],
|
|
42
|
+
Sponsors: ["TIERED", "FLAT_FEE"],
|
|
43
|
+
Venues: ["FLAT_FEE", "TICKET_REVENUE_PERCENTAGE"],
|
|
44
|
+
EntertainmentServices: ["HOURLY", "FLAT_FEE"],
|
|
45
|
+
EventServices: ["FLAT_FEE", "PER_ATTENDEE"],
|
|
46
|
+
Volunteers: ["FLAT_FEE"],
|
|
47
|
+
Organizations: ["FLAT_FEE"],
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=offerAllowListUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offerAllowListUtils.js","sourceRoot":"","sources":["../../../src/utils/service/offerAllowListUtils.ts"],"names":[],"mappings":"AAwBA;;;;;;;GAOG;AACH,MAAM,UAAU,6BAA6B,CAAC,IAG7C;IACC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,mCAAmC;YAC3C,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE;SAChC,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;IAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,OAAO;QACL,EAAE,EAAE,KAAK;QACT,MAAM,EAAE,+BAA+B,IAAI,CAAC,IAAI,CAC9C,IAAI,CACL,KAAK,IAAI,CAAC,OAAO,sBAAsB;QACxC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,SAAS,EAAE,IAAI;KAChB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gDAAgD,GAGzD;IACF,2EAA2E;IAC3E,OAAO,EAAE,CAAC,UAAU,EAAE,0BAA0B,CAAC;IACjD,UAAU,EAAE,CAAC,UAAU,CAAC;IACxB,QAAQ,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;IAChC,MAAM,EAAE,CAAC,UAAU,EAAE,2BAA2B,CAAC;IACjD,qBAAqB,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC7C,aAAa,EAAE,CAAC,UAAU,EAAE,cAAc,CAAC;IAC3C,UAAU,EAAE,CAAC,UAAU,CAAC;IACxB,aAAa,EAAE,CAAC,UAAU,CAAC;CAC5B,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 of the unified-pricing roadmap — pure projection of a `ServiceBookingOffer`
|
|
3
|
+
* candidate into expected / worst-case / best-case totals (in cents).
|
|
4
|
+
*
|
|
5
|
+
* Used by:
|
|
6
|
+
* - the offer-suggestion AI to return one suggestion per allowed model with
|
|
7
|
+
* bands the host can compare side-by-side,
|
|
8
|
+
* - the SendOfferSheet preview row that renders the same bands,
|
|
9
|
+
* - the api validator that double-checks an accepted offer against the
|
|
10
|
+
* host's expected attendees and any host-side caps.
|
|
11
|
+
*
|
|
12
|
+
* Heuristics for `worstCaseCents` / `bestCaseCents`:
|
|
13
|
+
* - `FLAT_FEE` / `HOURLY` / `PER_DAY` / `CUSTOM` — all three bands equal the
|
|
14
|
+
* expected total (no variability).
|
|
15
|
+
* - `PER_ATTENDEE` / `PER_PAID_ATTENDEE` — worst case = floor (minimumQuantity
|
|
16
|
+
* * baseRateCents, capped by minimumChargeCents); best case = capped to
|
|
17
|
+
* `maxBillableCents` when present, else `expectedAttendees * 1.5`.
|
|
18
|
+
* - `PER_BASHPOINTS_ATTENDEE` — same shape but in BashPoints, so we surface
|
|
19
|
+
* `expectedTotalBp` and the USD bands return null.
|
|
20
|
+
* - `TICKET_REVENUE_PERCENTAGE` / `PRODUCT_SALES_PERCENTAGE` — caller passes
|
|
21
|
+
* an `expectedBasisCents` (usually the bash's projected ticket revenue or
|
|
22
|
+
* vendor gross sales). Worst case = 0 if pre-sale, best case = basis * pct.
|
|
23
|
+
* - `TIERED` — worst case = lowest tier price, best case = highest tier
|
|
24
|
+
* price; expected resolves via the supplied `tieredExpectedTierCents`.
|
|
25
|
+
*/
|
|
26
|
+
import type { PricingModel } from "@prisma/client";
|
|
27
|
+
export interface OfferComparisonInput {
|
|
28
|
+
pricingModel: PricingModel | string;
|
|
29
|
+
baseRateCents?: number | null;
|
|
30
|
+
baseRateBashPoints?: number | null;
|
|
31
|
+
minimumChargeCents?: number | null;
|
|
32
|
+
minimumQuantity?: number | null;
|
|
33
|
+
maxBillableCents?: number | null;
|
|
34
|
+
/** Bash's expectedAttendees at offer time. */
|
|
35
|
+
expectedAttendees?: number | null;
|
|
36
|
+
/** Optional expected basis for revenue-share models. */
|
|
37
|
+
expectedBasisCents?: number | null;
|
|
38
|
+
/** Percentage (0..100) for revenue-share models. */
|
|
39
|
+
percentage?: number | null;
|
|
40
|
+
/** Pre-resolved expected total for TIERED (already chosen tier). */
|
|
41
|
+
tieredExpectedTierCents?: number | null;
|
|
42
|
+
tieredLowestTierCents?: number | null;
|
|
43
|
+
tieredHighestTierCents?: number | null;
|
|
44
|
+
/** Optional flat amount the caller already computed (e.g. from priceToBook). */
|
|
45
|
+
flatRateCents?: number | null;
|
|
46
|
+
}
|
|
47
|
+
export interface OfferComparisonResult {
|
|
48
|
+
/** Most-likely total in USD cents. `null` for BashPoints-only models. */
|
|
49
|
+
expectedTotalCents: number | null;
|
|
50
|
+
/** Worst case in USD cents the host could end up owing (or null for BP). */
|
|
51
|
+
worstCaseCents: number | null;
|
|
52
|
+
/** Best case for the host (cheapest plausible total). */
|
|
53
|
+
bestCaseCents: number | null;
|
|
54
|
+
/** Same triple in BashPoints when the model settles in BP. */
|
|
55
|
+
expectedTotalBp: number | null;
|
|
56
|
+
worstCaseBp: number | null;
|
|
57
|
+
bestCaseBp: number | null;
|
|
58
|
+
/**
|
|
59
|
+
* Plain-English tradeoff sentence to surface next to the bands. AI may
|
|
60
|
+
* override with its own copy; this is a deterministic fallback.
|
|
61
|
+
*/
|
|
62
|
+
reasoning: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Project an offer candidate into expected / worst / best totals.
|
|
66
|
+
* Returns sensible-but-defensive defaults if the input model is unknown.
|
|
67
|
+
*/
|
|
68
|
+
export declare function computeOfferComparisonBands(input: OfferComparisonInput): OfferComparisonResult;
|
|
69
|
+
//# sourceMappingURL=offerComparisonUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offerComparisonUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/service/offerComparisonUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,GAAG,MAAM,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,wDAAwD;IACxD,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,oEAAoE;IACpE,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,4EAA4E;IAC5E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,yDAAyD;IACzD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8DAA8D;IAC9D,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAUD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,qBAAqB,CAoHvB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
function nn(v) {
|
|
2
|
+
return v == null || !Number.isFinite(v) || v <= 0 ? 0 : Math.round(v);
|
|
3
|
+
}
|
|
4
|
+
function fmt(cents) {
|
|
5
|
+
return `$${(cents / 100).toFixed(0)}`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Project an offer candidate into expected / worst / best totals.
|
|
9
|
+
* Returns sensible-but-defensive defaults if the input model is unknown.
|
|
10
|
+
*/
|
|
11
|
+
export function computeOfferComparisonBands(input) {
|
|
12
|
+
const expected = nn(input.expectedAttendees);
|
|
13
|
+
const minQty = nn(input.minimumQuantity);
|
|
14
|
+
switch (input.pricingModel) {
|
|
15
|
+
case "FLAT_FEE":
|
|
16
|
+
case "HOURLY":
|
|
17
|
+
case "PER_DAY":
|
|
18
|
+
case "CUSTOM": {
|
|
19
|
+
const total = nn(input.flatRateCents) || nn(input.baseRateCents) || 0;
|
|
20
|
+
return {
|
|
21
|
+
expectedTotalCents: total,
|
|
22
|
+
worstCaseCents: total,
|
|
23
|
+
bestCaseCents: total,
|
|
24
|
+
expectedTotalBp: null,
|
|
25
|
+
worstCaseBp: null,
|
|
26
|
+
bestCaseBp: null,
|
|
27
|
+
reasoning: total > 0
|
|
28
|
+
? `Fixed price: you pay ${fmt(total)} regardless of turnout.`
|
|
29
|
+
: "Fixed price — provider has not set a rate.",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
case "PER_ATTENDEE":
|
|
33
|
+
case "PER_PAID_ATTENDEE": {
|
|
34
|
+
const rate = nn(input.baseRateCents);
|
|
35
|
+
const expectedTotal = expected * rate;
|
|
36
|
+
const floor = Math.max(nn(input.minimumChargeCents), minQty * rate);
|
|
37
|
+
const worst = Math.max(expectedTotal, floor);
|
|
38
|
+
const ceiling = nn(input.maxBillableCents) ||
|
|
39
|
+
Math.max(expectedTotal, Math.round(expected * rate * 1.5));
|
|
40
|
+
const best = Math.max(floor, 0);
|
|
41
|
+
return {
|
|
42
|
+
expectedTotalCents: expectedTotal,
|
|
43
|
+
worstCaseCents: ceiling,
|
|
44
|
+
bestCaseCents: best,
|
|
45
|
+
expectedTotalBp: null,
|
|
46
|
+
worstCaseBp: null,
|
|
47
|
+
bestCaseBp: null,
|
|
48
|
+
reasoning: `Per-${input.pricingModel === "PER_PAID_ATTENDEE" ? "paid-" : ""}attendee: expected ${fmt(expectedTotal)} at ${expected} guests, with a floor of ${fmt(floor)} and a host cap of ${fmt(ceiling)}.`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
case "PER_BASHPOINTS_ATTENDEE": {
|
|
52
|
+
const rate = nn(input.baseRateBashPoints);
|
|
53
|
+
const expectedTotalBp = expected * rate;
|
|
54
|
+
const floorBp = minQty * rate;
|
|
55
|
+
const worstBp = Math.max(expectedTotalBp, floorBp);
|
|
56
|
+
const bestBp = Math.max(floorBp, 0);
|
|
57
|
+
return {
|
|
58
|
+
expectedTotalCents: null,
|
|
59
|
+
worstCaseCents: null,
|
|
60
|
+
bestCaseCents: null,
|
|
61
|
+
expectedTotalBp,
|
|
62
|
+
worstCaseBp: worstBp,
|
|
63
|
+
bestCaseBp: bestBp,
|
|
64
|
+
reasoning: `Per-attendee BashPoints: expected ${expectedTotalBp} BP at ${expected} guests, floor ${floorBp} BP.`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
case "TICKET_REVENUE_PERCENTAGE":
|
|
68
|
+
case "PRODUCT_SALES_PERCENTAGE": {
|
|
69
|
+
const basis = nn(input.expectedBasisCents);
|
|
70
|
+
const pct = Math.max(0, Math.min(100, input.percentage ?? 0));
|
|
71
|
+
const expectedTotal = Math.round((basis * pct) / 100);
|
|
72
|
+
return {
|
|
73
|
+
expectedTotalCents: expectedTotal,
|
|
74
|
+
worstCaseCents: 0,
|
|
75
|
+
bestCaseCents: expectedTotal,
|
|
76
|
+
expectedTotalBp: null,
|
|
77
|
+
worstCaseBp: null,
|
|
78
|
+
bestCaseBp: null,
|
|
79
|
+
reasoning: `Revenue share: provider takes ${pct}% of ${fmt(basis)} projected. Worst case for you (slow night, no revenue): $0.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
case "TIERED": {
|
|
83
|
+
const exp = nn(input.tieredExpectedTierCents);
|
|
84
|
+
const low = nn(input.tieredLowestTierCents);
|
|
85
|
+
const high = nn(input.tieredHighestTierCents);
|
|
86
|
+
return {
|
|
87
|
+
expectedTotalCents: exp || low || high || 0,
|
|
88
|
+
worstCaseCents: high || exp || low,
|
|
89
|
+
bestCaseCents: low || exp || high,
|
|
90
|
+
expectedTotalBp: null,
|
|
91
|
+
worstCaseBp: null,
|
|
92
|
+
bestCaseBp: null,
|
|
93
|
+
reasoning: `Tiered: from ${fmt(low)} to ${fmt(high)} depending on the tier you pick.`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
default: {
|
|
97
|
+
const fallback = nn(input.flatRateCents) || nn(input.baseRateCents);
|
|
98
|
+
return {
|
|
99
|
+
expectedTotalCents: fallback,
|
|
100
|
+
worstCaseCents: fallback,
|
|
101
|
+
bestCaseCents: fallback,
|
|
102
|
+
expectedTotalBp: null,
|
|
103
|
+
worstCaseBp: null,
|
|
104
|
+
bestCaseBp: null,
|
|
105
|
+
reasoning: `Unrecognized pricing model "${input.pricingModel}" — treated as flat.`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=offerComparisonUtils.js.map
|