placementt-core 1.400.1249 → 1.400.1251

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/lib/bulkEngagement.d.ts +48 -0
  2. package/lib/bulkEngagement.js +72 -0
  3. package/lib/bulkEngagement.js.map +1 -0
  4. package/lib/bulkEngagement.test.d.ts +1 -0
  5. package/lib/bulkEngagement.test.js +63 -0
  6. package/lib/bulkEngagement.test.js.map +1 -0
  7. package/lib/callables/engagement.d.ts +134 -0
  8. package/lib/callables/engagement.js +61 -0
  9. package/lib/callables/engagement.js.map +1 -0
  10. package/lib/callables/index.d.ts +1 -0
  11. package/lib/callables/index.js +1 -0
  12. package/lib/callables/index.js.map +1 -1
  13. package/lib/callables/registry.d.ts +246 -0
  14. package/lib/callables/registry.js +3 -0
  15. package/lib/callables/registry.js.map +1 -1
  16. package/lib/features/search/globalSearch.d.ts +43 -0
  17. package/lib/features/search/globalSearch.js +88 -6
  18. package/lib/features/search/globalSearch.js.map +1 -1
  19. package/lib/features/search/globalSearch.test.d.ts +5 -0
  20. package/lib/features/search/globalSearch.test.js +119 -0
  21. package/lib/features/search/globalSearch.test.js.map +1 -0
  22. package/lib/index.d.ts +1 -0
  23. package/lib/index.js +1 -0
  24. package/lib/index.js.map +1 -1
  25. package/lib/renewals.d.ts +16 -60
  26. package/lib/renewals.js +35 -87
  27. package/lib/renewals.js.map +1 -1
  28. package/lib/renewals.test.js +12 -50
  29. package/lib/renewals.test.js.map +1 -1
  30. package/lib/typeDefinitions.d.ts +0 -11
  31. package/package.json +1 -1
  32. package/src/bulkEngagement.test.ts +72 -0
  33. package/src/bulkEngagement.ts +100 -0
  34. package/src/callables/engagement.ts +60 -0
  35. package/src/callables/index.ts +1 -0
  36. package/src/callables/registry.ts +3 -0
  37. package/src/features/search/globalSearch.test.ts +131 -0
  38. package/src/features/search/globalSearch.ts +88 -5
  39. package/src/index.ts +3 -2
  40. package/src/renewals.test.ts +14 -61
  41. package/src/renewals.ts +30 -122
  42. package/src/typeDefinitions.ts +0 -10
