@bash-app/bash-common 30.371.0 → 30.374.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/definitions.d.ts +6 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js.map +1 -1
- package/dist/utils/__tests__/organizationEntitlements.test.js +66 -27
- package/dist/utils/__tests__/organizationEntitlements.test.js.map +1 -1
- package/dist/utils/luxonUtils.d.ts +7 -7
- package/dist/utils/luxonUtils.d.ts.map +1 -1
- package/dist/utils/organizationEntitlements.d.ts +23 -18
- package/dist/utils/organizationEntitlements.d.ts.map +1 -1
- package/dist/utils/organizationEntitlements.js +37 -22
- package/dist/utils/organizationEntitlements.js.map +1 -1
- package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.js +66 -1
- package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.js.map +1 -1
- package/dist/utils/service/__tests__/frontendServiceBookingUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/frontendServiceBookingUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/frontendServiceBookingUtils.test.js +165 -0
- package/dist/utils/service/__tests__/frontendServiceBookingUtils.test.js.map +1 -0
- package/dist/utils/service/apiServiceBookingApiUtils.d.ts +5 -3
- package/dist/utils/service/apiServiceBookingApiUtils.d.ts.map +1 -1
- package/dist/utils/service/apiServiceBookingApiUtils.js +30 -1
- package/dist/utils/service/apiServiceBookingApiUtils.js.map +1 -1
- package/dist/utils/service/frontendServiceBookingUtils.d.ts +10 -4
- package/dist/utils/service/frontendServiceBookingUtils.d.ts.map +1 -1
- package/dist/utils/service/frontendServiceBookingUtils.js +16 -3
- package/dist/utils/service/frontendServiceBookingUtils.js.map +1 -1
- package/dist/utils/service/serviceBookingTypes.d.ts +2 -2
- package/dist/utils/service/serviceBookingTypes.d.ts.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +10 -7
- package/src/definitions.ts +7 -0
- package/src/utils/__tests__/organizationEntitlements.test.ts +79 -30
- package/src/utils/organizationEntitlements.ts +44 -27
- package/src/utils/service/__tests__/apiServiceBookingApiUtils.test.ts +102 -2
- package/src/utils/service/__tests__/frontendServiceBookingUtils.test.ts +217 -0
- package/src/utils/service/apiServiceBookingApiUtils.ts +53 -1
- package/src/utils/service/frontendServiceBookingUtils.ts +33 -2
- package/src/utils/service/serviceBookingTypes.ts +2 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { ServiceAddon, ServiceBookingRateOption } from "@prisma/client";
|
|
2
|
-
import { ServiceRatesAssociationExt } from "../../extendedSchemas.js";
|
|
2
|
+
import { ServicePackageExt, ServiceRatesAssociationExt } from "../../extendedSchemas.js";
|
|
3
3
|
import { ServiceAttendeeOption } from "./attendeeOptionUtils.js";
|
|
4
4
|
import { ServiceRatesLuxon } from "./serviceRateUtils.js";
|
|
5
5
|
import { LuxonDateRange } from "../luxonUtils.js";
|
|
6
|
-
import { ServiceBookingFeeBase, ServiceBookingPriceBreakdownBaseT, ServiceBookingAddOnBase } from "./serviceBookingTypes.js";
|
|
6
|
+
import { ServiceBookingFeeBase, ServiceBookingPriceBreakdownBaseT, ServiceBookingAddOnBase, ServiceBookingPackageBase } from "./serviceBookingTypes.js";
|
|
7
7
|
export interface ServiceAddonInput extends ServiceAddon {
|
|
8
8
|
chosenQuantity?: number;
|
|
9
9
|
}
|
|
10
|
+
export interface ServicePackageInput extends ServicePackageExt {
|
|
11
|
+
chosenQuantity?: number;
|
|
12
|
+
}
|
|
10
13
|
export interface FrontendServiceBookingDayInfoParams {
|
|
11
14
|
dateTimeRange: LuxonDateRange;
|
|
12
15
|
fees: ServiceBookingFeeBase[];
|
|
@@ -28,6 +31,7 @@ export interface FrontendServiceGetPriceToBookResult {
|
|
|
28
31
|
rateOption: ServiceBookingRateOption;
|
|
29
32
|
flatRateCents?: number;
|
|
30
33
|
addOns: ServiceBookingAddOnBase[];
|
|
34
|
+
packages: ServiceBookingPackageBase[];
|
|
31
35
|
daysTotalBeforeTaxesCents: number;
|
|
32
36
|
subtotalBeforeTaxesCents: number;
|
|
33
37
|
totalBeforeTaxesCents: number;
|
|
@@ -46,15 +50,17 @@ export interface FrontendServiceGetPriceToBookFeesParams {
|
|
|
46
50
|
selectedAttendeeOption: ServiceAttendeeOption;
|
|
47
51
|
additionalFees: ServiceBookingFeeBase[];
|
|
48
52
|
addOns: ServiceAddonInput[];
|
|
53
|
+
packages?: ServicePackageInput[];
|
|
49
54
|
timezone: string | null | undefined;
|
|
50
55
|
}
|
|
51
|
-
export declare function frontendServiceGetPriceToBookFees(serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined, { daysToBookParams, selectedAttendeeOption, addOns, additionalFees, timezone, }: FrontendServiceGetPriceToBookFeesParams): FrontendServiceGetPriceToBookResult;
|
|
56
|
+
export declare function frontendServiceGetPriceToBookFees(serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined, { daysToBookParams, selectedAttendeeOption, addOns, packages, additionalFees, timezone, }: FrontendServiceGetPriceToBookFeesParams): FrontendServiceGetPriceToBookResult;
|
|
52
57
|
export interface FrontendServiceGetPriceToBookParams {
|
|
53
58
|
addOns: ServiceAddonInput[];
|
|
59
|
+
packages?: ServicePackageInput[];
|
|
54
60
|
selectedAttendeeOption: ServiceAttendeeOption;
|
|
55
61
|
bookingDateTimeRanges: LuxonDateRange[];
|
|
56
62
|
timezone: string | null | undefined;
|
|
57
63
|
}
|
|
58
|
-
export declare function frontendServiceGetPriceToBook(serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined, { addOns, selectedAttendeeOption, bookingDateTimeRanges, //list of dateTime ranges to book
|
|
64
|
+
export declare function frontendServiceGetPriceToBook(serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined, { addOns, packages, selectedAttendeeOption, bookingDateTimeRanges, //list of dateTime ranges to book
|
|
59
65
|
timezone, }: FrontendServiceGetPriceToBookParams): FrontendServiceGetPriceToBookResult;
|
|
60
66
|
//# sourceMappingURL=frontendServiceBookingUtils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontendServiceBookingUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/service/frontendServiceBookingUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAGZ,wBAAwB,EACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAML,0BAA0B,EAE3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAML,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,cAAc,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,qBAAqB,EACrB,iCAAiC,EACjC,uBAAuB,
|
|
1
|
+
{"version":3,"file":"frontendServiceBookingUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/service/frontendServiceBookingUtils.ts"],"names":[],"mappings":"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"}
|
|
@@ -72,7 +72,7 @@ timezone, }) {
|
|
|
72
72
|
});
|
|
73
73
|
return bookingDayResults;
|
|
74
74
|
}
|
|
75
|
-
export function frontendServiceGetPriceToBookFees(serviceRatesAssociation, { daysToBookParams, selectedAttendeeOption, addOns, additionalFees,
|
|
75
|
+
export function frontendServiceGetPriceToBookFees(serviceRatesAssociation, { daysToBookParams, selectedAttendeeOption, addOns, packages = [], additionalFees,
|
|
76
76
|
// overlappingBusinessHours,
|
|
77
77
|
timezone, }) {
|
|
78
78
|
const bookingDateTimeRanges = daysToBookParams.map((daysToBookParams) => daysToBookParams.dateTimeRange);
|
|
@@ -92,6 +92,16 @@ timezone, }) {
|
|
|
92
92
|
};
|
|
93
93
|
})
|
|
94
94
|
: [];
|
|
95
|
+
// Packages are booking-level bundled line items (e.g. "Gold DJ Package") and apply
|
|
96
|
+
// on top of the booking regardless of hourly/daily/flat rate option.
|
|
97
|
+
const usedPackages = packages.map((pkg) => {
|
|
98
|
+
return {
|
|
99
|
+
package: pkg,
|
|
100
|
+
packageId: pkg.id,
|
|
101
|
+
chosenQuantity: pkg.chosenQuantity ?? 0,
|
|
102
|
+
costPQCents: pkg.priceCents,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
95
105
|
const bookingDayResults = frontendServiceGetBookingDayInfo({
|
|
96
106
|
rateOption: rateOption,
|
|
97
107
|
filteredRates: filteredRates,
|
|
@@ -120,7 +130,8 @@ timezone, }) {
|
|
|
120
130
|
const hasCustomProcessingFee = additionalFees.some((fee) => fee.feeType == ServiceBookingFeeType.ProcessingFee);
|
|
121
131
|
allAdditionalFees = allAdditionalFees.filter((fee) => fee.costPQCents > 0);
|
|
122
132
|
const addOnsTotalCents = usedAddons.reduce((sofar, addOn) => sofar + addOn.costPQCents * addOn.chosenQuantity, 0);
|
|
123
|
-
const
|
|
133
|
+
const packagesTotalCents = usedPackages.reduce((sofar, pkg) => sofar + pkg.costPQCents * pkg.chosenQuantity, 0);
|
|
134
|
+
const subtotalBeforeTaxesCents = daysTotalBeforeTaxesCents + addOnsTotalCents + packagesTotalCents;
|
|
124
135
|
if (!hasCustomProcessingFee) {
|
|
125
136
|
// Same pass-through formula as bash ticket/donation checkout (STRIPE_PROCESSING_FEE).
|
|
126
137
|
const processingFeeCents = calculateStripeProcessingFeeCents(subtotalBeforeTaxesCents);
|
|
@@ -143,6 +154,7 @@ timezone, }) {
|
|
|
143
154
|
totalWithFees: totalBeforeTaxesCents, // Same as totalBeforeTaxesCents
|
|
144
155
|
additionalFees: allAdditionalFees,
|
|
145
156
|
addOns: usedAddons,
|
|
157
|
+
packages: usedPackages,
|
|
146
158
|
minimumTimeBlockHours: minimumTimeBlockHours,
|
|
147
159
|
rateOption: rateOption,
|
|
148
160
|
flatRateCents: (filteredRates.generalRate?.flatRateCents ?? 0) > 0
|
|
@@ -150,7 +162,7 @@ timezone, }) {
|
|
|
150
162
|
: undefined,
|
|
151
163
|
};
|
|
152
164
|
}
|
|
153
|
-
export function frontendServiceGetPriceToBook(serviceRatesAssociation, { addOns, selectedAttendeeOption, bookingDateTimeRanges, //list of dateTime ranges to book
|
|
165
|
+
export function frontendServiceGetPriceToBook(serviceRatesAssociation, { addOns, packages = [], selectedAttendeeOption, bookingDateTimeRanges, //list of dateTime ranges to book
|
|
154
166
|
// overlappingBusinessHours,
|
|
155
167
|
timezone, }) {
|
|
156
168
|
const cleaningFeePerBookingCents = serviceRatesAssociation?.serviceGeneralRates?.cleaningFeePerBookingCents ??
|
|
@@ -176,6 +188,7 @@ timezone, }) {
|
|
|
176
188
|
selectedAttendeeOption: selectedAttendeeOption,
|
|
177
189
|
additionalFees: [],
|
|
178
190
|
addOns: addOns,
|
|
191
|
+
packages: packages,
|
|
179
192
|
timezone: timezone,
|
|
180
193
|
});
|
|
181
194
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontendServiceBookingUtils.js","sourceRoot":"","sources":["../../../src/utils/service/frontendServiceBookingUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"frontendServiceBookingUtils.js","sourceRoot":"","sources":["../../../src/utils/service/frontendServiceBookingUtils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ServiceBookingAddOnExt, ServiceBookingFeeExt, ServiceBookingPriceBreakdownExt, ServiceBookingDayExt, ServiceBookingExt } from "../../extendedSchemas.js";
|
|
1
|
+
import { ServiceBookingAddOnExt, ServiceBookingFeeExt, ServiceBookingPackageExt, ServiceBookingPriceBreakdownExt, ServiceBookingDayExt, ServiceBookingExt } from "../../extendedSchemas.js";
|
|
2
2
|
import { LuxonDateRange } from "../luxonUtils.js";
|
|
3
3
|
import { MakeNullablePropsOptional, MakeOptional } from "../typeUtils.js";
|
|
4
4
|
export type ServiceBookingAddOnBase = Omit<ServiceBookingAddOnExt, "id" | "serviceBookingDayId" | "serviceBookingDay" | "serviceBookingId" | "serviceBooking">;
|
|
5
|
-
export type ServiceBookingPackageBase = Omit<
|
|
5
|
+
export type ServiceBookingPackageBase = Omit<ServiceBookingPackageExt, "id" | "serviceBookingDayId" | "serviceBookingDay" | "serviceBookingId" | "serviceBooking">;
|
|
6
6
|
export type ServiceBookingFeeBase = Omit<ServiceBookingFeeExt, "id" | "serviceBookingDayId" | "serviceBookingDay" | "serviceBookingId" | "serviceBooking">;
|
|
7
7
|
export type ServiceBookingPriceBreakdownBase = Omit<ServiceBookingPriceBreakdownExt, "id" | "serviceBookingDayId" | "serviceBookingDay">;
|
|
8
8
|
export type ServiceBookingDayBase = Omit<ServiceBookingDayExt, "id" | "serviceBookingRequestId" | "serviceBookingRequest">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serviceBookingTypes.d.ts","sourceRoot":"","sources":["../../../src/utils/service/serviceBookingTypes.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,+BAA+B,EAC/B,oBAAoB,EAGpB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,sBAAsB,EACpB,IAAI,GACJ,qBAAqB,GACrB,mBAAmB,GACnB,kBAAkB,GAClB,gBAAgB,CACnB,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,
|
|
1
|
+
{"version":3,"file":"serviceBookingTypes.d.ts","sourceRoot":"","sources":["../../../src/utils/service/serviceBookingTypes.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,+BAA+B,EAC/B,oBAAoB,EAGpB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,sBAAsB,EACpB,IAAI,GACJ,qBAAqB,GACrB,mBAAmB,GACnB,kBAAkB,GAClB,gBAAgB,CACnB,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,wBAAwB,EACtB,IAAI,GACJ,qBAAqB,GACrB,mBAAmB,GACnB,kBAAkB,GAClB,gBAAgB,CACnB,CAAC;AACF,MAAM,MAAM,qBAAqB,GAAG,IAAI,CACtC,oBAAoB,EAClB,IAAI,GACJ,qBAAqB,GACrB,mBAAmB,GACnB,kBAAkB,GAClB,gBAAgB,CACnB,CAAC;AACF,MAAM,MAAM,gCAAgC,GAAG,IAAI,CACjD,+BAA+B,EAC/B,IAAI,GAAG,qBAAqB,GAAG,mBAAmB,CACnD,CAAC;AACF,MAAM,MAAM,qBAAqB,GAAG,IAAI,CACtC,oBAAoB,EACpB,IAAI,GAAG,yBAAyB,GAAG,uBAAuB,CAC3D,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,iBAAiB,EACjB,YAAY,GAAG,UAAU,GAAG,gBAAgB,CAC7C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,yBAAyB,CAChE,YAAY,CACV,iBAAiB,EACf,IAAI,GACJ,0BAA0B,GAC1B,aAAa,GACb,oBAAoB,GACpB,WAAW,GACX,SAAS,GACT,WAAW,GACX,SAAS,GACT,WAAW,GACX,SAAS,GACT,UAAU,GAGV,kBAAkB,GAClB,oBAAoB,GAGpB,kBAAkB,CACrB,CACF,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,CAAC;AAC/D,MAAM,MAAM,0BAA0B,GAAG,yBAAyB,CAAC;AACnE,MAAM,MAAM,sBAAsB,GAAG,qBAAqB,CAAC;AAC3D,MAAM,MAAM,iCAAiC,GAAG,IAAI,CAClD,gCAAgC,EAChC,WAAW,GAAG,SAAS,CACxB,GAAG;IACF,aAAa,EAAE,cAAc,CAAC;CAC/B,CAAC;AACF,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,qBAAqB,EACrB,WAAW,GAAG,SAAS,CACxB,GAAG;IACF,aAAa,EAAE,cAAc,CAAC;CAC/B,CAAC;AACF,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,CAAC"}
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -6026,13 +6026,16 @@ model Organization {
|
|
|
6026
6026
|
/// "Standard" | "Priority" | "Dedicated" — drives named-AM banner + support routing copy.
|
|
6027
6027
|
supportTier String @default("Standard")
|
|
6028
6028
|
/// True only when ops set subscriptionTier="Enterprise" via the admin
|
|
6029
|
-
/// enterprise-provisioning endpoint (a signed deal)
|
|
6030
|
-
///
|
|
6031
|
-
///
|
|
6032
|
-
/// SSO/audit-log/procurement/
|
|
6033
|
-
///
|
|
6034
|
-
/// (
|
|
6035
|
-
///
|
|
6029
|
+
/// enterprise-provisioning endpoint (a signed deal) — the only way to reach
|
|
6030
|
+
/// subscriptionTier="Enterprise" today, since self-serve Enterprise checkout
|
|
6031
|
+
/// is blocked (see docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md).
|
|
6032
|
+
/// organizationHasEnterprisePlatformAccess() and the SSO/audit-log/procurement/
|
|
6033
|
+
/// compliance/API entitlements below require this flag in addition to the
|
|
6034
|
+
/// Enterprise tier. organizationHasCustomDomainAccess() (custom domains,
|
|
6035
|
+
/// "Powered by Bash" removal) requires the Enterprise tier but NOT this flag,
|
|
6036
|
+
/// so a fast-follow self-serve Enterprise checkout could unlock white-label
|
|
6037
|
+
/// without the sales-gated platform surface. organizationHasVanitySubdomain()
|
|
6038
|
+
/// is a separate, cheaper perk available to Growth tier and up.
|
|
6036
6039
|
enterpriseSalesProvisioned Boolean @default(false)
|
|
6037
6040
|
|
|
6038
6041
|
/// SSO / identity (Organization Enterprise only). `ssoEnforced` blocks password
|
package/src/definitions.ts
CHANGED
|
@@ -709,6 +709,11 @@ export type ApiServiceAddonParams = {
|
|
|
709
709
|
chosenQuantity: number;
|
|
710
710
|
};
|
|
711
711
|
|
|
712
|
+
export type ApiServicePackageParams = {
|
|
713
|
+
packageId: string;
|
|
714
|
+
chosenQuantity: number;
|
|
715
|
+
};
|
|
716
|
+
|
|
712
717
|
export type ApiServiceBookedDayParams = {
|
|
713
718
|
serviceId: string;
|
|
714
719
|
forUserId: string;
|
|
@@ -741,6 +746,8 @@ export type ApiServiceBookingParams = {
|
|
|
741
746
|
timezone: string;
|
|
742
747
|
attendeeOption: ServiceAttendeeOption;
|
|
743
748
|
bashEventId?: string;
|
|
749
|
+
/** Booking-level bundled packages (e.g. "Gold DJ Package"), independent of hourly/daily rates. */
|
|
750
|
+
packages?: ApiServicePackageParams[];
|
|
744
751
|
};
|
|
745
752
|
|
|
746
753
|
export type ApiServiceCanBookParams = {} & ApiServiceBookingParams;
|
|
@@ -17,8 +17,8 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
17
17
|
expect(organizationHasVanitySubdomain(undefined)).toBe(false);
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
test("
|
|
21
|
-
for (const tier of ["Starter", "
|
|
20
|
+
test("below-Growth tiers are never entitled", () => {
|
|
21
|
+
for (const tier of ["Starter", "Church", "Legacy", null]) {
|
|
22
22
|
expect(
|
|
23
23
|
organizationHasVanitySubdomain({
|
|
24
24
|
subscriptionTier: tier,
|
|
@@ -28,29 +28,77 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test("Enterprise with active or trialing status is entitled", () => {
|
|
31
|
+
test("Growth or Enterprise with active or trialing status is entitled", () => {
|
|
32
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
33
|
+
expect(
|
|
34
|
+
organizationHasVanitySubdomain({
|
|
35
|
+
subscriptionTier: tier,
|
|
36
|
+
subscriptionStatus: "Active",
|
|
37
|
+
})
|
|
38
|
+
).toBe(true);
|
|
39
|
+
expect(
|
|
40
|
+
organizationHasVanitySubdomain({
|
|
41
|
+
subscriptionTier: tier,
|
|
42
|
+
subscriptionStatus: "Trialing",
|
|
43
|
+
})
|
|
44
|
+
).toBe(true);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("Growth or Enterprise with a missing status is treated as active (legacy/ops orgs)", () => {
|
|
49
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
50
|
+
expect(organizationHasVanitySubdomain({ subscriptionTier: tier })).toBe(true);
|
|
51
|
+
expect(
|
|
52
|
+
organizationHasVanitySubdomain({
|
|
53
|
+
subscriptionTier: tier,
|
|
54
|
+
subscriptionStatus: null,
|
|
55
|
+
})
|
|
56
|
+
).toBe(true);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("Growth or Enterprise with a lapsed status is not entitled", () => {
|
|
61
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
62
|
+
for (const status of ["Canceled", "PastDue", "Canceling"]) {
|
|
63
|
+
expect(
|
|
64
|
+
organizationHasVanitySubdomain({
|
|
65
|
+
subscriptionTier: tier,
|
|
66
|
+
subscriptionStatus: status,
|
|
67
|
+
})
|
|
68
|
+
).toBe(false);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("organizationHasCustomDomainAccess", () => {
|
|
75
|
+
test("null / undefined return false", () => {
|
|
76
|
+
expect(organizationHasCustomDomainAccess(null)).toBe(false);
|
|
77
|
+
expect(organizationHasCustomDomainAccess(undefined)).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("Growth is not entitled — custom domains stay Enterprise-only", () => {
|
|
32
81
|
expect(
|
|
33
|
-
|
|
34
|
-
subscriptionTier: "
|
|
82
|
+
organizationHasCustomDomainAccess({
|
|
83
|
+
subscriptionTier: "Growth",
|
|
35
84
|
subscriptionStatus: "Active",
|
|
36
85
|
})
|
|
37
|
-
).toBe(
|
|
38
|
-
expect(
|
|
39
|
-
organizationHasVanitySubdomain({
|
|
40
|
-
subscriptionTier: "Enterprise",
|
|
41
|
-
subscriptionStatus: "Trialing",
|
|
42
|
-
})
|
|
43
|
-
).toBe(true);
|
|
86
|
+
).toBe(false);
|
|
44
87
|
});
|
|
45
88
|
|
|
46
|
-
test("Enterprise with
|
|
89
|
+
test("Enterprise with active or trialing status is entitled, regardless of enterpriseSalesProvisioned", () => {
|
|
47
90
|
expect(
|
|
48
|
-
|
|
91
|
+
organizationHasCustomDomainAccess({
|
|
92
|
+
subscriptionTier: "Enterprise",
|
|
93
|
+
subscriptionStatus: "Active",
|
|
94
|
+
enterpriseSalesProvisioned: true,
|
|
95
|
+
})
|
|
49
96
|
).toBe(true);
|
|
50
97
|
expect(
|
|
51
|
-
|
|
98
|
+
organizationHasCustomDomainAccess({
|
|
52
99
|
subscriptionTier: "Enterprise",
|
|
53
|
-
subscriptionStatus:
|
|
100
|
+
subscriptionStatus: "Active",
|
|
101
|
+
enterpriseSalesProvisioned: false,
|
|
54
102
|
})
|
|
55
103
|
).toBe(true);
|
|
56
104
|
});
|
|
@@ -58,7 +106,7 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
58
106
|
test("Enterprise with a lapsed status is not entitled", () => {
|
|
59
107
|
for (const status of ["Canceled", "PastDue", "Canceling"]) {
|
|
60
108
|
expect(
|
|
61
|
-
|
|
109
|
+
organizationHasCustomDomainAccess({
|
|
62
110
|
subscriptionTier: "Enterprise",
|
|
63
111
|
subscriptionStatus: status,
|
|
64
112
|
})
|
|
@@ -75,8 +123,9 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
75
123
|
subscriptionStatus: "Active",
|
|
76
124
|
enterpriseSalesProvisioned: true,
|
|
77
125
|
};
|
|
78
|
-
//
|
|
79
|
-
|
|
126
|
+
// Not sales-provisioned (e.g. legacy data, or ops hasn't flipped the flag
|
|
127
|
+
// yet): Enterprise tier alone never grants the sales-gated platform surface.
|
|
128
|
+
const nonSalesProvisionedEnterprise = {
|
|
80
129
|
subscriptionTier: "Enterprise",
|
|
81
130
|
subscriptionStatus: "Active",
|
|
82
131
|
enterpriseSalesProvisioned: false,
|
|
@@ -85,7 +134,7 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
85
134
|
|
|
86
135
|
test("organizationHasEnterprisePlatformAccess requires enterpriseSalesProvisioned", () => {
|
|
87
136
|
expect(organizationHasEnterprisePlatformAccess(salesProvisionedEnterprise)).toBe(true);
|
|
88
|
-
expect(organizationHasEnterprisePlatformAccess(
|
|
137
|
+
expect(organizationHasEnterprisePlatformAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
89
138
|
expect(organizationHasEnterprisePlatformAccess(growthActive)).toBe(false);
|
|
90
139
|
expect(organizationHasEnterprisePlatformAccess(null)).toBe(false);
|
|
91
140
|
expect(organizationHasEnterprisePlatformAccess(undefined)).toBe(false);
|
|
@@ -102,45 +151,45 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
102
151
|
|
|
103
152
|
test("organizationHasAuditLogAccess follows the shared enterprise gate", () => {
|
|
104
153
|
expect(organizationHasAuditLogAccess(salesProvisionedEnterprise)).toBe(true);
|
|
105
|
-
expect(organizationHasAuditLogAccess(
|
|
154
|
+
expect(organizationHasAuditLogAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
106
155
|
expect(organizationHasAuditLogAccess(growthActive)).toBe(false);
|
|
107
156
|
});
|
|
108
157
|
|
|
109
158
|
test("organizationHasSsoAccess follows the shared enterprise gate", () => {
|
|
110
159
|
expect(organizationHasSsoAccess(salesProvisionedEnterprise)).toBe(true);
|
|
111
|
-
expect(organizationHasSsoAccess(
|
|
160
|
+
expect(organizationHasSsoAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
112
161
|
expect(organizationHasSsoAccess(growthActive)).toBe(false);
|
|
113
162
|
});
|
|
114
163
|
|
|
115
164
|
test("organizationHasProcurementAccess follows the shared enterprise gate", () => {
|
|
116
165
|
expect(organizationHasProcurementAccess(salesProvisionedEnterprise)).toBe(true);
|
|
117
|
-
expect(organizationHasProcurementAccess(
|
|
166
|
+
expect(organizationHasProcurementAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
118
167
|
expect(organizationHasProcurementAccess(growthActive)).toBe(false);
|
|
119
168
|
});
|
|
120
169
|
|
|
121
170
|
test("organizationHasApiAccess follows the shared enterprise gate", () => {
|
|
122
171
|
expect(organizationHasApiAccess(salesProvisionedEnterprise)).toBe(true);
|
|
123
|
-
expect(organizationHasApiAccess(
|
|
172
|
+
expect(organizationHasApiAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
124
173
|
expect(organizationHasApiAccess(growthActive)).toBe(false);
|
|
125
174
|
});
|
|
126
175
|
|
|
127
176
|
test("organizationHasComplianceAccess follows the shared enterprise gate", () => {
|
|
128
177
|
expect(organizationHasComplianceAccess(salesProvisionedEnterprise)).toBe(true);
|
|
129
|
-
expect(organizationHasComplianceAccess(
|
|
178
|
+
expect(organizationHasComplianceAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
130
179
|
expect(organizationHasComplianceAccess(growthActive)).toBe(false);
|
|
131
180
|
});
|
|
132
181
|
|
|
133
|
-
test("organizationHasCustomDomainAccess
|
|
182
|
+
test("organizationHasCustomDomainAccess does not require the sales flag, but does require Enterprise (not Growth)", () => {
|
|
134
183
|
expect(organizationHasCustomDomainAccess(salesProvisionedEnterprise)).toBe(true);
|
|
135
|
-
expect(organizationHasCustomDomainAccess(
|
|
184
|
+
expect(organizationHasCustomDomainAccess(nonSalesProvisionedEnterprise)).toBe(true);
|
|
136
185
|
expect(organizationHasCustomDomainAccess(growthActive)).toBe(false);
|
|
137
186
|
expect(organizationHasCustomDomainAccess(null)).toBe(false);
|
|
138
187
|
expect(organizationHasCustomDomainAccess(undefined)).toBe(false);
|
|
139
188
|
});
|
|
140
189
|
|
|
141
|
-
test("organizationHasVanitySubdomain
|
|
190
|
+
test("organizationHasVanitySubdomain does not require the sales flag, and Growth is entitled too", () => {
|
|
142
191
|
expect(organizationHasVanitySubdomain(salesProvisionedEnterprise)).toBe(true);
|
|
143
|
-
expect(organizationHasVanitySubdomain(
|
|
144
|
-
expect(organizationHasVanitySubdomain(growthActive)).toBe(
|
|
192
|
+
expect(organizationHasVanitySubdomain(nonSalesProvisionedEnterprise)).toBe(true);
|
|
193
|
+
expect(organizationHasVanitySubdomain(growthActive)).toBe(true);
|
|
145
194
|
});
|
|
146
195
|
});
|
|
@@ -17,47 +17,62 @@ export interface OrganizationEntitlementInput {
|
|
|
17
17
|
subscriptionStatus?: string | null;
|
|
18
18
|
/**
|
|
19
19
|
* True only when ops provisioned Enterprise via a signed deal (admin
|
|
20
|
-
* enterprise-provisioning endpoint)
|
|
21
|
-
* subscriptionTier="Enterprise"
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* enterprise-provisioning endpoint) — the only way to reach
|
|
21
|
+
* subscriptionTier="Enterprise" (see docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md;
|
|
22
|
+
* self-serve checkout rejects Enterprise). Optional/undefined is treated as
|
|
23
|
+
* false so legacy call sites that don't select this field fail closed on the
|
|
24
|
+
* sales-gated surface rather than open.
|
|
24
25
|
*/
|
|
25
26
|
enterpriseSalesProvisioned?: boolean | null;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
// Treat a missing status as active: legacy/ops-provisioned orgs may predate
|
|
30
|
+
// subscriptionStatus being populated, and we don't want to silently drop
|
|
31
|
+
// their entitlements.
|
|
32
|
+
function isActiveOrTrialingStatus(status?: string | null): boolean {
|
|
33
|
+
if (status == null) return true;
|
|
34
|
+
return (ACTIVE_ORG_SUBSCRIPTION_STATUSES as readonly string[]).includes(status);
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
function hasActiveEnterpriseTier(
|
|
29
38
|
org: OrganizationEntitlementInput | null | undefined
|
|
30
39
|
): boolean {
|
|
31
40
|
if (!org) return false;
|
|
32
41
|
if (org.subscriptionTier !== "Enterprise") return false;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
42
|
+
return isActiveOrTrialingStatus(org.subscriptionStatus);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasActiveGrowthOrAboveTier(
|
|
46
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
47
|
+
): boolean {
|
|
48
|
+
if (!org) return false;
|
|
49
|
+
if (org.subscriptionTier !== "Growth" && org.subscriptionTier !== "Enterprise") {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return isActiveOrTrialingStatus(org.subscriptionStatus);
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
/**
|
|
43
|
-
* Vanity subdomains ({slug}.bash.community) and
|
|
44
|
-
*
|
|
45
|
-
*
|
|
56
|
+
* Vanity subdomains ({slug}.bash.community) are a Growth-tier-and-up perk, not
|
|
57
|
+
* Enterprise-only: unlike custom domains, granting one costs nothing extra
|
|
58
|
+
* (a slug lookup against the existing bash.community CloudFront distribution,
|
|
59
|
+
* no per-org AWS resources) — it's a cheap, visible upgrade incentive to dangle
|
|
60
|
+
* in front of Starter orgs. See {@link organizationHasCustomDomainAccess} for
|
|
61
|
+
* the pricier, Enterprise-only white-label surface.
|
|
46
62
|
*/
|
|
47
63
|
export function organizationHasVanitySubdomain(
|
|
48
64
|
org: OrganizationEntitlementInput | null | undefined
|
|
49
65
|
): boolean {
|
|
50
|
-
return
|
|
66
|
+
return hasActiveGrowthOrAboveTier(org);
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
/**
|
|
54
70
|
* Shared gate for the enterprise platform surface: org-scoped audit logs,
|
|
55
71
|
* SSO/identity setup, procurement approval policy, API keys, webhooks, and
|
|
56
|
-
* third-party integrations (Mailchimp/Salesforce).
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* involve a security review or contract terms sales owns.
|
|
72
|
+
* third-party integrations (Mailchimp/Salesforce). Requires the explicit
|
|
73
|
+
* `enterpriseSalesProvisioned` flag set by ops after a signed deal, since SSO,
|
|
74
|
+
* compliance, procurement, and API access typically involve a security review
|
|
75
|
+
* or contract terms sales owns.
|
|
61
76
|
*/
|
|
62
77
|
export function organizationHasEnterprisePlatformAccess(
|
|
63
78
|
org: OrganizationEntitlementInput | null | undefined
|
|
@@ -103,15 +118,17 @@ export function organizationHasComplianceAccess(
|
|
|
103
118
|
/**
|
|
104
119
|
* Custom domains (white-label tier): real DNS (e.g. members.acme.com) provisioned
|
|
105
120
|
* via AWS CloudFront SaaS Manager Distribution Tenants, plus the "Powered by Bash"
|
|
106
|
-
* removal toggle.
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* `enterpriseSalesProvisioned`
|
|
121
|
+
* removal toggle. Unlike {@link organizationHasVanitySubdomain}, this needs a real
|
|
122
|
+
* per-org CloudFront Distribution Tenant + managed cert + the org's own DNS work —
|
|
123
|
+
* actual infra cost and complexity per org — so it stays Enterprise-only (today
|
|
124
|
+
* always sales-provisioned, since self-serve Enterprise checkout is blocked; see
|
|
125
|
+
* docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md). Does NOT require
|
|
126
|
+
* {@link organizationHasEnterprisePlatformAccess}'s `enterpriseSalesProvisioned`
|
|
127
|
+
* flag, so a fast-follow self-serve Enterprise checkout could unlock this without
|
|
128
|
+
* the sales-gated platform surface (SSO, audit logs, procurement, compliance, API).
|
|
112
129
|
*/
|
|
113
130
|
export function organizationHasCustomDomainAccess(
|
|
114
131
|
org: OrganizationEntitlementInput | null | undefined
|
|
115
132
|
): boolean {
|
|
116
|
-
return
|
|
133
|
+
return hasActiveEnterpriseTier(org);
|
|
117
134
|
}
|