placementt-core 1.400.1237 → 1.400.1239
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/lib/constants.d.ts +4 -1
- package/lib/constants.js +256 -3
- package/lib/constants.js.map +1 -1
- package/lib/onboardingPathways.d.ts +43 -20
- package/lib/onboardingPathways.js +57 -44
- package/lib/onboardingPathways.js.map +1 -1
- package/lib/onboardingPathways.test.js +18 -7
- package/lib/onboardingPathways.test.js.map +1 -1
- package/lib/renewals.d.ts +21 -6
- package/lib/renewals.js +42 -9
- package/lib/renewals.js.map +1 -1
- package/lib/renewals.test.js +17 -0
- package/lib/renewals.test.js.map +1 -1
- package/lib/typeDefinitions.d.ts +8 -70
- package/lib/typeDefinitions.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +256 -2
- package/src/onboardingPathways.test.ts +21 -6
- package/src/onboardingPathways.ts +60 -51
- package/src/renewals.test.ts +21 -1
- package/src/renewals.ts +45 -11
- package/src/typeDefinitions.ts +10 -70
package/lib/renewals.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.renewalSituation = exports.hasRenewal = exports.
|
|
3
|
+
exports.renewalTaskSchedule = exports.renewalSituation = exports.hasRenewal = exports.RENEWAL_EMAIL_TEMPLATES = exports.RENEWAL_FINANCE_STEPS = exports.RENEWAL_STEP_LABELS = exports.RENEWAL_STEPS = void 0;
|
|
4
4
|
const outreachRules_1 = require("./outreachRules");
|
|
5
5
|
exports.RENEWAL_STEPS = ["headsUp", "yearInReview", "sixtyDay", "thirtyDay", "thanks"];
|
|
6
6
|
exports.RENEWAL_STEP_LABELS = {
|
|
@@ -12,14 +12,13 @@ exports.RENEWAL_STEP_LABELS = {
|
|
|
12
12
|
};
|
|
13
13
|
/** Steps that copy in the finance contact as well as the school's contacts. */
|
|
14
14
|
exports.RENEWAL_FINANCE_STEPS = ["headsUp", "sixtyDay", "thirtyDay"];
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
thanks: 9105,
|
|
15
|
+
/** The admin email template (constants.ts emailTemplates) for each step. */
|
|
16
|
+
exports.RENEWAL_EMAIL_TEMPLATES = {
|
|
17
|
+
headsUp: "engagementRenewalPrice",
|
|
18
|
+
yearInReview: "engagementRenewalReview",
|
|
19
|
+
sixtyDay: "engagementRenewalDue",
|
|
20
|
+
thirtyDay: "engagementRenewalInvoice",
|
|
21
|
+
thanks: "engagementRenewalThanks",
|
|
23
22
|
};
|
|
24
23
|
const DAY_MS = 86400000;
|
|
25
24
|
const ymdToMs = (ymd) => Date.parse(`${ymd}T00:00:00Z`);
|
|
@@ -87,4 +86,38 @@ const renewalSituation = ({ institute, products, now }) => {
|
|
|
87
86
|
return { ...base, lapsed: false, stage: "early" };
|
|
88
87
|
};
|
|
89
88
|
exports.renewalSituation = renewalSituation;
|
|
89
|
+
const addDays = (ymd, days) => new Date(ymdToMs(ymd) + days * DAY_MS).toISOString().slice(0, 10);
|
|
90
|
+
/**
|
|
91
|
+
* The renewal tasks for one renewal date, each with the day it is due: the
|
|
92
|
+
* same timings as renewalSituation, worked out once when the date is set
|
|
93
|
+
* rather than checked daily. The heads-up is the last 20 April and the year
|
|
94
|
+
* in review the last 1 July that fall within the year before the 60-day
|
|
95
|
+
* email. A date set late skips the steps already gone, but keeps the most
|
|
96
|
+
* recent one, since that's the step the customer is in. The thanks is not
|
|
97
|
+
* here: it is due when the date moves on.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} renewalDate YYYY-MM-DD.
|
|
100
|
+
* @param {string} today YYYY-MM-DD, London.
|
|
101
|
+
* @return {object[]} each step and its due date, plus "lapsed" the day after renewal.
|
|
102
|
+
*/
|
|
103
|
+
const renewalTaskSchedule = (renewalDate, today) => {
|
|
104
|
+
const sixty = addDays(renewalDate, -60);
|
|
105
|
+
const yearBefore = addDays(renewalDate, -365);
|
|
106
|
+
const lastBefore = (md) => {
|
|
107
|
+
const year = Number(sixty.slice(0, 4));
|
|
108
|
+
const candidate = `${year}-${md}` < sixty ? `${year}-${md}` : `${year - 1}-${md}`;
|
|
109
|
+
return candidate >= yearBefore ? candidate : undefined;
|
|
110
|
+
};
|
|
111
|
+
const all = [
|
|
112
|
+
{ step: "headsUp", due: lastBefore("04-20") },
|
|
113
|
+
{ step: "yearInReview", due: lastBefore("07-01") },
|
|
114
|
+
{ step: "sixtyDay", due: sixty },
|
|
115
|
+
{ step: "thirtyDay", due: addDays(renewalDate, -30) },
|
|
116
|
+
]
|
|
117
|
+
.filter((t) => Boolean(t.due))
|
|
118
|
+
.sort((a, b) => a.due.localeCompare(b.due));
|
|
119
|
+
const gone = all.filter((t) => t.due < today);
|
|
120
|
+
return [...all.filter((t) => t.due >= today || t === gone[gone.length - 1]), { step: "lapsed", due: addDays(renewalDate, 1) }];
|
|
121
|
+
};
|
|
122
|
+
exports.renewalTaskSchedule = renewalTaskSchedule;
|
|
90
123
|
//# sourceMappingURL=renewals.js.map
|
package/lib/renewals.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renewals.js","sourceRoot":"","sources":["../src/renewals.ts"],"names":[],"mappings":";;;AAEA,mDAA0C;
|
|
1
|
+
{"version":3,"file":"renewals.js","sourceRoot":"","sources":["../src/renewals.ts"],"names":[],"mappings":"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"}
|
package/lib/renewals.test.js
CHANGED
|
@@ -50,4 +50,21 @@ describe("renewals", () => {
|
|
|
50
50
|
expect(at(thanked, "2026-11-10").due).toBeUndefined();
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
|
+
describe("renewalTaskSchedule", () => {
|
|
54
|
+
it("dates each renewal task from the renewal date", () => {
|
|
55
|
+
expect((0, renewals_1.renewalTaskSchedule)("2027-10-01", "2026-11-03")).toEqual([
|
|
56
|
+
{ step: "headsUp", due: "2027-04-20" },
|
|
57
|
+
{ step: "yearInReview", due: "2027-07-01" },
|
|
58
|
+
{ step: "sixtyDay", due: "2027-08-02" },
|
|
59
|
+
{ step: "thirtyDay", due: "2027-09-01" },
|
|
60
|
+
{ step: "lapsed", due: "2027-10-02" },
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
it("keeps only the step a late date is in, of the ones already gone", () => {
|
|
64
|
+
expect((0, renewals_1.renewalTaskSchedule)("2026-12-15", "2026-11-03").map((t) => t.step)).toEqual(["sixtyDay", "thirtyDay", "lapsed"]);
|
|
65
|
+
});
|
|
66
|
+
it("leaves out a heads-up or year in review more than a year before the 60-day email", () => {
|
|
67
|
+
expect((0, renewals_1.renewalTaskSchedule)("2027-06-01", "2026-11-03").map((t) => t.step)).toEqual(["yearInReview", "sixtyDay", "thirtyDay", "lapsed"]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
53
70
|
//# sourceMappingURL=renewals.test.js.map
|
package/lib/renewals.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renewals.test.js","sourceRoot":"","sources":["../src/renewals.test.ts"],"names":[],"mappings":";;AAAA,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,EAAC,CAAC,CAAC,CAAC;AAC5D,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,GAAG,EAAE,CAAC,CAAC,EAAC,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE;KAAQ,EAAC,CAAC,CAAC,CAAC;AAGrF,uDAAkD;AAClD,
|
|
1
|
+
{"version":3,"file":"renewals.test.js","sourceRoot":"","sources":["../src/renewals.test.ts"],"names":[],"mappings":"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"}
|
package/lib/typeDefinitions.d.ts
CHANGED
|
@@ -2026,7 +2026,7 @@ export type EmailTemplateConfig = {
|
|
|
2026
2026
|
text: string;
|
|
2027
2027
|
link: string;
|
|
2028
2028
|
}[];
|
|
2029
|
-
type: "workflow" | "feedback" | "event" | "campaign" | "database" | "onboarding" | "skillsInvite" | "aspirationInvite" | "leaverInvite" | "leaverResultsInvite" | "leaverChristmasInvite";
|
|
2029
|
+
type: "workflow" | "feedback" | "event" | "campaign" | "database" | "onboarding" | "skillsInvite" | "aspirationInvite" | "leaverInvite" | "leaverResultsInvite" | "leaverChristmasInvite" | "engagement";
|
|
2030
2030
|
product: EmailTemplateProduct;
|
|
2031
2031
|
sections?: EmailSectionNames[];
|
|
2032
2032
|
group?: string;
|
|
@@ -2038,7 +2038,7 @@ export type EmailTemplateConfig = {
|
|
|
2038
2038
|
locked?: boolean;
|
|
2039
2039
|
};
|
|
2040
2040
|
export type ProductTag = ProductKey | "employerEvents" | "hub";
|
|
2041
|
-
export type EmailTemplateProduct = ProductTag;
|
|
2041
|
+
export type EmailTemplateProduct = ProductTag | "admin";
|
|
2042
2042
|
export type EmailCampaign = {
|
|
2043
2043
|
id: string;
|
|
2044
2044
|
oId: string;
|
|
@@ -3870,7 +3870,8 @@ export type YearScoreData = {
|
|
|
3870
3870
|
campaignsClicked?: number;
|
|
3871
3871
|
campaignsFailed?: number;
|
|
3872
3872
|
};
|
|
3873
|
-
type MessageTypes = "hubOrTrustInviteSchool" | "
|
|
3873
|
+
type MessageTypes = "hubOrTrustInviteSchool" | "engagementEmail" | // an admin email template sent to a school (featureEngagement.ts)
|
|
3874
|
+
"sendPlacementActionRequired" | "sendPlacementAdditionalInformationRequired" | "sendFeedbackReminder" | "sendProviderContactInvite" | "sendProviderContactActivityInvite" | "sendActivateUserAccount" | "shareEmployerEventRequest" | "employerEventInvite" | "schoolEventInvite" | "nonUserSchoolEventInvite" | "reuploadPlacementDocument" | "requestEventDates" | "taskReminder" | "eventDatesReminder" | "parentDatabase" | "aspirationInvite" | "leaverInvite" | "leaverReminder" | "leaverResultsInvite" | "leaverResultsReminder" | "leaverChristmasInvite" | "leaverChristmasReminder" | "scheduledCheckUpcoming" | "scheduledCheckDue" | "scheduledCheckLate" | "alumniCheckInInvite" | // annual "where are you now" check-in — reminder chain falls back to the trusted contact
|
|
3874
3875
|
"alumniCheckInTrustedContact" | // parent/trusted-contact fallback invite — has its own reminder chain
|
|
3875
3876
|
SurveyMessageType;
|
|
3876
3877
|
/** Every message type the survey engine sends, derived from the registry so the two
|
|
@@ -3882,7 +3883,8 @@ export type SurveyMessageType = `survey${Capitalize<SurveyKind>}${"Invite" | "Re
|
|
|
3882
3883
|
* is exhausted with no response. Params are read from the interaction's additionalData. */
|
|
3883
3884
|
export type ReminderFallbackType = "checkInTrustedContact" | // legacy name for the below; live interaction docs still carry it
|
|
3884
3885
|
"surveyParentFallback" | // chase the parent/trusted contact when the subject ignores their whole chain
|
|
3885
|
-
"alumniCheckInFallback"
|
|
3886
|
+
"alumniCheckInFallback" | // the same, for the annual alumni check-in, which is not a survey
|
|
3887
|
+
"engagementStuck";
|
|
3886
3888
|
export type EmailInteraction = {
|
|
3887
3889
|
messageId: string;
|
|
3888
3890
|
collectionLink: string;
|
|
@@ -4473,16 +4475,7 @@ export type CustomerSuccessData = {
|
|
|
4473
4475
|
metrics?: CustomerSuccessMetrics;
|
|
4474
4476
|
};
|
|
4475
4477
|
export type InstituteEngagement = {
|
|
4476
|
-
/** Last engagement email of any kind, for the one-per-week cap. */
|
|
4477
|
-
lastEmailAt?: string;
|
|
4478
4478
|
onboarding?: {
|
|
4479
|
-
/** The step last nudged, so the same step is not sent twice in a row
|
|
4480
|
-
* without a change (the cron caps repeats of one step). */
|
|
4481
|
-
lastStepKey?: string;
|
|
4482
|
-
lastVariant?: "setup" | "resend";
|
|
4483
|
-
lastSentAt?: string;
|
|
4484
|
-
/** How many times in a row the same step has been sent. */
|
|
4485
|
-
repeats?: number;
|
|
4486
4479
|
/** Optional steps whose emails ran out unanswered, keyed by step, with
|
|
4487
4480
|
* the date. They count as done so the next step can start. */
|
|
4488
4481
|
passed?: {
|
|
@@ -4548,21 +4541,6 @@ export type FeatureCampaign = {
|
|
|
4548
4541
|
adoptedAt?: string;
|
|
4549
4542
|
endedAt?: string;
|
|
4550
4543
|
};
|
|
4551
|
-
/** `engagementEmails/{autoId}`: one flat log document per send, for the
|
|
4552
|
-
* admin view. */
|
|
4553
|
-
export type EngagementEmailLog = {
|
|
4554
|
-
oId: string;
|
|
4555
|
-
kind: "marketing" | "onboarding" | "renewal";
|
|
4556
|
-
key: string;
|
|
4557
|
-
variant?: "setup" | "resend";
|
|
4558
|
-
touch?: number;
|
|
4559
|
-
templateId: number;
|
|
4560
|
-
recipients: string[];
|
|
4561
|
-
sentAt: string;
|
|
4562
|
-
campaignId?: string;
|
|
4563
|
-
/** The approved draft it was sent from. */
|
|
4564
|
-
draftId?: string;
|
|
4565
|
-
};
|
|
4566
4544
|
export type StudentRoute = {
|
|
4567
4545
|
route: "wonde" | "mis" | "csv";
|
|
4568
4546
|
chosenAt: string;
|
|
@@ -4592,48 +4570,6 @@ export type EngagementPlan = {
|
|
|
4592
4570
|
savedAt: string;
|
|
4593
4571
|
savedBy: string;
|
|
4594
4572
|
};
|
|
4595
|
-
/** Where an engagement email is in its approval: written as a draft, approved
|
|
4596
|
-
* by an admin for the next school morning, then sent; or skipped by an admin,
|
|
4597
|
-
* expired unapproved, or dropped because the school did the step first. */
|
|
4598
|
-
export type EngagementDraftStatus = "draft" | "approved" | "sent" | "skipped" | "expired" | "dropped";
|
|
4599
|
-
/** `engagementDrafts/{autoId}`: one engagement email to one institute. The
|
|
4600
|
-
* daily check writes every email as one of these and nothing sends until an
|
|
4601
|
-
* admin approves it. */
|
|
4602
|
-
export type EngagementDraft = {
|
|
4603
|
-
oId: string;
|
|
4604
|
-
instituteName: string;
|
|
4605
|
-
kind: "marketing" | "onboarding" | "renewal";
|
|
4606
|
-
key: string;
|
|
4607
|
-
variant?: "setup" | "resend";
|
|
4608
|
-
touch?: number;
|
|
4609
|
-
templateId: number;
|
|
4610
|
-
label: string;
|
|
4611
|
-
description: string;
|
|
4612
|
-
path: string;
|
|
4613
|
-
campaignId?: string;
|
|
4614
|
-
/** Onboarding: `engagement.onboarding.repeats` once this is sent. */
|
|
4615
|
-
repeats?: number;
|
|
4616
|
-
recipients: {
|
|
4617
|
-
email: string;
|
|
4618
|
-
forename: string;
|
|
4619
|
-
surname: string;
|
|
4620
|
-
}[];
|
|
4621
|
-
/** Drafts with the same template and step, approved together. */
|
|
4622
|
-
batchKey: string;
|
|
4623
|
-
status: EngagementDraftStatus;
|
|
4624
|
-
createdAt: string;
|
|
4625
|
-
approvedAt?: string;
|
|
4626
|
-
approvedBy?: string;
|
|
4627
|
-
/** 7:30am London on the school day after approval. */
|
|
4628
|
-
sendAt?: string;
|
|
4629
|
-
sentAt?: string;
|
|
4630
|
-
sentTo?: string[];
|
|
4631
|
-
/** When it was skipped, expired or dropped, and why. */
|
|
4632
|
-
closedAt?: string;
|
|
4633
|
-
closedReason?: string;
|
|
4634
|
-
/** An admin changed the description or recipients. */
|
|
4635
|
-
edited?: boolean;
|
|
4636
|
-
};
|
|
4637
4573
|
export type CustomerTask = {
|
|
4638
4574
|
id: string;
|
|
4639
4575
|
customerId: string;
|
|
@@ -4651,6 +4587,8 @@ export type CustomerTask = {
|
|
|
4651
4587
|
completedBy?: string;
|
|
4652
4588
|
triggerType: "manual" | "renewalLogged" | "timeBased" | "metricThreshold" | "calendar";
|
|
4653
4589
|
triggerKey?: string;
|
|
4590
|
+
/** The admin email template (constants.ts emailTemplates) this task sends, from the task. */
|
|
4591
|
+
emailTemplate?: string;
|
|
4654
4592
|
commId?: string;
|
|
4655
4593
|
commType?: "users";
|
|
4656
4594
|
metadata?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AA+zIA,sDASC;AAeD,oDAKC;AAaD,0CAEC;AAztED;;;;;kFAKkF;AACrE,QAAA,qBAAqB,GAAG,SAAS,CAAC;AAClC,QAAA,iBAAiB,GAAG,OAAO,CAAC;AAqoEzC;yEACyE;AAC5D,QAAA,0BAA0B,GAA2C;IAC9E,gBAAgB,EAAE,2BAA2B;IAC7C,mBAAmB,EAAE,sBAAsB;IAC3C,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,oCAAoC;IAChD,aAAa,EAAE,yBAAyB;IACxC,aAAa,EAAE,wBAAwB;IACvC,KAAK,EAAE,OAAO;CACjB,CAAC;AAEW,QAAA,0BAA0B,GAA+C;IAClF,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,SAAgB,qBAAqB,CAAC,SAAsC;IACxE,QAAQ,SAAS,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC;QAC5B,KAAK,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC;QAC7B,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,OAAO,CAAC;QACb,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;IAC3B,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,oBAAoB,CAChC,IAAwC,EACxC,MAA6C;IAE7C,OAAO,IAAI,EAAE,WAAW,IAAI,MAAM,EAAE,YAAY,IAAI,KAAK,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,KAA6D;IACzF,OAAO,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;AAC/E,CAAC"}
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {AnalyticsItem, ApplicantStage, OutreachDeliverability, OutreachStatus, CommSchema, CustomFormSchema, CustomFormSchemaField, EmailTemplateConfig, FlagCodes, ProductTag, LeadStageSchema, StudentActivity, StudentActivityCategory, StudentActivityType, SurveyDateRule, SurveyOrdinal, SurveyWeekday, WorkflowStage} from "./typeDefinitions";
|
|
1
|
+
import {AnalyticsItem, ApplicantStage, OutreachDeliverability, OutreachStatus, CommSchema, CustomFormSchema, CustomFormSchemaField, EmailTemplateConfig, EmailTemplateProduct, FlagCodes, ProductTag, LeadStageSchema, StudentActivity, StudentActivityCategory, StudentActivityType, SurveyDateRule, SurveyOrdinal, SurveyWeekday, WorkflowStage} from "./typeDefinitions";
|
|
2
2
|
|
|
3
3
|
import type {ResolvedProducts} from "./productResolver";
|
|
4
4
|
|
|
@@ -1237,6 +1237,8 @@ const placementRejectedParams = [...placementEmailParams, "reason"];
|
|
|
1237
1237
|
// Emails to the cohort contact about the cohort as a whole.
|
|
1238
1238
|
const staffCohortEmailParams = ["cohortName", "submissionDeadline", "daysUntilDeadline", "instituteName", "cohortContactForename", "cohortContactSurname", "cohortContactEmail"];
|
|
1239
1239
|
// Cohort emails sent before a student has a placement.
|
|
1240
|
+
const engagementEmailParams = ["contactForename", "instituteName"];
|
|
1241
|
+
|
|
1240
1242
|
const cohortEmailParams = ["studentForename", "studentSurname", "studentEmail", "parentEmail", "submissionDeadline", "daysUntilDeadline", "cohortName", "instituteName", "cohortContactForename", "cohortContactSurname", "cohortContactEmail"];
|
|
1241
1243
|
|
|
1242
1244
|
export const emailTemplates:{[key: string]: EmailTemplateConfig} = {
|
|
@@ -1705,6 +1707,250 @@ export const emailTemplates:{[key: string]: EmailTemplateConfig} = {
|
|
|
1705
1707
|
type: "leaverChristmasInvite",
|
|
1706
1708
|
button: {text: "Quick check-in", link: "/leaverCheckpoint/{{requestId}}/christmas/{{token}}"},
|
|
1707
1709
|
},
|
|
1710
|
+
// CareerThread's own onboarding and renewal emails to schools, sent by an admin from the institute's
|
|
1711
|
+
// emails. product "admin" keeps them out of every school's template list.
|
|
1712
|
+
engagementPickFeatures: {
|
|
1713
|
+
product: "admin",
|
|
1714
|
+
description: "First onboarding email: asks the school which parts of CareerThread they will use.",
|
|
1715
|
+
params: [...engagementEmailParams],
|
|
1716
|
+
button: {text: "Pick your features", link: "/institutes/home"},
|
|
1717
|
+
type: "engagement",
|
|
1718
|
+
name: "Pick your features",
|
|
1719
|
+
recipient: "staff",
|
|
1720
|
+
group: "Joining",
|
|
1721
|
+
},
|
|
1722
|
+
engagementPlanYear: {
|
|
1723
|
+
product: "admin",
|
|
1724
|
+
description: "Asks the school to drag each feature into the term they want to start it.",
|
|
1725
|
+
params: [...engagementEmailParams],
|
|
1726
|
+
button: {text: "Plan your year", link: "/institutes/organisation/overview"},
|
|
1727
|
+
type: "engagement",
|
|
1728
|
+
name: "Plan your year",
|
|
1729
|
+
recipient: "staff",
|
|
1730
|
+
group: "Joining",
|
|
1731
|
+
},
|
|
1732
|
+
engagementInviteStaff: {
|
|
1733
|
+
product: "admin",
|
|
1734
|
+
description: "Suggests inviting the rest of the careers team. Sent once, no reminders.",
|
|
1735
|
+
params: [...engagementEmailParams],
|
|
1736
|
+
button: {text: "Invite staff", link: "/institutes/organisation/staff"},
|
|
1737
|
+
type: "engagement",
|
|
1738
|
+
name: "Invite staff",
|
|
1739
|
+
recipient: "staff",
|
|
1740
|
+
group: "Joining",
|
|
1741
|
+
},
|
|
1742
|
+
engagementStudentRoute: {
|
|
1743
|
+
product: "admin",
|
|
1744
|
+
description: "Asks how the school will add students: Wonde, an MIS file or a spreadsheet.",
|
|
1745
|
+
params: [...engagementEmailParams, "routeCode"],
|
|
1746
|
+
optionalButtons: [
|
|
1747
|
+
{text: "Connect through Wonde", link: "/students-route/{{routeCode}}?route=wonde"},
|
|
1748
|
+
{text: "Send a file from our MIS", link: "/students-route/{{routeCode}}?route=mis"},
|
|
1749
|
+
{text: "Upload a spreadsheet", link: "/students-route/{{routeCode}}?route=csv"},
|
|
1750
|
+
],
|
|
1751
|
+
type: "engagement",
|
|
1752
|
+
name: "Choose how to add students",
|
|
1753
|
+
recipient: "staff",
|
|
1754
|
+
group: "Students",
|
|
1755
|
+
},
|
|
1756
|
+
engagementWonde: {
|
|
1757
|
+
product: "admin",
|
|
1758
|
+
description: "Sent to the school and its IT contact once they choose Wonde.",
|
|
1759
|
+
params: [...engagementEmailParams],
|
|
1760
|
+
button: {text: "Data protection policy", link: "/data-protection-policy-july-2026.pdf"},
|
|
1761
|
+
type: "engagement",
|
|
1762
|
+
name: "Approve Wonde",
|
|
1763
|
+
recipient: "staff",
|
|
1764
|
+
group: "Students",
|
|
1765
|
+
},
|
|
1766
|
+
engagementMis: {
|
|
1767
|
+
product: "admin",
|
|
1768
|
+
description: "Sent to the school and its IT contact once they choose an MIS file transfer.",
|
|
1769
|
+
params: [...engagementEmailParams],
|
|
1770
|
+
button: {text: "Data protection policy", link: "/data-protection-policy-july-2026.pdf"},
|
|
1771
|
+
type: "engagement",
|
|
1772
|
+
name: "MIS file transfer",
|
|
1773
|
+
recipient: "staff",
|
|
1774
|
+
group: "Students",
|
|
1775
|
+
},
|
|
1776
|
+
engagementUploadStudents: {
|
|
1777
|
+
product: "admin",
|
|
1778
|
+
description: "Sent once the school chooses to upload a spreadsheet of students.",
|
|
1779
|
+
params: [...engagementEmailParams],
|
|
1780
|
+
button: {text: "Upload students", link: "/institutes/students"},
|
|
1781
|
+
type: "engagement",
|
|
1782
|
+
name: "Upload your students",
|
|
1783
|
+
recipient: "staff",
|
|
1784
|
+
group: "Students",
|
|
1785
|
+
},
|
|
1786
|
+
engagementWexGroups: {
|
|
1787
|
+
product: "admin",
|
|
1788
|
+
description: "Asks which year groups go on work experience, to set up a group for each.",
|
|
1789
|
+
params: [...engagementEmailParams],
|
|
1790
|
+
button: {text: "Set up a group", link: "/institutes/cohorts"},
|
|
1791
|
+
secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
|
|
1792
|
+
type: "engagement",
|
|
1793
|
+
name: "Set up work experience groups",
|
|
1794
|
+
recipient: "staff",
|
|
1795
|
+
group: "Work experience",
|
|
1796
|
+
},
|
|
1797
|
+
engagementWexCohort: {
|
|
1798
|
+
product: "admin",
|
|
1799
|
+
description: "Each new year: asks the school to check this year's cohort dates and students.",
|
|
1800
|
+
params: [...engagementEmailParams],
|
|
1801
|
+
button: {text: "Check this year's cohort", link: "/institutes/cohorts"},
|
|
1802
|
+
secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
|
|
1803
|
+
type: "engagement",
|
|
1804
|
+
name: "This year's cohort",
|
|
1805
|
+
recipient: "staff",
|
|
1806
|
+
group: "Work experience",
|
|
1807
|
+
},
|
|
1808
|
+
engagementWexWorkflow: {
|
|
1809
|
+
product: "admin",
|
|
1810
|
+
description: "Asks who signs off each placement, to build the workflow.",
|
|
1811
|
+
params: [...engagementEmailParams],
|
|
1812
|
+
button: {text: "Build the workflow", link: "/institutes/cohorts"},
|
|
1813
|
+
secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
|
|
1814
|
+
type: "engagement",
|
|
1815
|
+
name: "Sign-off workflow",
|
|
1816
|
+
recipient: "staff",
|
|
1817
|
+
group: "Work experience",
|
|
1818
|
+
},
|
|
1819
|
+
engagementWexActivate: {
|
|
1820
|
+
product: "admin",
|
|
1821
|
+
description: "Asks the school to activate the students in their work experience group.",
|
|
1822
|
+
params: [...engagementEmailParams],
|
|
1823
|
+
button: {text: "Activate students", link: "/institutes/students"},
|
|
1824
|
+
secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
|
|
1825
|
+
type: "engagement",
|
|
1826
|
+
name: "Activate students",
|
|
1827
|
+
recipient: "staff",
|
|
1828
|
+
group: "Work experience",
|
|
1829
|
+
},
|
|
1830
|
+
engagementWexPlacements: {
|
|
1831
|
+
product: "admin",
|
|
1832
|
+
description: "Suggests adding the first placement.",
|
|
1833
|
+
params: [...engagementEmailParams],
|
|
1834
|
+
button: {text: "Add a placement", link: "/institutes/cohorts"},
|
|
1835
|
+
secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
|
|
1836
|
+
type: "engagement",
|
|
1837
|
+
name: "First placements",
|
|
1838
|
+
recipient: "staff",
|
|
1839
|
+
group: "Work experience",
|
|
1840
|
+
},
|
|
1841
|
+
engagementWexFeedback: {
|
|
1842
|
+
product: "admin",
|
|
1843
|
+
description: "Suggests turning on feedback at the end of each placement.",
|
|
1844
|
+
params: [...engagementEmailParams],
|
|
1845
|
+
button: {text: "Set up feedback", link: "/institutes/cohorts"},
|
|
1846
|
+
secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
|
|
1847
|
+
type: "engagement",
|
|
1848
|
+
name: "Placement feedback",
|
|
1849
|
+
recipient: "staff",
|
|
1850
|
+
group: "Work experience",
|
|
1851
|
+
},
|
|
1852
|
+
engagementHubFirstEvent: {
|
|
1853
|
+
product: "admin",
|
|
1854
|
+
description: "Suggests a careers hub school requests its first employer event.",
|
|
1855
|
+
params: [...engagementEmailParams, "hubName"],
|
|
1856
|
+
button: {text: "Request an event", link: "/institutes/events"},
|
|
1857
|
+
type: "engagement",
|
|
1858
|
+
name: "Hub school first event",
|
|
1859
|
+
recipient: "staff",
|
|
1860
|
+
group: "Hub schools",
|
|
1861
|
+
},
|
|
1862
|
+
engagementReminderDay7: {
|
|
1863
|
+
product: "admin",
|
|
1864
|
+
description: "A week after any onboarding email, if the step still isn't done.",
|
|
1865
|
+
params: [...engagementEmailParams, "featureName", "featureDescription", "featurePath"],
|
|
1866
|
+
button: {text: "Get started", link: "/institutes{{featurePath}}"},
|
|
1867
|
+
type: "engagement",
|
|
1868
|
+
name: "Reminder: day 7",
|
|
1869
|
+
recipient: "staff",
|
|
1870
|
+
group: "Reminders",
|
|
1871
|
+
},
|
|
1872
|
+
engagementReminderDay7Plan: {
|
|
1873
|
+
product: "admin",
|
|
1874
|
+
description: "Day 7 reminder for a feature on the plan board, with a link to pick a different term.",
|
|
1875
|
+
params: [...engagementEmailParams, "featureName", "featureDescription", "featurePath"],
|
|
1876
|
+
button: {text: "Get started", link: "/institutes{{featurePath}}"},
|
|
1877
|
+
secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
|
|
1878
|
+
type: "engagement",
|
|
1879
|
+
name: "Reminder: day 7 (plan feature)",
|
|
1880
|
+
recipient: "staff",
|
|
1881
|
+
group: "Reminders",
|
|
1882
|
+
},
|
|
1883
|
+
engagementReminderDay21: {
|
|
1884
|
+
product: "admin",
|
|
1885
|
+
description: "Three weeks after any onboarding email, if the step still isn't done. Offers a call.",
|
|
1886
|
+
params: [...engagementEmailParams, "featureName", "featureSentence", "featurePath"],
|
|
1887
|
+
button: {text: "Get started", link: "/institutes{{featurePath}}"},
|
|
1888
|
+
type: "engagement",
|
|
1889
|
+
name: "Reminder: day 21",
|
|
1890
|
+
recipient: "staff",
|
|
1891
|
+
group: "Reminders",
|
|
1892
|
+
},
|
|
1893
|
+
engagementReminderDay21Plan: {
|
|
1894
|
+
product: "admin",
|
|
1895
|
+
description: "Day 21 reminder for a feature on the plan board: do it now or pick a term.",
|
|
1896
|
+
params: [...engagementEmailParams, "featureName", "featureSentence", "featurePath"],
|
|
1897
|
+
button: {text: "Get started", link: "/institutes{{featurePath}}"},
|
|
1898
|
+
secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
|
|
1899
|
+
type: "engagement",
|
|
1900
|
+
name: "Reminder: day 21 (plan feature)",
|
|
1901
|
+
recipient: "staff",
|
|
1902
|
+
group: "Reminders",
|
|
1903
|
+
},
|
|
1904
|
+
engagementRenewalPrice: {
|
|
1905
|
+
product: "admin",
|
|
1906
|
+
description: "Gives next year's price in time for the school's budget.",
|
|
1907
|
+
params: [...engagementEmailParams, "renewalDate", "quote"],
|
|
1908
|
+
button: {text: "See plan and invoices", link: "/institutes/organisation/billing"},
|
|
1909
|
+
type: "engagement",
|
|
1910
|
+
name: "Next year's price",
|
|
1911
|
+
recipient: "staff",
|
|
1912
|
+
group: "Renewals",
|
|
1913
|
+
},
|
|
1914
|
+
engagementRenewalReview: {
|
|
1915
|
+
product: "admin",
|
|
1916
|
+
description: "Before the summer: thanks the school and points to the impact report.",
|
|
1917
|
+
params: [...engagementEmailParams],
|
|
1918
|
+
button: {text: "Get your impact report", link: "/institutes/organisation/overview"},
|
|
1919
|
+
type: "engagement",
|
|
1920
|
+
name: "Year in review",
|
|
1921
|
+
recipient: "staff",
|
|
1922
|
+
group: "Renewals",
|
|
1923
|
+
},
|
|
1924
|
+
engagementRenewalDue: {
|
|
1925
|
+
product: "admin",
|
|
1926
|
+
description: "60 days before renewal: the date and next year's price.",
|
|
1927
|
+
params: [...engagementEmailParams, "renewalDate", "quote"],
|
|
1928
|
+
button: {text: "See your plan", link: "/institutes/organisation/billing"},
|
|
1929
|
+
type: "engagement",
|
|
1930
|
+
name: "60 days out",
|
|
1931
|
+
recipient: "staff",
|
|
1932
|
+
group: "Renewals",
|
|
1933
|
+
},
|
|
1934
|
+
engagementRenewalInvoice: {
|
|
1935
|
+
product: "admin",
|
|
1936
|
+
description: "Asks whether the renewal invoice needs a purchase order number.",
|
|
1937
|
+
params: [...engagementEmailParams, "renewalDate", "quote"],
|
|
1938
|
+
button: {text: "See your invoices", link: "/institutes/organisation/billing"},
|
|
1939
|
+
type: "engagement",
|
|
1940
|
+
name: "Invoice or PO",
|
|
1941
|
+
recipient: "staff",
|
|
1942
|
+
group: "Renewals",
|
|
1943
|
+
},
|
|
1944
|
+
engagementRenewalThanks: {
|
|
1945
|
+
product: "admin",
|
|
1946
|
+
description: "Sent once the school has renewed.",
|
|
1947
|
+
params: [...engagementEmailParams],
|
|
1948
|
+
button: {text: "Open CareerThread", link: "/institutes/home"},
|
|
1949
|
+
type: "engagement",
|
|
1950
|
+
name: "Thanks for renewing",
|
|
1951
|
+
recipient: "staff",
|
|
1952
|
+
group: "Renewals",
|
|
1953
|
+
},
|
|
1708
1954
|
}
|
|
1709
1955
|
|
|
1710
1956
|
// Whether a resource tagged with a product belongs to a product this organisation has. Untagged, or no product info, shows.
|
|
@@ -1720,7 +1966,10 @@ export function productTagAvailable(tag: ProductTag|undefined, products?: Resolv
|
|
|
1720
1966
|
export function emailTemplateAvailable(templateKey: string|undefined, products?: ResolvedProducts) {
|
|
1721
1967
|
// A saved template whose key has since been removed from emailTemplates is never sent, so hide it.
|
|
1722
1968
|
if (templateKey && !emailTemplates[templateKey]) return false;
|
|
1723
|
-
|
|
1969
|
+
const product = templateKey ? emailTemplates[templateKey].product : undefined;
|
|
1970
|
+
// CareerThread's own emails: only when there's no product info, which is an admin looking.
|
|
1971
|
+
if (product === "admin") return !products;
|
|
1972
|
+
return productTagAvailable(product, products);
|
|
1724
1973
|
}
|
|
1725
1974
|
|
|
1726
1975
|
export const productTagLabels: {[k in ProductTag]: string} = {
|
|
@@ -1736,6 +1985,11 @@ export const productTagLabels: {[k in ProductTag]: string} = {
|
|
|
1736
1985
|
hub: "Careers hub",
|
|
1737
1986
|
};
|
|
1738
1987
|
|
|
1988
|
+
export const emailTemplateProductLabels: {[k in EmailTemplateProduct]: string} = {
|
|
1989
|
+
...productTagLabels,
|
|
1990
|
+
admin: "CareerThread admin",
|
|
1991
|
+
};
|
|
1992
|
+
|
|
1739
1993
|
// Example values for previewing email templates, so customers see a filled-in email
|
|
1740
1994
|
// rather than {{placeholders}}. Shared values first, then per-template overrides where
|
|
1741
1995
|
// the same param means something different.
|
|
@@ -5,7 +5,8 @@ import {InstituteData, StudentGroup} from "./typeDefinitions";
|
|
|
5
5
|
import {yearGroupStudentGroup} from "./studentGroups";
|
|
6
6
|
import {resolveProducts} from "./productResolver";
|
|
7
7
|
import {SurveyGroupScope} from "./surveys/types";
|
|
8
|
-
import {
|
|
8
|
+
import {FEATURE_MARKETING} from "./featureMarketing";
|
|
9
|
+
import {emailTemplates} from "./constants";
|
|
9
10
|
import {
|
|
10
11
|
ONBOARDING_PATHWAYS,
|
|
11
12
|
OnboardingContext,
|
|
@@ -14,6 +15,8 @@ import {
|
|
|
14
15
|
evaluateOnboarding,
|
|
15
16
|
nextOnboardingStep,
|
|
16
17
|
onboardingCountsNeeded,
|
|
18
|
+
onboardingStepDone,
|
|
19
|
+
onboardingStepOfTemplate,
|
|
17
20
|
} from "./onboardingPathways";
|
|
18
21
|
|
|
19
22
|
/** A year group launched for one academic year — what every launch config holds. */
|
|
@@ -52,14 +55,26 @@ describe("registry", () => {
|
|
|
52
55
|
expect(new Set(keys).size).toBe(keys.length);
|
|
53
56
|
});
|
|
54
57
|
|
|
55
|
-
it("
|
|
58
|
+
it("points every step email at an admin email template, with a resend only on yearly steps", () => {
|
|
56
59
|
ONBOARDING_PATHWAYS.forEach((p) => p.steps.forEach((s) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
if (!s.emailTemplates) return;
|
|
61
|
+
expect(emailTemplates[s.emailTemplates.setup]?.product).toBe("admin");
|
|
62
|
+
if (s.emailTemplates.resend) {
|
|
63
|
+
expect(s.thisYear).toBeDefined();
|
|
64
|
+
expect(emailTemplates[s.emailTemplates.resend]?.product).toBe("admin");
|
|
65
|
+
}
|
|
60
66
|
}));
|
|
61
67
|
});
|
|
62
68
|
|
|
69
|
+
it("finds the step for a template, and judges it done on its own", () => {
|
|
70
|
+
expect(onboardingStepOfTemplate("engagementWexCohort")?.step.key).toBe("wex.cohort");
|
|
71
|
+
expect(onboardingStepOfTemplate("engagementRenewalThanks")).toBeUndefined();
|
|
72
|
+
// The Wonde step doesn't apply to a spreadsheet school, but students still finish it.
|
|
73
|
+
expect(onboardingStepDone(ctx(school(), {students: 0}), "students.wonde")).toBe(false);
|
|
74
|
+
expect(onboardingStepDone(ctx(school(), {students: 30}), "students.wonde")).toBe(true);
|
|
75
|
+
expect(onboardingStepDone(ctx(school()), "nope")).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
63
78
|
it("starts every marketed feature's pathway with its marketing detector", () => {
|
|
64
79
|
// The shared-detector promise: the day marketing stops pitching a feature
|
|
65
80
|
// is the day onboarding starts nudging the step after it.
|
|
@@ -140,7 +155,7 @@ describe("progress", () => {
|
|
|
140
155
|
expect(step?.key).toBe("activities.launch");
|
|
141
156
|
expect(step?.state).toBe("yearMissing");
|
|
142
157
|
expect(step?.variant).toBe("resend");
|
|
143
|
-
expect(step?.
|
|
158
|
+
expect(step?.emailTemplate).toBeUndefined();
|
|
144
159
|
expect(pathway(ctx(school()), "activities").next?.state).toBe("unconfigured");
|
|
145
160
|
const live = school({activityLoggingConfig: {
|
|
146
161
|
setupCompletedAt: "2026-09-02", groups: [launchedYearGroup("Year 9", "2026-27")],
|