placementt-core 1.400.1080 → 1.400.1081
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/activityLogging.d.ts +67 -0
- package/lib/activityLogging.js +102 -0
- package/lib/activityLogging.js.map +1 -0
- package/lib/activityLogging.test.d.ts +1 -0
- package/lib/activityLogging.test.js +103 -0
- package/lib/activityLogging.test.js.map +1 -0
- package/lib/callables/activityLogging.d.ts +58 -0
- package/lib/callables/activityLogging.js +42 -0
- package/lib/callables/activityLogging.js.map +1 -0
- package/lib/callables/index.d.ts +1 -0
- package/lib/callables/index.js +1 -0
- package/lib/callables/index.js.map +1 -1
- package/lib/callables/registry.d.ts +70 -0
- package/lib/callables/registry.js +3 -1
- package/lib/callables/registry.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/typeDefinitions.d.ts +38 -0
- package/lib/typeDefinitions.js.map +1 -1
- package/package.json +1 -1
- package/src/activityLogging.test.ts +133 -0
- package/src/activityLogging.ts +97 -0
- package/src/callables/activityLogging.ts +40 -0
- package/src/callables/index.ts +1 -0
- package/src/callables/registry.ts +3 -1
- package/src/index.ts +1 -0
- package/src/typeDefinitions.ts +37 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ActivityLoggingConfig, InstituteData } from "./typeDefinitions";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a student can log activities, and whether we may email them about it.
|
|
4
|
+
*
|
|
5
|
+
* Two different switches are easy to confuse here, so they are named apart:
|
|
6
|
+
*
|
|
7
|
+
* - `trackExtracurriculars` is the PRODUCT. Absent means on, which is right for
|
|
8
|
+
* staff: a school that has never touched the switch still gets the Activities
|
|
9
|
+
* area, its analytics and its Gatsby reporting.
|
|
10
|
+
* - `activityLogging` is the LAUNCH. It is what a school says to its students, and
|
|
11
|
+
* absent means nothing has been said. A feature that appears in a pupil's account
|
|
12
|
+
* unannounced gets used by nobody and asked about by everybody, so a year group
|
|
13
|
+
* sees activity logging only once the school has launched it to them.
|
|
14
|
+
*
|
|
15
|
+
* The launch is per year group and, unless the school says otherwise, per academic
|
|
16
|
+
* year: last year's Year 11 is not this year's Year 11, and a school introducing it
|
|
17
|
+
* to each new cohort in assembly should not find it already live.
|
|
18
|
+
*/
|
|
19
|
+
/** Does the school have the Activities area at all? Absent means yes. */
|
|
20
|
+
export declare const activityLoggingProductOn: (institute?: Pick<InstituteData, "trackExtracurriculars">) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Does the stored launch still count today?
|
|
23
|
+
*
|
|
24
|
+
* A launch is made for one academic year. With `rollOver` on it carries forward
|
|
25
|
+
* forever; without it, September spends it.
|
|
26
|
+
*
|
|
27
|
+
* @param {ActivityLoggingConfig} [config] the school's launch.
|
|
28
|
+
* @param {Date} [now] the date to judge it against.
|
|
29
|
+
* @return {boolean} whether the year groups below are live.
|
|
30
|
+
*/
|
|
31
|
+
export declare const activityLaunchCurrent: (config?: ActivityLoggingConfig, now?: Date) => boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The year groups activity logging is live for right now.
|
|
34
|
+
*
|
|
35
|
+
* @param {InstituteData} [institute] the school.
|
|
36
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
37
|
+
* @return {string[]} the year group tags, empty when nothing is live.
|
|
38
|
+
*/
|
|
39
|
+
export declare const activityLoggingLiveYearGroups: (institute?: InstituteData, now?: Date) => string[];
|
|
40
|
+
/**
|
|
41
|
+
* Can THIS student log activities, and may we email them about it?
|
|
42
|
+
*
|
|
43
|
+
* The one gate every student-facing surface asks. A student with no year group on
|
|
44
|
+
* their record is not in a launched year group, so the answer is no — which is the
|
|
45
|
+
* safe way round: they see nothing rather than being shown something their school
|
|
46
|
+
* hasn't announced.
|
|
47
|
+
*
|
|
48
|
+
* @param {InstituteData} [institute] their school.
|
|
49
|
+
* @param {string} [yearGroup] their year group tag, as their record holds it.
|
|
50
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
51
|
+
* @return {boolean} whether activity logging is live for them.
|
|
52
|
+
*/
|
|
53
|
+
export declare const activityLoggingLive: (institute?: InstituteData, yearGroup?: string, now?: Date) => boolean;
|
|
54
|
+
/** Where a school's activity logging stands, in the vocabulary the careers year
|
|
55
|
+
* setup uses for every other programme. */
|
|
56
|
+
export type ActivityLoggingState = "unconfigured" | "needsLaunch" | "live";
|
|
57
|
+
/**
|
|
58
|
+
* Has this school launched activity logging, and does the launch still stand?
|
|
59
|
+
*
|
|
60
|
+
* The three answers are the three different things to do about it: ask the setup
|
|
61
|
+
* questions, re-launch a year that has rolled over, or nothing.
|
|
62
|
+
*
|
|
63
|
+
* @param {InstituteData} [institute] the school.
|
|
64
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
65
|
+
* @return {ActivityLoggingState} what is left to do.
|
|
66
|
+
*/
|
|
67
|
+
export declare const activityLoggingState: (institute?: InstituteData, now?: Date) => ActivityLoggingState;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.activityLoggingState = exports.activityLoggingLive = exports.activityLoggingLiveYearGroups = exports.activityLaunchCurrent = exports.activityLoggingProductOn = void 0;
|
|
4
|
+
const schedule_1 = require("./surveys/schedule");
|
|
5
|
+
/**
|
|
6
|
+
* Whether a student can log activities, and whether we may email them about it.
|
|
7
|
+
*
|
|
8
|
+
* Two different switches are easy to confuse here, so they are named apart:
|
|
9
|
+
*
|
|
10
|
+
* - `trackExtracurriculars` is the PRODUCT. Absent means on, which is right for
|
|
11
|
+
* staff: a school that has never touched the switch still gets the Activities
|
|
12
|
+
* area, its analytics and its Gatsby reporting.
|
|
13
|
+
* - `activityLogging` is the LAUNCH. It is what a school says to its students, and
|
|
14
|
+
* absent means nothing has been said. A feature that appears in a pupil's account
|
|
15
|
+
* unannounced gets used by nobody and asked about by everybody, so a year group
|
|
16
|
+
* sees activity logging only once the school has launched it to them.
|
|
17
|
+
*
|
|
18
|
+
* The launch is per year group and, unless the school says otherwise, per academic
|
|
19
|
+
* year: last year's Year 11 is not this year's Year 11, and a school introducing it
|
|
20
|
+
* to each new cohort in assembly should not find it already live.
|
|
21
|
+
*/
|
|
22
|
+
/** Does the school have the Activities area at all? Absent means yes. */
|
|
23
|
+
const activityLoggingProductOn = (institute) => institute?.trackExtracurriculars !== false;
|
|
24
|
+
exports.activityLoggingProductOn = activityLoggingProductOn;
|
|
25
|
+
/**
|
|
26
|
+
* Does the stored launch still count today?
|
|
27
|
+
*
|
|
28
|
+
* A launch is made for one academic year. With `rollOver` on it carries forward
|
|
29
|
+
* forever; without it, September spends it.
|
|
30
|
+
*
|
|
31
|
+
* @param {ActivityLoggingConfig} [config] the school's launch.
|
|
32
|
+
* @param {Date} [now] the date to judge it against.
|
|
33
|
+
* @return {boolean} whether the year groups below are live.
|
|
34
|
+
*/
|
|
35
|
+
const activityLaunchCurrent = (config, now = new Date()) => {
|
|
36
|
+
if (!config?.launchedYear)
|
|
37
|
+
return false;
|
|
38
|
+
return Boolean(config.rollOver) || config.launchedYear === (0, schedule_1.academicYearOf)(now);
|
|
39
|
+
};
|
|
40
|
+
exports.activityLaunchCurrent = activityLaunchCurrent;
|
|
41
|
+
/**
|
|
42
|
+
* The year groups activity logging is live for right now.
|
|
43
|
+
*
|
|
44
|
+
* @param {InstituteData} [institute] the school.
|
|
45
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
46
|
+
* @return {string[]} the year group tags, empty when nothing is live.
|
|
47
|
+
*/
|
|
48
|
+
const activityLoggingLiveYearGroups = (institute, now = new Date()) => {
|
|
49
|
+
if (!(0, exports.activityLoggingProductOn)(institute))
|
|
50
|
+
return [];
|
|
51
|
+
const config = institute?.activityLogging;
|
|
52
|
+
if (!(0, exports.activityLaunchCurrent)(config, now))
|
|
53
|
+
return [];
|
|
54
|
+
return Object.entries(config?.yearGroups || {}).filter(([, on]) => on).map(([yg]) => yg);
|
|
55
|
+
};
|
|
56
|
+
exports.activityLoggingLiveYearGroups = activityLoggingLiveYearGroups;
|
|
57
|
+
/**
|
|
58
|
+
* Can THIS student log activities, and may we email them about it?
|
|
59
|
+
*
|
|
60
|
+
* The one gate every student-facing surface asks. A student with no year group on
|
|
61
|
+
* their record is not in a launched year group, so the answer is no — which is the
|
|
62
|
+
* safe way round: they see nothing rather than being shown something their school
|
|
63
|
+
* hasn't announced.
|
|
64
|
+
*
|
|
65
|
+
* @param {InstituteData} [institute] their school.
|
|
66
|
+
* @param {string} [yearGroup] their year group tag, as their record holds it.
|
|
67
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
68
|
+
* @return {boolean} whether activity logging is live for them.
|
|
69
|
+
*/
|
|
70
|
+
const activityLoggingLive = (institute, yearGroup, now = new Date()) => {
|
|
71
|
+
if (!yearGroup)
|
|
72
|
+
return false;
|
|
73
|
+
if (!(0, exports.activityLoggingProductOn)(institute))
|
|
74
|
+
return false;
|
|
75
|
+
const config = institute?.activityLogging;
|
|
76
|
+
if (!(0, exports.activityLaunchCurrent)(config, now))
|
|
77
|
+
return false;
|
|
78
|
+
return config?.yearGroups?.[yearGroup] === true;
|
|
79
|
+
};
|
|
80
|
+
exports.activityLoggingLive = activityLoggingLive;
|
|
81
|
+
/**
|
|
82
|
+
* Has this school launched activity logging, and does the launch still stand?
|
|
83
|
+
*
|
|
84
|
+
* The three answers are the three different things to do about it: ask the setup
|
|
85
|
+
* questions, re-launch a year that has rolled over, or nothing.
|
|
86
|
+
*
|
|
87
|
+
* @param {InstituteData} [institute] the school.
|
|
88
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
89
|
+
* @return {ActivityLoggingState} what is left to do.
|
|
90
|
+
*/
|
|
91
|
+
const activityLoggingState = (institute, now = new Date()) => {
|
|
92
|
+
const config = institute?.activityLogging;
|
|
93
|
+
// Never answered, or answered and launched to nobody — both leave a school with
|
|
94
|
+
// the questions still to answer.
|
|
95
|
+
if (!config?.setupCompletedAt || !Object.values(config.yearGroups || {}).some(Boolean))
|
|
96
|
+
return "unconfigured";
|
|
97
|
+
if ((0, exports.activityLoggingLiveYearGroups)(institute, now).length)
|
|
98
|
+
return "live";
|
|
99
|
+
return "needsLaunch";
|
|
100
|
+
};
|
|
101
|
+
exports.activityLoggingState = activityLoggingState;
|
|
102
|
+
//# sourceMappingURL=activityLogging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activityLogging.js","sourceRoot":"","sources":["../src/activityLogging.ts"],"names":[],"mappings":";;;AACA,iDAAkD;AAElD;;;;;;;;;;;;;;;;GAgBG;AAEH,yEAAyE;AAClE,MAAM,wBAAwB,GAAG,CAAC,SAAwD,EAAW,EAAE,CAC1G,SAAS,EAAE,qBAAqB,KAAK,KAAK,CAAC;AADlC,QAAA,wBAAwB,4BACU;AAE/C;;;;;;;;;GASG;AACI,MAAM,qBAAqB,GAAG,CAAC,MAA8B,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAW,EAAE;IAC/F,IAAI,CAAC,MAAM,EAAE,YAAY;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,YAAY,KAAK,IAAA,yBAAc,EAAC,GAAG,CAAC,CAAC;AACnF,CAAC,CAAC;AAHW,QAAA,qBAAqB,yBAGhC;AAEF;;;;;;GAMG;AACI,MAAM,6BAA6B,GAAG,CAAC,SAAyB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAY,EAAE;IACnG,IAAI,CAAC,IAAA,gCAAwB,EAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,SAAS,EAAE,eAAe,CAAC;IAC1C,IAAI,CAAC,IAAA,6BAAqB,EAAC,MAAM,EAAE,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACnD,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;AAC7F,CAAC,CAAC;AALW,QAAA,6BAA6B,iCAKxC;AAEF;;;;;;;;;;;;GAYG;AACI,MAAM,mBAAmB,GAAG,CAAC,SAAyB,EAAE,SAAkB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAW,EAAE;IAC5G,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,IAAI,CAAC,IAAA,gCAAwB,EAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,MAAM,MAAM,GAAG,SAAS,EAAE,eAAe,CAAC;IAC1C,IAAI,CAAC,IAAA,6BAAqB,EAAC,MAAM,EAAE,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,MAAM,EAAE,UAAU,EAAE,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;AACpD,CAAC,CAAC;AANW,QAAA,mBAAmB,uBAM9B;AAMF;;;;;;;;;GASG;AACI,MAAM,oBAAoB,GAAG,CAAC,SAAyB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAwB,EAAE;IACtG,MAAM,MAAM,GAAG,SAAS,EAAE,eAAe,CAAC;IAC1C,gFAAgF;IAChF,iCAAiC;IACjC,IAAI,CAAC,MAAM,EAAE,gBAAgB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,cAAc,CAAC;IAC9G,IAAI,IAAA,qCAA6B,EAAC,SAAS,EAAE,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,MAAM,CAAC;IACxE,OAAO,aAAa,CAAC;AACzB,CAAC,CAAC;AAPW,QAAA,oBAAoB,wBAO/B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const activityLogging_1 = require("./activityLogging");
|
|
4
|
+
// 1 September is the academic-year boundary the whole platform uses, so a launch
|
|
5
|
+
// made in one year is spent the moment the next one starts.
|
|
6
|
+
const IN_2026_27 = new Date("2026-10-01T09:00:00Z");
|
|
7
|
+
const IN_2027_28 = new Date("2027-10-01T09:00:00Z");
|
|
8
|
+
// The August before 2026-27 starts: still the 2025-26 academic year.
|
|
9
|
+
const AUGUST_2026 = new Date("2026-08-20T09:00:00Z");
|
|
10
|
+
const school = (overrides = {}) => ({
|
|
11
|
+
name: "Calder Park Academy",
|
|
12
|
+
...overrides,
|
|
13
|
+
});
|
|
14
|
+
const launched = (yearGroups, extra = {}) => school({ activityLogging: {
|
|
15
|
+
setupCompletedAt: "2026-09-02T10:00:00Z",
|
|
16
|
+
launchedYear: "2026-27",
|
|
17
|
+
yearGroups,
|
|
18
|
+
...extra,
|
|
19
|
+
} });
|
|
20
|
+
describe("activityLoggingProductOn", () => {
|
|
21
|
+
it("is on when the flag is absent, because staff have the area by default", () => {
|
|
22
|
+
expect((0, activityLogging_1.activityLoggingProductOn)(school())).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it("is on when explicitly true", () => {
|
|
25
|
+
expect((0, activityLogging_1.activityLoggingProductOn)(school({ trackExtracurriculars: true }))).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it("is off only when explicitly false", () => {
|
|
28
|
+
expect((0, activityLogging_1.activityLoggingProductOn)(school({ trackExtracurriculars: false }))).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("activityLaunchCurrent", () => {
|
|
32
|
+
it("is false with nothing launched", () => {
|
|
33
|
+
expect((0, activityLogging_1.activityLaunchCurrent)(undefined, IN_2026_27)).toBe(false);
|
|
34
|
+
expect((0, activityLogging_1.activityLaunchCurrent)({ yearGroups: { "Year 12": true } }, IN_2026_27)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it("holds within the year it was launched for", () => {
|
|
37
|
+
expect((0, activityLogging_1.activityLaunchCurrent)({ launchedYear: "2026-27" }, IN_2026_27)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it("is spent by the next academic year", () => {
|
|
40
|
+
expect((0, activityLogging_1.activityLaunchCurrent)({ launchedYear: "2026-27" }, IN_2027_28)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
it("carries forward when the school asked it to", () => {
|
|
43
|
+
expect((0, activityLogging_1.activityLaunchCurrent)({ launchedYear: "2026-27", rollOver: true }, IN_2027_28)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it("counts August as the year that is ending, not the one about to start", () => {
|
|
46
|
+
expect((0, activityLogging_1.activityLaunchCurrent)({ launchedYear: "2025-26" }, AUGUST_2026)).toBe(true);
|
|
47
|
+
expect((0, activityLogging_1.activityLaunchCurrent)({ launchedYear: "2026-27" }, AUGUST_2026)).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("activityLoggingLive", () => {
|
|
51
|
+
it("is live for a launched year group", () => {
|
|
52
|
+
expect((0, activityLogging_1.activityLoggingLive)(launched({ "Year 12": true }), "Year 12", IN_2026_27)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
it("is not live for a year group left out of the launch", () => {
|
|
55
|
+
expect((0, activityLogging_1.activityLoggingLive)(launched({ "Year 12": true }), "Year 9", IN_2026_27)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
it("is not live for a year group switched off", () => {
|
|
58
|
+
expect((0, activityLogging_1.activityLoggingLive)(launched({ "Year 12": false }), "Year 12", IN_2026_27)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it("is not live for a student with no year group on their record", () => {
|
|
61
|
+
expect((0, activityLogging_1.activityLoggingLive)(launched({ "Year 12": true }), undefined, IN_2026_27)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it("stops at the new academic year unless the launch rolls over", () => {
|
|
64
|
+
expect((0, activityLogging_1.activityLoggingLive)(launched({ "Year 12": true }), "Year 12", IN_2027_28)).toBe(false);
|
|
65
|
+
expect((0, activityLogging_1.activityLoggingLive)(launched({ "Year 12": true }, { rollOver: true }), "Year 12", IN_2027_28)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it("is never live when the product itself is off, whatever the launch says", () => {
|
|
68
|
+
const off = launched({ "Year 12": true });
|
|
69
|
+
off.trackExtracurriculars = false;
|
|
70
|
+
expect((0, activityLogging_1.activityLoggingLive)(off, "Year 12", IN_2026_27)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
it("matches the school's own year tag verbatim rather than normalising it", () => {
|
|
73
|
+
expect((0, activityLogging_1.activityLoggingLive)(launched({ "Yr 12 (KS5)": true }), "Yr 12 (KS5)", IN_2026_27)).toBe(true);
|
|
74
|
+
expect((0, activityLogging_1.activityLoggingLive)(launched({ "Yr 12 (KS5)": true }), "Year 12", IN_2026_27)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe("activityLoggingLiveYearGroups", () => {
|
|
78
|
+
it("lists only the year groups switched on", () => {
|
|
79
|
+
expect((0, activityLogging_1.activityLoggingLiveYearGroups)(launched({ "Year 9": true, "Year 10": false, "Year 12": true }), IN_2026_27))
|
|
80
|
+
.toEqual(["Year 9", "Year 12"]);
|
|
81
|
+
});
|
|
82
|
+
it("is empty once the launch is spent", () => {
|
|
83
|
+
expect((0, activityLogging_1.activityLoggingLiveYearGroups)(launched({ "Year 9": true }), IN_2027_28)).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe("activityLoggingState", () => {
|
|
87
|
+
it("is unconfigured before the school has answered the setup", () => {
|
|
88
|
+
expect((0, activityLogging_1.activityLoggingState)(school(), IN_2026_27)).toBe("unconfigured");
|
|
89
|
+
});
|
|
90
|
+
it("is unconfigured when setup was answered but nobody was launched to", () => {
|
|
91
|
+
expect((0, activityLogging_1.activityLoggingState)(launched({ "Year 9": false }), IN_2026_27)).toBe("unconfigured");
|
|
92
|
+
});
|
|
93
|
+
it("is live while a launch stands", () => {
|
|
94
|
+
expect((0, activityLogging_1.activityLoggingState)(launched({ "Year 9": true }), IN_2026_27)).toBe("live");
|
|
95
|
+
});
|
|
96
|
+
it("asks for a new launch once the year has rolled over", () => {
|
|
97
|
+
expect((0, activityLogging_1.activityLoggingState)(launched({ "Year 9": true }), IN_2027_28)).toBe("needsLaunch");
|
|
98
|
+
});
|
|
99
|
+
it("stays live across years when the school said it should", () => {
|
|
100
|
+
expect((0, activityLogging_1.activityLoggingState)(launched({ "Year 9": true }, { rollOver: true }), IN_2027_28)).toBe("live");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=activityLogging.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activityLogging.test.js","sourceRoot":"","sources":["../src/activityLogging.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Contract for launching activity logging to students
|
|
4
|
+
* (`Placementt-Backend/functions/src/activityLogging.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Launching is a callable rather than a client write for one reason: it emails
|
|
7
|
+
* pupils. Which year groups a school has told about activity logging decides who
|
|
8
|
+
* gets that email, so the list and the send have to be the same act, checked in one
|
|
9
|
+
* place — a client that could write the year groups itself could mail a school's
|
|
10
|
+
* whole roll by editing one document.
|
|
11
|
+
*/
|
|
12
|
+
export declare const activityLoggingCallables: {
|
|
13
|
+
"activityLogging-launchActivityLogging": {
|
|
14
|
+
request: z.ZodObject<{
|
|
15
|
+
oId: z.ZodString;
|
|
16
|
+
/** Every year group tag the school wants live, exactly as its MIS writes
|
|
17
|
+
* them. The stored map is replaced by this list, so a tag left out is a
|
|
18
|
+
* year group switched off. */
|
|
19
|
+
yearGroups: z.ZodArray<z.ZodString, "many">;
|
|
20
|
+
/** Whether the launch carries into each new academic year, or has to be
|
|
21
|
+
* made again every September. */
|
|
22
|
+
rollOver: z.ZodOptional<z.ZodBoolean>;
|
|
23
|
+
/** Skip the announcement email. For a school switching a year group off,
|
|
24
|
+
* correcting a tag, or turning rollOver on and off — none of which is
|
|
25
|
+
* news a pupil needs. Newly launched year groups are emailed otherwise. */
|
|
26
|
+
silent: z.ZodOptional<z.ZodBoolean>;
|
|
27
|
+
}, "strip", z.ZodTypeAny, {
|
|
28
|
+
oId: string;
|
|
29
|
+
yearGroups: string[];
|
|
30
|
+
silent?: boolean | undefined;
|
|
31
|
+
rollOver?: boolean | undefined;
|
|
32
|
+
}, {
|
|
33
|
+
oId: string;
|
|
34
|
+
yearGroups: string[];
|
|
35
|
+
silent?: boolean | undefined;
|
|
36
|
+
rollOver?: boolean | undefined;
|
|
37
|
+
}>;
|
|
38
|
+
response: z.ZodObject<{
|
|
39
|
+
launched: z.ZodBoolean;
|
|
40
|
+
academicYear: z.ZodString;
|
|
41
|
+
/** Year groups live after this call. */
|
|
42
|
+
yearGroups: z.ZodArray<z.ZodString, "many">;
|
|
43
|
+
/** How many students were emailed. Zero on a silent launch, and on one
|
|
44
|
+
* that only switched year groups off. */
|
|
45
|
+
emailed: z.ZodNumber;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
yearGroups: string[];
|
|
48
|
+
academicYear: string;
|
|
49
|
+
launched: boolean;
|
|
50
|
+
emailed: number;
|
|
51
|
+
}, {
|
|
52
|
+
yearGroups: string[];
|
|
53
|
+
academicYear: string;
|
|
54
|
+
launched: boolean;
|
|
55
|
+
emailed: number;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.activityLoggingCallables = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* Contract for launching activity logging to students
|
|
7
|
+
* (`Placementt-Backend/functions/src/activityLogging.ts`).
|
|
8
|
+
*
|
|
9
|
+
* Launching is a callable rather than a client write for one reason: it emails
|
|
10
|
+
* pupils. Which year groups a school has told about activity logging decides who
|
|
11
|
+
* gets that email, so the list and the send have to be the same act, checked in one
|
|
12
|
+
* place — a client that could write the year groups itself could mail a school's
|
|
13
|
+
* whole roll by editing one document.
|
|
14
|
+
*/
|
|
15
|
+
exports.activityLoggingCallables = {
|
|
16
|
+
"activityLogging-launchActivityLogging": {
|
|
17
|
+
request: zod_1.z.object({
|
|
18
|
+
oId: zod_1.z.string(),
|
|
19
|
+
/** Every year group tag the school wants live, exactly as its MIS writes
|
|
20
|
+
* them. The stored map is replaced by this list, so a tag left out is a
|
|
21
|
+
* year group switched off. */
|
|
22
|
+
yearGroups: zod_1.z.array(zod_1.z.string()),
|
|
23
|
+
/** Whether the launch carries into each new academic year, or has to be
|
|
24
|
+
* made again every September. */
|
|
25
|
+
rollOver: zod_1.z.boolean().optional(),
|
|
26
|
+
/** Skip the announcement email. For a school switching a year group off,
|
|
27
|
+
* correcting a tag, or turning rollOver on and off — none of which is
|
|
28
|
+
* news a pupil needs. Newly launched year groups are emailed otherwise. */
|
|
29
|
+
silent: zod_1.z.boolean().optional(),
|
|
30
|
+
}),
|
|
31
|
+
response: zod_1.z.object({
|
|
32
|
+
launched: zod_1.z.boolean(),
|
|
33
|
+
academicYear: zod_1.z.string(),
|
|
34
|
+
/** Year groups live after this call. */
|
|
35
|
+
yearGroups: zod_1.z.array(zod_1.z.string()),
|
|
36
|
+
/** How many students were emailed. Zero on a silent launch, and on one
|
|
37
|
+
* that only switched year groups off. */
|
|
38
|
+
emailed: zod_1.z.number(),
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=activityLogging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activityLogging.js","sourceRoot":"","sources":["../../src/callables/activityLogging.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;;;;;;;GASG;AACU,QAAA,wBAAwB,GAAG;IACpC,uCAAuC,EAAE;QACrC,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf;;0CAE8B;YAC9B,UAAU,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;YAC/B;6CACiC;YACjC,QAAQ,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;YAChC;;uFAE2E;YAC3E,MAAM,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;SACjC,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,QAAQ,EAAE,OAAC,CAAC,OAAO,EAAE;YACrB,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;YACxB,wCAAwC;YACxC,UAAU,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;YAC/B;qDACyC;YACzC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;SACtB,CAAC;KACL;CACoB,CAAC"}
|
package/lib/callables/index.d.ts
CHANGED
package/lib/callables/index.js
CHANGED
|
@@ -23,4 +23,5 @@ __exportStar(require("./jobRoles"), exports);
|
|
|
23
23
|
__exportStar(require("./apprenticeships"), exports);
|
|
24
24
|
__exportStar(require("./he"), exports);
|
|
25
25
|
__exportStar(require("./nextSteps"), exports);
|
|
26
|
+
__exportStar(require("./activityLogging"), exports);
|
|
26
27
|
//# 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,8CAA4B"}
|
|
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,8CAA4B;AAC5B,oDAAkC"}
|
|
@@ -11,6 +11,40 @@ import { z } from "zod";
|
|
|
11
11
|
* equals the sum of the group key counts.
|
|
12
12
|
*/
|
|
13
13
|
export declare const allCallables: {
|
|
14
|
+
"activityLogging-launchActivityLogging": {
|
|
15
|
+
request: z.ZodObject<{
|
|
16
|
+
oId: z.ZodString;
|
|
17
|
+
yearGroups: z.ZodArray<z.ZodString, "many">;
|
|
18
|
+
rollOver: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
silent: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
}, "strip", z.ZodTypeAny, {
|
|
21
|
+
oId: string;
|
|
22
|
+
yearGroups: string[];
|
|
23
|
+
silent?: boolean | undefined;
|
|
24
|
+
rollOver?: boolean | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
oId: string;
|
|
27
|
+
yearGroups: string[];
|
|
28
|
+
silent?: boolean | undefined;
|
|
29
|
+
rollOver?: boolean | undefined;
|
|
30
|
+
}>;
|
|
31
|
+
response: z.ZodObject<{
|
|
32
|
+
launched: z.ZodBoolean;
|
|
33
|
+
academicYear: z.ZodString;
|
|
34
|
+
yearGroups: z.ZodArray<z.ZodString, "many">;
|
|
35
|
+
emailed: z.ZodNumber;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
yearGroups: string[];
|
|
38
|
+
academicYear: string;
|
|
39
|
+
launched: boolean;
|
|
40
|
+
emailed: number;
|
|
41
|
+
}, {
|
|
42
|
+
yearGroups: string[];
|
|
43
|
+
academicYear: string;
|
|
44
|
+
launched: boolean;
|
|
45
|
+
emailed: number;
|
|
46
|
+
}>;
|
|
47
|
+
};
|
|
14
48
|
"nextSteps-search": {
|
|
15
49
|
request: z.ZodObject<{
|
|
16
50
|
query: z.ZodOptional<z.ZodString>;
|
|
@@ -5395,4 +5429,40 @@ export declare const callableGroups: {
|
|
|
5395
5429
|
}>;
|
|
5396
5430
|
};
|
|
5397
5431
|
};
|
|
5432
|
+
activityLoggingCallables: {
|
|
5433
|
+
"activityLogging-launchActivityLogging": {
|
|
5434
|
+
request: z.ZodObject<{
|
|
5435
|
+
oId: z.ZodString;
|
|
5436
|
+
yearGroups: z.ZodArray<z.ZodString, "many">;
|
|
5437
|
+
rollOver: z.ZodOptional<z.ZodBoolean>;
|
|
5438
|
+
silent: z.ZodOptional<z.ZodBoolean>;
|
|
5439
|
+
}, "strip", z.ZodTypeAny, {
|
|
5440
|
+
oId: string;
|
|
5441
|
+
yearGroups: string[];
|
|
5442
|
+
silent?: boolean | undefined;
|
|
5443
|
+
rollOver?: boolean | undefined;
|
|
5444
|
+
}, {
|
|
5445
|
+
oId: string;
|
|
5446
|
+
yearGroups: string[];
|
|
5447
|
+
silent?: boolean | undefined;
|
|
5448
|
+
rollOver?: boolean | undefined;
|
|
5449
|
+
}>;
|
|
5450
|
+
response: z.ZodObject<{
|
|
5451
|
+
launched: z.ZodBoolean;
|
|
5452
|
+
academicYear: z.ZodString;
|
|
5453
|
+
yearGroups: z.ZodArray<z.ZodString, "many">;
|
|
5454
|
+
emailed: z.ZodNumber;
|
|
5455
|
+
}, "strip", z.ZodTypeAny, {
|
|
5456
|
+
yearGroups: string[];
|
|
5457
|
+
academicYear: string;
|
|
5458
|
+
launched: boolean;
|
|
5459
|
+
emailed: number;
|
|
5460
|
+
}, {
|
|
5461
|
+
yearGroups: string[];
|
|
5462
|
+
academicYear: string;
|
|
5463
|
+
launched: boolean;
|
|
5464
|
+
emailed: number;
|
|
5465
|
+
}>;
|
|
5466
|
+
};
|
|
5467
|
+
};
|
|
5398
5468
|
};
|
|
@@ -11,6 +11,7 @@ const benchmarks_1 = require("./benchmarks");
|
|
|
11
11
|
const apprenticeships_1 = require("./apprenticeships");
|
|
12
12
|
const he_1 = require("./he");
|
|
13
13
|
const nextSteps_1 = require("./nextSteps");
|
|
14
|
+
const activityLogging_1 = require("./activityLogging");
|
|
14
15
|
/**
|
|
15
16
|
* The single source of truth for every typed callable. Add a feature group's
|
|
16
17
|
* spread here and its callables are automatically covered by:
|
|
@@ -33,7 +34,8 @@ exports.allCallables = {
|
|
|
33
34
|
...apprenticeships_1.apprenticeshipsCallables,
|
|
34
35
|
...he_1.heCallables,
|
|
35
36
|
...nextSteps_1.nextStepsCallables,
|
|
37
|
+
...activityLogging_1.activityLoggingCallables,
|
|
36
38
|
};
|
|
37
39
|
/** The per-group maps, exported so tests can assert no key collisions. */
|
|
38
|
-
exports.callableGroups = { destinationsConfigCallables: destinationsConfig_1.destinationsConfigCallables, placementCallables: placement_1.placementCallables, eventsCallables: events_1.eventsCallables, surveyCallables: surveys_1.surveyCallables, jobRolesCallables: jobRoles_1.jobRolesCallables, checksCallables: checks_1.checksCallables, benchmarksCallables: benchmarks_1.benchmarksCallables, apprenticeshipsCallables: apprenticeships_1.apprenticeshipsCallables, heCallables: he_1.heCallables, nextStepsCallables: nextSteps_1.nextStepsCallables };
|
|
40
|
+
exports.callableGroups = { destinationsConfigCallables: destinationsConfig_1.destinationsConfigCallables, placementCallables: placement_1.placementCallables, eventsCallables: events_1.eventsCallables, surveyCallables: surveys_1.surveyCallables, jobRolesCallables: jobRoles_1.jobRolesCallables, checksCallables: checks_1.checksCallables, benchmarksCallables: benchmarks_1.benchmarksCallables, apprenticeshipsCallables: apprenticeships_1.apprenticeshipsCallables, heCallables: he_1.heCallables, nextStepsCallables: nextSteps_1.nextStepsCallables, activityLoggingCallables: activityLogging_1.activityLoggingCallables };
|
|
39
41
|
//# sourceMappingURL=registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/callables/registry.ts"],"names":[],"mappings":";;;AAEA,6DAAiE;AACjE,2CAA+C;AAC/C,qCAAyC;AACzC,uCAA0C;AAC1C,yCAA6C;AAC7C,qCAAyC;AACzC,6CAAiD;AACjD,uDAA2D;AAC3D,6BAAiC;AACjC,2CAA+C;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/callables/registry.ts"],"names":[],"mappings":";;;AAEA,6DAAiE;AACjE,2CAA+C;AAC/C,qCAAyC;AACzC,uCAA0C;AAC1C,yCAA6C;AAC7C,qCAAyC;AACzC,6CAAiD;AACjD,uDAA2D;AAC3D,6BAAiC;AACjC,2CAA+C;AAC/C,uDAA2D;AAE3D;;;;;;;;;;GAUG;AACU,QAAA,YAAY,GAAG;IACxB,GAAG,gDAA2B;IAC9B,GAAG,8BAAkB;IACrB,GAAG,wBAAe;IAClB,GAAG,yBAAe;IAClB,GAAG,4BAAiB;IACpB,GAAG,wBAAe;IAClB,GAAG,gCAAmB;IACtB,GAAG,0CAAwB;IAC3B,GAAG,gBAAW;IACd,GAAG,8BAAkB;IACrB,GAAG,0CAAwB;CACN,CAAC;AAiB1B,0EAA0E;AAC7D,QAAA,cAAc,GAAG,EAAC,2BAA2B,EAA3B,gDAA2B,EAAE,kBAAkB,EAAlB,8BAAkB,EAAE,eAAe,EAAf,wBAAe,EAAE,eAAe,EAAf,yBAAe,EAAE,iBAAiB,EAAjB,4BAAiB,EAAE,eAAe,EAAf,wBAAe,EAAE,mBAAmB,EAAnB,gCAAmB,EAAE,wBAAwB,EAAxB,0CAAwB,EAAE,WAAW,EAAX,gBAAW,EAAE,kBAAkB,EAAlB,8BAAkB,EAAE,wBAAwB,EAAxB,0CAAwB,EAAC,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./surveys/checks";
|
|
|
15
15
|
export * from "./surveys/schedule";
|
|
16
16
|
export * from "./skillsResolver";
|
|
17
17
|
export * from "./activities";
|
|
18
|
+
export * from "./activityLogging";
|
|
18
19
|
export * from "./lmi";
|
|
19
20
|
export * from "./productResolver";
|
|
20
21
|
export * from "./pricing";
|
package/lib/index.js
CHANGED
|
@@ -35,6 +35,7 @@ __exportStar(require("./surveys/checks"), exports);
|
|
|
35
35
|
__exportStar(require("./surveys/schedule"), exports);
|
|
36
36
|
__exportStar(require("./skillsResolver"), exports);
|
|
37
37
|
__exportStar(require("./activities"), exports);
|
|
38
|
+
__exportStar(require("./activityLogging"), exports);
|
|
38
39
|
__exportStar(require("./lmi"), exports);
|
|
39
40
|
__exportStar(require("./productResolver"), exports);
|
|
40
41
|
__exportStar(require("./pricing"), exports);
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,mDAAiC;AACjC,+CAA6B;AAC7B,wCAAsB;AACtB,oDAAkC;AAClC,4CAA0B;AAC1B,2CAAyB;AACzB,kDAAgC;AAChC,uDAAqC;AACrC,oDAAkC;AAClC,kDAAgC;AAChC,0DAAwC;AACxC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,wCAAsB;AACtB,oDAAkC;AAClC,4CAA0B;AAC1B,2CAAyB;AACzB,kDAAgC;AAChC,uDAAqC;AACrC,oDAAkC;AAClC,kDAAgC;AAChC,0DAAwC;AACxC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
|
package/lib/typeDefinitions.d.ts
CHANGED
|
@@ -1143,6 +1143,15 @@ export type InstituteData = {
|
|
|
1143
1143
|
careersEvents?: boolean;
|
|
1144
1144
|
skillsAssessmentsProduct?: boolean;
|
|
1145
1145
|
trackExtracurriculars?: boolean;
|
|
1146
|
+
/** Activity logging as STUDENTS meet it.
|
|
1147
|
+
*
|
|
1148
|
+
* `trackExtracurriculars` above is the product: absent or true means the school
|
|
1149
|
+
* has the area and its staff can use it, which is why absent reads as on. This
|
|
1150
|
+
* is the launch, and it is a different question — a school switching the product
|
|
1151
|
+
* on has not put the feature in front of a single student until it says which
|
|
1152
|
+
* year groups have been told about it. Nothing is shown to a student, and no
|
|
1153
|
+
* student is emailed, outside the year groups in here. */
|
|
1154
|
+
activityLogging?: ActivityLoggingConfig;
|
|
1146
1155
|
destinationTracking?: boolean;
|
|
1147
1156
|
docCheck?: boolean;
|
|
1148
1157
|
adminLastCheckedDocs?: string;
|
|
@@ -1291,6 +1300,35 @@ export type InstituteData = {
|
|
|
1291
1300
|
};
|
|
1292
1301
|
cs?: CustomerSuccessData;
|
|
1293
1302
|
} & Address;
|
|
1303
|
+
/**
|
|
1304
|
+
* Which year groups activity logging has been launched to, and whether that lasts.
|
|
1305
|
+
*
|
|
1306
|
+
* Kept on the institute rather than per student because it is a decision about a
|
|
1307
|
+
* year group, not about a person: a school launches to Year 12 in September and
|
|
1308
|
+
* everyone in Year 12 has it that day, including the pupil who joins in November.
|
|
1309
|
+
*/
|
|
1310
|
+
export type ActivityLoggingConfig = {
|
|
1311
|
+
/** Year groups it is live for, keyed by the school's own year tag exactly as its
|
|
1312
|
+
* MIS writes it. A tag that is absent or false is a year that has not been
|
|
1313
|
+
* launched to, and its students see nothing. */
|
|
1314
|
+
yearGroups?: {
|
|
1315
|
+
[yearGroup: string]: boolean;
|
|
1316
|
+
};
|
|
1317
|
+
/** Whether a launch carries into each new academic year, or has to be made again
|
|
1318
|
+
* every September. Off by default, and deliberately: last year's Year 11 is not
|
|
1319
|
+
* this year's Year 11, and a school that wants to introduce it to each new
|
|
1320
|
+
* cohort in person should not find it already live. */
|
|
1321
|
+
rollOver?: boolean;
|
|
1322
|
+
/** The academic year the launch was made for, "2026-27". With `rollOver` off, a
|
|
1323
|
+
* launch from an earlier year is spent and the school is asked to launch again. */
|
|
1324
|
+
launchedYear?: string;
|
|
1325
|
+
launchedAt?: string;
|
|
1326
|
+
/** uid of the staff member who launched it. */
|
|
1327
|
+
launchedBy?: string;
|
|
1328
|
+
/** Set the first time the school answers the setup, so the Activities area stops
|
|
1329
|
+
* showing its first-run wizard. Stamped server-side. */
|
|
1330
|
+
setupCompletedAt?: string;
|
|
1331
|
+
};
|
|
1294
1332
|
export type ExternalActivity = {
|
|
1295
1333
|
title?: string;
|
|
1296
1334
|
description: string;
|
|
@@ -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":";;;AA6lIA,sDASC;AAeD,oDAKC;AAaD,0CAEC;AAjoED;;;;;kFAKkF;AACrE,QAAA,qBAAqB,GAAG,SAAS,CAAC;AAClC,QAAA,iBAAiB,GAAG,OAAO,CAAC;AA6iEzC;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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {InstituteData} from "./typeDefinitions";
|
|
2
|
+
import {
|
|
3
|
+
activityLaunchCurrent,
|
|
4
|
+
activityLoggingLive,
|
|
5
|
+
activityLoggingLiveYearGroups,
|
|
6
|
+
activityLoggingProductOn,
|
|
7
|
+
activityLoggingState,
|
|
8
|
+
} from "./activityLogging";
|
|
9
|
+
|
|
10
|
+
// 1 September is the academic-year boundary the whole platform uses, so a launch
|
|
11
|
+
// made in one year is spent the moment the next one starts.
|
|
12
|
+
const IN_2026_27 = new Date("2026-10-01T09:00:00Z");
|
|
13
|
+
const IN_2027_28 = new Date("2027-10-01T09:00:00Z");
|
|
14
|
+
// The August before 2026-27 starts: still the 2025-26 academic year.
|
|
15
|
+
const AUGUST_2026 = new Date("2026-08-20T09:00:00Z");
|
|
16
|
+
|
|
17
|
+
const school = (overrides: Partial<InstituteData> = {}): InstituteData => ({
|
|
18
|
+
name: "Calder Park Academy",
|
|
19
|
+
...overrides,
|
|
20
|
+
} as InstituteData);
|
|
21
|
+
|
|
22
|
+
const launched = (yearGroups: {[yg: string]: boolean}, extra: Partial<NonNullable<InstituteData["activityLogging"]>> = {}) =>
|
|
23
|
+
school({activityLogging: {
|
|
24
|
+
setupCompletedAt: "2026-09-02T10:00:00Z",
|
|
25
|
+
launchedYear: "2026-27",
|
|
26
|
+
yearGroups,
|
|
27
|
+
...extra,
|
|
28
|
+
}});
|
|
29
|
+
|
|
30
|
+
describe("activityLoggingProductOn", () => {
|
|
31
|
+
it("is on when the flag is absent, because staff have the area by default", () => {
|
|
32
|
+
expect(activityLoggingProductOn(school())).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("is on when explicitly true", () => {
|
|
36
|
+
expect(activityLoggingProductOn(school({trackExtracurriculars: true}))).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("is off only when explicitly false", () => {
|
|
40
|
+
expect(activityLoggingProductOn(school({trackExtracurriculars: false}))).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("activityLaunchCurrent", () => {
|
|
45
|
+
it("is false with nothing launched", () => {
|
|
46
|
+
expect(activityLaunchCurrent(undefined, IN_2026_27)).toBe(false);
|
|
47
|
+
expect(activityLaunchCurrent({yearGroups: {"Year 12": true}}, IN_2026_27)).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("holds within the year it was launched for", () => {
|
|
51
|
+
expect(activityLaunchCurrent({launchedYear: "2026-27"}, IN_2026_27)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("is spent by the next academic year", () => {
|
|
55
|
+
expect(activityLaunchCurrent({launchedYear: "2026-27"}, IN_2027_28)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("carries forward when the school asked it to", () => {
|
|
59
|
+
expect(activityLaunchCurrent({launchedYear: "2026-27", rollOver: true}, IN_2027_28)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("counts August as the year that is ending, not the one about to start", () => {
|
|
63
|
+
expect(activityLaunchCurrent({launchedYear: "2025-26"}, AUGUST_2026)).toBe(true);
|
|
64
|
+
expect(activityLaunchCurrent({launchedYear: "2026-27"}, AUGUST_2026)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("activityLoggingLive", () => {
|
|
69
|
+
it("is live for a launched year group", () => {
|
|
70
|
+
expect(activityLoggingLive(launched({"Year 12": true}), "Year 12", IN_2026_27)).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("is not live for a year group left out of the launch", () => {
|
|
74
|
+
expect(activityLoggingLive(launched({"Year 12": true}), "Year 9", IN_2026_27)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("is not live for a year group switched off", () => {
|
|
78
|
+
expect(activityLoggingLive(launched({"Year 12": false}), "Year 12", IN_2026_27)).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("is not live for a student with no year group on their record", () => {
|
|
82
|
+
expect(activityLoggingLive(launched({"Year 12": true}), undefined, IN_2026_27)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("stops at the new academic year unless the launch rolls over", () => {
|
|
86
|
+
expect(activityLoggingLive(launched({"Year 12": true}), "Year 12", IN_2027_28)).toBe(false);
|
|
87
|
+
expect(activityLoggingLive(launched({"Year 12": true}, {rollOver: true}), "Year 12", IN_2027_28)).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("is never live when the product itself is off, whatever the launch says", () => {
|
|
91
|
+
const off = launched({"Year 12": true});
|
|
92
|
+
off.trackExtracurriculars = false;
|
|
93
|
+
expect(activityLoggingLive(off, "Year 12", IN_2026_27)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("matches the school's own year tag verbatim rather than normalising it", () => {
|
|
97
|
+
expect(activityLoggingLive(launched({"Yr 12 (KS5)": true}), "Yr 12 (KS5)", IN_2026_27)).toBe(true);
|
|
98
|
+
expect(activityLoggingLive(launched({"Yr 12 (KS5)": true}), "Year 12", IN_2026_27)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("activityLoggingLiveYearGroups", () => {
|
|
103
|
+
it("lists only the year groups switched on", () => {
|
|
104
|
+
expect(activityLoggingLiveYearGroups(launched({"Year 9": true, "Year 10": false, "Year 12": true}), IN_2026_27))
|
|
105
|
+
.toEqual(["Year 9", "Year 12"]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("is empty once the launch is spent", () => {
|
|
109
|
+
expect(activityLoggingLiveYearGroups(launched({"Year 9": true}), IN_2027_28)).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("activityLoggingState", () => {
|
|
114
|
+
it("is unconfigured before the school has answered the setup", () => {
|
|
115
|
+
expect(activityLoggingState(school(), IN_2026_27)).toBe("unconfigured");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("is unconfigured when setup was answered but nobody was launched to", () => {
|
|
119
|
+
expect(activityLoggingState(launched({"Year 9": false}), IN_2026_27)).toBe("unconfigured");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("is live while a launch stands", () => {
|
|
123
|
+
expect(activityLoggingState(launched({"Year 9": true}), IN_2026_27)).toBe("live");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("asks for a new launch once the year has rolled over", () => {
|
|
127
|
+
expect(activityLoggingState(launched({"Year 9": true}), IN_2027_28)).toBe("needsLaunch");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("stays live across years when the school said it should", () => {
|
|
131
|
+
expect(activityLoggingState(launched({"Year 9": true}, {rollOver: true}), IN_2027_28)).toBe("live");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {ActivityLoggingConfig, InstituteData} from "./typeDefinitions";
|
|
2
|
+
import {academicYearOf} from "./surveys/schedule";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Whether a student can log activities, and whether we may email them about it.
|
|
6
|
+
*
|
|
7
|
+
* Two different switches are easy to confuse here, so they are named apart:
|
|
8
|
+
*
|
|
9
|
+
* - `trackExtracurriculars` is the PRODUCT. Absent means on, which is right for
|
|
10
|
+
* staff: a school that has never touched the switch still gets the Activities
|
|
11
|
+
* area, its analytics and its Gatsby reporting.
|
|
12
|
+
* - `activityLogging` is the LAUNCH. It is what a school says to its students, and
|
|
13
|
+
* absent means nothing has been said. A feature that appears in a pupil's account
|
|
14
|
+
* unannounced gets used by nobody and asked about by everybody, so a year group
|
|
15
|
+
* sees activity logging only once the school has launched it to them.
|
|
16
|
+
*
|
|
17
|
+
* The launch is per year group and, unless the school says otherwise, per academic
|
|
18
|
+
* year: last year's Year 11 is not this year's Year 11, and a school introducing it
|
|
19
|
+
* to each new cohort in assembly should not find it already live.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Does the school have the Activities area at all? Absent means yes. */
|
|
23
|
+
export const activityLoggingProductOn = (institute?: Pick<InstituteData, "trackExtracurriculars">): boolean =>
|
|
24
|
+
institute?.trackExtracurriculars !== false;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Does the stored launch still count today?
|
|
28
|
+
*
|
|
29
|
+
* A launch is made for one academic year. With `rollOver` on it carries forward
|
|
30
|
+
* forever; without it, September spends it.
|
|
31
|
+
*
|
|
32
|
+
* @param {ActivityLoggingConfig} [config] the school's launch.
|
|
33
|
+
* @param {Date} [now] the date to judge it against.
|
|
34
|
+
* @return {boolean} whether the year groups below are live.
|
|
35
|
+
*/
|
|
36
|
+
export const activityLaunchCurrent = (config?: ActivityLoggingConfig, now = new Date()): boolean => {
|
|
37
|
+
if (!config?.launchedYear) return false;
|
|
38
|
+
return Boolean(config.rollOver) || config.launchedYear === academicYearOf(now);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The year groups activity logging is live for right now.
|
|
43
|
+
*
|
|
44
|
+
* @param {InstituteData} [institute] the school.
|
|
45
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
46
|
+
* @return {string[]} the year group tags, empty when nothing is live.
|
|
47
|
+
*/
|
|
48
|
+
export const activityLoggingLiveYearGroups = (institute?: InstituteData, now = new Date()): string[] => {
|
|
49
|
+
if (!activityLoggingProductOn(institute)) return [];
|
|
50
|
+
const config = institute?.activityLogging;
|
|
51
|
+
if (!activityLaunchCurrent(config, now)) return [];
|
|
52
|
+
return Object.entries(config?.yearGroups || {}).filter(([, on]) => on).map(([yg]) => yg);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Can THIS student log activities, and may we email them about it?
|
|
57
|
+
*
|
|
58
|
+
* The one gate every student-facing surface asks. A student with no year group on
|
|
59
|
+
* their record is not in a launched year group, so the answer is no — which is the
|
|
60
|
+
* safe way round: they see nothing rather than being shown something their school
|
|
61
|
+
* hasn't announced.
|
|
62
|
+
*
|
|
63
|
+
* @param {InstituteData} [institute] their school.
|
|
64
|
+
* @param {string} [yearGroup] their year group tag, as their record holds it.
|
|
65
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
66
|
+
* @return {boolean} whether activity logging is live for them.
|
|
67
|
+
*/
|
|
68
|
+
export const activityLoggingLive = (institute?: InstituteData, yearGroup?: string, now = new Date()): boolean => {
|
|
69
|
+
if (!yearGroup) return false;
|
|
70
|
+
if (!activityLoggingProductOn(institute)) return false;
|
|
71
|
+
const config = institute?.activityLogging;
|
|
72
|
+
if (!activityLaunchCurrent(config, now)) return false;
|
|
73
|
+
return config?.yearGroups?.[yearGroup] === true;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Where a school's activity logging stands, in the vocabulary the careers year
|
|
77
|
+
* setup uses for every other programme. */
|
|
78
|
+
export type ActivityLoggingState = "unconfigured"|"needsLaunch"|"live";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Has this school launched activity logging, and does the launch still stand?
|
|
82
|
+
*
|
|
83
|
+
* The three answers are the three different things to do about it: ask the setup
|
|
84
|
+
* questions, re-launch a year that has rolled over, or nothing.
|
|
85
|
+
*
|
|
86
|
+
* @param {InstituteData} [institute] the school.
|
|
87
|
+
* @param {Date} [now] the date to judge the launch against.
|
|
88
|
+
* @return {ActivityLoggingState} what is left to do.
|
|
89
|
+
*/
|
|
90
|
+
export const activityLoggingState = (institute?: InstituteData, now = new Date()): ActivityLoggingState => {
|
|
91
|
+
const config = institute?.activityLogging;
|
|
92
|
+
// Never answered, or answered and launched to nobody — both leave a school with
|
|
93
|
+
// the questions still to answer.
|
|
94
|
+
if (!config?.setupCompletedAt || !Object.values(config.yearGroups || {}).some(Boolean)) return "unconfigured";
|
|
95
|
+
if (activityLoggingLiveYearGroups(institute, now).length) return "live";
|
|
96
|
+
return "needsLaunch";
|
|
97
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {z} from "zod";
|
|
2
|
+
import {CallableGroup} from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Contract for launching activity logging to students
|
|
6
|
+
* (`Placementt-Backend/functions/src/activityLogging.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Launching is a callable rather than a client write for one reason: it emails
|
|
9
|
+
* pupils. Which year groups a school has told about activity logging decides who
|
|
10
|
+
* gets that email, so the list and the send have to be the same act, checked in one
|
|
11
|
+
* place — a client that could write the year groups itself could mail a school's
|
|
12
|
+
* whole roll by editing one document.
|
|
13
|
+
*/
|
|
14
|
+
export const activityLoggingCallables = {
|
|
15
|
+
"activityLogging-launchActivityLogging": {
|
|
16
|
+
request: z.object({
|
|
17
|
+
oId: z.string(),
|
|
18
|
+
/** Every year group tag the school wants live, exactly as its MIS writes
|
|
19
|
+
* them. The stored map is replaced by this list, so a tag left out is a
|
|
20
|
+
* year group switched off. */
|
|
21
|
+
yearGroups: z.array(z.string()),
|
|
22
|
+
/** Whether the launch carries into each new academic year, or has to be
|
|
23
|
+
* made again every September. */
|
|
24
|
+
rollOver: z.boolean().optional(),
|
|
25
|
+
/** Skip the announcement email. For a school switching a year group off,
|
|
26
|
+
* correcting a tag, or turning rollOver on and off — none of which is
|
|
27
|
+
* news a pupil needs. Newly launched year groups are emailed otherwise. */
|
|
28
|
+
silent: z.boolean().optional(),
|
|
29
|
+
}),
|
|
30
|
+
response: z.object({
|
|
31
|
+
launched: z.boolean(),
|
|
32
|
+
academicYear: z.string(),
|
|
33
|
+
/** Year groups live after this call. */
|
|
34
|
+
yearGroups: z.array(z.string()),
|
|
35
|
+
/** How many students were emailed. Zero on a silent launch, and on one
|
|
36
|
+
* that only switched year groups off. */
|
|
37
|
+
emailed: z.number(),
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
} satisfies CallableGroup;
|
package/src/callables/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {benchmarksCallables} from "./benchmarks";
|
|
|
10
10
|
import {apprenticeshipsCallables} from "./apprenticeships";
|
|
11
11
|
import {heCallables} from "./he";
|
|
12
12
|
import {nextStepsCallables} from "./nextSteps";
|
|
13
|
+
import {activityLoggingCallables} from "./activityLogging";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* The single source of truth for every typed callable. Add a feature group's
|
|
@@ -33,6 +34,7 @@ export const allCallables = {
|
|
|
33
34
|
...apprenticeshipsCallables,
|
|
34
35
|
...heCallables,
|
|
35
36
|
...nextStepsCallables,
|
|
37
|
+
...activityLoggingCallables,
|
|
36
38
|
} satisfies CallableGroup;
|
|
37
39
|
|
|
38
40
|
/** Every registered callable name. */
|
|
@@ -51,4 +53,4 @@ export type CallableContracts = {
|
|
|
51
53
|
};
|
|
52
54
|
|
|
53
55
|
/** The per-group maps, exported so tests can assert no key collisions. */
|
|
54
|
-
export const callableGroups = {destinationsConfigCallables, placementCallables, eventsCallables, surveyCallables, jobRolesCallables, checksCallables, benchmarksCallables, apprenticeshipsCallables, heCallables, nextStepsCallables};
|
|
56
|
+
export const callableGroups = {destinationsConfigCallables, placementCallables, eventsCallables, surveyCallables, jobRolesCallables, checksCallables, benchmarksCallables, apprenticeshipsCallables, heCallables, nextStepsCallables, activityLoggingCallables};
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from "./surveys/checks";
|
|
|
17
17
|
export * from "./surveys/schedule";
|
|
18
18
|
export * from "./skillsResolver";
|
|
19
19
|
export * from "./activities";
|
|
20
|
+
export * from "./activityLogging";
|
|
20
21
|
export * from "./lmi";
|
|
21
22
|
export * from "./productResolver";
|
|
22
23
|
export * from "./pricing";
|
package/src/typeDefinitions.ts
CHANGED
|
@@ -1171,6 +1171,15 @@ export type InstituteData = {
|
|
|
1171
1171
|
careersEvents?: boolean,
|
|
1172
1172
|
skillsAssessmentsProduct?: boolean,
|
|
1173
1173
|
trackExtracurriculars?: boolean,
|
|
1174
|
+
/** Activity logging as STUDENTS meet it.
|
|
1175
|
+
*
|
|
1176
|
+
* `trackExtracurriculars` above is the product: absent or true means the school
|
|
1177
|
+
* has the area and its staff can use it, which is why absent reads as on. This
|
|
1178
|
+
* is the launch, and it is a different question — a school switching the product
|
|
1179
|
+
* on has not put the feature in front of a single student until it says which
|
|
1180
|
+
* year groups have been told about it. Nothing is shown to a student, and no
|
|
1181
|
+
* student is emailed, outside the year groups in here. */
|
|
1182
|
+
activityLogging?: ActivityLoggingConfig,
|
|
1174
1183
|
destinationTracking?: boolean,
|
|
1175
1184
|
docCheck?: boolean,
|
|
1176
1185
|
adminLastCheckedDocs?: string,
|
|
@@ -1327,6 +1336,34 @@ export type InstituteData = {
|
|
|
1327
1336
|
cs?: CustomerSuccessData,
|
|
1328
1337
|
}&Address;
|
|
1329
1338
|
|
|
1339
|
+
/**
|
|
1340
|
+
* Which year groups activity logging has been launched to, and whether that lasts.
|
|
1341
|
+
*
|
|
1342
|
+
* Kept on the institute rather than per student because it is a decision about a
|
|
1343
|
+
* year group, not about a person: a school launches to Year 12 in September and
|
|
1344
|
+
* everyone in Year 12 has it that day, including the pupil who joins in November.
|
|
1345
|
+
*/
|
|
1346
|
+
export type ActivityLoggingConfig = {
|
|
1347
|
+
/** Year groups it is live for, keyed by the school's own year tag exactly as its
|
|
1348
|
+
* MIS writes it. A tag that is absent or false is a year that has not been
|
|
1349
|
+
* launched to, and its students see nothing. */
|
|
1350
|
+
yearGroups?: {[yearGroup: string]: boolean},
|
|
1351
|
+
/** Whether a launch carries into each new academic year, or has to be made again
|
|
1352
|
+
* every September. Off by default, and deliberately: last year's Year 11 is not
|
|
1353
|
+
* this year's Year 11, and a school that wants to introduce it to each new
|
|
1354
|
+
* cohort in person should not find it already live. */
|
|
1355
|
+
rollOver?: boolean,
|
|
1356
|
+
/** The academic year the launch was made for, "2026-27". With `rollOver` off, a
|
|
1357
|
+
* launch from an earlier year is spent and the school is asked to launch again. */
|
|
1358
|
+
launchedYear?: string,
|
|
1359
|
+
launchedAt?: string,
|
|
1360
|
+
/** uid of the staff member who launched it. */
|
|
1361
|
+
launchedBy?: string,
|
|
1362
|
+
/** Set the first time the school answers the setup, so the Activities area stops
|
|
1363
|
+
* showing its first-run wizard. Stamped server-side. */
|
|
1364
|
+
setupCompletedAt?: string,
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1330
1367
|
export type ExternalActivity = {
|
|
1331
1368
|
title?: string,
|
|
1332
1369
|
description: string,
|