placementt-core 1.400.990 → 1.400.992

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.
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Contracts for the Destinations setup wizard
4
+ * (`Placementt-Backend/functions/src/destinationsConfig.ts`).
5
+ *
6
+ * These were `aspirations-*` while they lived in the legacy survey backend. That
7
+ * module is gone, but the config it happened to host is institute setup that the
8
+ * survey engine READS and does not own, so the two callables moved rather than being
9
+ * deleted. The names moved with them: never rename a deployed function in place, so
10
+ * the old `aspirations-saveDestinationsConfig` and `aspirations-provisionDestinationsNow`
11
+ * must be deleted explicitly at deploy time.
12
+ */
13
+ export declare const destinationsConfigCallables: {
14
+ "destinationsConfig-saveDestinationsConfig": {
15
+ request: z.ZodObject<{
16
+ oId: z.ZodString;
17
+ /** The full DestinationsConfig from the setup wizard. Validated server
18
+ * side, which is also where provenance fields are stamped. */
19
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ config: Record<string, unknown>;
22
+ oId: string;
23
+ }, {
24
+ config: Record<string, unknown>;
25
+ oId: string;
26
+ }>;
27
+ response: z.ZodObject<{
28
+ saved: z.ZodBoolean;
29
+ /** True on a first save, which provisions the current year immediately. */
30
+ provisioned: z.ZodBoolean;
31
+ created: z.ZodOptional<z.ZodNumber>;
32
+ }, "strip", z.ZodTypeAny, {
33
+ saved: boolean;
34
+ provisioned: boolean;
35
+ created?: number | undefined;
36
+ }, {
37
+ saved: boolean;
38
+ provisioned: boolean;
39
+ created?: number | undefined;
40
+ }>;
41
+ };
42
+ "destinationsConfig-provisionDestinationsNow": {
43
+ request: z.ZodObject<{
44
+ oId: z.ZodString;
45
+ /** Provision the upcoming academic year early ("Set Up Next Year"). */
46
+ nextYear: z.ZodOptional<z.ZodBoolean>;
47
+ /** Limit to one feature; omit for everything the institute has configured. */
48
+ scope: z.ZodOptional<z.ZodEnum<["aspirations", "leavers"]>>;
49
+ }, "strip", z.ZodTypeAny, {
50
+ oId: string;
51
+ scope?: "aspirations" | "leavers" | undefined;
52
+ nextYear?: boolean | undefined;
53
+ }, {
54
+ oId: string;
55
+ scope?: "aspirations" | "leavers" | undefined;
56
+ nextYear?: boolean | undefined;
57
+ }>;
58
+ response: z.ZodObject<{
59
+ provisioned: z.ZodBoolean;
60
+ created: z.ZodNumber;
61
+ academicYear: z.ZodString;
62
+ }, "strip", z.ZodTypeAny, {
63
+ created: number;
64
+ academicYear: string;
65
+ provisioned: boolean;
66
+ }, {
67
+ created: number;
68
+ academicYear: string;
69
+ provisioned: boolean;
70
+ }>;
71
+ };
72
+ };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.destinationsConfigCallables = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * Contracts for the Destinations setup wizard
7
+ * (`Placementt-Backend/functions/src/destinationsConfig.ts`).
8
+ *
9
+ * These were `aspirations-*` while they lived in the legacy survey backend. That
10
+ * module is gone, but the config it happened to host is institute setup that the
11
+ * survey engine READS and does not own, so the two callables moved rather than being
12
+ * deleted. The names moved with them: never rename a deployed function in place, so
13
+ * the old `aspirations-saveDestinationsConfig` and `aspirations-provisionDestinationsNow`
14
+ * must be deleted explicitly at deploy time.
15
+ */
16
+ exports.destinationsConfigCallables = {
17
+ "destinationsConfig-saveDestinationsConfig": {
18
+ request: zod_1.z.object({
19
+ oId: zod_1.z.string(),
20
+ /** The full DestinationsConfig from the setup wizard. Validated server
21
+ * side, which is also where provenance fields are stamped. */
22
+ config: zod_1.z.record(zod_1.z.unknown()),
23
+ }),
24
+ response: zod_1.z.object({
25
+ saved: zod_1.z.boolean(),
26
+ /** True on a first save, which provisions the current year immediately. */
27
+ provisioned: zod_1.z.boolean(),
28
+ created: zod_1.z.number().optional(),
29
+ }),
30
+ },
31
+ "destinationsConfig-provisionDestinationsNow": {
32
+ request: zod_1.z.object({
33
+ oId: zod_1.z.string(),
34
+ /** Provision the upcoming academic year early ("Set Up Next Year"). */
35
+ nextYear: zod_1.z.boolean().optional(),
36
+ /** Limit to one feature; omit for everything the institute has configured. */
37
+ scope: zod_1.z.enum(["aspirations", "leavers"]).optional(),
38
+ }),
39
+ response: zod_1.z.object({
40
+ provisioned: zod_1.z.boolean(),
41
+ created: zod_1.z.number(),
42
+ academicYear: zod_1.z.string(),
43
+ }),
44
+ },
45
+ };
46
+ //# sourceMappingURL=destinationsConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destinationsConfig.js","sourceRoot":"","sources":["../../src/callables/destinationsConfig.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;;;;;;;;GAUG;AACU,QAAA,2BAA2B,GAAG;IACvC,2CAA2C,EAAE;QACzC,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf;0EAC8D;YAC9D,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC;SAChC,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,OAAO,EAAE;YAClB,2EAA2E;YAC3E,WAAW,EAAE,OAAC,CAAC,OAAO,EAAE;YACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SACjC,CAAC;KACL;IACD,6CAA6C,EAAE;QAC3C,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf,uEAAuE;YACvE,QAAQ,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;YAChC,8EAA8E;YAC9E,KAAK,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;SACvD,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,WAAW,EAAE,OAAC,CAAC,OAAO,EAAE;YACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;YACnB,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;SAC3B,CAAC;KACL;CACoB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  export * from "./types";
2
2
  export * from "./registry";
