placementt-core 1.400.1143 → 1.400.1144

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.
@@ -31,12 +31,12 @@ export declare const checksCallables: {
31
31
  }, "strip", z.ZodTypeAny, {
32
32
  label: string;
33
33
  done?: boolean | undefined;
34
- owner?: "staff" | "student" | undefined;
34
+ owner?: "student" | "staff" | undefined;
35
35
  dueDate?: string | undefined;
36
36
  }, {
37
37
  label: string;
38
38
  done?: boolean | undefined;
39
- owner?: "staff" | "student" | undefined;
39
+ owner?: "student" | "staff" | undefined;
40
40
  dueDate?: string | undefined;
41
41
  }>, "many">>;
42
42
  concern: z.ZodOptional<z.ZodBoolean>;
@@ -48,7 +48,7 @@ export declare const checksCallables: {
48
48
  actions?: {
49
49
  label: string;
50
50
  done?: boolean | undefined;
51
- owner?: "staff" | "student" | undefined;
51
+ owner?: "student" | "staff" | undefined;
52
52
  dueDate?: string | undefined;
53
53
  }[] | undefined;
54
54
  staffNotes?: string | undefined;
@@ -63,7 +63,7 @@ export declare const checksCallables: {
63
63
  actions?: {
64
64
  label: string;
65
65
  done?: boolean | undefined;
66
- owner?: "staff" | "student" | undefined;
66
+ owner?: "student" | "staff" | undefined;
67
67
  dueDate?: string | undefined;
68
68
  }[] | undefined;
69
69
  staffNotes?: string | undefined;
@@ -804,6 +804,179 @@ export declare const allCallables: {
804
804
  taskId: string;
805
805
  }>;
806
806
  };
807
+ "wonde-findDualRegistrations": {
808
+ request: z.ZodObject<{
809
+ oId: z.ZodString;
810
+ }, "strip", z.ZodTypeAny, {
811
+ oId: string;
812
+ }, {
813
+ oId: string;
814
+ }>;
815
+ response: z.ZodObject<{
816
+ trustId: z.ZodString;
817
+ schools: z.ZodArray<z.ZodObject<{
818
+ oId: z.ZodString;
819
+ name: z.ZodString;
820
+ rosterSize: z.ZodNumber;
821
+ missingUpn: z.ZodNumber;
822
+ enrolmentStatuses: z.ZodRecord<z.ZodString, z.ZodNumber>;
823
+ }, "strip", z.ZodTypeAny, {
824
+ name: string;
825
+ oId: string;
826
+ rosterSize: number;
827
+ missingUpn: number;
828
+ enrolmentStatuses: Record<string, number>;
829
+ }, {
830
+ name: string;
831
+ oId: string;
832
+ rosterSize: number;
833
+ missingUpn: number;
834
+ enrolmentStatuses: Record<string, number>;
835
+ }>, "many">;
836
+ skipped: z.ZodArray<z.ZodObject<{
837
+ oId: z.ZodString;
838
+ name: z.ZodString;
839
+ reason: z.ZodString;
840
+ }, "strip", z.ZodTypeAny, {
841
+ name: string;
842
+ reason: string;
843
+ oId: string;
844
+ }, {
845
+ name: string;
846
+ reason: string;
847
+ oId: string;
848
+ }>, "many">;
849
+ matches: z.ZodArray<z.ZodObject<{
850
+ matchedOn: z.ZodEnum<["upn", "nameDob"]>;
851
+ upn: z.ZodNullable<z.ZodString>;
852
+ forename: z.ZodString;
853
+ surname: z.ZodString;
854
+ dateOfBirth: z.ZodNullable<z.ZodString>;
855
+ enrolments: z.ZodArray<z.ZodObject<{
856
+ oId: z.ZodString;
857
+ schoolName: z.ZodString;
858
+ wondeId: z.ZodString;
859
+ email: z.ZodNullable<z.ZodString>;
860
+ year: z.ZodNullable<z.ZodString>;
861
+ formGroup: z.ZodNullable<z.ZodString>;
862
+ enrolmentStatus: z.ZodNullable<z.ZodString>;
863
+ userId: z.ZodNullable<z.ZodString>;
864
+ }, "strip", z.ZodTypeAny, {
865
+ email: string | null;
866
+ year: string | null;
867
+ oId: string;
868
+ userId: string | null;
869
+ schoolName: string;
870
+ wondeId: string;
871
+ formGroup: string | null;
872
+ enrolmentStatus: string | null;
873
+ }, {
874
+ email: string | null;
875
+ year: string | null;
876
+ oId: string;
877
+ userId: string | null;
878
+ schoolName: string;
879
+ wondeId: string;
880
+ formGroup: string | null;
881
+ enrolmentStatus: string | null;
882
+ }>, "many">;
883
+ }, "strip", z.ZodTypeAny, {
884
+ upn: string | null;
885
+ matchedOn: "upn" | "nameDob";
886
+ forename: string;
887
+ surname: string;
888
+ dateOfBirth: string | null;
889
+ enrolments: {
890
+ email: string | null;
891
+ year: string | null;
892
+ oId: string;
893
+ userId: string | null;
894
+ schoolName: string;
895
+ wondeId: string;
896
+ formGroup: string | null;
897
+ enrolmentStatus: string | null;
898
+ }[];
899
+ }, {
900
+ upn: string | null;
901
+ matchedOn: "upn" | "nameDob";
902
+ forename: string;
903
+ surname: string;
904
+ dateOfBirth: string | null;
905
+ enrolments: {
906
+ email: string | null;
907
+ year: string | null;
908
+ oId: string;
909
+ userId: string | null;
910
+ schoolName: string;
911
+ wondeId: string;
912
+ formGroup: string | null;
913
+ enrolmentStatus: string | null;
914
+ }[];
915
+ }>, "many">;
916
+ }, "strip", z.ZodTypeAny, {
917
+ skipped: {
918
+ name: string;
919
+ reason: string;
920
+ oId: string;
921
+ }[];
922
+ matches: {
923
+ upn: string | null;
924
+ matchedOn: "upn" | "nameDob";
925
+ forename: string;
926
+ surname: string;
927
+ dateOfBirth: string | null;
928
+ enrolments: {
929
+ email: string | null;
930
+ year: string | null;
931
+ oId: string;
932
+ userId: string | null;
933
+ schoolName: string;
934
+ wondeId: string;
935
+ formGroup: string | null;
936
+ enrolmentStatus: string | null;
937
+ }[];
938
+ }[];
939
+ schools: {
940
+ name: string;
941
+ oId: string;
942
+ rosterSize: number;
943
+ missingUpn: number;
944
+ enrolmentStatuses: Record<string, number>;
945
+ }[];
946
+ trustId: string;
947
+ }, {
948
+ skipped: {
949
+ name: string;
950
+ reason: string;
951
+ oId: string;
952
+ }[];
953
+ matches: {
954
+ upn: string | null;
955
+ matchedOn: "upn" | "nameDob";
956
+ forename: string;
957
+ surname: string;
958
+ dateOfBirth: string | null;
959
+ enrolments: {
960
+ email: string | null;
961
+ year: string | null;
962
+ oId: string;
963
+ userId: string | null;
964
+ schoolName: string;
965
+ wondeId: string;
966
+ formGroup: string | null;
967
+ enrolmentStatus: string | null;
968
+ }[];
969
+ }[];
970
+ schools: {
971
+ name: string;
972
+ oId: string;
973
+ rosterSize: number;
974
+ missingUpn: number;
975
+ enrolmentStatuses: Record<string, number>;
976
+ }[];
977
+ trustId: string;
978
+ }>;
979
+ };
807
980
  "e2e-redeemSignInLink": {
808
981
  request: z.ZodObject<{
809
982
  token: z.ZodString;
@@ -1499,12 +1672,12 @@ export declare const allCallables: {
1499
1672
  }, "strip", z.ZodTypeAny, {
1500
1673
  label: string;
1501
1674
  done?: boolean | undefined;
1502
- owner?: "staff" | "student" | undefined;
1675
+ owner?: "student" | "staff" | undefined;
1503
1676
  dueDate?: string | undefined;
1504
1677
  }, {
1505
1678
  label: string;
1506
1679
  done?: boolean | undefined;
1507
- owner?: "staff" | "student" | undefined;
1680
+ owner?: "student" | "staff" | undefined;
1508
1681
  dueDate?: string | undefined;
1509
1682
  }>, "many">>;
1510
1683
  concern: z.ZodOptional<z.ZodBoolean>;
@@ -1516,7 +1689,7 @@ export declare const allCallables: {
1516
1689
  actions?: {
1517
1690
  label: string;
1518
1691
  done?: boolean | undefined;
1519
- owner?: "staff" | "student" | undefined;
1692
+ owner?: "student" | "staff" | undefined;
1520
1693
  dueDate?: string | undefined;
1521
1694
  }[] | undefined;
1522
1695
  staffNotes?: string | undefined;
@@ -1531,7 +1704,7 @@ export declare const allCallables: {
1531
1704
  actions?: {
1532
1705
  label: string;
1533
1706
  done?: boolean | undefined;
1534
- owner?: "staff" | "student" | undefined;
1707
+ owner?: "student" | "staff" | undefined;
1535
1708
  dueDate?: string | undefined;
1536
1709
  }[] | undefined;
1537
1710
  staffNotes?: string | undefined;
@@ -2076,14 +2249,14 @@ export declare const allCallables: {
2076
2249
  requestId: string;
2077
2250
  token?: string | undefined;
2078
2251
  response?: unknown;
2079
- submittedBy?: "parent" | "staff" | "student" | "trustedContact" | "employer" | undefined;
2252
+ submittedBy?: "parent" | "employer" | "student" | "staff" | "trustedContact" | undefined;
2080
2253
  rater?: "students" | "parents" | "trustedContact" | "employers" | undefined;
2081
2254
  noChange?: boolean | undefined;
2082
2255
  }, {
2083
2256
  requestId: string;
2084
2257
  token?: string | undefined;
2085
2258
  response?: unknown;
2086
- submittedBy?: "parent" | "staff" | "student" | "trustedContact" | "employer" | undefined;
2259
+ submittedBy?: "parent" | "employer" | "student" | "staff" | "trustedContact" | undefined;
2087
2260
  rater?: "students" | "parents" | "trustedContact" | "employers" | undefined;
2088
2261
  noChange?: boolean | undefined;
2089
2262
  }>;
@@ -2862,14 +3035,14 @@ export declare const callableGroups: {
2862
3035
  requestId: string;
2863
3036
  token?: string | undefined;
2864
3037
  response?: unknown;
2865
- submittedBy?: "parent" | "staff" | "student" | "trustedContact" | "employer" | undefined;
3038
+ submittedBy?: "parent" | "employer" | "student" | "staff" | "trustedContact" | undefined;
2866
3039
  rater?: "students" | "parents" | "trustedContact" | "employers" | undefined;
2867
3040
  noChange?: boolean | undefined;
2868
3041
  }, {
2869
3042
  requestId: string;
2870
3043
  token?: string | undefined;
2871
3044
  response?: unknown;
2872
- submittedBy?: "parent" | "staff" | "student" | "trustedContact" | "employer" | undefined;
3045
+ submittedBy?: "parent" | "employer" | "student" | "staff" | "trustedContact" | undefined;
2873
3046
  rater?: "students" | "parents" | "trustedContact" | "employers" | undefined;
2874
3047
  noChange?: boolean | undefined;
2875
3048
  }>;
@@ -2900,12 +3073,12 @@ export declare const callableGroups: {
2900
3073
  }, "strip", z.ZodTypeAny, {
2901
3074
  label: string;
2902
3075
  done?: boolean | undefined;
2903
- owner?: "staff" | "student" | undefined;
3076
+ owner?: "student" | "staff" | undefined;
2904
3077
  dueDate?: string | undefined;
2905
3078
  }, {
2906
3079
  label: string;
2907
3080
  done?: boolean | undefined;
2908
- owner?: "staff" | "student" | undefined;
3081
+ owner?: "student" | "staff" | undefined;
2909
3082
  dueDate?: string | undefined;
2910
3083
  }>, "many">>;
2911
3084
  concern: z.ZodOptional<z.ZodBoolean>;
@@ -2917,7 +3090,7 @@ export declare const callableGroups: {
2917
3090
  actions?: {
2918
3091
  label: string;
2919
3092
  done?: boolean | undefined;
2920
- owner?: "staff" | "student" | undefined;
3093
+ owner?: "student" | "staff" | undefined;
2921
3094
  dueDate?: string | undefined;
2922
3095
  }[] | undefined;
2923
3096
  staffNotes?: string | undefined;
@@ -2932,7 +3105,7 @@ export declare const callableGroups: {
2932
3105
  actions?: {
2933
3106
  label: string;
2934
3107
  done?: boolean | undefined;
2935
- owner?: "staff" | "student" | undefined;
3108
+ owner?: "student" | "staff" | undefined;
2936
3109
  dueDate?: string | undefined;
2937
3110
  }[] | undefined;
2938
3111
  staffNotes?: string | undefined;
@@ -3669,6 +3842,179 @@ export declare const callableGroups: {
3669
3842
  taskId: string;
3670
3843
  }>;
3671
3844
  };
3845
+ "wonde-findDualRegistrations": {
3846
+ request: z.ZodObject<{
3847
+ oId: z.ZodString;
3848
+ }, "strip", z.ZodTypeAny, {
3849
+ oId: string;
3850
+ }, {
3851
+ oId: string;
3852
+ }>;
3853
+ response: z.ZodObject<{
3854
+ trustId: z.ZodString;
3855
+ schools: z.ZodArray<z.ZodObject<{
3856
+ oId: z.ZodString;
3857
+ name: z.ZodString;
3858
+ rosterSize: z.ZodNumber;
3859
+ missingUpn: z.ZodNumber;
3860
+ enrolmentStatuses: z.ZodRecord<z.ZodString, z.ZodNumber>;
3861
+ }, "strip", z.ZodTypeAny, {
3862
+ name: string;
3863
+ oId: string;
3864
+ rosterSize: number;
3865
+ missingUpn: number;
3866
+ enrolmentStatuses: Record<string, number>;
3867
+ }, {
3868
+ name: string;
3869
+ oId: string;
3870
+ rosterSize: number;
3871
+ missingUpn: number;
3872
+ enrolmentStatuses: Record<string, number>;
3873
+ }>, "many">;
3874
+ skipped: z.ZodArray<z.ZodObject<{
3875
+ oId: z.ZodString;
3876
+ name: z.ZodString;
3877
+ reason: z.ZodString;
3878
+ }, "strip", z.ZodTypeAny, {
3879
+ name: string;
3880
+ reason: string;
3881
+ oId: string;
3882
+ }, {
3883
+ name: string;
3884
+ reason: string;
3885
+ oId: string;
3886
+ }>, "many">;
3887
+ matches: z.ZodArray<z.ZodObject<{
3888
+ matchedOn: z.ZodEnum<["upn", "nameDob"]>;
3889
+ upn: z.ZodNullable<z.ZodString>;
3890
+ forename: z.ZodString;
3891
+ surname: z.ZodString;
3892
+ dateOfBirth: z.ZodNullable<z.ZodString>;
3893
+ enrolments: z.ZodArray<z.ZodObject<{
3894
+ oId: z.ZodString;
3895
+ schoolName: z.ZodString;
3896
+ wondeId: z.ZodString;
3897
+ email: z.ZodNullable<z.ZodString>;
3898
+ year: z.ZodNullable<z.ZodString>;
3899
+ formGroup: z.ZodNullable<z.ZodString>;
3900
+ enrolmentStatus: z.ZodNullable<z.ZodString>;
3901
+ userId: z.ZodNullable<z.ZodString>;
3902
+ }, "strip", z.ZodTypeAny, {
3903
+ email: string | null;
3904
+ year: string | null;
3905
+ oId: string;
3906
+ userId: string | null;
3907
+ schoolName: string;
3908
+ wondeId: string;
3909
+ formGroup: string | null;
3910
+ enrolmentStatus: string | null;
3911
+ }, {
3912
+ email: string | null;
3913
+ year: string | null;
3914
+ oId: string;
3915
+ userId: string | null;
3916
+ schoolName: string;
3917
+ wondeId: string;
3918
+ formGroup: string | null;
3919
+ enrolmentStatus: string | null;
3920
+ }>, "many">;
3921
+ }, "strip", z.ZodTypeAny, {
3922
+ upn: string | null;
3923
+ matchedOn: "upn" | "nameDob";
3924
+ forename: string;
3925
+ surname: string;
3926
+ dateOfBirth: string | null;
3927
+ enrolments: {
3928
+ email: string | null;
3929
+ year: string | null;
3930
+ oId: string;
3931
+ userId: string | null;
3932
+ schoolName: string;
3933
+ wondeId: string;
3934
+ formGroup: string | null;
3935
+ enrolmentStatus: string | null;
3936
+ }[];
3937
+ }, {
3938
+ upn: string | null;
3939
+ matchedOn: "upn" | "nameDob";
3940
+ forename: string;
3941
+ surname: string;
3942
+ dateOfBirth: string | null;
3943
+ enrolments: {
3944
+ email: string | null;
3945
+ year: string | null;
3946
+ oId: string;
3947
+ userId: string | null;
3948
+ schoolName: string;
3949
+ wondeId: string;
3950
+ formGroup: string | null;
3951
+ enrolmentStatus: string | null;
3952
+ }[];
3953
+ }>, "many">;
3954
+ }, "strip", z.ZodTypeAny, {
3955
+ skipped: {
3956
+ name: string;
3957
+ reason: string;
3958
+ oId: string;
3959
+ }[];
3960
+ matches: {
3961
+ upn: string | null;
3962
+ matchedOn: "upn" | "nameDob";
3963
+ forename: string;
3964
+ surname: string;
3965
+ dateOfBirth: string | null;
3966
+ enrolments: {
3967
+ email: string | null;
3968
+ year: string | null;
3969
+ oId: string;
3970
+ userId: string | null;
3971
+ schoolName: string;
3972
+ wondeId: string;
3973
+ formGroup: string | null;
3974
+ enrolmentStatus: string | null;
3975
+ }[];
3976
+ }[];
3977
+ schools: {
3978
+ name: string;
3979
+ oId: string;
3980
+ rosterSize: number;
3981
+ missingUpn: number;
3982
+ enrolmentStatuses: Record<string, number>;
3983
+ }[];
3984
+ trustId: string;
3985
+ }, {
3986
+ skipped: {
3987
+ name: string;
3988
+ reason: string;
3989
+ oId: string;
3990
+ }[];
3991
+ matches: {
3992
+ upn: string | null;
3993
+ matchedOn: "upn" | "nameDob";
3994
+ forename: string;
3995
+ surname: string;
3996
+ dateOfBirth: string | null;
3997
+ enrolments: {
3998
+ email: string | null;
3999
+ year: string | null;
4000
+ oId: string;
4001
+ userId: string | null;
4002
+ schoolName: string;
4003
+ wondeId: string;
4004
+ formGroup: string | null;
4005
+ enrolmentStatus: string | null;
4006
+ }[];
4007
+ }[];
4008
+ schools: {
4009
+ name: string;
4010
+ oId: string;
4011
+ rosterSize: number;
4012
+ missingUpn: number;
4013
+ enrolmentStatuses: Record<string, number>;
4014
+ }[];
4015
+ trustId: string;
4016
+ }>;
4017
+ };
3672
4018
  };
3673
4019
  misSyncCallables: {
3674
4020
  "misSync-saveConfig": {
@@ -580,14 +580,14 @@ export declare const surveyCallables: {
580
580
  requestId: string;
581
581
  token?: string | undefined;
582
582
  response?: unknown;
583
- submittedBy?: "parent" | "staff" | "student" | "trustedContact" | "employer" | undefined;
583
+ submittedBy?: "parent" | "employer" | "student" | "staff" | "trustedContact" | undefined;
584
584
  rater?: "students" | "parents" | "trustedContact" | "employers" | undefined;
585
585
  noChange?: boolean | undefined;
586
586
  }, {
587
587
  requestId: string;
588
588
  token?: string | undefined;
589
589
  response?: unknown;
590
- submittedBy?: "parent" | "staff" | "student" | "trustedContact" | "employer" | undefined;
590
+ submittedBy?: "parent" | "employer" | "student" | "staff" | "trustedContact" | undefined;
591
591
  rater?: "students" | "parents" | "trustedContact" | "employers" | undefined;
592
592
  noChange?: boolean | undefined;
593
593
  }>;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  /**
3
- * Contracts for the Wonde connection request
3
+ * Contracts for the Wonde connection request and the dual registration check
4
4
  * (`Placementt-Backend/functions/src/wonde.ts`).
5
5
  *
6
6
  * Wonde only approves a connection once the school's IT manager signs it off, so
@@ -32,4 +32,190 @@ export declare const wondeCallables: {
32
32
  taskId: string;
33
33
  }>;
34
34
  };
35
+ /**
36
+ * Admin only, read only. Pulls the full Wonde roster of every connected school
37
+ * in the trust `oId` belongs to (or is) and lists the pupils on more than one,
38
+ * such as a SEND school's pupils based in satellite centres at other schools.
39
+ * Writes nothing, so it is safe to run against a live trust.
40
+ */
41
+ "wonde-findDualRegistrations": {
42
+ request: z.ZodObject<{
43
+ /** The trust, or any school in it. */
44
+ oId: z.ZodString;
45
+ }, "strip", z.ZodTypeAny, {
46
+ oId: string;
47
+ }, {
48
+ oId: string;
49
+ }>;
50
+ response: z.ZodObject<{
51
+ trustId: z.ZodString;
52
+ schools: z.ZodArray<z.ZodObject<{
53
+ oId: z.ZodString;
54
+ name: z.ZodString;
55
+ rosterSize: z.ZodNumber;
56
+ /** Pupils Wonde returned with no UPN, matched on name and date of birth instead. */
57
+ missingUpn: z.ZodNumber;
58
+ /** Distinct `extended_details.enrolment_status` values on the roster, with counts. */
59
+ enrolmentStatuses: z.ZodRecord<z.ZodString, z.ZodNumber>;
60
+ }, "strip", z.ZodTypeAny, {
61
+ name: string;
62
+ oId: string;
63
+ rosterSize: number;
64
+ missingUpn: number;
65
+ enrolmentStatuses: Record<string, number>;
66
+ }, {
67
+ name: string;
68
+ oId: string;
69
+ rosterSize: number;
70
+ missingUpn: number;
71
+ enrolmentStatuses: Record<string, number>;
72
+ }>, "many">;
73
+ /** Trust schools left out, and why. */
74
+ skipped: z.ZodArray<z.ZodObject<{
75
+ oId: z.ZodString;
76
+ name: z.ZodString;
77
+ reason: z.ZodString;
78
+ }, "strip", z.ZodTypeAny, {
79
+ name: string;
80
+ reason: string;
81
+ oId: string;
82
+ }, {
83
+ name: string;
84
+ reason: string;
85
+ oId: string;
86
+ }>, "many">;
87
+ matches: z.ZodArray<z.ZodObject<{
88
+ /** What the pupils were matched on. */
89
+ matchedOn: z.ZodEnum<["upn", "nameDob"]>;
90
+ upn: z.ZodNullable<z.ZodString>;
91
+ forename: z.ZodString;
92
+ surname: z.ZodString;
93
+ dateOfBirth: z.ZodNullable<z.ZodString>;
94
+ /** One per school the pupil is on roll at. */
95
+ enrolments: z.ZodArray<z.ZodObject<{
96
+ oId: z.ZodString;
97
+ schoolName: z.ZodString;
98
+ wondeId: z.ZodString;
99
+ email: z.ZodNullable<z.ZodString>;
100
+ year: z.ZodNullable<z.ZodString>;
101
+ formGroup: z.ZodNullable<z.ZodString>;
102
+ enrolmentStatus: z.ZodNullable<z.ZodString>;
103
+ /** The CareerThread student at this school, if we hold one. */
104
+ userId: z.ZodNullable<z.ZodString>;
105
+ }, "strip", z.ZodTypeAny, {
106
+ email: string | null;
107
+ year: string | null;
108
+ oId: string;
109
+ userId: string | null;
110
+ schoolName: string;
111
+ wondeId: string;
112
+ formGroup: string | null;
113
+ enrolmentStatus: string | null;
114
+ }, {
115
+ email: string | null;
116
+ year: string | null;
117
+ oId: string;
118
+ userId: string | null;
119
+ schoolName: string;
120
+ wondeId: string;
121
+ formGroup: string | null;
122
+ enrolmentStatus: string | null;
123
+ }>, "many">;
124
+ }, "strip", z.ZodTypeAny, {
125
+ upn: string | null;
126
+ matchedOn: "upn" | "nameDob";
127
+ forename: string;
128
+ surname: string;
129
+ dateOfBirth: string | null;
130
+ enrolments: {
131
+ email: string | null;
132
+ year: string | null;
133
+ oId: string;
134
+ userId: string | null;
135
+ schoolName: string;
136
+ wondeId: string;
137
+ formGroup: string | null;
138
+ enrolmentStatus: string | null;
139
+ }[];
140
+ }, {
141
+ upn: string | null;
142
+ matchedOn: "upn" | "nameDob";
143
+ forename: string;
144
+ surname: string;
145
+ dateOfBirth: string | null;
146
+ enrolments: {
147
+ email: string | null;
148
+ year: string | null;
149
+ oId: string;
150
+ userId: string | null;
151
+ schoolName: string;
152
+ wondeId: string;
153
+ formGroup: string | null;
154
+ enrolmentStatus: string | null;
155
+ }[];
156
+ }>, "many">;
157
+ }, "strip", z.ZodTypeAny, {
158
+ skipped: {
159
+ name: string;
160
+ reason: string;
161
+ oId: string;
162
+ }[];
163
+ matches: {
164
+ upn: string | null;
165
+ matchedOn: "upn" | "nameDob";
166
+ forename: string;
167
+ surname: string;
168
+ dateOfBirth: string | null;
169
+ enrolments: {
170
+ email: string | null;
171
+ year: string | null;
172
+ oId: string;
173
+ userId: string | null;
174
+ schoolName: string;
175
+ wondeId: string;
176
+ formGroup: string | null;
177
+ enrolmentStatus: string | null;
178
+ }[];
179
+ }[];
180
+ schools: {
181
+ name: string;
182
+ oId: string;
183
+ rosterSize: number;
184
+ missingUpn: number;
185
+ enrolmentStatuses: Record<string, number>;
186
+ }[];
187
+ trustId: string;
188
+ }, {
189
+ skipped: {
190
+ name: string;
191
+ reason: string;
192
+ oId: string;
193
+ }[];
194
+ matches: {
195
+ upn: string | null;
196
+ matchedOn: "upn" | "nameDob";
197
+ forename: string;
198
+ surname: string;
199
+ dateOfBirth: string | null;
200
+ enrolments: {
201
+ email: string | null;
202
+ year: string | null;
203
+ oId: string;
204
+ userId: string | null;
205
+ schoolName: string;
206
+ wondeId: string;
207
+ formGroup: string | null;
208
+ enrolmentStatus: string | null;
209
+ }[];
210
+ }[];
211
+ schools: {
212
+ name: string;
213
+ oId: string;
214
+ rosterSize: number;
215
+ missingUpn: number;
216
+ enrolmentStatuses: Record<string, number>;
217
+ }[];
218
+ trustId: string;
219
+ }>;
220
+ };
35
221
  };
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.wondeCallables = void 0;
4
4
  const zod_1 = require("zod");
5
5
  /**
6
- * Contracts for the Wonde connection request
6
+ * Contracts for the Wonde connection request and the dual registration check
7
7
  * (`Placementt-Backend/functions/src/wonde.ts`).
8
8
  *
9
9
  * Wonde only approves a connection once the school's IT manager signs it off, so
@@ -23,5 +23,51 @@ exports.wondeCallables = {
23
23
  taskId: zod_1.z.string(),
24
24
  }),
25
25
  },
26
+ /**
27
+ * Admin only, read only. Pulls the full Wonde roster of every connected school
28
+ * in the trust `oId` belongs to (or is) and lists the pupils on more than one,
29
+ * such as a SEND school's pupils based in satellite centres at other schools.
30
+ * Writes nothing, so it is safe to run against a live trust.
31
+ */
32
+ "wonde-findDualRegistrations": {
33
+ request: zod_1.z.object({
34
+ /** The trust, or any school in it. */
35
+ oId: zod_1.z.string().min(1),
36
+ }),
37
+ response: zod_1.z.object({
38
+ trustId: zod_1.z.string(),
39
+ schools: zod_1.z.array(zod_1.z.object({
40
+ oId: zod_1.z.string(),
41
+ name: zod_1.z.string(),
42
+ rosterSize: zod_1.z.number(),
43
+ /** Pupils Wonde returned with no UPN, matched on name and date of birth instead. */
44
+ missingUpn: zod_1.z.number(),
45
+ /** Distinct `extended_details.enrolment_status` values on the roster, with counts. */
46
+ enrolmentStatuses: zod_1.z.record(zod_1.z.string(), zod_1.z.number()),
47
+ })),
48
+ /** Trust schools left out, and why. */
49
+ skipped: zod_1.z.array(zod_1.z.object({ oId: zod_1.z.string(), name: zod_1.z.string(), reason: zod_1.z.string() })),
50
+ matches: zod_1.z.array(zod_1.z.object({
51
+ /** What the pupils were matched on. */
52
+ matchedOn: zod_1.z.enum(["upn", "nameDob"]),
53
+ upn: zod_1.z.string().nullable(),
54
+ forename: zod_1.z.string(),
55
+ surname: zod_1.z.string(),
56
+ dateOfBirth: zod_1.z.string().nullable(),
57
+ /** One per school the pupil is on roll at. */
58
+ enrolments: zod_1.z.array(zod_1.z.object({
59
+ oId: zod_1.z.string(),
60
+ schoolName: zod_1.z.string(),
61
+ wondeId: zod_1.z.string(),
62
+ email: zod_1.z.string().nullable(),
63
+ year: zod_1.z.string().nullable(),
64
+ formGroup: zod_1.z.string().nullable(),
65
+ enrolmentStatus: zod_1.z.string().nullable(),
66
+ /** The CareerThread student at this school, if we hold one. */
67
+ userId: zod_1.z.string().nullable(),
68
+ })),
69
+ })),
70
+ }),
71
+ },
26
72
  };
27
73
  //# sourceMappingURL=wonde.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wonde.js","sourceRoot":"","sources":["../../src/callables/wonde.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;;;;;GAOG;AACU,QAAA,cAAc,GAAG;IAC1B,yBAAyB,EAAE;QACvB,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,wCAAwC;YACxC,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACtB,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;YAChD,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC;SACrD,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,4DAA4D;YAC5D,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;SACrB,CAAC;KACL;CACoB,CAAC"}
1
+ {"version":3,"file":"wonde.js","sourceRoot":"","sources":["../../src/callables/wonde.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -44,6 +44,7 @@ export * from "./benchmarkAggregate";
44
44
  export * from "./portfolioAggregate";
45
45
  export * from "./tutorialTips";
46
46
  export * from "./constants";
47
+ export * from "./letters";
47
48
  export * from "./dataViewerReload";
48
49
  export * from "./hooks";
49
50
  export * from "./workflowBands";
package/lib/index.js CHANGED
@@ -64,6 +64,7 @@ __exportStar(require("./benchmarkAggregate"), exports);
64
64
  __exportStar(require("./portfolioAggregate"), exports);
65
65
  __exportStar(require("./tutorialTips"), exports);
66
66
  __exportStar(require("./constants"), exports);
67
+ __exportStar(require("./letters"), exports);
67
68
  __exportStar(require("./dataViewerReload"), exports);
68
69
  __exportStar(require("./hooks"), exports);
69
70
  __exportStar(require("./workflowBands"), 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,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,iDAA+B;AAC/B,qDAAmC;AACnC,uDAAqC;AACrC,gDAA8B;AAC9B,4CAA0B;AAC1B,wCAAsB;AACtB,kDAAgC;AAChC,6CAA2B;AAC3B,8CAA4B;AAC5B,oDAAkC;AAClC,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB;AACzB,wDAAsC;AACtC,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,4CAA0B;AAC1B,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,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,iDAA+B;AAC/B,qDAAmC;AACnC,uDAAqC;AACrC,gDAA8B;AAC9B,4CAA0B;AAC1B,wCAAsB;AACtB,kDAAgC;AAChC,6CAA2B;AAC3B,8CAA4B;AAC5B,oDAAkC;AAClC,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB;AACzB,wDAAsC;AACtC,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,4CAA0B;AAC1B,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,4CAA0B;AAC1B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
@@ -0,0 +1,22 @@
1
+ import { Descendant } from "slate";
2
+ import type { ProductTag } from "./typeDefinitions";
3
+ export type WexLetterKey = "employer" | "parent" | "student";
4
+ export type WexLetterOverride = {
5
+ body: Descendant[];
6
+ updated: string;
7
+ updatedBy?: string;
8
+ };
9
+ export type WexLetterConfig = {
10
+ name: string;
11
+ audience: "employers" | "parents" | "students";
12
+ description: string;
13
+ productTag: ProductTag;
14
+ body: Descendant[];
15
+ };
16
+ export declare const wexLetterParams: string[];
17
+ export declare const wexLetters: {
18
+ [key in WexLetterKey]: WexLetterConfig;
19
+ };
20
+ export declare function fillLetterParams(text: string, values: {
21
+ [key: string]: string | undefined;
22
+ }): string;
package/lib/letters.js ADDED
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wexLetters = exports.wexLetterParams = void 0;
4
+ exports.fillLetterParams = fillLetterParams;
5
+ // Params available in every letter. Filled in when the letter is previewed or downloaded.
6
+ exports.wexLetterParams = ["instituteName", "academicYear", "cohortName", "placementStart", "placementEnd", "staffName", "staffEmail"];
7
+ // Builds Slate content from lines. "# " and "## " start headings, **text** is bold, {{param}} becomes a param node.
8
+ function toLetterBody(lines) {
9
+ return lines.map((line) => {
10
+ const type = line.startsWith("# ") ? "heading-one" : line.startsWith("## ") ? "heading-two" : "paragraph";
11
+ const text = type === "heading-one" ? line.slice(2) : type === "heading-two" ? line.slice(3) : line;
12
+ const children = text.split(/(\*\*[^*]+\*\*|\{\{\w+\}\})/).filter(Boolean).map((part) => {
13
+ if (part.startsWith("**"))
14
+ return { text: part.slice(2, -2), bold: true };
15
+ if (part.startsWith("{{"))
16
+ return { text: part, param: true };
17
+ return { text: part };
18
+ });
19
+ return { type, children: children.length ? children : [{ text: "" }] };
20
+ });
21
+ }
22
+ const signOff = ["Kind regards,", "", "Thomas Barton", "Founder, Director", "tom@careerthread.co.uk", "CareerThread"];
23
+ exports.wexLetters = {
24
+ employer: {
25
+ name: "Employer letter",
26
+ audience: "employers",
27
+ description: "Introduces CareerThread to businesses hosting a student, and what we will ask them for.",
28
+ productTag: "wex",
29
+ body: toLetterBody([
30
+ "# Employer Guidance",
31
+ "{{instituteName}} are using CareerThread through the academic year {{academicYear}} to support their students in managing work experience.",
32
+ "CareerThread helps students find placements, manages administrative sign-off and provides tools for schools to build lasting connections to businesses. As you are hosting a work experience student, we will contact you over the next few months to request additional information. This is to ensure students have all the information they need to prepare effectively for their placement.",
33
+ "We aim to build a community of businesses willing to support work experience at the school and the surrounding area. If you would like to advertise additional placements this year or in the future, please contact the school. Likewise, if you would like any support on hosting a student, please get in touch.",
34
+ "All automated correspondence from CareerThread will be sent from admin@careerthread.co.uk. If you have any questions specific to your placement, please contact the school. If you have any questions regarding CareerThread, please contact Tom at tom@careerthread.co.uk.",
35
+ "Thank you for agreeing to host a student for work experience. We hope it will be an enriching experience for you and your student.",
36
+ ...signOff,
37
+ ]),
38
+ },
39
+ parent: {
40
+ name: "Parent letter",
41
+ audience: "parents",
42
+ description: "Explains to parents how work experience is managed and what we will email them about.",
43
+ productTag: "wex",
44
+ body: toLetterBody([
45
+ "# Parent Guidance",
46
+ "{{instituteName}} is using CareerThread to manage your child's work experience. CareerThread helps secondary schools to coordinate work experience placements while supporting students with daily reflections and post-placement feedback, ensuring a meaningful and structured experience.",
47
+ "While CareerThread supports secondary schools in managing work experience, we do not currently find placements for your child. The students remain responsible for organising placements directly with an employer.",
48
+ "Your school will securely upload your email address to our platform so we can keep you informed about your child's placement and any potential issues. If you have any concerns or would prefer not to share your email, please contact the school.",
49
+ "Once your child has uploaded details of their work experience, we will retrieve necessary documentation from the employer. We may also email you requesting sign-off on the placement. This is necessary for us to go ahead with the placement.",
50
+ "During your child's work experience, you can view placement details and feedback through the email links we send you, so you can monitor your child's progress.",
51
+ "If you have any questions on how CareerThread works, please get in touch by emailing Tom at tom@careerthread.co.uk.",
52
+ ...signOff,
53
+ ]),
54
+ },
55
+ student: {
56
+ name: "Student guide",
57
+ audience: "students",
58
+ description: "Helps students prepare for their placement once it has been confirmed.",
59
+ productTag: "wex",
60
+ body: toLetterBody([
61
+ "# Preparing for Your Work Experience Placement",
62
+ "Congratulations on finding a placement! Now it's time to make the most of the opportunity. Good preparation can make all the difference, and this guide will walk you through some key steps to help you feel confident and ready to thrive.",
63
+ "**Confirm Details:** Once your placement has been verified on our portal, you'll see key information like your responsibilities, dress code, working hours, and any required preparation on your placement page. Review these carefully, and don't hesitate to contact the business if anything is unclear.",
64
+ "**Research the Company:** Take time to explore the business's website and any recent news or updates about them. Get familiar with their mission and the sector they work in. Think of a few questions you'd like to ask your new colleagues - it's a great way to learn and show your interest.",
65
+ "**Sharpen Your Skills:** Consider what skills might be useful during your placement. These could be technical skills (like using a specific type of software) or general ones like teamwork or communication. Practising these beforehand will help you feel ready and confident.",
66
+ "**Plan Your Travel:** Punctuality is key to a great first impression. Aim to arrive 15 minutes before your start time. Don't be too early, as your contact may not be ready for you. Before you start your placement, plan your travel route. If necessary, do a trial commute to make sure you will be on time.",
67
+ "**Pack Your Essentials:** Bring everything you'll need to feel prepared - this might include a notebook, pen, water bottle, lunch, or anything specific the business has asked you to bring. If you're unsure, ask ahead. Being well-prepared shows you're reliable, enthusiastic, and organised.",
68
+ ]),
69
+ },
70
+ };
71
+ // Replaces {{param}} text with its value. Unknown params are left as they are, so a missing value is visible rather than blank.
72
+ function fillLetterParams(text, values) {
73
+ return text.replace(/\{\{(\w+)\}\}/g, (match, key) => values[key] ?? match);
74
+ }
75
+ //# sourceMappingURL=letters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"letters.js","sourceRoot":"","sources":["../src/letters.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import type { StudentTaskConfigMap, SurveyKind, SurveySendPolicyMap, SurveySlot
7
7
  import type { InstitutePricing } from "./pricing";
8
8
  import type { CompassActivityType, CompassCareersPartnerType, CompassDeliveryMethod } from "./compassTemplate";
9
9
  import type { MisColumnMapping } from "./misSync";
10
+ import type { WexLetterKey, WexLetterOverride } from "./letters";
10
11
  export type FilterObject = {
11
12
  [key: string]: {
12
13
  label: string;
@@ -1078,6 +1079,10 @@ export type EmployerField = {
1078
1079
  /** The areas of the staff platform that each own part of the careers year. */
1079
1080
  export type CareersSetupArea = "development" | "destinations";
1080
1081
  export type InstituteData = {
1082
+ /** The school's own edited versions of our work experience letters. Missing keys use our default. */
1083
+ wexLetters?: {
1084
+ [key in WexLetterKey]?: WexLetterOverride;
1085
+ };
1081
1086
  /** MIS file sync. `mapping` holds the college's own header for any field not named exactly as we specify. */
1082
1087
  mis?: {
1083
1088
  mapping?: MisColumnMapping;
@@ -1277,6 +1282,9 @@ export type InstituteData = {
1277
1282
  * screen shown before the platform on first sign-in. Registration sets it false;
1278
1283
  * absent means the account predates the screen and is never asked. */
1279
1284
  featureSetupComplete?: boolean;
1285
+ /** Every feature ticked on that screen, including those with no flag of their own yet
1286
+ * (aspirations, leavers, careersLibrary), so the platform knows what to show. */
1287
+ selectedFeatures?: string[];
1280
1288
  deactivated?: boolean;
1281
1289
  isTest?: boolean;
1282
1290
  cohortFilters?: {
@@ -1 +1 @@
1
- {"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AAmnIA,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"}
1
+ {"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AAynIA,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
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.400.1143",
5
+ "version": "1.400.1144",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -2,7 +2,7 @@ import {z} from "zod";
2
2
  import {CallableGroup} from "./types";
3
3
 
4
4
  /**
5
- * Contracts for the Wonde connection request
5
+ * Contracts for the Wonde connection request and the dual registration check
6
6
  * (`Placementt-Backend/functions/src/wonde.ts`).
7
7
  *
8
8
  * Wonde only approves a connection once the school's IT manager signs it off, so
@@ -22,4 +22,50 @@ export const wondeCallables = {
22
22
  taskId: z.string(),
23
23
  }),
24
24
  },
25
+ /**
26
+ * Admin only, read only. Pulls the full Wonde roster of every connected school
27
+ * in the trust `oId` belongs to (or is) and lists the pupils on more than one,
28
+ * such as a SEND school's pupils based in satellite centres at other schools.
29
+ * Writes nothing, so it is safe to run against a live trust.
30
+ */
31
+ "wonde-findDualRegistrations": {
32
+ request: z.object({
33
+ /** The trust, or any school in it. */
34
+ oId: z.string().min(1),
35
+ }),
36
+ response: z.object({
37
+ trustId: z.string(),
38
+ schools: z.array(z.object({
39
+ oId: z.string(),
40
+ name: z.string(),
41
+ rosterSize: z.number(),
42
+ /** Pupils Wonde returned with no UPN, matched on name and date of birth instead. */
43
+ missingUpn: z.number(),
44
+ /** Distinct `extended_details.enrolment_status` values on the roster, with counts. */
45
+ enrolmentStatuses: z.record(z.string(), z.number()),
46
+ })),
47
+ /** Trust schools left out, and why. */
48
+ skipped: z.array(z.object({oId: z.string(), name: z.string(), reason: z.string()})),
49
+ matches: z.array(z.object({
50
+ /** What the pupils were matched on. */
51
+ matchedOn: z.enum(["upn", "nameDob"]),
52
+ upn: z.string().nullable(),
53
+ forename: z.string(),
54
+ surname: z.string(),
55
+ dateOfBirth: z.string().nullable(),
56
+ /** One per school the pupil is on roll at. */
57
+ enrolments: z.array(z.object({
58
+ oId: z.string(),
59
+ schoolName: z.string(),
60
+ wondeId: z.string(),
61
+ email: z.string().nullable(),
62
+ year: z.string().nullable(),
63
+ formGroup: z.string().nullable(),
64
+ enrolmentStatus: z.string().nullable(),
65
+ /** The CareerThread student at this school, if we hold one. */
66
+ userId: z.string().nullable(),
67
+ })),
68
+ })),
69
+ }),
70
+ },
25
71
  } satisfies CallableGroup;
package/src/index.ts CHANGED
@@ -46,6 +46,7 @@ export * from "./benchmarkAggregate";
46
46
  export * from "./portfolioAggregate";
47
47
  export * from "./tutorialTips";
48
48
  export * from "./constants";
49
+ export * from "./letters";
49
50
  export * from "./dataViewerReload";
50
51
  export * from "./hooks";
51
52
  export * from "./workflowBands";
package/src/letters.ts ADDED
@@ -0,0 +1,91 @@
1
+ import {Descendant} from "slate";
2
+ import type {ProductTag} from "./typeDefinitions";
3
+
4
+ // Work experience letters schools hand to employers, parents and students. We provide the defaults below.
5
+ // A school can edit a letter once, saved to InstituteData.wexLetters[key], and that version is used for all their cohorts.
6
+ // Cohort specific details (name, dates) come in through params, so one school version suits every cohort.
7
+
8
+ export type WexLetterKey = "employer"|"parent"|"student";
9
+
10
+ export type WexLetterOverride = {body: Descendant[], updated: string, updatedBy?: string};
11
+
12
+ export type WexLetterConfig = {
13
+ name: string,
14
+ audience: "employers"|"parents"|"students",
15
+ description: string,
16
+ productTag: ProductTag,
17
+ body: Descendant[],
18
+ };
19
+
20
+ // Params available in every letter. Filled in when the letter is previewed or downloaded.
21
+ export const wexLetterParams = ["instituteName", "academicYear", "cohortName", "placementStart", "placementEnd", "staffName", "staffEmail"];
22
+
23
+ // Builds Slate content from lines. "# " and "## " start headings, **text** is bold, {{param}} becomes a param node.
24
+ function toLetterBody(lines: string[]): Descendant[] {
25
+ return lines.map((line) => {
26
+ const type = line.startsWith("# ") ? "heading-one" : line.startsWith("## ") ? "heading-two" : "paragraph";
27
+ const text = type === "heading-one" ? line.slice(2) : type === "heading-two" ? line.slice(3) : line;
28
+ const children = text.split(/(\*\*[^*]+\*\*|\{\{\w+\}\})/).filter(Boolean).map((part) => {
29
+ if (part.startsWith("**")) return {text: part.slice(2, -2), bold: true};
30
+ if (part.startsWith("{{")) return {text: part, param: true};
31
+ return {text: part};
32
+ });
33
+ return {type, children: children.length ? children : [{text: ""}]} as unknown as Descendant;
34
+ });
35
+ }
36
+
37
+ const signOff = ["Kind regards,", "", "Thomas Barton", "Founder, Director", "tom@careerthread.co.uk", "CareerThread"];
38
+
39
+ export const wexLetters: {[key in WexLetterKey]: WexLetterConfig} = {
40
+ employer: {
41
+ name: "Employer letter",
42
+ audience: "employers",
43
+ description: "Introduces CareerThread to businesses hosting a student, and what we will ask them for.",
44
+ productTag: "wex",
45
+ body: toLetterBody([
46
+ "# Employer Guidance",
47
+ "{{instituteName}} are using CareerThread through the academic year {{academicYear}} to support their students in managing work experience.",
48
+ "CareerThread helps students find placements, manages administrative sign-off and provides tools for schools to build lasting connections to businesses. As you are hosting a work experience student, we will contact you over the next few months to request additional information. This is to ensure students have all the information they need to prepare effectively for their placement.",
49
+ "We aim to build a community of businesses willing to support work experience at the school and the surrounding area. If you would like to advertise additional placements this year or in the future, please contact the school. Likewise, if you would like any support on hosting a student, please get in touch.",
50
+ "All automated correspondence from CareerThread will be sent from admin@careerthread.co.uk. If you have any questions specific to your placement, please contact the school. If you have any questions regarding CareerThread, please contact Tom at tom@careerthread.co.uk.",
51
+ "Thank you for agreeing to host a student for work experience. We hope it will be an enriching experience for you and your student.",
52
+ ...signOff,
53
+ ]),
54
+ },
55
+ parent: {
56
+ name: "Parent letter",
57
+ audience: "parents",
58
+ description: "Explains to parents how work experience is managed and what we will email them about.",
59
+ productTag: "wex",
60
+ body: toLetterBody([
61
+ "# Parent Guidance",
62
+ "{{instituteName}} is using CareerThread to manage your child's work experience. CareerThread helps secondary schools to coordinate work experience placements while supporting students with daily reflections and post-placement feedback, ensuring a meaningful and structured experience.",
63
+ "While CareerThread supports secondary schools in managing work experience, we do not currently find placements for your child. The students remain responsible for organising placements directly with an employer.",
64
+ "Your school will securely upload your email address to our platform so we can keep you informed about your child's placement and any potential issues. If you have any concerns or would prefer not to share your email, please contact the school.",
65
+ "Once your child has uploaded details of their work experience, we will retrieve necessary documentation from the employer. We may also email you requesting sign-off on the placement. This is necessary for us to go ahead with the placement.",
66
+ "During your child's work experience, you can view placement details and feedback through the email links we send you, so you can monitor your child's progress.",
67
+ "If you have any questions on how CareerThread works, please get in touch by emailing Tom at tom@careerthread.co.uk.",
68
+ ...signOff,
69
+ ]),
70
+ },
71
+ student: {
72
+ name: "Student guide",
73
+ audience: "students",
74
+ description: "Helps students prepare for their placement once it has been confirmed.",
75
+ productTag: "wex",
76
+ body: toLetterBody([
77
+ "# Preparing for Your Work Experience Placement",
78
+ "Congratulations on finding a placement! Now it's time to make the most of the opportunity. Good preparation can make all the difference, and this guide will walk you through some key steps to help you feel confident and ready to thrive.",
79
+ "**Confirm Details:** Once your placement has been verified on our portal, you'll see key information like your responsibilities, dress code, working hours, and any required preparation on your placement page. Review these carefully, and don't hesitate to contact the business if anything is unclear.",
80
+ "**Research the Company:** Take time to explore the business's website and any recent news or updates about them. Get familiar with their mission and the sector they work in. Think of a few questions you'd like to ask your new colleagues - it's a great way to learn and show your interest.",
81
+ "**Sharpen Your Skills:** Consider what skills might be useful during your placement. These could be technical skills (like using a specific type of software) or general ones like teamwork or communication. Practising these beforehand will help you feel ready and confident.",
82
+ "**Plan Your Travel:** Punctuality is key to a great first impression. Aim to arrive 15 minutes before your start time. Don't be too early, as your contact may not be ready for you. Before you start your placement, plan your travel route. If necessary, do a trial commute to make sure you will be on time.",
83
+ "**Pack Your Essentials:** Bring everything you'll need to feel prepared - this might include a notebook, pen, water bottle, lunch, or anything specific the business has asked you to bring. If you're unsure, ask ahead. Being well-prepared shows you're reliable, enthusiastic, and organised.",
84
+ ]),
85
+ },
86
+ };
87
+
88
+ // Replaces {{param}} text with its value. Unknown params are left as they are, so a missing value is visible rather than blank.
89
+ export function fillLetterParams(text: string, values: {[key: string]: string|undefined}) {
90
+ return text.replace(/\{\{(\w+)\}\}/g, (match, key) => values[key] ?? match);
91
+ }
@@ -7,6 +7,7 @@ import type {StudentTaskConfigMap, SurveyKind, SurveySendPolicyMap, SurveySlot}
7
7
  import type {InstitutePricing} from "./pricing";
8
8
  import type {CompassActivityType, CompassCareersPartnerType, CompassDeliveryMethod} from "./compassTemplate";
9
9
  import type {MisColumnMapping} from "./misSync";
10
+ import type {WexLetterKey, WexLetterOverride} from "./letters";
10
11
 
11
12
  export type FilterObject = {
12
13
  [key: string]: {
@@ -1125,6 +1126,8 @@ export type EmployerField = {
1125
1126
  export type CareersSetupArea = "development"|"destinations";
1126
1127
 
1127
1128
  export type InstituteData = {
1129
+ /** The school's own edited versions of our work experience letters. Missing keys use our default. */
1130
+ wexLetters?: {[key in WexLetterKey]?: WexLetterOverride},
1128
1131
  /** MIS file sync. `mapping` holds the college's own header for any field not named exactly as we specify. */
1129
1132
  mis?: {mapping?: MisColumnMapping},
1130
1133
  students: number,
@@ -1305,6 +1308,9 @@ export type InstituteData = {
1305
1308
  * screen shown before the platform on first sign-in. Registration sets it false;
1306
1309
  * absent means the account predates the screen and is never asked. */
1307
1310
  featureSetupComplete?: boolean,
1311
+ /** Every feature ticked on that screen, including those with no flag of their own yet
1312
+ * (aspirations, leavers, careersLibrary), so the platform knows what to show. */
1313
+ selectedFeatures?: string[],
1308
1314
  deactivated?: boolean,
1309
1315
  isTest?: boolean,
1310
1316
  cohortFilters?: {