@@ -0,0 +1,48 @@
1
+ import { InstituteData } from "./typeDefinitions";
2
+ import { ResolvedProducts } from "./productResolver";
3
+ /**
4
+ * Bulk engagement emails: one admin email template, sent now to many schools.
5
+ * The admin decides what goes out and when; this only suggests which schools
6
+ * it fits, and flags what is missing for each.
7
+ *
8
+ * Pure: the backend reads each school and hands it in.
9
+ */
10
+ /** Something about a school that makes the email go out incomplete, or not at all. */
11
+ export type BulkEngagementFlag = "noContacts" | "noRenewalDate" | "noQuote" | "noFinanceContact" | "sentRecently";
12
+ export declare const BULK_ENGAGEMENT_FLAG_LABELS: Record<BulkEngagementFlag, string>;
13
+ /** Flags that stop a school being sent the email at all. */
14
+ export declare const BULK_ENGAGEMENT_BLOCKING_FLAGS: BulkEngagementFlag[];
15
+ /** A school that had this email this recently isn't suggested it again. */
16
+ export declare const RECENTLY_SENT_DAYS = 60;
17
+ export type BulkEngagementSchool = {
18
+ institute: InstituteData;
19
+ products: ResolvedProducts;
20
+ now: Date;
21
+ /** How many of the school's contacts it would go to. */
22
+ contactCount: number;
23
+ /** Whether the school has a price for next year. */
24
+ hasQuote: boolean;
25
+ /** The email of the school's next onboarding step, if any. */
26
+ nextOnboardingTemplate?: string;
27
+ /** ISO date this email last went to the school, if it has. */
28
+ lastSent?: string;
29
+ };
30
+ export type BulkEngagementSuggestion = {
31
+ /** Ticked to start with in the send popup. */
32
+ suggested: boolean;
33
+ /** Why it is suggested, for the admin. */
34
+ reason?: string;
35
+ flags: BulkEngagementFlag[];
36
+ };
37
+ /**
38
+ * Whether one email suits one school, and what is missing for it.
39
+ *
40
+ * Renewal emails are suggested by days to renewal, and any other admin email
41
+ * to schools whose next onboarding step it is. Neither is suggested to a
42
+ * school that had it in the last 60 days.
43
+ *
44
+ * @param {string} templateKey the admin email template.
45
+ * @param {BulkEngagementSchool} school the school.
46
+ * @return {BulkEngagementSuggestion} whether to suggest it, why, and its flags.
47
+ */
48
+ export declare const bulkEngagementSuggestion: (templateKey: string, school: BulkEngagementSchool) => BulkEngagementSuggestion;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bulkEngagementSuggestion = exports.RECENTLY_SENT_DAYS = exports.BULK_ENGAGEMENT_BLOCKING_FLAGS = exports.BULK_ENGAGEMENT_FLAG_LABELS = void 0;
4
+ const renewals_1 = require("./renewals");
5
+ const constants_1 = require("./constants");
6
+ exports.BULK_ENGAGEMENT_FLAG_LABELS = {
7
+ noContacts: "No one to send to",
8
+ noRenewalDate: "No renewal date",
9
+ noQuote: "No quote",
10
+ noFinanceContact: "No finance contact",
11
+ sentRecently: "Sent in the last 60 days",
12
+ };
13
+ /** Flags that stop a school being sent the email at all. */
14
+ exports.BULK_ENGAGEMENT_BLOCKING_FLAGS = ["noContacts"];
15
+ /** A school that had this email this recently isn't suggested it again. */
16
+ exports.RECENTLY_SENT_DAYS = 60;
17
+ /**
18
+ * Days to renewal each renewal email is suggested for, inclusive. "paying"
19
+ * suggests it to every paying school, date or not.
20
+ */
21
+ const RENEWAL_SUGGEST_DAYS = {
22
+ engagementRenewalPrice: [61, 365],
23
+ engagementRenewalReview: "paying",
24
+ engagementRenewalDue: [31, 90],
25
+ engagementRenewalInvoice: [0, 45],
26
+ // A date this far off has most likely just moved on a year.
27
+ engagementRenewalThanks: [300, 400],
28
+ };
29
+ /**
30
+ * Whether one email suits one school, and what is missing for it.
31
+ *
32
+ * Renewal emails are suggested by days to renewal, and any other admin email
33
+ * to schools whose next onboarding step it is. Neither is suggested to a
34
+ * school that had it in the last 60 days.
35
+ *
36
+ * @param {string} templateKey the admin email template.
37
+ * @param {BulkEngagementSchool} school the school.
38
+ * @return {BulkEngagementSuggestion} whether to suggest it, why, and its flags.
39
+ */
40
+ const bulkEngagementSuggestion = (templateKey, school) => {
41
+ const { institute, products, now, contactCount, hasQuote, nextOnboardingTemplate, lastSent } = school;
42
+ const params = constants_1.emailTemplates[templateKey]?.params || [];
43
+ const renewal = (0, renewals_1.renewalInfo)({ institute, products, now });
44
+ const recent = Boolean(lastSent) && now.getTime() - Date.parse(lastSent) < exports.RECENTLY_SENT_DAYS * 86400000;
45
+ const flags = [];
46
+ if (!contactCount)
47
+ flags.push("noContacts");
48
+ if (params.includes("renewalDate") && !renewal.renewalDate)
49
+ flags.push("noRenewalDate");
50
+ if (params.includes("quote") && !hasQuote)
51
+ flags.push("noQuote");
52
+ if (renewals_1.RENEWAL_FINANCE_TEMPLATES.includes(templateKey) && !institute.financeContact)
53
+ flags.push("noFinanceContact");
54
+ if (recent)
55
+ flags.push("sentRecently");
56
+ const none = { suggested: false, flags };
57
+ if (recent || flags.some((f) => exports.BULK_ENGAGEMENT_BLOCKING_FLAGS.includes(f)))
58
+ return none;
59
+ const window = RENEWAL_SUGGEST_DAYS[templateKey];
60
+ if (!window)
61
+ return nextOnboardingTemplate === templateKey ? { suggested: true, reason: "Their next onboarding step", flags } : none;
62
+ if (!renewal.applies)
63
+ return none;
64
+ if (window === "paying")
65
+ return { suggested: true, reason: "Paying customer", flags };
66
+ const days = renewal.daysToRenewal;
67
+ if (days === undefined || days < window[0] || days > window[1])
68
+ return none;
69
+ return { suggested: true, reason: `Renews in ${days} days`, flags };
70
+ };
71
+ exports.bulkEngagementSuggestion = bulkEngagementSuggestion;
72
+ //# sourceMappingURL=bulkEngagement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulkEngagement.js","sourceRoot":"","sources":["../src/bulkEngagement.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ jest.mock("firebase/firestore", () => ({ where: jest.fn() }));
4
+ jest.mock("./firebase/firebaseQuery", () => ({ __esModule: true, default: class {
5
+ } }));
6
+ const productResolver_1 = require("./productResolver");
7
+ const bulkEngagement_1 = require("./bulkEngagement");
8
+ const school = (renewalDate, overrides = {}) => ({
9
+ name: "Calder Park Academy",
10
+ package: "institutes-one",
11
+ workExperience: true,
12
+ cs: { lifecycleStatus: "maintenance", productTypes: ["workExperience"], ...(renewalDate ? { renewalDate } : {}) },
13
+ ...overrides,
14
+ });
15
+ const suggest = (templateKey, institute, date, extra = {}) => (0, bulkEngagement_1.bulkEngagementSuggestion)(templateKey, {
16
+ institute,
17
+ products: (0, productResolver_1.resolveProducts)(institute),
18
+ now: new Date(`${date}T09:00:00Z`),
19
+ contactCount: extra.contactCount ?? 2,
20
+ hasQuote: extra.hasQuote ?? true,
21
+ nextOnboardingTemplate: extra.nextOnboardingTemplate,
22
+ lastSent: extra.lastSent,
23
+ });
24
+ describe("bulk engagement suggestions", () => {
25
+ it("suggests the 60-day email to schools renewing in 31 to 90 days", () => {
26
+ expect(suggest("engagementRenewalDue", school("2027-01-10"), "2026-11-04")).toMatchObject({ suggested: true, reason: "Renews in 67 days" });
27
+ expect(suggest("engagementRenewalDue", school("2027-06-10"), "2026-11-04").suggested).toBe(false);
28
+ expect(suggest("engagementRenewalDue", school("2026-11-20"), "2026-11-04").suggested).toBe(false);
29
+ });
30
+ it("suggests the invoice email inside 45 days, and next year's price further out", () => {
31
+ expect(suggest("engagementRenewalInvoice", school("2026-11-20"), "2026-11-04").suggested).toBe(true);
32
+ expect(suggest("engagementRenewalPrice", school("2027-06-10"), "2026-11-04").suggested).toBe(true);
33
+ expect(suggest("engagementRenewalPrice", school("2026-11-20"), "2026-11-04").suggested).toBe(false);
34
+ });
35
+ it("suggests the year in review to every paying school, and thanks to those just renewed", () => {
36
+ expect(suggest("engagementRenewalReview", school(), "2026-11-04").suggested).toBe(true);
37
+ expect(suggest("engagementRenewalThanks", school("2027-10-20"), "2026-11-04").suggested).toBe(true);
38
+ expect(suggest("engagementRenewalThanks", school("2027-01-10"), "2026-11-04").suggested).toBe(false);
39
+ });
40
+ it("leaves out hub schools", () => {
41
+ expect(suggest("engagementRenewalDue", school("2027-01-10", { package: "careersSchool" }), "2026-11-04").suggested).toBe(false);
42
+ });
43
+ it("doesn't suggest an email the school had in the last 60 days", () => {
44
+ const recent = suggest("engagementRenewalDue", school("2027-01-10"), "2026-11-04", { lastSent: "2026-10-01T09:00:00Z" });
45
+ expect(recent).toMatchObject({ suggested: false, flags: expect.arrayContaining(["sentRecently"]) });
46
+ expect(suggest("engagementRenewalDue", school("2027-01-10"), "2026-11-04", { lastSent: "2026-06-01T09:00:00Z" }).suggested).toBe(true);
47
+ });
48
+ it("flags what the email is missing", () => {
49
+ expect(suggest("engagementRenewalDue", school(), "2026-11-04", { hasQuote: false }).flags)
50
+ .toEqual(["noRenewalDate", "noQuote", "noFinanceContact"]);
51
+ const withFinance = school("2027-01-10", { financeContact: { name: "Pat Lee", email: "pat@school.org" } });
52
+ expect(suggest("engagementRenewalDue", withFinance, "2026-11-04").flags).toEqual([]);
53
+ });
54
+ it("never suggests a school with no one to send to", () => {
55
+ expect(suggest("engagementRenewalReview", school(), "2026-11-04", { contactCount: 0 })).toMatchObject({ suggested: false, flags: ["noContacts"] });
56
+ });
57
+ it("suggests an onboarding email to schools whose next step it is", () => {
58
+ expect(suggest("engagementPickFeatures", school(), "2026-11-04", { nextOnboardingTemplate: "engagementPickFeatures" }))
59
+ .toMatchObject({ suggested: true, reason: "Their next onboarding step" });
60
+ expect(suggest("engagementPickFeatures", school(), "2026-11-04", { nextOnboardingTemplate: "engagementPlanYear" }).suggested).toBe(false);
61
+ });
62
+ });
63
+ //# sourceMappingURL=bulkEngagement.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulkEngagement.test.js","sourceRoot":"","sources":["../src/bulkEngagement.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ import { z } from "zod";
2
+ export declare const engagementCallables: {
3
+ "bulkEngagement-getBulkEngagementSchools": {
4
+ request: z.ZodObject<{
5
+ /** An admin email template (product "admin" in emailTemplates). */
6
+ templateKey: z.ZodString;
7
+ }, "strip", z.ZodTypeAny, {
8
+ templateKey: string;
9
+ }, {
10
+ templateKey: string;
11
+ }>;
12
+ response: z.ZodObject<{
13
+ schools: z.ZodArray<z.ZodObject<{
14
+ oId: z.ZodString;
15
+ name: z.ZodString;
16
+ package: z.ZodString;
17
+ suggested: z.ZodBoolean;
18
+ reason: z.ZodOptional<z.ZodString>;
19
+ flags: z.ZodArray<z.ZodEnum<["noContacts", "noRenewalDate", "noQuote", "noFinanceContact", "sentRecently"]>, "many">;
20
+ /** How many of the school's contacts it would go to. */
21
+ contacts: z.ZodNumber;
22
+ /** ISO date this email last went to the school, if it has. */
23
+ lastSent: z.ZodOptional<z.ZodString>;
24
+ /** The school's params, for previewing the email as they'd get it. */
25
+ params: z.ZodRecord<z.ZodString, z.ZodString>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ name: string;
28
+ oId: string;
29
+ package: string;
30
+ flags: ("noContacts" | "noRenewalDate" | "noQuote" | "noFinanceContact" | "sentRecently")[];
31
+ contacts: number;
32
+ params: Record<string, string>;
33
+ suggested: boolean;
34
+ reason?: string | undefined;
35
+ lastSent?: string | undefined;
36
+ }, {
37
+ name: string;
38
+ oId: string;
39
+ package: string;
40
+ flags: ("noContacts" | "noRenewalDate" | "noQuote" | "noFinanceContact" | "sentRecently")[];
41
+ contacts: number;
42
+ params: Record<string, string>;
43
+ suggested: boolean;
44
+ reason?: string | undefined;
45
+ lastSent?: string | undefined;
46
+ }>, "many">;
47
+ }, "strip", z.ZodTypeAny, {
48
+ schools: {
49
+ name: string;
50
+ oId: string;
51
+ package: string;
52
+ flags: ("noContacts" | "noRenewalDate" | "noQuote" | "noFinanceContact" | "sentRecently")[];
53
+ contacts: number;
54
+ params: Record<string, string>;
55
+ suggested: boolean;
56
+ reason?: string | undefined;
57
+ lastSent?: string | undefined;
58
+ }[];
59
+ }, {
60
+ schools: {
61
+ name: string;
62
+ oId: string;
63
+ package: string;
64
+ flags: ("noContacts" | "noRenewalDate" | "noQuote" | "noFinanceContact" | "sentRecently")[];
65
+ contacts: number;
66
+ params: Record<string, string>;
67
+ suggested: boolean;
68
+ reason?: string | undefined;
69
+ lastSent?: string | undefined;
70
+ }[];
71
+ }>;
72
+ };
73
+ "bulkEngagement-sendBulkEngagementEmail": {
74
+ request: z.ZodObject<{
75
+ templateKey: z.ZodString;
76
+ oIds: z.ZodArray<z.ZodString, "many">;
77
+ subject: z.ZodString;
78
+ /** The Slate body, edited for this send. The template itself is unchanged. */
79
+ body: z.ZodArray<z.ZodUnknown, "many">;
80
+ /** Must be in our storage bucket; checked on the server. */
81
+ heroImage: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | undefined, string | null | undefined>;
82
+ /** Send the weekly follow-ups: up to three, or until an onboarding step is done. */
83
+ followUps: z.ZodBoolean;
84
+ }, "strip", z.ZodTypeAny, {
85
+ body: unknown[];
86
+ subject: string;
87
+ templateKey: string;
88
+ oIds: string[];
89
+ followUps: boolean;
90
+ heroImage?: string | undefined;
91
+ }, {
92
+ body: unknown[];
93
+ subject: string;
94
+ templateKey: string;
95
+ oIds: string[];
96
+ followUps: boolean;
97
+ heroImage?: string | null | undefined;
98
+ }>;
99
+ /** Each school's outcome: who it went to, or why it didn't. */
100
+ response: z.ZodObject<{
101
+ results: z.ZodRecord<z.ZodString, z.ZodObject<{
102
+ sent: z.ZodArray<z.ZodString, "many">;
103
+ error: z.ZodOptional<z.ZodString>;
104
+ }, "strip", z.ZodTypeAny, {
105
+ sent: string[];
106
+ error?: string | undefined;
107
+ }, {
108
+ sent: string[];
109
+ error?: string | undefined;
110
+ }>>;
111
+ }, "strip", z.ZodTypeAny, {
112
+ results: Record<string, {
113
+ sent: string[];
114
+ error?: string | undefined;
115
+ }>;
116
+ }, {
117
+ results: Record<string, {
118
+ sent: string[];
119
+ error?: string | undefined;
120
+ }>;
121
+ }>;
122
+ };
123
+ /** One-off: removes the open renewal email tasks the old automatic schedule made. */
124
+ "bulkEngagement-clearRenewalEmailTasks": {
125
+ request: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
126
+ response: z.ZodObject<{
127
+ cleared: z.ZodNumber;
128
+ }, "strip", z.ZodTypeAny, {
129
+ cleared: number;
130
+ }, {
131
+ cleared: number;
132
+ }>;
133
+ };
134
+ };
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.engagementCallables = void 0;
4
+ const zod_1 = require("zod");
5
+ const types_1 = require("./types");
6
+ /**
7
+ * Contracts for bulk engagement emails (`Placementt-Backend/functions/src/bulkEngagement.ts`).
8
+ *
9
+ * An admin picks one admin email template and the schools to send it to (the
10
+ * backend suggests some; bulkEngagement.ts in core says which), and it goes
11
+ * now, through the same path as a single engagement email, so each send is
12
+ * an email interaction on the school. All of them are admin-only.
13
+ */
14
+ const flag = zod_1.z.enum(["noContacts", "noRenewalDate", "noQuote", "noFinanceContact", "sentRecently"]);
15
+ exports.engagementCallables = {
16
+ "bulkEngagement-getBulkEngagementSchools": {
17
+ request: zod_1.z.object({
18
+ /** An admin email template (product "admin" in emailTemplates). */
19
+ templateKey: zod_1.z.string().min(1),
20
+ }),
21
+ response: zod_1.z.object({
22
+ schools: zod_1.z.array(zod_1.z.object({
23
+ oId: zod_1.z.string(),
24
+ name: zod_1.z.string(),
25
+ package: zod_1.z.string(),
26
+ suggested: zod_1.z.boolean(),
27
+ reason: zod_1.z.string().optional(),
28
+ flags: zod_1.z.array(flag),
29
+ /** How many of the school's contacts it would go to. */
30
+ contacts: zod_1.z.number(),
31
+ /** ISO date this email last went to the school, if it has. */
32
+ lastSent: zod_1.z.string().optional(),
33
+ /** The school's params, for previewing the email as they'd get it. */
34
+ params: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
35
+ })),
36
+ }),
37
+ },
38
+ "bulkEngagement-sendBulkEngagementEmail": {
39
+ request: zod_1.z.object({
40
+ templateKey: zod_1.z.string().min(1),
41
+ oIds: zod_1.z.array(zod_1.z.string().min(1)).min(1, "Pick at least one school.").max(500),
42
+ subject: zod_1.z.string().trim().min(1, "Give it a subject."),
43
+ /** The Slate body, edited for this send. The template itself is unchanged. */
44
+ body: zod_1.z.array(zod_1.z.unknown()).min(1),
45
+ /** Must be in our storage bucket; checked on the server. */
46
+ heroImage: (0, types_1.optionalField)(zod_1.z.string()),
47
+ /** Send the weekly follow-ups: up to three, or until an onboarding step is done. */
48
+ followUps: zod_1.z.boolean(),
49
+ }),
50
+ /** Each school's outcome: who it went to, or why it didn't. */
51
+ response: zod_1.z.object({
52
+ results: zod_1.z.record(zod_1.z.string(), zod_1.z.object({ sent: zod_1.z.array(zod_1.z.string()), error: zod_1.z.string().optional() })),
53
+ }),
54
+ },
55
+ /** One-off: removes the open renewal email tasks the old automatic schedule made. */
56
+ "bulkEngagement-clearRenewalEmailTasks": {
57
+ request: zod_1.z.object({}).optional(),
58
+ response: zod_1.z.object({ cleared: zod_1.z.number() }),
59
+ },
60
+ };
61
+ //# sourceMappingURL=engagement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engagement.js","sourceRoot":"","sources":["../../src/callables/engagement.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AACtB,mCAAqD;AAErD;;;;;;;GAOG;AAEH,MAAM,IAAI,GAAG,OAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,kBAAkB,EAAE,cAAc,CAAC,CAAC,CAAC;AAEvF,QAAA,mBAAmB,GAAG;IAC/B,yCAAyC,EAAE;QACvC,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,mEAAmE;YACnE,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;SACjC,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,OAAO,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,CAAC;gBACtB,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;gBACf,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;gBAChB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;gBACnB,SAAS,EAAE,OAAC,CAAC,OAAO,EAAE;gBACtB,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;gBAC7B,KAAK,EAAE,OAAC,CAAC,KAAK,CAAC,IAAI,CAAC;gBACpB,wDAAwD;gBACxD,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;gBACpB,8DAA8D;gBAC9D,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;gBAC/B,sEAAsE;gBACtE,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC;aAC3C,CAAC,CAAC;SACN,CAAC;KACL;IACD,wCAAwC,EAAE;QACtC,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,2BAA2B,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;YAC7E,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC;YACvD,8EAA8E;YAC9E,IAAI,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACjC,4DAA4D;YAC5D,SAAS,EAAE,IAAA,qBAAa,EAAC,OAAC,CAAC,MAAM,EAAE,CAAC;YACpC,oFAAoF;YACpF,SAAS,EAAE,OAAC,CAAC,OAAO,EAAE;SACzB,CAAC;QACF,+DAA+D;QAC/D,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,MAAM,CAAC,EAAC,IAAI,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAC,CAAC,CAAC;SACrG,CAAC;KACL;IACD,qFAAqF;IACrF,uCAAuC,EAAE;QACrC,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;QAChC,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC,EAAC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,EAAC,CAAC;KAC5C;CACoB,CAAC"}
@@ -16,3 +16,4 @@ export * from "./userManagement";
16
16
  export * from "./outreach";