3
- export * from "./aspirations";
3
+ export * from "./destinationsConfig";
4
4
  export * from "./placement";
5
5
  export * from "./events";
@@ -16,7 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./types"), exports);
18
18
  __exportStar(require("./registry"), exports);
19
- __exportStar(require("./aspirations"), exports);
19
+ __exportStar(require("./destinationsConfig"), exports);
20
20
  __exportStar(require("./placement"), exports);
21
21
  __exportStar(require("./events"), exports);
22
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/callables/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,6CAA2B;AAC3B,gDAA8B;AAC9B,8CAA4B;AAC5B,2CAAyB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/callables/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,6CAA2B;AAC3B,uDAAqC;AACrC,8CAA4B;AAC5B,2CAAyB"}
@@ -419,30 +419,58 @@ export declare const allCallables: {
419
419
  }>;
420
420
  response: z.ZodAny;
421
421
  };
422
- "aspirations-createAspirationCycle": {
422
+ "destinationsConfig-saveDestinationsConfig": {
423
423
  request: z.ZodObject<{
424
424
  oId: z.ZodString;
425
- yearGroupsIncluded: z.ZodArray<z.ZodNumber, "many">;
426
- scheduledSendDate: z.ZodString;
427
- closeAfterDays: z.ZodOptional<z.ZodNumber>;
428
- emailTemplateId: z.ZodOptional<z.ZodString>;
429
- students: z.ZodOptional<z.ZodString>;
425
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
430
426
  }, "strip", z.ZodTypeAny, {
427
+ config: Record<string, unknown>;
431
428
  oId: string;
432
- yearGroupsIncluded: number[];
433
- scheduledSendDate: string;
434
- students?: string | undefined;
435
- closeAfterDays?: number | undefined;
436
- emailTemplateId?: string | undefined;
437
429
  }, {
430
+ config: Record<string, unknown>;
438
431
  oId: string;
439
- yearGroupsIncluded: number[];
440
- scheduledSendDate: string;
441
- students?: string | undefined;
442
- closeAfterDays?: number | undefined;
443
- emailTemplateId?: string | undefined;
444
432
  }>;
445
- response: z.ZodAny;
433
+ response: z.ZodObject<{
434
+ saved: z.ZodBoolean;
435
+ provisioned: z.ZodBoolean;
436
+ created: z.ZodOptional<z.ZodNumber>;
437
+ }, "strip", z.ZodTypeAny, {
438
+ saved: boolean;
439
+ provisioned: boolean;
440
+ created?: number | undefined;
441
+ }, {
442
+ saved: boolean;
443
+ provisioned: boolean;
444
+ created?: number | undefined;
445
+ }>;
446
+ };
447
+ "destinationsConfig-provisionDestinationsNow": {
448
+ request: z.ZodObject<{
449
+ oId: z.ZodString;
450
+ nextYear: z.ZodOptional<z.ZodBoolean>;
451
+ scope: z.ZodOptional<z.ZodEnum<["aspirations", "leavers"]>>;
452
+ }, "strip", z.ZodTypeAny, {
453
+ oId: string;
454
+ scope?: "aspirations" | "leavers" | undefined;
455
+ nextYear?: boolean | undefined;
456
+ }, {
457
+ oId: string;
458
+ scope?: "aspirations" | "leavers" | undefined;
459
+ nextYear?: boolean | undefined;
460
+ }>;
461
+ response: z.ZodObject<{
462
+ provisioned: z.ZodBoolean;
463
+ created: z.ZodNumber;
464
+ academicYear: z.ZodString;
465
+ }, "strip", z.ZodTypeAny, {
466
+ created: number;
467
+ academicYear: string;
468
+ provisioned: boolean;
469
+ }, {
470
+ created: number;
471
+ academicYear: string;
472
+ provisioned: boolean;
473
+ }>;
446
474
  };
447
475
  };
