placementt-core 1.400.1187 → 1.400.1189
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/callables/destinationsConfig.d.ts +2 -2
- package/lib/callables/registry.d.ts +4 -4
- package/lib/kcsie.d.ts +9 -3
- package/lib/kcsie.js +35 -8
- package/lib/kcsie.js.map +1 -1
- package/lib/kcsie.test.js +59 -7
- package/lib/kcsie.test.js.map +1 -1
- package/lib/typeDefinitions.d.ts +5 -3
- package/lib/typeDefinitions.js.map +1 -1
- package/package.json +1 -1
- package/src/kcsie.test.ts +70 -8
- package/src/kcsie.ts +36 -9
- package/src/typeDefinitions.ts +8 -3
|
@@ -37,12 +37,12 @@ export declare const destinationsConfigCallables: {
|
|
|
37
37
|
academicYear: z.ZodString;
|
|
38
38
|
}, "strip", z.ZodTypeAny, {
|
|
39
39
|
created: number;
|
|
40
|
-
academicYear: string;
|
|
41
40
|
provisioned: boolean;
|
|
41
|
+
academicYear: string;
|
|
42
42
|
}, {
|
|
43
43
|
created: number;
|
|
44
|
-
academicYear: string;
|
|
45
44
|
provisioned: boolean;
|
|
45
|
+
academicYear: string;
|
|
46
46
|
}>;
|
|
47
47
|
};
|
|
48
48
|
};
|
|
@@ -2645,12 +2645,12 @@ export declare const allCallables: {
|
|
|
2645
2645
|
academicYear: z.ZodString;
|
|
2646
2646
|
}, "strip", z.ZodTypeAny, {
|
|
2647
2647
|
created: number;
|
|
2648
|
-
academicYear: string;
|
|
2649
2648
|
provisioned: boolean;
|
|
2649
|
+
academicYear: string;
|
|
2650
2650
|
}, {
|
|
2651
2651
|
created: number;
|
|
2652
|
-
academicYear: string;
|
|
2653
2652
|
provisioned: boolean;
|
|
2653
|
+
academicYear: string;
|
|
2654
2654
|
}>;
|
|
2655
2655
|
};
|
|
2656
2656
|
};
|
|
@@ -2690,12 +2690,12 @@ export declare const callableGroups: {
|
|
|
2690
2690
|
academicYear: z.ZodString;
|
|
2691
2691
|
}, "strip", z.ZodTypeAny, {
|
|
2692
2692
|
created: number;
|
|
2693
|
-
academicYear: string;
|
|
2694
2693
|
provisioned: boolean;
|
|
2694
|
+
academicYear: string;
|
|
2695
2695
|
}, {
|
|
2696
2696
|
created: number;
|
|
2697
|
-
academicYear: string;
|
|
2698
2697
|
provisioned: boolean;
|
|
2698
|
+
academicYear: string;
|
|
2699
2699
|
}>;
|
|
2700
2700
|
};
|
|
2701
2701
|
};
|
package/lib/kcsie.d.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import { StudentPlacementData, WorkflowStage } from "./typeDefinitions";
|
|
1
|
+
import { PlacementQuestions, StudentPlacementData, WorkflowStage } from "./typeDefinitions";
|
|
2
2
|
type KcsiePerson = NonNullable<StudentPlacementData["kcsiePeople"]>[string];
|
|
3
3
|
export declare const KCSIE_BARRED_LIST_URL = "https://check-the-childrens-barred-list.education.gov.uk/";
|
|
4
4
|
export declare const KCSIE_STATUS_LABELS: Record<NonNullable<StudentPlacementData["kcsie"]>, string>;
|
|
5
5
|
export declare const KCSIE_NOT_REQUESTED: Pick<StudentPlacementData, "kcsie" | "kcsieStatuses">;
|
|
6
|
+
export declare const KCSIE_PARENT_EXEMPT: Pick<StudentPlacementData, "kcsie" | "kcsieStatuses">;
|
|
6
7
|
export declare const kcsiePersonId: ({ surname, dob }: Pick<KcsiePerson, "surname" | "dob">) => string;
|
|
7
|
-
export declare const
|
|
8
|
+
export declare const kcsieCheckedOverAYearAgo: (person: Pick<KcsiePerson, "status" | "reviewed">, now?: Date) => boolean;
|
|
9
|
+
export declare const kcsiePeopleDetails: (people?: StudentPlacementData["kcsiePeople"]) => NonNullable<PlacementQuestions["kcsie"]>["people"];
|
|
10
|
+
export declare const matchingKcsiePeople: (previous: NonNullable<PlacementQuestions["kcsie"]>["people"], surname: string) => NonNullable<PlacementQuestions["kcsie"]>["people"];
|
|
8
11
|
export declare const kcsieOutstanding: (status?: StudentPlacementData["kcsie"]) => boolean;
|
|
9
|
-
export declare const
|
|
12
|
+
export declare const stageRequiresKcsie: (stage: Pick<WorkflowStage, "kcsie" | "kcsieIncludeParentEmployers">, placement?: Pick<StudentPlacementData, "parentIsEmployer">) => boolean;
|
|
13
|
+
export declare const workflowRequiresKcsie: (workflow?: WorkflowStage[], placement?: Pick<StudentPlacementData, "parentIsEmployer">) => boolean;
|
|
14
|
+
export declare const kcsieExemptionUpdate: (workflow: WorkflowStage[] | undefined, placement: Pick<StudentPlacementData, "kcsie" | "parentIsEmployer">) => Pick<StudentPlacementData, "kcsie" | "kcsieStatuses"> | undefined;
|
|
10
15
|
export declare const placementKcsieFields: (people: NonNullable<StudentPlacementData["kcsiePeople"]>) => Pick<StudentPlacementData, "kcsie" | "kcsieStatuses">;
|
|
11
16
|
export declare const kcsieRowsForPlacement: (placementId: string, placement: StudentPlacementData, status?: StudentPlacementData["kcsie"]) => ({
|
|
12
17
|
placementId: string;
|
|
13
18
|
providerContactId: string | undefined;
|
|
19
|
+
parentIsEmployer: boolean | undefined;
|
|
14
20
|
studentForename: string | undefined;
|
|
15
21
|
studentSurname: string | undefined;
|
|
16
22
|
employerName: string;
|
package/lib/kcsie.js
CHANGED
|
@@ -3,9 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getKcsieCohortCounts = exports.reviewKcsiePerson = exports.kcsieRowsForPlacement = exports.placementKcsieFields = exports.workflowRequiresKcsie = exports.kcsieOutstanding = exports.
|
|
6
|
+
exports.getKcsieCohortCounts = exports.reviewKcsiePerson = exports.kcsieRowsForPlacement = exports.placementKcsieFields = exports.kcsieExemptionUpdate = exports.workflowRequiresKcsie = exports.stageRequiresKcsie = exports.kcsieOutstanding = exports.matchingKcsiePeople = exports.kcsiePeopleDetails = exports.kcsieCheckedOverAYearAgo = exports.kcsiePersonId = exports.KCSIE_PARENT_EXEMPT = exports.KCSIE_NOT_REQUESTED = exports.KCSIE_STATUS_LABELS = exports.KCSIE_BARRED_LIST_URL = void 0;
|
|
7
7
|
const firestore_1 = require("firebase/firestore");
|
|
8
|
-
const benchmarkEvidenceRules_1 = require("./benchmarkEvidenceRules");
|
|
9
8
|
const firebaseQuery_1 = __importDefault(require("./firebase/firebaseQuery"));
|
|
10
9
|
const firebaseQuery = new firebaseQuery_1.default();
|
|
11
10
|
exports.KCSIE_BARRED_LIST_URL = "https://check-the-childrens-barred-list.education.gov.uk/";
|
|
@@ -16,18 +15,46 @@ exports.KCSIE_STATUS_LABELS = {
|
|
|
16
15
|
rejected: "Rejected",
|
|
17
16
|
notRequired: "Not required",
|
|
18
17
|
notRequested: "Not yet requested",
|
|
18
|
+
parentExempt: "Exempt: employer is parent",
|
|
19
19
|
};
|
|
20
20
|
exports.KCSIE_NOT_REQUESTED = { kcsie: "notRequested", kcsieStatuses: ["notRequested"] };
|
|
21
|
+
exports.KCSIE_PARENT_EXEMPT = { kcsie: "parentExempt", kcsieStatuses: ["parentExempt"] };
|
|
21
22
|
// Surname and DOB are what the DfE search uses, so they identify a person across placements.
|
|
22
23
|
const kcsiePersonId = ({ surname, dob }) => `${surname.trim().toLowerCase().replace(/[^a-z]/g, "")}_${dob}`;
|
|
23
24
|
exports.kcsiePersonId = kcsiePersonId;
|
|
24
|
-
// Outcomes
|
|
25
|
-
const
|
|
26
|
-
exports.
|
|
25
|
+
// Outcomes carry over indefinitely; the school is just told when one is over a year old.
|
|
26
|
+
const kcsieCheckedOverAYearAgo = (person, now = new Date()) => person.status !== "pending" && Boolean(person.reviewed) && now.getTime() - new Date(person.reviewed).getTime() > 365 * 24 * 60 * 60 * 1000;
|
|
27
|
+
exports.kcsieCheckedOverAYearAgo = kcsieCheckedOverAYearAgo;
|
|
28
|
+
// Just the details the employer enters, e.g. to import people from another placement.
|
|
29
|
+
const kcsiePeopleDetails = (people) => Object.values(people || {}).map(({ forename, surname, dob, previousSurnames }) => ({ forename, surname, dob, ...(previousSurnames?.length ? { previousSurnames } : {}) }));
|
|
30
|
+
exports.kcsiePeopleDetails = kcsiePeopleDetails;
|
|
31
|
+
// Previous individuals whose surname starts with what's been typed, for the employer to pick from.
|
|
32
|
+
const matchingKcsiePeople = (previous, surname) => {
|
|
33
|
+
const typed = surname.trim().toLowerCase();
|
|
34
|
+
return typed.length < 2 ? [] : previous.filter((p) => p.surname.toLowerCase().startsWith(typed));
|
|
35
|
+
};
|
|
36
|
+
exports.matchingKcsiePeople = matchingKcsiePeople;
|
|
27
37
|
const kcsieOutstanding = (status) => status === "pending" || status === "flagged" || status === "rejected";
|
|
28
38
|
exports.kcsieOutstanding = kcsieOutstanding;
|
|
29
|
-
|
|
39
|
+
// Parent employers are exempt unless the stage opts them in.
|
|
40
|
+
const stageRequiresKcsie = (stage, placement) => Boolean(stage.kcsie) && !(placement?.parentIsEmployer && !stage.kcsieIncludeParentEmployers);
|
|
41
|
+
exports.stageRequiresKcsie = stageRequiresKcsie;
|
|
42
|
+
// Without a placement, whether any placement on the workflow could need a check.
|
|
43
|
+
const workflowRequiresKcsie = (workflow, placement) => Boolean(workflow?.some((s) => s.userType === "Provider" && (0, exports.stageRequiresKcsie)(s, placement)));
|
|
30
44
|
exports.workflowRequiresKcsie = workflowRequiresKcsie;
|
|
45
|
+
// The KCSIE fields to set once it's known whether the employer is an exempt parent, or undefined to leave them.
|
|
46
|
+
// Placements the employer has already answered for keep their answer.
|
|
47
|
+
const kcsieExemptionUpdate = (workflow, placement) => {
|
|
48
|
+
if (!(0, exports.workflowRequiresKcsie)(workflow))
|
|
49
|
+
return undefined;
|
|
50
|
+
const exempt = !(0, exports.workflowRequiresKcsie)(workflow, placement);
|
|
51
|
+
if (exempt && (!placement.kcsie || placement.kcsie === "notRequested"))
|
|
52
|
+
return exports.KCSIE_PARENT_EXEMPT;
|
|
53
|
+
if (!exempt && (!placement.kcsie || placement.kcsie === "parentExempt"))
|
|
54
|
+
return exports.KCSIE_NOT_REQUESTED;
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
57
|
+
exports.kcsieExemptionUpdate = kcsieExemptionUpdate;
|
|
31
58
|
// The worst person's status wins, so one rejection holds the placement.
|
|
32
59
|
const placementKcsieFields = (people) => {
|
|
33
60
|
const statuses = Object.values(people).map((p) => p.status);
|
|
@@ -37,7 +64,7 @@ const placementKcsieFields = (people) => {
|
|
|
37
64
|
exports.placementKcsieFields = placementKcsieFields;
|
|
38
65
|
// One row per person, so a placement naming two people appears twice.
|
|
39
66
|
const kcsieRowsForPlacement = (placementId, placement, status) => {
|
|
40
|
-
const base = { placementId, providerContactId: placement.providerContactId, studentForename: placement.studentForename, studentSurname: placement.studentSurname, employerName: placement.name, startDate: placement.startDate, endDate: placement.endDate };
|
|
67
|
+
const base = { placementId, providerContactId: placement.providerContactId, parentIsEmployer: placement.parentIsEmployer, studentForename: placement.studentForename, studentSurname: placement.studentSurname, employerName: placement.name, startDate: placement.startDate, endDate: placement.endDate };
|
|
41
68
|
const rows = placement.kcsie === "notRequested" ?
|
|
42
69
|
[{ ...base, status: "notRequested" }] :
|
|
43
70
|
Object.entries(placement.kcsiePeople || {}).map(([personId, person]) => ({ ...base, status: person.status, personId, person }));
|
|
@@ -47,7 +74,7 @@ exports.kcsieRowsForPlacement = kcsieRowsForPlacement;
|
|
|
47
74
|
// Applies the outcome to every placement at the school with this employer naming the person, and saves it on the employer for future placements.
|
|
48
75
|
const reviewKcsiePerson = async ({ oId, providerContactId, personId, dob, previousSurnames, status, note, reviewedBy, reviewedByName }) => {
|
|
49
76
|
const now = new Date();
|
|
50
|
-
const outcome = { status,
|
|
77
|
+
const outcome = { status, reviewed: now.toISOString(), reviewedBy, reviewedByName, ...(note?.trim() ? { note: note.trim() } : {}) };
|
|
51
78
|
await firebaseQuery.update(["providerContacts", providerContactId], { [`savedBy.${oId}.kcsie.${personId}`]: { ...outcome, ...(previousSurnames?.length ? { previousSurnames } : {}) } });
|
|
52
79
|
const placements = await firebaseQuery.getDocsWhere(["placements"], [
|
|
53
80
|
(0, firestore_1.where)("oId", "==", oId),
|
package/lib/kcsie.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kcsie.js","sourceRoot":"","sources":["../src/kcsie.ts"],"names":[],"mappings":";;;;;;AAAA,kDAAyC;AACzC,
|
|
1
|
+
{"version":3,"file":"kcsie.js","sourceRoot":"","sources":["../src/kcsie.ts"],"names":[],"mappings":"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"}
|
package/lib/kcsie.test.js
CHANGED
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
jest.mock("firebase/firestore", () => ({ where: jest.fn() }));
|
|
4
4
|
jest.mock("./firebase/firebaseQuery", () => jest.fn());
|
|
5
5
|
const kcsie_1 = require("./kcsie");
|
|
6
|
-
const person = (status, surname = "Smith",
|
|
7
|
-
forename: "Jane", surname, dob: "1980-02-03", status,
|
|
6
|
+
const person = (status, surname = "Smith", extra = {}) => ({
|
|
7
|
+
forename: "Jane", surname, dob: "1980-02-03", status, submitted: "2026-09-01T00:00:00.000Z", ...extra,
|
|
8
8
|
});
|
|
9
9
|
describe("kcsiePersonId", () => {
|
|
10
10
|
it("matches the same surname and DOB however it is typed", () => {
|
|
@@ -12,11 +12,27 @@ describe("kcsiePersonId", () => {
|
|
|
12
12
|
expect((0, kcsie_1.kcsiePersonId)({ surname: "Smith", dob: "1980-02-03" })).not.toBe((0, kcsie_1.kcsiePersonId)({ surname: "Smith", dob: "1981-02-03" }));
|
|
13
13
|
});
|
|
14
14
|
});
|
|
15
|
-
describe("
|
|
16
|
-
const
|
|
17
|
-
it("
|
|
18
|
-
expect((0, kcsie_1.
|
|
19
|
-
expect((0, kcsie_1.
|
|
15
|
+
describe("kcsieCheckedOverAYearAgo", () => {
|
|
16
|
+
const now = new Date("2026-10-08T12:00:00Z");
|
|
17
|
+
it("flags outcomes over a year old without resetting them", () => {
|
|
18
|
+
expect((0, kcsie_1.kcsieCheckedOverAYearAgo)(person("approved", "Smith", { reviewed: "2025-09-01T00:00:00Z" }), now)).toBe(true);
|
|
19
|
+
expect((0, kcsie_1.kcsieCheckedOverAYearAgo)(person("approved", "Smith", { reviewed: "2026-01-01T00:00:00Z" }), now)).toBe(false);
|
|
20
|
+
expect((0, kcsie_1.kcsieCheckedOverAYearAgo)(person("pending", "Smith", { reviewed: "2025-09-01T00:00:00Z" }), now)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("kcsiePeopleDetails", () => {
|
|
24
|
+
it("imports names and DOBs from another placement, not its outcomes", () => {
|
|
25
|
+
expect((0, kcsie_1.kcsiePeopleDetails)({ smith: person("approved", "Smith", { previousSurnames: ["Jones"], reviewedBy: "uid1" }) }))
|
|
26
|
+
.toEqual([{ forename: "Jane", surname: "Smith", dob: "1980-02-03", previousSurnames: ["Jones"] }]);
|
|
27
|
+
expect((0, kcsie_1.kcsiePeopleDetails)(undefined)).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe("matchingKcsiePeople", () => {
|
|
31
|
+
const previous = [{ forename: "Jane", surname: "Smith", dob: "1980-02-03" }, { forename: "Bob", surname: "Smythe", dob: "1975-05-05" }, { forename: "Al", surname: "Lee", dob: "1990-01-01" }];
|
|
32
|
+
it("suggests previous individuals by surname once two letters are typed", () => {
|
|
33
|
+
expect((0, kcsie_1.matchingKcsiePeople)(previous, "S")).toEqual([]);
|
|
34
|
+
expect((0, kcsie_1.matchingKcsiePeople)(previous, "sm").map((p) => p.forename)).toEqual(["Jane", "Bob"]);
|
|
35
|
+
expect((0, kcsie_1.matchingKcsiePeople)(previous, "Smi").map((p) => p.forename)).toEqual(["Jane"]);
|
|
20
36
|
});
|
|
21
37
|
});
|
|
22
38
|
describe("kcsieOutstanding", () => {
|
|
@@ -30,6 +46,42 @@ describe("workflowRequiresKcsie", () => {
|
|
|
30
46
|
expect((0, kcsie_1.workflowRequiresKcsie)([{ id: 3, name: "Employer", userType: "Provider", kcsie: true }])).toBe(true);
|
|
31
47
|
expect((0, kcsie_1.workflowRequiresKcsie)([{ id: 2, name: "Staff", userType: "Staff", kcsie: true }])).toBe(false);
|
|
32
48
|
});
|
|
49
|
+
it("exempts parent employers unless the stage includes them", () => {
|
|
50
|
+
expect((0, kcsie_1.workflowRequiresKcsie)([{ id: 3, name: "Employer", userType: "Provider", kcsie: true }], { parentIsEmployer: true })).toBe(false);
|
|
51
|
+
expect((0, kcsie_1.workflowRequiresKcsie)([{ id: 3, name: "Employer", userType: "Provider", kcsie: true, kcsieIncludeParentEmployers: true }], { parentIsEmployer: true })).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe("stageRequiresKcsie", () => {
|
|
55
|
+
it("needs the stage to ask about KCSIE", () => {
|
|
56
|
+
expect((0, kcsie_1.stageRequiresKcsie)({ kcsie: false }, { parentIsEmployer: false })).toBe(false);
|
|
57
|
+
expect((0, kcsie_1.stageRequiresKcsie)({ kcsie: true }, { parentIsEmployer: false })).toBe(true);
|
|
58
|
+
expect((0, kcsie_1.stageRequiresKcsie)({ kcsie: true })).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it("exempts parent employers by default", () => {
|
|
61
|
+
expect((0, kcsie_1.stageRequiresKcsie)({ kcsie: true }, { parentIsEmployer: true })).toBe(false);
|
|
62
|
+
expect((0, kcsie_1.stageRequiresKcsie)({ kcsie: true, kcsieIncludeParentEmployers: true }, { parentIsEmployer: true })).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("kcsieExemptionUpdate", () => {
|
|
66
|
+
const workflow = [{ id: 3, name: "Employer", userType: "Provider", kcsie: true }];
|
|
67
|
+
it("leaves placements alone when the workflow doesn't ask about KCSIE", () => {
|
|
68
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)([{ id: 3, name: "Employer", userType: "Provider" }], { parentIsEmployer: true })).toBeUndefined();
|
|
69
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)(undefined, {})).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
it("marks exempt parent employers so they aren't counted as not yet requested", () => {
|
|
72
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)(workflow, { parentIsEmployer: true })).toEqual(kcsie_1.KCSIE_PARENT_EXEMPT);
|
|
73
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)(workflow, { parentIsEmployer: true, kcsie: "notRequested" })).toEqual(kcsie_1.KCSIE_PARENT_EXEMPT);
|
|
74
|
+
});
|
|
75
|
+
it("asks once the employer is no longer an exempt parent", () => {
|
|
76
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)(workflow, {})).toEqual(kcsie_1.KCSIE_NOT_REQUESTED);
|
|
77
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)(workflow, { parentIsEmployer: false, kcsie: "parentExempt" })).toEqual(kcsie_1.KCSIE_NOT_REQUESTED);
|
|
78
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)([{ ...workflow[0], kcsieIncludeParentEmployers: true }], { parentIsEmployer: true, kcsie: "parentExempt" })).toEqual(kcsie_1.KCSIE_NOT_REQUESTED);
|
|
79
|
+
});
|
|
80
|
+
it("keeps an answer the employer has already given", () => {
|
|
81
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)(workflow, { parentIsEmployer: true, kcsie: "pending" })).toBeUndefined();
|
|
82
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)(workflow, { parentIsEmployer: false, kcsie: "approved" })).toBeUndefined();
|
|
83
|
+
expect((0, kcsie_1.kcsieExemptionUpdate)(workflow, { parentIsEmployer: false, kcsie: "notRequested" })).toBeUndefined();
|
|
84
|
+
});
|
|
33
85
|
});
|
|
34
86
|
describe("placementKcsieFields", () => {
|
|
35
87
|
it("is not required when nobody was named", () => {
|
package/lib/kcsie.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kcsie.test.js","sourceRoot":"","sources":["../src/kcsie.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,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;AAGvD,
|
|
1
|
+
{"version":3,"file":"kcsie.test.js","sourceRoot":"","sources":["../src/kcsie.test.ts"],"names":[],"mappings":"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"}
|
package/lib/typeDefinitions.d.ts
CHANGED
|
@@ -135,11 +135,10 @@ export type StudentPlacementData = {
|
|
|
135
135
|
dbsCheckData?: {
|
|
136
136
|
[key: string]: unknown;
|
|
137
137
|
}[] | string;
|
|
138
|
-
kcsie?: "pending" | "approved" | "flagged" | "rejected" | "notRequired" | "notRequested";
|
|
138
|
+
kcsie?: "pending" | "approved" | "flagged" | "rejected" | "notRequired" | "notRequested" | "parentExempt";
|
|
139
139
|
kcsiePeople?: {
|
|
140
140
|
[personId: string]: NonNullable<PlacementQuestions["kcsie"]>["people"][number] & {
|
|
141
141
|
status: "pending" | "approved" | "flagged" | "rejected";
|
|
142
|
-
academicYear: string;
|
|
143
142
|
submitted: string;
|
|
144
143
|
reviewed?: string;
|
|
145
144
|
reviewedBy?: string;
|
|
@@ -938,6 +937,7 @@ export type WorkflowStage = {
|
|
|
938
937
|
riskAssessment?: boolean;
|
|
939
938
|
dbsCheck?: boolean;
|
|
940
939
|
kcsie?: boolean;
|
|
940
|
+
kcsieIncludeParentEmployers?: boolean;
|
|
941
941
|
defaultForms?: {
|
|
942
942
|
name: string;
|
|
943
943
|
description: string;
|
|
@@ -1087,7 +1087,7 @@ export type ProviderContactData = {
|
|
|
1087
1087
|
[key: string]: unknown;
|
|
1088
1088
|
};
|
|
1089
1089
|
kcsie?: {
|
|
1090
|
-
[personId: string]: Pick<NonNullable<StudentPlacementData["kcsiePeople"]>[string], "status" | "
|
|
1090
|
+
[personId: string]: Pick<NonNullable<StudentPlacementData["kcsiePeople"]>[string], "status" | "reviewed" | "reviewedBy" | "reviewedByName" | "note" | "previousSurnames">;
|
|
1091
1091
|
};
|
|
1092
1092
|
status: "uploaded" | "approved" | "rejected" | "providerReviewed" | "blocked";
|
|
1093
1093
|
};
|
|
@@ -1678,6 +1678,7 @@ export type PlacementReviewDetails = {
|
|
|
1678
1678
|
dbsCheckData?: {
|
|
1679
1679
|
[key: string]: unknown;
|
|
1680
1680
|
} | string;
|
|
1681
|
+
kcsiePeople?: NonNullable<PlacementQuestions["kcsie"]>["people"];
|
|
1681
1682
|
};
|
|
1682
1683
|
};
|
|
1683
1684
|
requireELI?: boolean;
|
|
@@ -1685,6 +1686,7 @@ export type PlacementReviewDetails = {
|
|
|
1685
1686
|
requireDBS?: boolean;
|
|
1686
1687
|
requireKCSIE?: boolean;
|
|
1687
1688
|
kcsiePeople?: NonNullable<PlacementQuestions["kcsie"]>["people"];
|
|
1689
|
+
kcsiePrevious?: NonNullable<PlacementQuestions["kcsie"]>["people"];
|
|
1688
1690
|
kcsie?: StudentPlacementData["kcsie"];
|
|
1689
1691
|
importedProviderForms: boolean;
|
|
1690
1692
|
currentStage: WorkflowStage & {
|
|
@@ -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":";;;AA4sIA,sDASC;AAeD,oDAKC;AAaD,0CAEC;AA9pED;;;;;kFAKkF;AACrE,QAAA,qBAAqB,GAAG,SAAS,CAAC;AAClC,QAAA,iBAAiB,GAAG,OAAO,CAAC;AA0kEzC;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/kcsie.test.ts
CHANGED
|
@@ -2,12 +2,12 @@ jest.mock("firebase/firestore", () => ({where: jest.fn()}));
|
|
|
2
2
|
jest.mock("./firebase/firebaseQuery", () => jest.fn());
|
|
3
3
|
|
|
4
4
|
import {StudentPlacementData} from "./typeDefinitions";
|
|
5
|
-
import {
|
|
5
|
+
import {KCSIE_NOT_REQUESTED, KCSIE_PARENT_EXEMPT, kcsieExemptionUpdate, stageRequiresKcsie, kcsieCheckedOverAYearAgo, kcsieOutstanding, kcsiePeopleDetails, matchingKcsiePeople, kcsiePersonId, kcsieRowsForPlacement, placementKcsieFields, workflowRequiresKcsie} from "./kcsie";
|
|
6
6
|
|
|
7
7
|
type KcsiePerson = NonNullable<StudentPlacementData["kcsiePeople"]>[string];
|
|
8
8
|
|
|
9
|
-
const person = (status: KcsiePerson["status"], surname = "Smith",
|
|
10
|
-
forename: "Jane", surname, dob: "1980-02-03", status,
|
|
9
|
+
const person = (status: KcsiePerson["status"], surname = "Smith", extra: Partial<KcsiePerson> = {}): KcsiePerson => ({
|
|
10
|
+
forename: "Jane", surname, dob: "1980-02-03", status, submitted: "2026-09-01T00:00:00.000Z", ...extra,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
describe("kcsiePersonId", () => {
|
|
@@ -17,11 +17,29 @@ describe("kcsiePersonId", () => {
|
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
describe("
|
|
21
|
-
const
|
|
22
|
-
it("
|
|
23
|
-
expect(
|
|
24
|
-
expect(
|
|
20
|
+
describe("kcsieCheckedOverAYearAgo", () => {
|
|
21
|
+
const now = new Date("2026-10-08T12:00:00Z");
|
|
22
|
+
it("flags outcomes over a year old without resetting them", () => {
|
|
23
|
+
expect(kcsieCheckedOverAYearAgo(person("approved", "Smith", {reviewed: "2025-09-01T00:00:00Z"}), now)).toBe(true);
|
|
24
|
+
expect(kcsieCheckedOverAYearAgo(person("approved", "Smith", {reviewed: "2026-01-01T00:00:00Z"}), now)).toBe(false);
|
|
25
|
+
expect(kcsieCheckedOverAYearAgo(person("pending", "Smith", {reviewed: "2025-09-01T00:00:00Z"}), now)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("kcsiePeopleDetails", () => {
|
|
30
|
+
it("imports names and DOBs from another placement, not its outcomes", () => {
|
|
31
|
+
expect(kcsiePeopleDetails({smith: person("approved", "Smith", {previousSurnames: ["Jones"], reviewedBy: "uid1"})}))
|
|
32
|
+
.toEqual([{forename: "Jane", surname: "Smith", dob: "1980-02-03", previousSurnames: ["Jones"]}]);
|
|
33
|
+
expect(kcsiePeopleDetails(undefined)).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("matchingKcsiePeople", () => {
|
|
38
|
+
const previous = [{forename: "Jane", surname: "Smith", dob: "1980-02-03"}, {forename: "Bob", surname: "Smythe", dob: "1975-05-05"}, {forename: "Al", surname: "Lee", dob: "1990-01-01"}];
|
|
39
|
+
it("suggests previous individuals by surname once two letters are typed", () => {
|
|
40
|
+
expect(matchingKcsiePeople(previous, "S")).toEqual([]);
|
|
41
|
+
expect(matchingKcsiePeople(previous, "sm").map((p) => p.forename)).toEqual(["Jane", "Bob"]);
|
|
42
|
+
expect(matchingKcsiePeople(previous, "Smi").map((p) => p.forename)).toEqual(["Jane"]);
|
|
25
43
|
});
|
|
26
44
|
});
|
|
27
45
|
|
|
@@ -37,6 +55,50 @@ describe("workflowRequiresKcsie", () => {
|
|
|
37
55
|
expect(workflowRequiresKcsie([{id: 3, name: "Employer", userType: "Provider", kcsie: true}])).toBe(true);
|
|
38
56
|
expect(workflowRequiresKcsie([{id: 2, name: "Staff", userType: "Staff", kcsie: true}])).toBe(false);
|
|
39
57
|
});
|
|
58
|
+
|
|
59
|
+
it("exempts parent employers unless the stage includes them", () => {
|
|
60
|
+
expect(workflowRequiresKcsie([{id: 3, name: "Employer", userType: "Provider", kcsie: true}], {parentIsEmployer: true})).toBe(false);
|
|
61
|
+
expect(workflowRequiresKcsie([{id: 3, name: "Employer", userType: "Provider", kcsie: true, kcsieIncludeParentEmployers: true}], {parentIsEmployer: true})).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("stageRequiresKcsie", () => {
|
|
66
|
+
it("needs the stage to ask about KCSIE", () => {
|
|
67
|
+
expect(stageRequiresKcsie({kcsie: false}, {parentIsEmployer: false})).toBe(false);
|
|
68
|
+
expect(stageRequiresKcsie({kcsie: true}, {parentIsEmployer: false})).toBe(true);
|
|
69
|
+
expect(stageRequiresKcsie({kcsie: true})).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("exempts parent employers by default", () => {
|
|
73
|
+
expect(stageRequiresKcsie({kcsie: true}, {parentIsEmployer: true})).toBe(false);
|
|
74
|
+
expect(stageRequiresKcsie({kcsie: true, kcsieIncludeParentEmployers: true}, {parentIsEmployer: true})).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("kcsieExemptionUpdate", () => {
|
|
79
|
+
const workflow = [{id: 3, name: "Employer", userType: "Provider" as const, kcsie: true}];
|
|
80
|
+
|
|
81
|
+
it("leaves placements alone when the workflow doesn't ask about KCSIE", () => {
|
|
82
|
+
expect(kcsieExemptionUpdate([{id: 3, name: "Employer", userType: "Provider"}], {parentIsEmployer: true})).toBeUndefined();
|
|
83
|
+
expect(kcsieExemptionUpdate(undefined, {})).toBeUndefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("marks exempt parent employers so they aren't counted as not yet requested", () => {
|
|
87
|
+
expect(kcsieExemptionUpdate(workflow, {parentIsEmployer: true})).toEqual(KCSIE_PARENT_EXEMPT);
|
|
88
|
+
expect(kcsieExemptionUpdate(workflow, {parentIsEmployer: true, kcsie: "notRequested"})).toEqual(KCSIE_PARENT_EXEMPT);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("asks once the employer is no longer an exempt parent", () => {
|
|
92
|
+
expect(kcsieExemptionUpdate(workflow, {})).toEqual(KCSIE_NOT_REQUESTED);
|
|
93
|
+
expect(kcsieExemptionUpdate(workflow, {parentIsEmployer: false, kcsie: "parentExempt"})).toEqual(KCSIE_NOT_REQUESTED);
|
|
94
|
+
expect(kcsieExemptionUpdate([{...workflow[0], kcsieIncludeParentEmployers: true}], {parentIsEmployer: true, kcsie: "parentExempt"})).toEqual(KCSIE_NOT_REQUESTED);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("keeps an answer the employer has already given", () => {
|
|
98
|
+
expect(kcsieExemptionUpdate(workflow, {parentIsEmployer: true, kcsie: "pending"})).toBeUndefined();
|
|
99
|
+
expect(kcsieExemptionUpdate(workflow, {parentIsEmployer: false, kcsie: "approved"})).toBeUndefined();
|
|
100
|
+
expect(kcsieExemptionUpdate(workflow, {parentIsEmployer: false, kcsie: "notRequested"})).toBeUndefined();
|
|
101
|
+
});
|
|
40
102
|
});
|
|
41
103
|
|
|
42
104
|
describe("placementKcsieFields", () => {
|
package/src/kcsie.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {where} from "firebase/firestore";
|
|
2
|
-
import {academicYearForDate} from "./benchmarkEvidenceRules";
|
|
3
2
|
import FirebaseQuery from "./firebase/firebaseQuery";
|
|
4
|
-
import {StudentPlacementData, WorkflowStage} from "./typeDefinitions";
|
|
3
|
+
import {PlacementQuestions, StudentPlacementData, WorkflowStage} from "./typeDefinitions";
|
|
5
4
|
|
|
6
5
|
const firebaseQuery = new FirebaseQuery();
|
|
7
6
|
|
|
@@ -16,23 +15,51 @@ export const KCSIE_STATUS_LABELS: Record<NonNullable<StudentPlacementData["kcsie
|
|
|
16
15
|
rejected: "Rejected",
|
|
17
16
|
notRequired: "Not required",
|
|
18
17
|
notRequested: "Not yet requested",
|
|
18
|
+
parentExempt: "Exempt: employer is parent",
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const KCSIE_NOT_REQUESTED: Pick<StudentPlacementData, "kcsie"|"kcsieStatuses"> = {kcsie: "notRequested", kcsieStatuses: ["notRequested"]};
|
|
22
22
|
|
|
23
|
+
export const KCSIE_PARENT_EXEMPT: Pick<StudentPlacementData, "kcsie"|"kcsieStatuses"> = {kcsie: "parentExempt", kcsieStatuses: ["parentExempt"]};
|
|
24
|
+
|
|
23
25
|
// Surname and DOB are what the DfE search uses, so they identify a person across placements.
|
|
24
26
|
export const kcsiePersonId = ({surname, dob}: Pick<KcsiePerson, "surname"|"dob">): string =>
|
|
25
27
|
`${surname.trim().toLowerCase().replace(/[^a-z]/g, "")}_${dob}`;
|
|
26
28
|
|
|
27
|
-
// Outcomes
|
|
28
|
-
export const
|
|
29
|
-
person.
|
|
29
|
+
// Outcomes carry over indefinitely; the school is just told when one is over a year old.
|
|
30
|
+
export const kcsieCheckedOverAYearAgo = (person: Pick<KcsiePerson, "status"|"reviewed">, now = new Date()): boolean =>
|
|
31
|
+
person.status !== "pending" && Boolean(person.reviewed) && now.getTime() - new Date(person.reviewed as string).getTime() > 365 * 24 * 60 * 60 * 1000;
|
|
32
|
+
|
|
33
|
+
// Just the details the employer enters, e.g. to import people from another placement.
|
|
34
|
+
export const kcsiePeopleDetails = (people?: StudentPlacementData["kcsiePeople"]): NonNullable<PlacementQuestions["kcsie"]>["people"] =>
|
|
35
|
+
Object.values(people || {}).map(({forename, surname, dob, previousSurnames}) => ({forename, surname, dob, ...(previousSurnames?.length ? {previousSurnames} : {})}));
|
|
36
|
+
|
|
37
|
+
// Previous individuals whose surname starts with what's been typed, for the employer to pick from.
|
|
38
|
+
export const matchingKcsiePeople = (previous: NonNullable<PlacementQuestions["kcsie"]>["people"], surname: string): NonNullable<PlacementQuestions["kcsie"]>["people"] => {
|
|
39
|
+
const typed = surname.trim().toLowerCase();
|
|
40
|
+
return typed.length < 2 ? [] : previous.filter((p) => p.surname.toLowerCase().startsWith(typed));
|
|
41
|
+
};
|
|
30
42
|
|
|
31
43
|
export const kcsieOutstanding = (status?: StudentPlacementData["kcsie"]): boolean =>
|
|
32
44
|
status === "pending" || status === "flagged" || status === "rejected";
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
// Parent employers are exempt unless the stage opts them in.
|
|
47
|
+
export const stageRequiresKcsie = (stage: Pick<WorkflowStage, "kcsie"|"kcsieIncludeParentEmployers">, placement?: Pick<StudentPlacementData, "parentIsEmployer">): boolean =>
|
|
48
|
+
Boolean(stage.kcsie) && !(placement?.parentIsEmployer && !stage.kcsieIncludeParentEmployers);
|
|
49
|
+
|
|
50
|
+
// Without a placement, whether any placement on the workflow could need a check.
|
|
51
|
+
export const workflowRequiresKcsie = (workflow?: WorkflowStage[], placement?: Pick<StudentPlacementData, "parentIsEmployer">): boolean =>
|
|
52
|
+
Boolean(workflow?.some((s) => s.userType === "Provider" && stageRequiresKcsie(s, placement)));
|
|
53
|
+
|
|
54
|
+
// The KCSIE fields to set once it's known whether the employer is an exempt parent, or undefined to leave them.
|
|
55
|
+
// Placements the employer has already answered for keep their answer.
|
|
56
|
+
export const kcsieExemptionUpdate = (workflow: WorkflowStage[]|undefined, placement: Pick<StudentPlacementData, "kcsie"|"parentIsEmployer">): Pick<StudentPlacementData, "kcsie"|"kcsieStatuses">|undefined => {
|
|
57
|
+
if (!workflowRequiresKcsie(workflow)) return undefined;
|
|
58
|
+
const exempt = !workflowRequiresKcsie(workflow, placement);
|
|
59
|
+
if (exempt && (!placement.kcsie || placement.kcsie === "notRequested")) return KCSIE_PARENT_EXEMPT;
|
|
60
|
+
if (!exempt && (!placement.kcsie || placement.kcsie === "parentExempt")) return KCSIE_NOT_REQUESTED;
|
|
61
|
+
return undefined;
|
|
62
|
+
};
|
|
36
63
|
|
|
37
64
|
// The worst person's status wins, so one rejection holds the placement.
|
|
38
65
|
export const placementKcsieFields = (people: NonNullable<StudentPlacementData["kcsiePeople"]>): Pick<StudentPlacementData, "kcsie"|"kcsieStatuses"> => {
|
|
@@ -43,7 +70,7 @@ export const placementKcsieFields = (people: NonNullable<StudentPlacementData["k
|
|
|
43
70
|
|
|
44
71
|
// One row per person, so a placement naming two people appears twice.
|
|
45
72
|
export const kcsieRowsForPlacement = (placementId: string, placement: StudentPlacementData, status?: StudentPlacementData["kcsie"]) => {
|
|
46
|
-
const base = {placementId, providerContactId: placement.providerContactId, studentForename: placement.studentForename, studentSurname: placement.studentSurname, employerName: placement.name, startDate: placement.startDate, endDate: placement.endDate};
|
|
73
|
+
const base = {placementId, providerContactId: placement.providerContactId, parentIsEmployer: placement.parentIsEmployer, studentForename: placement.studentForename, studentSurname: placement.studentSurname, employerName: placement.name, startDate: placement.startDate, endDate: placement.endDate};
|
|
47
74
|
const rows: (typeof base & {status: StudentPlacementData["kcsie"], personId?: string, person?: KcsiePerson})[] = placement.kcsie === "notRequested" ?
|
|
48
75
|
[{...base, status: "notRequested"}] :
|
|
49
76
|
Object.entries(placement.kcsiePeople || {}).map(([personId, person]) => ({...base, status: person.status, personId, person}));
|
|
@@ -53,7 +80,7 @@ export const kcsieRowsForPlacement = (placementId: string, placement: StudentPla
|
|
|
53
80
|
// Applies the outcome to every placement at the school with this employer naming the person, and saves it on the employer for future placements.
|
|
54
81
|
export const reviewKcsiePerson = async ({oId, providerContactId, personId, dob, previousSurnames, status, note, reviewedBy, reviewedByName}: {oId: string, providerContactId: string, personId: string, dob: string, previousSurnames?: string[], status: KcsiePerson["status"], note?: string, reviewedBy: string, reviewedByName: string}): Promise<void> => {
|
|
55
82
|
const now = new Date();
|
|
56
|
-
const outcome = {status,
|
|
83
|
+
const outcome = {status, reviewed: now.toISOString(), reviewedBy, reviewedByName, ...(note?.trim() ? {note: note.trim()} : {})};
|
|
57
84
|
|
|
58
85
|
await firebaseQuery.update(["providerContacts", providerContactId], {[`savedBy.${oId}.kcsie.${personId}`]: {...outcome, ...(previousSurnames?.length ? {previousSurnames} : {})}});
|
|
59
86
|
|
package/src/typeDefinitions.ts
CHANGED
|
@@ -123,11 +123,11 @@ export type StudentPlacementData = {
|
|
|
123
123
|
dbsCheck?: boolean|"awaitingReview",
|
|
124
124
|
dbsCheckType?: "file"|"form"|"skipped",
|
|
125
125
|
dbsCheckData?: {[key:string]: unknown}[]|string,
|
|
126
|
-
|
|
126
|
+
// parentExempt: the employer is the student's parent and the workflow exempts them.
|
|
127
|
+
kcsie?: "pending"|"approved"|"flagged"|"rejected"|"notRequired"|"notRequested"|"parentExempt",
|
|
127
128
|
// Keyed by kcsiePersonId (surname and DOB).
|
|
128
129
|
kcsiePeople?: {[personId: string]: NonNullable<PlacementQuestions["kcsie"]>["people"][number] & {
|
|
129
130
|
status: "pending"|"approved"|"flagged"|"rejected",
|
|
130
|
-
academicYear: string,
|
|
131
131
|
submitted: string,
|
|
132
132
|
reviewed?: string,
|
|
133
133
|
reviewedBy?: string, // uid
|
|
@@ -949,6 +949,8 @@ export type WorkflowStage = {
|
|
|
949
949
|
riskAssessment?: boolean,
|
|
950
950
|
dbsCheck?: boolean,
|
|
951
951
|
kcsie?: boolean,
|
|
952
|
+
// Placements with the student's parent as employer are exempt from KCSIE unless this is set.
|
|
953
|
+
kcsieIncludeParentEmployers?: boolean,
|
|
952
954
|
defaultForms?: {
|
|
953
955
|
name: string,
|
|
954
956
|
description: string,
|
|
@@ -1130,7 +1132,7 @@ export type ProviderContactData = {
|
|
|
1130
1132
|
sharing?: EmployerSharing,
|
|
1131
1133
|
additionalData?: {[key: string]: unknown},
|
|
1132
1134
|
// This school's KCSIE outcomes, so new placements pick them up.
|
|
1133
|
-
kcsie?: {[personId: string]: Pick<NonNullable<StudentPlacementData["kcsiePeople"]>[string], "status"|"
|
|
1135
|
+
kcsie?: {[personId: string]: Pick<NonNullable<StudentPlacementData["kcsiePeople"]>[string], "status"|"reviewed"|"reviewedBy"|"reviewedByName"|"note"|"previousSurnames">},
|
|
1134
1136
|
status: "uploaded"|"approved"|"rejected"|"providerReviewed"|"blocked"}},
|
|
1135
1137
|
}&Address
|
|
1136
1138
|
|
|
@@ -1706,6 +1708,7 @@ export type PlacementReviewDetails = {
|
|
|
1706
1708
|
dbsCheck: boolean|"awaitingReview",
|
|
1707
1709
|
dbsCheckType: "form"|"file",
|
|
1708
1710
|
dbsCheckData?: {[key: string]: unknown}|string,
|
|
1711
|
+
kcsiePeople?: NonNullable<PlacementQuestions["kcsie"]>["people"],
|
|
1709
1712
|
}
|
|
1710
1713
|
},
|
|
1711
1714
|
requireELI?: boolean,
|
|
@@ -1713,6 +1716,8 @@ export type PlacementReviewDetails = {
|
|
|
1713
1716
|
requireDBS?: boolean,
|
|
1714
1717
|
requireKCSIE?: boolean,
|
|
1715
1718
|
kcsiePeople?: NonNullable<PlacementQuestions["kcsie"]>["people"],
|
|
1719
|
+
// Everyone the employer has named to this school before, offered as suggestions.
|
|
1720
|
+
kcsiePrevious?: NonNullable<PlacementQuestions["kcsie"]>["people"],
|
|
1716
1721
|
kcsie?: StudentPlacementData["kcsie"],
|
|
1717
1722
|
importedProviderForms: boolean,
|
|
1718
1723
|
currentStage: WorkflowStage&{files: {[fileId: string]: {name: string, url: string}}},
|