17
17
  export * from "./formImport";
18
18
  export * from "./activities";
19
+ export * from "./engagement";
@@ -32,4 +32,5 @@ __exportStar(require("./userManagement"), exports);
32
32
  __exportStar(require("./outreach"), exports);
33
33
  __exportStar(require("./formImport"), exports);
34
34
  __exportStar(require("./activities"), exports);
35
+ __exportStar(require("./engagement"), exports);
35
36
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/callables/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,6CAA2B;AAC3B,uDAAqC;AACrC,8CAA4B;AAC5B,2CAAyB;AACzB,6CAA2B;AAC3B,oDAAkC;AAClC,uCAAqB;AACrB,oDAAkC;AAClC,4CAA0B;AAC1B,oDAAkC;AAClC,wCAAsB;AACtB,0CAAwB;AACxB,4CAA0B;AAC1B,mDAAiC;AACjC,6CAA2B;AAC3B,+CAA6B;AAC7B,+CAA6B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/callables/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,6CAA2B;AAC3B,uDAAqC;AACrC,8CAA4B;AAC5B,2CAAyB;AACzB,6CAA2B;AAC3B,oDAAkC;AAClC,uCAAqB;AACrB,oDAAkC;AAClC,4CAA0B;AAC1B,oDAAkC;AAClC,wCAAsB;AACtB,0CAAwB;AACxB,4CAA0B;AAC1B,mDAAiC;AACjC,6CAA2B;AAC3B,+CAA6B;AAC7B,+CAA6B;AAC7B,+CAA6B"}