448
476
  /** Every registered callable name. */
@@ -460,31 +488,59 @@ export type CallableContracts = {
460
488
  };
461
489
  /** The per-group maps, exported so tests can assert no key collisions. */
462
490
  export declare const callableGroups: {
463
- aspirationCallables: {
464
- "aspirations-createAspirationCycle": {
491
+ destinationsConfigCallables: {
492
+ "destinationsConfig-saveDestinationsConfig": {
465
493
  request: z.ZodObject<{
466
494
  oId: z.ZodString;
467
- yearGroupsIncluded: z.ZodArray<z.ZodNumber, "many">;
468
- scheduledSendDate: z.ZodString;
469
- closeAfterDays: z.ZodOptional<z.ZodNumber>;
470
- emailTemplateId: z.ZodOptional<z.ZodString>;
471
- students: z.ZodOptional<z.ZodString>;
495
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
472
496
  }, "strip", z.ZodTypeAny, {
497
+ config: Record<string, unknown>;
473
498
  oId: string;
474
- yearGroupsIncluded: number[];
475
- scheduledSendDate: string;
476
- students?: string | undefined;
477
- closeAfterDays?: number | undefined;
478
- emailTemplateId?: string | undefined;
479
499
  }, {
500
+ config: Record<string, unknown>;
480
501
  oId: string;
481
- yearGroupsIncluded: number[];
482
- scheduledSendDate: string;
483
- students?: string | undefined;
484
- closeAfterDays?: number | undefined;
485
- emailTemplateId?: string | undefined;
486
502
  }>;
487
- response: z.ZodAny;
503
+ response: z.ZodObject<{
504
+ saved: z.ZodBoolean;
505
+ provisioned: z.ZodBoolean;
506
+ created: z.ZodOptional<z.ZodNumber>;
507
+ }, "strip", z.ZodTypeAny, {
508
+ saved: boolean;
509
+ provisioned: boolean;
510
+ created?: number | undefined;
511
+ }, {
512
+ saved: boolean;
513
+ provisioned: boolean;
514
+ created?: number | undefined;
515
+ }>;
516
+ };
517
+ "destinationsConfig-provisionDestinationsNow": {
518
+ request: z.ZodObject<{
519
+ oId: z.ZodString;
520
+ nextYear: z.ZodOptional<z.ZodBoolean>;
521
+ scope: z.ZodOptional<z.ZodEnum<["aspirations", "leavers"]>>;
522
+ }, "strip", z.ZodTypeAny, {
523
+ oId: string;
524
+ scope?: "aspirations" | "leavers" | undefined;
525
+ nextYear?: boolean | undefined;
526
+ }, {
527
+ oId: string;
528
+ scope?: "aspirations" | "leavers" | undefined;
529
+ nextYear?: boolean | undefined;
530
+ }>;
531
+ response: z.ZodObject<{
532
+ provisioned: z.ZodBoolean;
533
+ created: z.ZodNumber;
534
+ academicYear: z.ZodString;
535
+ }, "strip", z.ZodTypeAny, {
536
+ created: number;
537
+ academicYear: string;
538
+ provisioned: boolean;
539
+ }, {
540
+ created: number;
541
+ academicYear: string;
542
+ provisioned: boolean;
543
+ }>;
488
544
  };
489
545
  };
490
546
  placementCallables: {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.callableGroups = exports.allCallables = void 0;
4
- const aspirations_1 = require("./aspirations");
4
+ const destinationsConfig_1 = require("./destinationsConfig");
5
5
  const placement_1 = require("./placement");
6
6
  const events_1 = require("./events");
7
7
  const surveys_1 = require("./surveys");
@@ -17,11 +17,11 @@ const surveys_1 = require("./surveys");
17
17
  * equals the sum of the group key counts.
18
18
  */
19
19
  exports.allCallables = {
20
- ...aspirations_1.aspirationCallables,
20
+ ...destinationsConfig_1.destinationsConfigCallables,
21
21
  ...placement_1.placementCallables,
22
22
  ...events_1.eventsCallables,
23
23
  ...surveys_1.surveyCallables,
24
24
  };
25
25
  /** The per-group maps, exported so tests can assert no key collisions. */
26
- exports.callableGroups = { aspirationCallables: aspirations_1.aspirationCallables, placementCallables: placement_1.placementCallables, eventsCallables: events_1.eventsCallables, surveyCallables: surveys_1.surveyCallables };
26
+ exports.callableGroups = { destinationsConfigCallables: destinationsConfig_1.destinationsConfigCallables, placementCallables: placement_1.placementCallables, eventsCallables: events_1.eventsCallables, surveyCallables: surveys_1.surveyCallables };
27
27
  //# sourceMappingURL=registry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/callables/registry.ts"],"names":[],"mappings":";;;AAEA,+CAAkD;AAClD,2CAA+C;AAC/C,qCAAyC;AACzC,uCAA0C;AAE1C;;;;;;;;;;GAUG;AACU,QAAA,YAAY,GAAG;IACxB,GAAG,iCAAmB;IACtB,GAAG,8BAAkB;IACrB,GAAG,wBAAe;IAClB,GAAG,yBAAe;CACG,CAAC;AAiB1B,0EAA0E;AAC7D,QAAA,cAAc,GAAG,EAAC,mBAAmB,EAAnB,iCAAmB,EAAE,kBAAkB,EAAlB,8BAAkB,EAAE,eAAe,EAAf,wBAAe,EAAE,eAAe,EAAf,yBAAe,EAAC,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/callables/registry.ts"],"names":[],"mappings":";;;AAEA,6DAAiE;AACjE,2CAA+C;AAC/C,qCAAyC;AACzC,uCAA0C;AAE1C;;;;;;;;;;GAUG;AACU,QAAA,YAAY,GAAG;IACxB,GAAG,gDAA2B;IAC9B,GAAG,8BAAkB;IACrB,GAAG,wBAAe;IAClB,GAAG,yBAAe;CACG,CAAC;AAiB1B,0EAA0E;AAC7D,QAAA,cAAc,GAAG,EAAC,2BAA2B,EAA3B,gDAA2B,EAAE,kBAAkB,EAAlB,8BAAkB,EAAE,eAAe,EAAf,wBAAe,EAAE,eAAe,EAAf,yBAAe,EAAC,CAAC"}
@@ -2080,19 +2080,30 @@ export type DestinationsRecipientFilter = {
2080
2080
  */
2081
2081
  export type AspirationSurveySlot = SurveySlot;
2082
2082
  /**
2083
- * Per-year-group leaver settings. Used as an override entry in
2083
+ * Per-year-group leaver settings.
2084
+ *
2085
+ * Each leg is scheduled the way every other survey is: a relative rule ("third
2086
+ * Thursday of August") with a materialised MM-DD alongside it as the concrete
2087
+ * fallback — the same pair SurveySlot carries. Results day is why: the exam boards
2088
+ * move it by a few days each year but it is always a Thursday. Staff can still
2089
+ * correct one year's date on the survey page (updateSurveyCycleSettings). Used as an override entry in
2084
2090
  * DestinationsConfig.leavers.byYearGroup — each field replaces the shared leaver
2085
2091
  * default for that year group only. All MM-DD strings are zero-padded.
2086
2092
  */
2087
2093
  export type LeaverYearGroupConfig = {
2088
2094
  intentionsSendMMDD: string;
2095
+ /** Relative rule for the intentions form, materialised into intentionsSendMMDD.
2096
+ * Absent on configs written before leaver dates became rules. */
2097
+ intentionsRule?: SurveyDateRule;
2089
2098
  resultsDay: {
2090
2099
  enabled: boolean;
2091
2100
  sendMMDD: string;
2101
+ rule?: SurveyDateRule;
2092
2102
  };
2093
2103
  christmas: {
2094
2104
  enabled: boolean;
2095
2105
  sendMMDD: string;
2106
+ rule?: SurveyDateRule;
2096
2107
  };
2097
2108
  allStudentsLeave?: boolean;
2098
2109
  };
@@ -2107,7 +2118,8 @@ export type DestinationsConfig = {
2107
2118
  aspirations: {
2108
2119
  enabled: boolean;
2109
2120
  yearGroups: number[];
2110
- /** Shared schedule applied to every selected year group (1–3 sends/year). */
2121
+ /** Shared schedule applied to every selected year group. Any number of sends
2122
+ * per year — a school running one a term is as valid as one a year. */
2111
2123
  surveys: AspirationSurveySlot[];
2112
2124
  /** Optional per-year-group override. When a year group key is present its
2113
2125
  * schedule REPLACES `surveys` for that year group only. */
@@ -2122,13 +2134,16 @@ export type DestinationsConfig = {
2122
2134
  enabled: boolean;
2123
2135
  yearGroups: number[];
2124
2136
  intentionsSendMMDD: string;
2137
+ intentionsRule?: SurveyDateRule;
2125
2138
  resultsDay: {
2126
2139
  enabled: boolean;
2127
2140
  sendMMDD: string;
2141
+ rule?: SurveyDateRule;
2128
2142
  };
2129
2143
  christmas: {
2130
2144
  enabled: boolean;
2131
2145
  sendMMDD: string;
2146
+ rule?: SurveyDateRule;
2132
2147
  };
2133
2148
  /** true → every student in these groups leaves (no internal sixth form). */
2134
2149
  allStudentsLeave?: boolean;
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.990",
5
+ "version": "1.400.992",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -0,0 +1,44 @@
1
+ import {z} from "zod";
2
+ import {CallableGroup} from "./types";
3
+
4
+ /**
5
+ * Contracts for the Destinations setup wizard
6
+ * (`Placementt-Backend/functions/src/destinationsConfig.ts`).
7
+ *
8
+ * These were `aspirations-*` while they lived in the legacy survey backend. That
9
+ * module is gone, but the config it happened to host is institute setup that the
10
+ * survey engine READS and does not own, so the two callables moved rather than being
11
+ * deleted. The names moved with them: never rename a deployed function in place, so
12
+ * the old `aspirations-saveDestinationsConfig` and `aspirations-provisionDestinationsNow`
13
+ * must be deleted explicitly at deploy time.
14
+ */
15
+ export const destinationsConfigCallables = {
16
+ "destinationsConfig-saveDestinationsConfig": {
17
+ request: z.object({
18
+ oId: z.string(),
19
+ /** The full DestinationsConfig from the setup wizard. Validated server
20
+ * side, which is also where provenance fields are stamped. */
21
+ config: z.record(z.unknown()),
22
+ }),
23
+ response: z.object({
24
+ saved: z.boolean(),
25
+ /** True on a first save, which provisions the current year immediately. */
26
+ provisioned: z.boolean(),
27
+ created: z.number().optional(),
28
+ }),
29
+ },
30
+ "destinationsConfig-provisionDestinationsNow": {
31
+ request: z.object({
32
+ oId: z.string(),
33
+ /** Provision the upcoming academic year early ("Set Up Next Year"). */
34
+ nextYear: z.boolean().optional(),
35
+ /** Limit to one feature; omit for everything the institute has configured. */
36
+ scope: z.enum(["aspirations", "leavers"]).optional(),
37
+ }),
38
+ response: z.object({
39
+ provisioned: z.boolean(),
40
+ created: z.number(),
41
+ academicYear: z.string(),
42
+ }),
43
+ },
44
+ } satisfies CallableGroup;
@@ -1,5 +1,5 @@
1
1
  export * from "./types";
2
2
  export * from "./registry";
3
- export * from "./aspirations";
3
+ export * from "./destinationsConfig";
4
4
  export * from "./placement";
5
5
  export * from "./events";
@@ -1,6 +1,6 @@
1
1
  import {z} from "zod";
2
2
  import {CallableGroup} from "./types";
3
- import {aspirationCallables} from "./aspirations";
3
+ import {destinationsConfigCallables} from "./destinationsConfig";
4
4
  import {placementCallables} from "./placement";
5
5
  import {eventsCallables} from "./events";
6
6
  import {surveyCallables} from "./surveys";
@@ -17,7 +17,7 @@ import {surveyCallables} from "./surveys";
17
17
  * equals the sum of the group key counts.
18
18
  */
19
19
  export const allCallables = {
20
- ...aspirationCallables,
20
+ ...destinationsConfigCallables,
21
21
  ...placementCallables,
22
22
  ...eventsCallables,
23
23
  ...surveyCallables,
@@ -39,4 +39,4 @@ export type CallableContracts = {
39
39
  };
40
40
 
41
41
  /** The per-group maps, exported so tests can assert no key collisions. */
42
- export const callableGroups = {aspirationCallables, placementCallables, eventsCallables, surveyCallables};
42
+ export const callableGroups = {destinationsConfigCallables, placementCallables, eventsCallables, surveyCallables};
@@ -2022,14 +2022,23 @@ export type DestinationsRecipientFilter = {k: string, e?: WhereFilterOp, v: unkn
2022
2022
  export type AspirationSurveySlot = SurveySlot;
2023
2023
 
2024
2024
  /**
2025
- * Per-year-group leaver settings. Used as an override entry in
2025
+ * Per-year-group leaver settings.
2026
+ *
2027
+ * Each leg is scheduled the way every other survey is: a relative rule ("third
2028
+ * Thursday of August") with a materialised MM-DD alongside it as the concrete
2029
+ * fallback — the same pair SurveySlot carries. Results day is why: the exam boards
2030
+ * move it by a few days each year but it is always a Thursday. Staff can still
2031
+ * correct one year's date on the survey page (updateSurveyCycleSettings). Used as an override entry in
2026
2032
  * DestinationsConfig.leavers.byYearGroup — each field replaces the shared leaver
2027
2033
  * default for that year group only. All MM-DD strings are zero-padded.
2028
2034
  */
2029
2035
  export type LeaverYearGroupConfig = {
2030
2036
  intentionsSendMMDD: string,
2031
- resultsDay: {enabled: boolean, sendMMDD: string},
2032
- christmas: {enabled: boolean, sendMMDD: string},
2037
+ /** Relative rule for the intentions form, materialised into intentionsSendMMDD.
2038
+ * Absent on configs written before leaver dates became rules. */
2039
+ intentionsRule?: SurveyDateRule,
2040
+ resultsDay: {enabled: boolean, sendMMDD: string, rule?: SurveyDateRule},
2041
+ christmas: {enabled: boolean, sendMMDD: string, rule?: SurveyDateRule},
2033
2042
  allStudentsLeave?: boolean,
2034
2043
  };
2035
2044
 
@@ -2045,7 +2054,8 @@ export type DestinationsConfig = {
2045
2054
  aspirations: {
2046
2055
  enabled: boolean,
2047
2056
  yearGroups: number[], // e.g. [7,8,9,10,11]
2048
- /** Shared schedule applied to every selected year group (1–3 sends/year). */
2057
+ /** Shared schedule applied to every selected year group. Any number of sends
2058
+ * per year — a school running one a term is as valid as one a year. */
2049
2059
  surveys: AspirationSurveySlot[],
2050
2060
  /** Optional per-year-group override. When a year group key is present its
2051
2061
  * schedule REPLACES `surveys` for that year group only. */
@@ -2058,9 +2068,12 @@ export type DestinationsConfig = {
2058
2068
  leavers: {
2059
2069
  enabled: boolean,
2060
2070
  yearGroups: number[], // subset of [11, 13]
2071
+ // Each leg is scheduled by relative rule with the MM-DD as its materialised
2072
+ // fallback, the same pair aspiration and skills slots use.
2061
2073
  intentionsSendMMDD: string, // Form B, before they leave — default "06-15"
2062
- resultsDay: {enabled: boolean, sendMMDD: string}, // default "08-29"
2063
- christmas: {enabled: boolean, sendMMDD: string}, // default "12-08"
2074
+ intentionsRule?: SurveyDateRule,
2075
+ resultsDay: {enabled: boolean, sendMMDD: string, rule?: SurveyDateRule}, // default "08-29"
2076
+ christmas: {enabled: boolean, sendMMDD: string, rule?: SurveyDateRule}, // default "12-08"
2064
2077
  /** true → every student in these groups leaves (no internal sixth form). */
2065
2078
  allStudentsLeave?: boolean,
2066
2079
  /** Optional per-year-group override. When a year group key is present its
@@ -1,21 +0,0 @@
1
- import {z} from "zod";
2
- import {CallableGroup} from "./types";
3
-
4
- /**
5
- * Contracts for the `aspirations` backend module
6
- * (`Placementt-Backend/functions/src/aspirations.ts`).
7
- */
8
- export const aspirationCallables = {
9
- "aspirations-createAspirationCycle": {
10
- request: z.object({
11
- oId: z.string(),
12
- yearGroupsIncluded: z.array(z.number()),
13
- scheduledSendDate: z.string(), // ISO string
14
- closeAfterDays: z.number().optional(),
15
- emailTemplateId: z.string().optional(),
16
- students: z.string().optional(), // JSON-serialised DataViewerSelectionBackend
17
- }),
18
- // TODO: tighten once the return shape is confirmed.
19
- response: z.any(),
20
- },
21
- } satisfies CallableGroup;