@bash-app/bash-common 30.360.0 → 30.361.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/utils/__tests__/organizationEntitlements.test.js +34 -1
- package/dist/utils/__tests__/organizationEntitlements.test.js.map +1 -1
- package/dist/utils/organizationEntitlements.d.ts +19 -0
- package/dist/utils/organizationEntitlements.d.ts.map +1 -1
- package/dist/utils/organizationEntitlements.js +32 -0
- package/dist/utils/organizationEntitlements.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +345 -0
- package/src/utils/__tests__/organizationEntitlements.test.ts +49 -1
- package/src/utils/organizationEntitlements.ts +49 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
|
-
import { organizationHasVanitySubdomain } from "../organizationEntitlements.js";
|
|
2
|
+
import { organizationHasApiAccess, organizationHasAuditLogAccess, organizationHasComplianceAccess, organizationHasEnterprisePlatformAccess, organizationHasProcurementAccess, organizationHasSsoAccess, organizationHasVanitySubdomain, } from "../organizationEntitlements.js";
|
|
3
3
|
describe("organizationHasVanitySubdomain", () => {
|
|
4
4
|
test("null / undefined return false", () => {
|
|
5
5
|
expect(organizationHasVanitySubdomain(null)).toBe(false);
|
|
@@ -39,4 +39,37 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
|
+
describe("enterprise platform gates (audit logs, SSO, procurement, API access)", () => {
|
|
43
|
+
const enterpriseActive = {
|
|
44
|
+
subscriptionTier: "Enterprise",
|
|
45
|
+
subscriptionStatus: "Active",
|
|
46
|
+
};
|
|
47
|
+
const growthActive = { subscriptionTier: "Growth", subscriptionStatus: "Active" };
|
|
48
|
+
test("organizationHasEnterprisePlatformAccess mirrors organizationHasVanitySubdomain", () => {
|
|
49
|
+
expect(organizationHasEnterprisePlatformAccess(enterpriseActive)).toBe(true);
|
|
50
|
+
expect(organizationHasEnterprisePlatformAccess(growthActive)).toBe(false);
|
|
51
|
+
expect(organizationHasEnterprisePlatformAccess(null)).toBe(false);
|
|
52
|
+
expect(organizationHasEnterprisePlatformAccess(undefined)).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
test("organizationHasAuditLogAccess follows the shared enterprise gate", () => {
|
|
55
|
+
expect(organizationHasAuditLogAccess(enterpriseActive)).toBe(true);
|
|
56
|
+
expect(organizationHasAuditLogAccess(growthActive)).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
test("organizationHasSsoAccess follows the shared enterprise gate", () => {
|
|
59
|
+
expect(organizationHasSsoAccess(enterpriseActive)).toBe(true);
|
|
60
|
+
expect(organizationHasSsoAccess(growthActive)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
test("organizationHasProcurementAccess follows the shared enterprise gate", () => {
|
|
63
|
+
expect(organizationHasProcurementAccess(enterpriseActive)).toBe(true);
|
|
64
|
+
expect(organizationHasProcurementAccess(growthActive)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
test("organizationHasApiAccess follows the shared enterprise gate", () => {
|
|
67
|
+
expect(organizationHasApiAccess(enterpriseActive)).toBe(true);
|
|
68
|
+
expect(organizationHasApiAccess(growthActive)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
test("organizationHasComplianceAccess follows the shared enterprise gate", () => {
|
|
71
|
+
expect(organizationHasComplianceAccess(enterpriseActive)).toBe(true);
|
|
72
|
+
expect(organizationHasComplianceAccess(growthActive)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
42
75
|
//# sourceMappingURL=organizationEntitlements.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizationEntitlements.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/organizationEntitlements.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,
|
|
1
|
+
{"version":3,"file":"organizationEntitlements.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/organizationEntitlements.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,4 +17,23 @@ export interface OrganizationEntitlementInput {
|
|
|
17
17
|
* (or trialing) subscription so a lapsed Enterprise org falls back to the path.
|
|
18
18
|
*/
|
|
19
19
|
export declare function organizationHasVanitySubdomain(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Shared gate for the enterprise platform surface: org-scoped audit logs,
|
|
22
|
+
* SSO/identity setup, procurement approval policy, API keys, webhooks, and
|
|
23
|
+
* third-party integrations (Mailchimp/Salesforce). All are Enterprise-tier
|
|
24
|
+
* perks today — same predicate as {@link organizationHasVanitySubdomain}, kept
|
|
25
|
+
* as a distinct name so the two can diverge later (e.g. Growth gets audit
|
|
26
|
+
* logs before SSO) without touching every call site.
|
|
27
|
+
*/
|
|
28
|
+
export declare function organizationHasEnterprisePlatformAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
29
|
+
/** Org-admin audit log viewer + CSV export. */
|
|
30
|
+
export declare function organizationHasAuditLogAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
31
|
+
/** SSO/identity setup. */
|
|
32
|
+
export declare function organizationHasSsoAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
33
|
+
/** Procurement approval workflows (PO-gated billing/spend). */
|
|
34
|
+
export declare function organizationHasProcurementAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
35
|
+
/** API keys, outbound webhooks, and third-party integrations. */
|
|
36
|
+
export declare function organizationHasApiAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
37
|
+
/** Compliance docs (W-9 tax collection, certificate-of-insurance tracking). */
|
|
38
|
+
export declare function organizationHasComplianceAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
20
39
|
//# sourceMappingURL=organizationEntitlements.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizationEntitlements.d.ts","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,iCAGnC,CAAC;AAEX,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAUT"}
|
|
1
|
+
{"version":3,"file":"organizationEntitlements.d.ts","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,iCAGnC,CAAC;AAEX,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAUT;AAED;;;;;;;GAOG;AACH,wBAAgB,uCAAuC,CACrD,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+CAA+C;AAC/C,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,0BAA0B;AAC1B,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+DAA+D;AAC/D,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,iEAAiE;AACjE,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+EAA+E;AAC/E,wBAAgB,+BAA+B,CAC7C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET"}
|
|
@@ -27,3 +27,35 @@ export function organizationHasVanitySubdomain(org) {
|
|
|
27
27
|
return true;
|
|
28
28
|
return ACTIVE_ORG_SUBSCRIPTION_STATUSES.includes(org.subscriptionStatus);
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Shared gate for the enterprise platform surface: org-scoped audit logs,
|
|
32
|
+
* SSO/identity setup, procurement approval policy, API keys, webhooks, and
|
|
33
|
+
* third-party integrations (Mailchimp/Salesforce). All are Enterprise-tier
|
|
34
|
+
* perks today — same predicate as {@link organizationHasVanitySubdomain}, kept
|
|
35
|
+
* as a distinct name so the two can diverge later (e.g. Growth gets audit
|
|
36
|
+
* logs before SSO) without touching every call site.
|
|
37
|
+
*/
|
|
38
|
+
export function organizationHasEnterprisePlatformAccess(org) {
|
|
39
|
+
return organizationHasVanitySubdomain(org);
|
|
40
|
+
}
|
|
41
|
+
/** Org-admin audit log viewer + CSV export. */
|
|
42
|
+
export function organizationHasAuditLogAccess(org) {
|
|
43
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
44
|
+
}
|
|
45
|
+
/** SSO/identity setup. */
|
|
46
|
+
export function organizationHasSsoAccess(org) {
|
|
47
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
48
|
+
}
|
|
49
|
+
/** Procurement approval workflows (PO-gated billing/spend). */
|
|
50
|
+
export function organizationHasProcurementAccess(org) {
|
|
51
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
52
|
+
}
|
|
53
|
+
/** API keys, outbound webhooks, and third-party integrations. */
|
|
54
|
+
export function organizationHasApiAccess(org) {
|
|
55
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
56
|
+
}
|
|
57
|
+
/** Compliance docs (W-9 tax collection, certificate-of-insurance tracking). */
|
|
58
|
+
export function organizationHasComplianceAccess(org) {
|
|
59
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=organizationEntitlements.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizationEntitlements.js","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,QAAQ;IACR,UAAU;CACF,CAAC;AAOX;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAC5C,GAAoD;IAEpD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,gBAAgB,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IACxD,+EAA+E;IAC/E,4EAA4E;IAC5E,4BAA4B;IAC5B,IAAI,GAAG,CAAC,kBAAkB,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAChD,OAAQ,gCAAsD,CAAC,QAAQ,CACrE,GAAG,CAAC,kBAAkB,CACvB,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"organizationEntitlements.js","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,QAAQ;IACR,UAAU;CACF,CAAC;AAOX;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAC5C,GAAoD;IAEpD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,gBAAgB,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IACxD,+EAA+E;IAC/E,4EAA4E;IAC5E,4BAA4B;IAC5B,IAAI,GAAG,CAAC,kBAAkB,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAChD,OAAQ,gCAAsD,CAAC,QAAQ,CACrE,GAAG,CAAC,kBAAkB,CACvB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uCAAuC,CACrD,GAAoD;IAEpD,OAAO,8BAA8B,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,6BAA6B,CAC3C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,wBAAwB,CACtC,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gCAAgC,CAC9C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,wBAAwB,CACtC,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,+BAA+B,CAC7C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC"}
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -4457,6 +4457,20 @@ model User {
|
|
|
4457
4457
|
submittedBudgets Budget[] @relation("BudgetSubmitter")
|
|
4458
4458
|
ownedBudgets Budget[] @relation("UserBudgets")
|
|
4459
4459
|
departmentsHeaded Department[] @relation("DepartmentHead")
|
|
4460
|
+
/// Enterprise platform (Phase 0-4): internal team members assigned as a named
|
|
4461
|
+
/// account manager, and org members who submit/approve procurement requests
|
|
4462
|
+
/// or create API keys.
|
|
4463
|
+
accountManagerForOrganizations Organization[] @relation("OrganizationAccountManager")
|
|
4464
|
+
procurementRequestsSubmitted ProcurementRequest[] @relation("ProcurementRequestSubmitter")
|
|
4465
|
+
procurementRequestsApproved ProcurementRequest[] @relation("ProcurementRequestApprover")
|
|
4466
|
+
organizationApiKeysCreated OrganizationApiKey[] @relation("OrganizationApiKeyCreator")
|
|
4467
|
+
/// SSO (Phase 2) + compliance (Phase 5): who configured a connection, and
|
|
4468
|
+
/// who submitted/reviewed a W-9 or certificate of insurance.
|
|
4469
|
+
organizationSsoConnectionsConfigured OrganizationSsoConnection[] @relation("OrganizationSsoConfiguredBy")
|
|
4470
|
+
organizationTaxDocumentsSubmitted OrganizationTaxDocument[] @relation("OrgTaxDocumentSubmittedBy")
|
|
4471
|
+
organizationTaxDocumentsReviewed OrganizationTaxDocument[] @relation("OrgTaxDocumentReviewedBy")
|
|
4472
|
+
organizationInsuranceCertsSubmitted OrganizationInsuranceCertificate[] @relation("OrgInsuranceCertSubmittedBy")
|
|
4473
|
+
organizationInsuranceCertsReviewed OrganizationInsuranceCertificate[] @relation("OrgInsuranceCertReviewedBy")
|
|
4460
4474
|
favoriteOrganizations Organization[] @relation("FavoriteOrganizations")
|
|
4461
4475
|
organizationEmailSubscriptions OrganizationSubscriber[]
|
|
4462
4476
|
eventSeriesCreated EventSeries[] @relation("EventSeriesCreator")
|
|
@@ -5909,10 +5923,55 @@ model Organization {
|
|
|
5909
5923
|
agencyBookingSettlements AgencyBookingSettlement[]
|
|
5910
5924
|
representedBookings ServiceBooking[]
|
|
5911
5925
|
|
|
5926
|
+
/// Enterprise billing + support (ops-provisioned after a signed pilot/annual
|
|
5927
|
+
/// deal — not self-serve). See docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md.
|
|
5928
|
+
billingPoNumber String? // Purchase order number for invoiced billing
|
|
5929
|
+
billingContactEmail String? // AP / finance contact, distinct from pocEmail
|
|
5930
|
+
invoiceNetDays Int? // e.g. 30 for Net 30 terms; null = card/Stripe billing only
|
|
5931
|
+
enterpriseContractStartAt DateTime?
|
|
5932
|
+
enterpriseContractEndAt DateTime?
|
|
5933
|
+
accountManagerUserId String?
|
|
5934
|
+
accountManager User? @relation("OrganizationAccountManager", fields: [accountManagerUserId], references: [id], onDelete: SetNull)
|
|
5935
|
+
/// "Standard" | "Priority" | "Dedicated" — drives named-AM banner + support routing copy.
|
|
5936
|
+
supportTier String @default("Standard")
|
|
5937
|
+
|
|
5938
|
+
/// SSO / identity (Organization Enterprise only). `ssoEnforced` blocks password
|
|
5939
|
+
/// login for members whose email matches `ssoDomain` once `ssoConnection.status`
|
|
5940
|
+
/// is Active. Denormalized here (rather than requiring a join) so the
|
|
5941
|
+
/// pre-login domain check on every sign-in attempt stays a single indexed
|
|
5942
|
+
/// lookup; `ssoConnection` holds the protocol-specific IdP configuration.
|
|
5943
|
+
ssoProvider String? // "saml" | "oidc" — mirrors ssoConnection.protocol; drives login routing
|
|
5944
|
+
ssoDomain String? // Verified email domain that triggers SSO redirect
|
|
5945
|
+
ssoEnforced Boolean @default(false)
|
|
5946
|
+
ssoConfiguredAt DateTime?
|
|
5947
|
+
ssoConnection OrganizationSsoConnection?
|
|
5948
|
+
|
|
5949
|
+
/// Procurement approval policy (nullable JSON). Null = no gate, billing/spend
|
|
5950
|
+
/// proceeds without a PO. Shape when set:
|
|
5951
|
+
/// { requirePoForSubscription: boolean, approverUserIds: string[], minAmountCentsForApproval: number }
|
|
5952
|
+
procurementApprovalPolicy Json?
|
|
5953
|
+
procurementRequests ProcurementRequest[]
|
|
5954
|
+
|
|
5955
|
+
/// Enterprise integrations + developer platform (API keys, outbound webhooks,
|
|
5956
|
+
/// third-party connectors like Mailchimp/Salesforce).
|
|
5957
|
+
integrations OrganizationIntegration[]
|
|
5958
|
+
apiKeys OrganizationApiKey[]
|
|
5959
|
+
webhooks OrganizationWebhook[]
|
|
5960
|
+
|
|
5961
|
+
/// Compliance (deal-driven, Phase 5): W-9 collection for institutional
|
|
5962
|
+
/// finance + certificate-of-insurance tracking. Ops reviews both manually.
|
|
5963
|
+
taxDocuments OrganizationTaxDocument[]
|
|
5964
|
+
insuranceCertificates OrganizationInsuranceCertificate[]
|
|
5965
|
+
|
|
5966
|
+
/// Org-scoped slice of the platform audit trail (member approvals, budget
|
|
5967
|
+
/// approvals, settings changes, SSO logins, API key/webhook/integration events).
|
|
5968
|
+
auditLogs AuditLog[] @relation("OrganizationAuditLogs")
|
|
5969
|
+
|
|
5912
5970
|
@@index([ownerId])
|
|
5913
5971
|
@@index([slug])
|
|
5914
5972
|
@@index([parentOrganizationId])
|
|
5915
5973
|
@@index([nonprofitVerifiedAt])
|
|
5974
|
+
@@index([accountManagerUserId])
|
|
5916
5975
|
}
|
|
5917
5976
|
|
|
5918
5977
|
/// Email-first followers of a public organizer (org landing). Authenticated users who
|
|
@@ -10840,6 +10899,12 @@ model AuditLog {
|
|
|
10840
10899
|
userId String?
|
|
10841
10900
|
user User? @relation("UserAuditLogs", fields: [userId], references: [id], onDelete: SetNull)
|
|
10842
10901
|
|
|
10902
|
+
/// Set when the action happened in the context of an Organization (member
|
|
10903
|
+
/// approvals, budget approvals, settings changes, SSO logins, API keys,
|
|
10904
|
+
/// webhooks, integrations, procurement). Null for platform-wide/user actions.
|
|
10905
|
+
organizationId String?
|
|
10906
|
+
organization Organization? @relation("OrganizationAuditLogs", fields: [organizationId], references: [id], onDelete: SetNull)
|
|
10907
|
+
|
|
10843
10908
|
// Action details
|
|
10844
10909
|
action AuditAction // LOGIN_SUCCESS, LOGIN_FAILED, PAYMENT_PROCESSED, etc.
|
|
10845
10910
|
category AuditCategory // Authentication, Payment, Admin, DataAccess, etc.
|
|
@@ -10864,6 +10929,7 @@ model AuditLog {
|
|
|
10864
10929
|
@@index([category, severity, createdAt])
|
|
10865
10930
|
@@index([resourceType, resourceId])
|
|
10866
10931
|
@@index([ipAddress])
|
|
10932
|
+
@@index([organizationId, createdAt])
|
|
10867
10933
|
}
|
|
10868
10934
|
|
|
10869
10935
|
/// Token Revocation System
|
|
@@ -10959,6 +11025,30 @@ enum AuditAction {
|
|
|
10959
11025
|
RATE_LIMIT_EXCEEDED
|
|
10960
11026
|
IP_BLOCKED
|
|
10961
11027
|
TOKEN_REVOKED
|
|
11028
|
+
|
|
11029
|
+
// Organization (enterprise platform — member/budget approvals, settings,
|
|
11030
|
+
// SSO, procurement, API keys, webhooks, integrations)
|
|
11031
|
+
ORG_MEMBER_APPROVED
|
|
11032
|
+
ORG_MEMBER_REJECTED
|
|
11033
|
+
ORG_BUDGET_APPROVED
|
|
11034
|
+
ORG_SETTINGS_CHANGED
|
|
11035
|
+
ORG_BILLING_UPDATED
|
|
11036
|
+
ORG_SSO_CONFIGURED
|
|
11037
|
+
ORG_SSO_LOGIN
|
|
11038
|
+
ORG_PROCUREMENT_SUBMITTED
|
|
11039
|
+
ORG_PROCUREMENT_APPROVED
|
|
11040
|
+
ORG_PROCUREMENT_REJECTED
|
|
11041
|
+
ORG_API_KEY_CREATED
|
|
11042
|
+
ORG_API_KEY_REVOKED
|
|
11043
|
+
ORG_WEBHOOK_CREATED
|
|
11044
|
+
ORG_WEBHOOK_DELETED
|
|
11045
|
+
ORG_INTEGRATION_CONNECTED
|
|
11046
|
+
ORG_INTEGRATION_DISCONNECTED
|
|
11047
|
+
ORG_AUDIT_LOG_EXPORTED
|
|
11048
|
+
ORG_TAX_DOCUMENT_SUBMITTED
|
|
11049
|
+
ORG_TAX_DOCUMENT_REVIEWED
|
|
11050
|
+
ORG_INSURANCE_CERTIFICATE_SUBMITTED
|
|
11051
|
+
ORG_INSURANCE_CERTIFICATE_REVIEWED
|
|
10962
11052
|
}
|
|
10963
11053
|
|
|
10964
11054
|
enum AuditCategory {
|
|
@@ -10973,6 +11063,7 @@ enum AuditCategory {
|
|
|
10973
11063
|
DataAccess
|
|
10974
11064
|
Compliance
|
|
10975
11065
|
Security
|
|
11066
|
+
Organization
|
|
10976
11067
|
}
|
|
10977
11068
|
|
|
10978
11069
|
enum AuditSeverity {
|
|
@@ -10981,6 +11072,260 @@ enum AuditSeverity {
|
|
|
10981
11072
|
Critical // Security incidents
|
|
10982
11073
|
}
|
|
10983
11074
|
|
|
11075
|
+
/// ─── Enterprise platform: procurement, API keys, webhooks, integrations ────
|
|
11076
|
+
/// Shared by universities/nonprofits/venues/brands buying Organization
|
|
11077
|
+
/// Enterprise. See docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md for sales framing.
|
|
11078
|
+
|
|
11079
|
+
model ProcurementRequest {
|
|
11080
|
+
id String @id @default(cuid())
|
|
11081
|
+
organizationId String
|
|
11082
|
+
|
|
11083
|
+
/// "OrgSubscription" | "EventSpend" | "VendorContract" | "Other"
|
|
11084
|
+
type String
|
|
11085
|
+
|
|
11086
|
+
amountCents Int
|
|
11087
|
+
poNumber String?
|
|
11088
|
+
description String?
|
|
11089
|
+
|
|
11090
|
+
/// "Draft" | "Submitted" | "Approved" | "Rejected" | "Cancelled"
|
|
11091
|
+
status String @default("Draft")
|
|
11092
|
+
|
|
11093
|
+
submittedById String?
|
|
11094
|
+
/// Snapshot of who is entitled to approve at submit time (org's
|
|
11095
|
+
/// `procurementApprovalPolicy.approverUserIds`, or Owner/Admin if unset).
|
|
11096
|
+
approverIds String[] @default([])
|
|
11097
|
+
approvedById String?
|
|
11098
|
+
approvedAt DateTime?
|
|
11099
|
+
rejectedById String?
|
|
11100
|
+
rejectedAt DateTime?
|
|
11101
|
+
rejectionReason String?
|
|
11102
|
+
|
|
11103
|
+
/// Free-form context: linked subscription tier, budget id, vendor name, etc.
|
|
11104
|
+
metadata Json?
|
|
11105
|
+
|
|
11106
|
+
createdAt DateTime @default(now())
|
|
11107
|
+
updatedAt DateTime @updatedAt
|
|
11108
|
+
|
|
11109
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
11110
|
+
submittedBy User? @relation("ProcurementRequestSubmitter", fields: [submittedById], references: [id], onDelete: SetNull)
|
|
11111
|
+
approvedBy User? @relation("ProcurementRequestApprover", fields: [approvedById], references: [id], onDelete: SetNull)
|
|
11112
|
+
|
|
11113
|
+
@@index([organizationId, status])
|
|
11114
|
+
@@index([submittedById])
|
|
11115
|
+
}
|
|
11116
|
+
|
|
11117
|
+
model OrganizationApiKey {
|
|
11118
|
+
id String @id @default(cuid())
|
|
11119
|
+
organizationId String
|
|
11120
|
+
name String // Host-chosen label, e.g. "Zapier sync"
|
|
11121
|
+
|
|
11122
|
+
/// SHA-256 hash of the secret; the plaintext key is shown once at creation
|
|
11123
|
+
/// and never persisted. Prefix is safe to display for identification.
|
|
11124
|
+
keyHash String @unique
|
|
11125
|
+
keyPrefix String // e.g. "bsh_live_ab12" — shown in the UI list
|
|
11126
|
+
|
|
11127
|
+
scopes String[] @default([]) // e.g. ["read:members", "read:events"]
|
|
11128
|
+
createdById String?
|
|
11129
|
+
lastUsedAt DateTime?
|
|
11130
|
+
revokedAt DateTime?
|
|
11131
|
+
expiresAt DateTime?
|
|
11132
|
+
|
|
11133
|
+
createdAt DateTime @default(now())
|
|
11134
|
+
|
|
11135
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
11136
|
+
createdBy User? @relation("OrganizationApiKeyCreator", fields: [createdById], references: [id], onDelete: SetNull)
|
|
11137
|
+
|
|
11138
|
+
@@index([organizationId])
|
|
11139
|
+
}
|
|
11140
|
+
|
|
11141
|
+
model OrganizationWebhook {
|
|
11142
|
+
id String @id @default(cuid())
|
|
11143
|
+
organizationId String
|
|
11144
|
+
|
|
11145
|
+
url String
|
|
11146
|
+
/// Event types this subscription receives, e.g. "ticket.checked_in",
|
|
11147
|
+
/// "member.approved", "procurement.approved".
|
|
11148
|
+
eventTypes String[] @default([])
|
|
11149
|
+
/// HMAC-SHA256 signing secret (shown once at creation) — verify the
|
|
11150
|
+
/// `X-Bash-Signature` header before trusting the payload.
|
|
11151
|
+
signingSecret String
|
|
11152
|
+
|
|
11153
|
+
isActive Boolean @default(true)
|
|
11154
|
+
lastDeliveryAt DateTime?
|
|
11155
|
+
lastDeliveryStatus String? // "success" | "failed"
|
|
11156
|
+
failureCount Int @default(0)
|
|
11157
|
+
|
|
11158
|
+
createdAt DateTime @default(now())
|
|
11159
|
+
updatedAt DateTime @updatedAt
|
|
11160
|
+
|
|
11161
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
11162
|
+
|
|
11163
|
+
@@index([organizationId])
|
|
11164
|
+
}
|
|
11165
|
+
|
|
11166
|
+
model OrganizationIntegration {
|
|
11167
|
+
id String @id @default(cuid())
|
|
11168
|
+
organizationId String
|
|
11169
|
+
|
|
11170
|
+
/// "Mailchimp" | "Salesforce" — extend as connectors ship.
|
|
11171
|
+
provider String
|
|
11172
|
+
|
|
11173
|
+
/// "Connected" | "Error" | "Disconnected"
|
|
11174
|
+
status String @default("Connected")
|
|
11175
|
+
|
|
11176
|
+
/// Provider credentials, AES-256-CBC encrypted at rest (see
|
|
11177
|
+
/// api/src/utils/organization/organizationIntegrationCrypto.ts). Never
|
|
11178
|
+
/// return this field to the client.
|
|
11179
|
+
encryptedCredentials String?
|
|
11180
|
+
|
|
11181
|
+
/// Non-secret provider config, e.g. { audienceId: "abc123" } for Mailchimp.
|
|
11182
|
+
settings Json?
|
|
11183
|
+
|
|
11184
|
+
lastSyncAt DateTime?
|
|
11185
|
+
lastSyncStatus String? // "success" | "failed"
|
|
11186
|
+
lastSyncError String?
|
|
11187
|
+
connectedById String?
|
|
11188
|
+
|
|
11189
|
+
createdAt DateTime @default(now())
|
|
11190
|
+
updatedAt DateTime @updatedAt
|
|
11191
|
+
|
|
11192
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
11193
|
+
|
|
11194
|
+
@@unique([organizationId, provider])
|
|
11195
|
+
@@index([organizationId])
|
|
11196
|
+
}
|
|
11197
|
+
|
|
11198
|
+
enum SsoProtocol {
|
|
11199
|
+
Saml
|
|
11200
|
+
Oidc
|
|
11201
|
+
}
|
|
11202
|
+
|
|
11203
|
+
enum SsoConnectionStatus {
|
|
11204
|
+
Draft // Admin has started filling in IdP details; not yet validated.
|
|
11205
|
+
Testing // "Test connection" is in flight or failed; ssoEnforced cannot be true.
|
|
11206
|
+
Active // Test succeeded; org can enforce SSO for its verified domain.
|
|
11207
|
+
Disabled // Was Active; admin turned it off without deleting the config.
|
|
11208
|
+
}
|
|
11209
|
+
|
|
11210
|
+
/// Generic SAML 2.0 (as Service Provider) or OIDC (as Relying Party) connection
|
|
11211
|
+
/// for one Organization. No third-party IdP broker (e.g. WorkOS) — Bash acts as
|
|
11212
|
+
/// its own SP/RP against whatever IdP the customer supplies (Okta, Entra ID,
|
|
11213
|
+
/// Google Workspace, generic OIDC). One connection per org for v1.
|
|
11214
|
+
model OrganizationSsoConnection {
|
|
11215
|
+
id String @id @default(cuid())
|
|
11216
|
+
organizationId String @unique
|
|
11217
|
+
protocol SsoProtocol
|
|
11218
|
+
status SsoConnectionStatus @default(Draft)
|
|
11219
|
+
|
|
11220
|
+
/// SAML 2.0 — values copied from the customer's IdP app configuration.
|
|
11221
|
+
samlIdpEntityId String?
|
|
11222
|
+
samlIdpSsoUrl String? // IdP's Single Sign-On (AuthnRequest destination) URL
|
|
11223
|
+
samlIdpCertificate String? // IdP's public signing cert (PEM), used to verify the SAMLResponse signature
|
|
11224
|
+
|
|
11225
|
+
/// OIDC — values copied from the customer's IdP app configuration. The
|
|
11226
|
+
/// authorization/token/jwks endpoints are filled in by discovery against
|
|
11227
|
+
/// `oidcIssuer` when the connection is tested, not entered by the admin.
|
|
11228
|
+
oidcIssuer String?
|
|
11229
|
+
oidcClientId String?
|
|
11230
|
+
/// AES-256-CBC encrypted (see organizationIntegrationCrypto.ts). Never
|
|
11231
|
+
/// returned to the client.
|
|
11232
|
+
oidcClientSecretEncrypted String?
|
|
11233
|
+
oidcAuthorizationEndpoint String?
|
|
11234
|
+
oidcTokenEndpoint String?
|
|
11235
|
+
oidcJwksUri String?
|
|
11236
|
+
|
|
11237
|
+
lastTestedAt DateTime?
|
|
11238
|
+
lastTestStatus String? // "Success" | "Failed"
|
|
11239
|
+
lastTestError String?
|
|
11240
|
+
|
|
11241
|
+
configuredByUserId String?
|
|
11242
|
+
configuredBy User? @relation("OrganizationSsoConfiguredBy", fields: [configuredByUserId], references: [id], onDelete: SetNull)
|
|
11243
|
+
|
|
11244
|
+
createdAt DateTime @default(now())
|
|
11245
|
+
updatedAt DateTime @updatedAt
|
|
11246
|
+
|
|
11247
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
11248
|
+
|
|
11249
|
+
@@index([organizationId])
|
|
11250
|
+
}
|
|
11251
|
+
|
|
11252
|
+
enum OrgComplianceDocStatus {
|
|
11253
|
+
Pending
|
|
11254
|
+
Approved
|
|
11255
|
+
Rejected
|
|
11256
|
+
}
|
|
11257
|
+
|
|
11258
|
+
enum OrgTaxDocumentType {
|
|
11259
|
+
W9
|
|
11260
|
+
W8BEN
|
|
11261
|
+
}
|
|
11262
|
+
|
|
11263
|
+
/// Institutional tax documentation (deal-driven, Phase 5). Ops reviews the
|
|
11264
|
+
/// uploaded PDF manually — same "admin approves after out-of-band review"
|
|
11265
|
+
/// shape as nonprofit verification, just with a real self-serve upload
|
|
11266
|
+
/// (presigned S3, see AwsS3Utils.createUrlFromAssetKey) instead of an
|
|
11267
|
+
/// ops-entered doc id.
|
|
11268
|
+
model OrganizationTaxDocument {
|
|
11269
|
+
id String @id @default(cuid())
|
|
11270
|
+
organizationId String
|
|
11271
|
+
type OrgTaxDocumentType @default(W9)
|
|
11272
|
+
|
|
11273
|
+
legalBusinessName String
|
|
11274
|
+
taxClassification String? // e.g. "C Corporation", "LLC", "Nonprofit" — free text, mirrors the W-9 form
|
|
11275
|
+
taxIdLast4 String? // Last 4 of EIN/SSN only; never store the full TIN
|
|
11276
|
+
docUrl String // Uploaded W-9 PDF
|
|
11277
|
+
|
|
11278
|
+
status OrgComplianceDocStatus @default(Pending)
|
|
11279
|
+
rejectionReason String?
|
|
11280
|
+
|
|
11281
|
+
submittedByUserId String?
|
|
11282
|
+
reviewedByUserId String?
|
|
11283
|
+
reviewedAt DateTime?
|
|
11284
|
+
|
|
11285
|
+
createdAt DateTime @default(now())
|
|
11286
|
+
updatedAt DateTime @updatedAt
|
|
11287
|
+
|
|
11288
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
11289
|
+
submittedBy User? @relation("OrgTaxDocumentSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
|
|
11290
|
+
reviewedBy User? @relation("OrgTaxDocumentReviewedBy", fields: [reviewedByUserId], references: [id], onDelete: SetNull)
|
|
11291
|
+
|
|
11292
|
+
@@index([organizationId])
|
|
11293
|
+
@@index([status])
|
|
11294
|
+
}
|
|
11295
|
+
|
|
11296
|
+
/// Certificate of insurance tracking at the Organization level (deal-driven,
|
|
11297
|
+
/// Phase 5) — e.g. a university requiring proof of general liability coverage
|
|
11298
|
+
/// before an annual agreement. Distinct from the per-`Vendor` insurance flags
|
|
11299
|
+
/// on `VendorInsurance`, which cover marketplace vendors, not the org itself.
|
|
11300
|
+
model OrganizationInsuranceCertificate {
|
|
11301
|
+
id String @id @default(cuid())
|
|
11302
|
+
organizationId String
|
|
11303
|
+
|
|
11304
|
+
insurerName String
|
|
11305
|
+
policyNumber String?
|
|
11306
|
+
coverageAmountCents Int?
|
|
11307
|
+
docUrl String // Uploaded certificate-of-insurance PDF
|
|
11308
|
+
expiresAt DateTime
|
|
11309
|
+
|
|
11310
|
+
status OrgComplianceDocStatus @default(Pending)
|
|
11311
|
+
rejectionReason String?
|
|
11312
|
+
|
|
11313
|
+
submittedByUserId String?
|
|
11314
|
+
reviewedByUserId String?
|
|
11315
|
+
reviewedAt DateTime?
|
|
11316
|
+
|
|
11317
|
+
createdAt DateTime @default(now())
|
|
11318
|
+
updatedAt DateTime @updatedAt
|
|
11319
|
+
|
|
11320
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
11321
|
+
submittedBy User? @relation("OrgInsuranceCertSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
|
|
11322
|
+
reviewedBy User? @relation("OrgInsuranceCertReviewedBy", fields: [reviewedByUserId], references: [id], onDelete: SetNull)
|
|
11323
|
+
|
|
11324
|
+
@@index([organizationId])
|
|
11325
|
+
@@index([status])
|
|
11326
|
+
@@index([expiresAt])
|
|
11327
|
+
}
|
|
11328
|
+
|
|
10984
11329
|
// Service Analytics Tracking
|
|
10985
11330
|
model ServiceAnalytics {
|
|
10986
11331
|
id String @id @default(cuid())
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
organizationHasApiAccess,
|
|
5
|
+
organizationHasAuditLogAccess,
|
|
6
|
+
organizationHasComplianceAccess,
|
|
7
|
+
organizationHasEnterprisePlatformAccess,
|
|
8
|
+
organizationHasProcurementAccess,
|
|
9
|
+
organizationHasSsoAccess,
|
|
10
|
+
organizationHasVanitySubdomain,
|
|
11
|
+
} from "../organizationEntitlements.js";
|
|
4
12
|
|
|
5
13
|
describe("organizationHasVanitySubdomain", () => {
|
|
6
14
|
test("null / undefined return false", () => {
|
|
@@ -57,3 +65,43 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
57
65
|
}
|
|
58
66
|
});
|
|
59
67
|
});
|
|
68
|
+
|
|
69
|
+
describe("enterprise platform gates (audit logs, SSO, procurement, API access)", () => {
|
|
70
|
+
const enterpriseActive = {
|
|
71
|
+
subscriptionTier: "Enterprise",
|
|
72
|
+
subscriptionStatus: "Active",
|
|
73
|
+
};
|
|
74
|
+
const growthActive = { subscriptionTier: "Growth", subscriptionStatus: "Active" };
|
|
75
|
+
|
|
76
|
+
test("organizationHasEnterprisePlatformAccess mirrors organizationHasVanitySubdomain", () => {
|
|
77
|
+
expect(organizationHasEnterprisePlatformAccess(enterpriseActive)).toBe(true);
|
|
78
|
+
expect(organizationHasEnterprisePlatformAccess(growthActive)).toBe(false);
|
|
79
|
+
expect(organizationHasEnterprisePlatformAccess(null)).toBe(false);
|
|
80
|
+
expect(organizationHasEnterprisePlatformAccess(undefined)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("organizationHasAuditLogAccess follows the shared enterprise gate", () => {
|
|
84
|
+
expect(organizationHasAuditLogAccess(enterpriseActive)).toBe(true);
|
|
85
|
+
expect(organizationHasAuditLogAccess(growthActive)).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("organizationHasSsoAccess follows the shared enterprise gate", () => {
|
|
89
|
+
expect(organizationHasSsoAccess(enterpriseActive)).toBe(true);
|
|
90
|
+
expect(organizationHasSsoAccess(growthActive)).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("organizationHasProcurementAccess follows the shared enterprise gate", () => {
|
|
94
|
+
expect(organizationHasProcurementAccess(enterpriseActive)).toBe(true);
|
|
95
|
+
expect(organizationHasProcurementAccess(growthActive)).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("organizationHasApiAccess follows the shared enterprise gate", () => {
|
|
99
|
+
expect(organizationHasApiAccess(enterpriseActive)).toBe(true);
|
|
100
|
+
expect(organizationHasApiAccess(growthActive)).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("organizationHasComplianceAccess follows the shared enterprise gate", () => {
|
|
104
|
+
expect(organizationHasComplianceAccess(enterpriseActive)).toBe(true);
|
|
105
|
+
expect(organizationHasComplianceAccess(growthActive)).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -35,3 +35,52 @@ export function organizationHasVanitySubdomain(
|
|
|
35
35
|
org.subscriptionStatus
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Shared gate for the enterprise platform surface: org-scoped audit logs,
|
|
41
|
+
* SSO/identity setup, procurement approval policy, API keys, webhooks, and
|
|
42
|
+
* third-party integrations (Mailchimp/Salesforce). All are Enterprise-tier
|
|
43
|
+
* perks today — same predicate as {@link organizationHasVanitySubdomain}, kept
|
|
44
|
+
* as a distinct name so the two can diverge later (e.g. Growth gets audit
|
|
45
|
+
* logs before SSO) without touching every call site.
|
|
46
|
+
*/
|
|
47
|
+
export function organizationHasEnterprisePlatformAccess(
|
|
48
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
49
|
+
): boolean {
|
|
50
|
+
return organizationHasVanitySubdomain(org);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Org-admin audit log viewer + CSV export. */
|
|
54
|
+
export function organizationHasAuditLogAccess(
|
|
55
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
56
|
+
): boolean {
|
|
57
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** SSO/identity setup. */
|
|
61
|
+
export function organizationHasSsoAccess(
|
|
62
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
63
|
+
): boolean {
|
|
64
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Procurement approval workflows (PO-gated billing/spend). */
|
|
68
|
+
export function organizationHasProcurementAccess(
|
|
69
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
70
|
+
): boolean {
|
|
71
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** API keys, outbound webhooks, and third-party integrations. */
|
|
75
|
+
export function organizationHasApiAccess(
|
|
76
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
77
|
+
): boolean {
|
|
78
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Compliance docs (W-9 tax collection, certificate-of-insurance tracking). */
|
|
82
|
+
export function organizationHasComplianceAccess(
|
|
83
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
84
|
+
): boolean {
|
|
85
|
+
return organizationHasEnterprisePlatformAccess(org);
|
|
86
|
+
}
|